diff --git a/.github/actions/build-jtreg/action.yml b/.github/actions/build-jtreg/action.yml index 0ba9937fb45..a9c046e9dd9 100644 --- a/.github/actions/build-jtreg/action.yml +++ b/.github/actions/build-jtreg/action.yml @@ -1,5 +1,5 @@ # -# Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. # 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/.github/actions/get-bundles/action.yml b/.github/actions/get-bundles/action.yml index 270d15159a0..a356aa9fd8d 100644 --- a/.github/actions/get-bundles/action.yml +++ b/.github/actions/get-bundles/action.yml @@ -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 diff --git a/.github/actions/get-gtest/action.yml b/.github/actions/get-gtest/action.yml index d38d33eabd8..7a329460a6e 100644 --- a/.github/actions/get-gtest/action.yml +++ b/.github/actions/get-gtest/action.yml @@ -1,5 +1,5 @@ # -# Copyright (c) 2022, 2023, 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 diff --git a/.github/actions/get-jtreg/action.yml b/.github/actions/get-jtreg/action.yml index 4bb671d25d1..36c895fc59d 100644 --- a/.github/actions/get-jtreg/action.yml +++ b/.github/actions/get-jtreg/action.yml @@ -1,5 +1,5 @@ # -# Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. # 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/.github/actions/get-msys2/action.yml b/.github/actions/get-msys2/action.yml index d93b6e3763b..308230ebf2e 100644 --- a/.github/actions/get-msys2/action.yml +++ b/.github/actions/get-msys2/action.yml @@ -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 diff --git a/.github/actions/upload-bundles/action.yml b/.github/actions/upload-bundles/action.yml index ca5366f3d6c..78fb0a94bfd 100644 --- a/.github/actions/upload-bundles/action.yml +++ b/.github/actions/upload-bundles/action.yml @@ -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 diff --git a/.github/workflows/build-alpine-linux.yml b/.github/workflows/build-alpine-linux.yml index 0d366a4bdd0..c39962fa07f 100644 --- a/.github/workflows/build-alpine-linux.yml +++ b/.github/workflows/build-alpine-linux.yml @@ -1,5 +1,5 @@ # -# Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -59,7 +59,7 @@ on: jobs: build-linux: name: build - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 container: image: alpine:3.20 @@ -96,6 +96,8 @@ jobs: --with-boot-jdk=${{ steps.bootjdk.outputs.path }} --with-zlib=system --with-jmod-compress=zip-1 + --with-external-symbols-in-bundles=none + --with-native-debug-symbols-level=1 ${{ inputs.extra-conf-options }} ${{ inputs.configure-arguments }} || ( echo "Dumping config.log:" && cat config.log && diff --git a/.github/workflows/build-cross-compile.yml b/.github/workflows/build-cross-compile.yml index b3c63f488a0..a0642d469aa 100644 --- a/.github/workflows/build-cross-compile.yml +++ b/.github/workflows/build-cross-compile.yml @@ -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 @@ -48,7 +48,7 @@ on: jobs: build-cross-compile: name: build - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 strategy: fail-fast: false @@ -179,6 +179,8 @@ jobs: --openjdk-target=${{ matrix.gnu-arch }}-linux-gnu${{ matrix.gnu-abi}} --with-sysroot=sysroot --with-jmod-compress=zip-1 + --with-external-symbols-in-bundles=none + --with-native-debug-symbols-level=1 CC=${{ matrix.gnu-arch }}-linux-gnu${{ matrix.gnu-abi}}-gcc-${{ inputs.gcc-major-version }} CXX=${{ matrix.gnu-arch }}-linux-gnu${{ matrix.gnu-abi}}-g++-${{ inputs.gcc-major-version }} ${{ inputs.extra-conf-options }} ${{ inputs.configure-arguments }} || ( diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index f398625cb2c..791b53a3f04 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -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 @@ -75,7 +75,7 @@ on: jobs: build-linux: name: build - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 strategy: fail-fast: false @@ -115,9 +115,21 @@ jobs: if [[ '${{ inputs.apt-architecture }}' != '' ]]; then sudo dpkg --add-architecture ${{ inputs.apt-architecture }} fi - sudo apt-get update - sudo apt-get install --only-upgrade apt - sudo apt-get install gcc-${{ inputs.gcc-major-version }}${{ inputs.gcc-package-suffix }} g++-${{ inputs.gcc-major-version }}${{ inputs.gcc-package-suffix }} libxrandr-dev${{ steps.arch.outputs.suffix }} libxtst-dev${{ steps.arch.outputs.suffix }} libcups2-dev${{ steps.arch.outputs.suffix }} libasound2-dev${{ steps.arch.outputs.suffix }} ${{ inputs.apt-extra-packages }} + sudo apt update + sudo apt install --only-upgrade apt + sudo apt install \ + gcc-${{ inputs.gcc-major-version }}${{ inputs.gcc-package-suffix }} \ + g++-${{ inputs.gcc-major-version }}${{ inputs.gcc-package-suffix }} \ + libasound2-dev${{ steps.arch.outputs.suffix }} \ + libcups2-dev${{ steps.arch.outputs.suffix }} \ + libfontconfig1-dev${{ steps.arch.outputs.suffix }} \ + libx11-dev${{ steps.arch.outputs.suffix }} \ + libxext-dev${{ steps.arch.outputs.suffix }} \ + libxrandr-dev${{ steps.arch.outputs.suffix }} \ + libxrender-dev${{ steps.arch.outputs.suffix }} \ + libxt-dev${{ steps.arch.outputs.suffix }} \ + libxtst-dev${{ steps.arch.outputs.suffix }} \ + ${{ inputs.apt-extra-packages }} sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-${{ inputs.gcc-major-version }} 100 --slave /usr/bin/g++ g++ /usr/bin/g++-${{ inputs.gcc-major-version }} - name: 'Configure' @@ -131,6 +143,8 @@ jobs: --with-gtest=${{ steps.gtest.outputs.path }} --with-zlib=system --with-jmod-compress=zip-1 + --with-external-symbols-in-bundles=none + --with-native-debug-symbols-level=1 ${{ inputs.extra-conf-options }} ${{ inputs.configure-arguments }} || ( echo "Dumping config.log:" && cat config.log && diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 0a12df668e5..484e616fad7 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -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 @@ -110,6 +110,8 @@ jobs: --with-gtest=${{ steps.gtest.outputs.path }} --with-zlib=system --with-jmod-compress=zip-1 + --with-external-symbols-in-bundles=none + --with-native-debug-symbols-level=1 ${{ inputs.extra-conf-options }} ${{ inputs.configure-arguments }} || ( echo "Dumping config.log:" && cat config.log && diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index a3091b94cef..4dafc016a99 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -1,5 +1,5 @@ # -# Copyright (c) 2022, 2023, 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 @@ -134,6 +134,7 @@ jobs: --with-gtest=${{ steps.gtest.outputs.path }} --with-msvc-toolset-version=${{ inputs.msvc-toolset-version }} --with-jmod-compress=zip-1 + --with-external-symbols-in-bundles=none ${{ inputs.extra-conf-options }} ${{ inputs.configure-arguments }} || ( echo "Dumping config.log:" && cat config.log && diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4d1e8a8be3d..85ec75f343c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -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 @@ -57,7 +57,7 @@ jobs: prepare: name: 'Prepare the run' - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 env: # List of platforms to exclude by default EXCLUDED_PLATFORMS: 'alpine-linux-x64' @@ -405,7 +405,7 @@ jobs: with: platform: linux-x64 bootjdk-platform: linux-x64 - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 dry-run: ${{ needs.prepare.outputs.dry-run == 'true' }} debug-suffix: -debug @@ -419,7 +419,7 @@ jobs: with: platform: linux-x64 bootjdk-platform: linux-x64 - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 dry-run: ${{ needs.prepare.outputs.dry-run == 'true' }} static-suffix: "-static" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f2c8916a369..8f33454305e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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 diff --git a/.jcheck/conf b/.jcheck/conf index 60881e74d2a..25af49f8ef8 100644 --- a/.jcheck/conf +++ b/.jcheck/conf @@ -1,7 +1,7 @@ [general] project=jdk jbs=JDK -version=26 +version=27 [checks] error=author,committer,reviewers,merge,issues,executable,symlink,message,hg-tag,whitespace,problemlists,copyright diff --git a/README.md b/README.md index b3f30676b3c..e939f6a9ca4 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Welcome to the JDK! For build instructions please see the -[online documentation](https://openjdk.org/groups/build/doc/building.html), +[online documentation](https://git.openjdk.org/jdk/blob/master/doc/building.md), or either of these files: - [doc/building.html](doc/building.html) (html version) diff --git a/bin/generate-symbol-data.sh b/bin/generate-symbol-data.sh index 283757a6918..14d8763ad81 100644 --- a/bin/generate-symbol-data.sh +++ b/bin/generate-symbol-data.sh @@ -38,7 +38,7 @@ # directory. # - open a terminal program and run these commands: # cd "${JDK_CHECKOUT}"/src/jdk.compiler/share/data/symbols -# bash ../../../../../make/scripts/generate-symbol-data.sh "${JDK_N_INSTALL}" +# bash ../../../../../bin/generate-symbol-data.sh "${JDK_N_INSTALL}" # - this command will generate or update data for "--release N" into the ${JDK_CHECKOUT}/src/jdk.compiler/share/data/symbols # directory, updating all registration necessary. If the goal was to update the data, and there are no # new or changed files in the ${JDK_CHECKOUT}/src/jdk.compiler/share/data/symbols directory after running this script, diff --git a/doc/building.html b/doc/building.html index 19313ebf43a..8e5a7625371 100644 --- a/doc/building.html +++ b/doc/building.html @@ -541,6 +541,11 @@ href="#apple-xcode">Apple Xcode on some strategies to deal with this.

It is recommended that you use at least macOS 14 and Xcode 15.4, but earlier versions may also work.

+

Starting with Xcode 26, introduced in macOS 26, the Metal toolchain +no longer comes bundled with Xcode, so it needs to be installed +separately. This can either be done via the Xcode's Settings/Components +UI, or in the command line calling +xcodebuild -downloadComponent metalToolchain.

The standard macOS environment contains the basic tooling needed to build, but for external libraries a package manager is recommended. The JDK uses homebrew in the examples, but diff --git a/doc/building.md b/doc/building.md index 1fbd395a9d1..b626027f101 100644 --- a/doc/building.md +++ b/doc/building.md @@ -352,6 +352,11 @@ on some strategies to deal with this. It is recommended that you use at least macOS 14 and Xcode 15.4, but earlier versions may also work. +Starting with Xcode 26, introduced in macOS 26, the Metal toolchain no longer +comes bundled with Xcode, so it needs to be installed separately. This can +either be done via the Xcode's Settings/Components UI, or in the command line +calling `xcodebuild -downloadComponent metalToolchain`. + The standard macOS environment contains the basic tooling needed to build, but for external libraries a package manager is recommended. The JDK uses [homebrew](https://brew.sh/) in the examples, but feel free to use whatever diff --git a/doc/hotspot-style.html b/doc/hotspot-style.html index a2ffb57e5a3..362245cd00a 100644 --- a/doc/hotspot-style.html +++ b/doc/hotspot-style.html @@ -1037,8 +1037,8 @@ running destructors at exit can lead to problems.

Some of the approaches used in HotSpot to avoid dynamic initialization include:

diff --git a/doc/starting-next-release.md b/doc/starting-next-release.md index 10bc364a3e4..5bb9c5839a4 100644 --- a/doc/starting-next-release.md +++ b/doc/starting-next-release.md @@ -65,4 +65,4 @@ to be updated for a particular release. * `test/langtools/tools/javac/lib/JavacTestingAbstractProcessor.java` update annotation processor extended by `javac` tests to cover the new source version * `test/langtools/tools/javac/preview/classReaderTest/Client.nopreview.out` and `test/langtools/tools/javac/preview/classReaderTest/Client.preview.out`: update expected messages for preview errors and warnings - +* `test/langtools/tools/javac/versions/Versions.java`: add new source version to the set of valid sources and add new enum constant for the new class file version. diff --git a/doc/testing.html b/doc/testing.html index b9838735e4f..31f4fbd1778 100644 --- a/doc/testing.html +++ b/doc/testing.html @@ -535,6 +535,8 @@ failure. This helps to reproduce intermittent test failures. Defaults to

REPORT

Use this report style when reporting test results (sent to JTReg as -report). Defaults to files.

+

MANUAL

+

Set to true to execute manual tests only.

Gtest keywords

REPEAT

The number of times to repeat the tests diff --git a/doc/testing.md b/doc/testing.md index 0144610a5bf..b95f59de9fd 100644 --- a/doc/testing.md +++ b/doc/testing.md @@ -512,6 +512,10 @@ helps to reproduce intermittent test failures. Defaults to 0. Use this report style when reporting test results (sent to JTReg as `-report`). Defaults to `files`. +#### MANUAL + +Set to `true` to execute manual tests only. + ### Gtest keywords #### REPEAT diff --git a/make/Bundles.gmk b/make/Bundles.gmk index cf3b77e4e52..0b324e7e3f3 100644 --- a/make/Bundles.gmk +++ b/make/Bundles.gmk @@ -125,13 +125,6 @@ define SetupBundleFileBody && $(TAR) cf - -$(TAR_INCLUDE_PARAM) $$($1_$$d_LIST_FILE) \ $(TAR_IGNORE_EXIT_VALUE) ) \ | ( $(CD) $(SUPPORT_OUTPUTDIR)/bundles/$1/$$($1_SUBDIR) && $(TAR) xf - )$$(NEWLINE) ) - # Rename stripped pdb files - ifeq ($(call isTargetOs, windows)+$(SHIP_DEBUG_SYMBOLS), true+public) - for f in `$(FIND) $(SUPPORT_OUTPUTDIR)/bundles/$1/$$($1_SUBDIR) -name "*.stripped.pdb"`; do \ - $(ECHO) Renaming $$$${f} to $$$${f%stripped.pdb}pdb $(LOG_INFO); \ - $(MV) $$$${f} $$$${f%stripped.pdb}pdb; \ - done - endif # Unzip any zipped debuginfo files ifeq ($$($1_UNZIP_DEBUGINFO), true) for f in `$(FIND) $(SUPPORT_OUTPUTDIR)/bundles/$1/$$($1_SUBDIR) -name "*.diz"`; do \ @@ -192,96 +185,30 @@ endif ifneq ($(filter product-bundles% legacy-bundles, $(MAKECMDGOALS)), ) - SYMBOLS_EXCLUDE_PATTERN := %.debuginfo %.diz %.map - - # There may be files with spaces in the names, so use ShellFindFiles - # explicitly. + # There may be files with spaces in the names, so use ShellFindFiles explicitly. ALL_JDK_FILES := $(call ShellFindFiles, $(JDK_IMAGE_DIR)) - ifneq ($(JDK_IMAGE_DIR), $(JDK_SYMBOLS_IMAGE_DIR)) - ALL_JDK_SYMBOLS_FILES := $(call ShellFindFiles, $(JDK_SYMBOLS_IMAGE_DIR)) - else - ALL_JDK_SYMBOLS_FILES := $(ALL_JDK_FILES) - endif ifneq ($(JDK_IMAGE_DIR), $(JDK_DEMOS_IMAGE_DIR)) ALL_JDK_DEMOS_FILES := $(call ShellFindFiles, $(JDK_DEMOS_IMAGE_DIR)) else ALL_JDK_DEMOS_FILES := $(ALL_JDK_FILES) endif - # Create special filter rules when dealing with unzipped .dSYM directories on - # macosx - ifeq ($(call isTargetOs, macosx), true) - ifeq ($(ZIP_EXTERNAL_DEBUG_SYMBOLS), false) - JDK_SYMBOLS_EXCLUDE_PATTERN := $(addprefix %, \ - $(call containing, .dSYM/, $(patsubst $(JDK_IMAGE_DIR)/%, %, \ - $(ALL_JDK_SYMBOLS_FILES)))) - endif - endif - - # Create special filter rules when dealing with debug symbols on windows - ifeq ($(call isTargetOs, windows), true) - ifeq ($(SHIP_DEBUG_SYMBOLS), ) - JDK_SYMBOLS_EXCLUDE_PATTERN := %.pdb - else - ifeq ($(SHIP_DEBUG_SYMBOLS), public) - JDK_SYMBOLS_EXCLUDE_PATTERN := \ - $(filter-out \ - %.stripped.pdb, \ - $(filter %.pdb, $(ALL_JDK_FILES)) \ - ) - endif - endif - endif - JDK_BUNDLE_FILES := \ $(filter-out \ - $(JDK_SYMBOLS_EXCLUDE_PATTERN) \ $(JDK_EXTRA_EXCLUDES) \ - $(SYMBOLS_EXCLUDE_PATTERN) \ $(JDK_IMAGE_HOMEDIR)/demo/% \ , \ $(ALL_JDK_FILES) \ ) - JDK_SYMBOLS_BUNDLE_FILES := \ - $(filter-out \ - %.stripped.pdb, \ - $(call FindFiles, $(SYMBOLS_IMAGE_DIR)) \ - ) + JDK_SYMBOLS_BUNDLE_FILES := $(call FindFiles, $(SYMBOLS_IMAGE_DIR)) TEST_DEMOS_BUNDLE_FILES := $(filter $(JDK_DEMOS_IMAGE_HOMEDIR)/demo/%, \ $(ALL_JDK_DEMOS_FILES)) ALL_JRE_FILES := $(call ShellFindFiles, $(JRE_IMAGE_DIR)) - # Create special filter rules when dealing with unzipped .dSYM directories on - # macosx - ifeq ($(OPENJDK_TARGET_OS), macosx) - ifeq ($(ZIP_EXTERNAL_DEBUG_SYMBOLS), false) - JRE_SYMBOLS_EXCLUDE_PATTERN := $(addprefix %, \ - $(call containing, .dSYM/, $(patsubst $(JRE_IMAGE_DIR)/%, %, $(ALL_JRE_FILES)))) - endif - endif - - # Create special filter rules when dealing with debug symbols on windows - ifeq ($(call isTargetOs, windows), true) - ifeq ($(SHIP_DEBUG_SYMBOLS), ) - JRE_SYMBOLS_EXCLUDE_PATTERN := %.pdb - else - ifeq ($(SHIP_DEBUG_SYMBOLS), public) - JRE_SYMBOLS_EXCLUDE_PATTERN := \ - $(filter-out \ - %.stripped.pdb, \ - $(filter %.pdb, $(ALL_JRE_FILES)) \ - ) - endif - endif - endif - - JRE_BUNDLE_FILES := $(filter-out \ - $(JRE_SYMBOLS_EXCLUDE_PATTERN) \ - $(SYMBOLS_EXCLUDE_PATTERN), \ - $(ALL_JRE_FILES)) + JRE_BUNDLE_FILES := $(ALL_JRE_FILES) ifeq ($(MACOSX_CODESIGN_MODE), hardened) # Macosx release build and code signing available. diff --git a/make/CreateJmods.gmk b/make/CreateJmods.gmk index 3280b24924a..32f107b6bb6 100644 --- a/make/CreateJmods.gmk +++ b/make/CreateJmods.gmk @@ -218,10 +218,14 @@ ifeq ($(call isTargetOs, windows), true) ifeq ($(SHIP_DEBUG_SYMBOLS), ) JMOD_FLAGS += --exclude '**{_the.*,_*.marker*,*.diz,*.pdb,*.map}' else - JMOD_FLAGS += --exclude '**{_the.*,_*.marker*,*.diz,*.map}' + JMOD_FLAGS += --exclude '**{_the.*,_*.marker*,*.map}' endif else - JMOD_FLAGS += --exclude '**{_the.*,_*.marker*,*.diz,*.debuginfo,*.dSYM/**,*.dSYM}' + ifeq ($(SHIP_DEBUG_SYMBOLS), ) + JMOD_FLAGS += --exclude '**{_the.*,_*.marker*,*.diz,*.debuginfo,*.dSYM/**,*.dSYM}' + else + JMOD_FLAGS += --exclude '**{_the.*,_*.marker*}' + endif endif # Unless we are creating a very large module, use the small tool JVM options diff --git a/make/Docs.gmk b/make/Docs.gmk index b105e1d8683..a8d40e078e2 100644 --- a/make/Docs.gmk +++ b/make/Docs.gmk @@ -93,16 +93,19 @@ JAVADOC_DISABLED_DOCLINT_WARNINGS := missing JAVADOC_DISABLED_DOCLINT_PACKAGES := org.w3c.* javax.smartcardio # The initial set of options for javadoc +# -XDaccessInternalAPI is a temporary workaround, see 8373909 JAVADOC_OPTIONS := -use -keywords -notimestamp \ -serialwarn -encoding utf-8 -docencoding utf-8 -breakiterator \ -splitIndex --system none -javafx --expand-requires transitive \ - --override-methods=summary + --override-methods=summary \ + -XDaccessInternalAPI # The reference options must stay stable to allow for comparisons across the # development cycle. REFERENCE_OPTIONS := -XDignore.symbol.file=true -use -keywords -notimestamp \ -serialwarn -encoding utf-8 -breakiterator -splitIndex --system none \ - -html5 -javafx --expand-requires transitive + -html5 -javafx --expand-requires transitive \ + -XDaccessInternalAPI # Should we add DRAFT stamps to the generated javadoc? ifeq ($(VERSION_IS_GA), true) diff --git a/make/Images.gmk b/make/Images.gmk index c5877e44c22..89c0a834477 100644 --- a/make/Images.gmk +++ b/make/Images.gmk @@ -282,29 +282,33 @@ else endif CMDS_TARGET_SUBDIR := bin -# Param 1 - either JDK or JRE +# Copy debug info files into symbols bundle. +# In case of Windows and --with-external-symbols-in-bundles=public, take care to remove *.stripped.pdb files SetupCopyDebuginfo = \ $(foreach m, $(ALL_$1_MODULES), \ + $(eval dbgfiles := $(call FindDebuginfoFiles, $(SUPPORT_OUTPUTDIR)/modules_libs/$m)) \ + $(eval dbgfiles := $(if $(filter true+public,$(call isTargetOs,windows)+$(SHIP_DEBUG_SYMBOLS)), \ + $(filter-out %.stripped.pdb,$(dbgfiles)),$(dbgfiles)) \ + ) \ $(eval $(call SetupCopyFiles, COPY_$1_LIBS_DEBUGINFO_$m, \ SRC := $(SUPPORT_OUTPUTDIR)/modules_libs/$m, \ DEST := $($1_IMAGE_DIR)/$(LIBS_TARGET_SUBDIR), \ - FILES := $(call FindDebuginfoFiles, \ - $(SUPPORT_OUTPUTDIR)/modules_libs/$m), \ + FILES := $(dbgfiles), \ )) \ $(eval $1_TARGETS += $$(COPY_$1_LIBS_DEBUGINFO_$m)) \ + $(eval dbgfiles := $(call FindDebuginfoFiles, $(SUPPORT_OUTPUTDIR)/modules_cmds/$m)) \ + $(eval dbgfiles := $(if $(filter true+public,$(call isTargetOs,windows)+$(SHIP_DEBUG_SYMBOLS)), \ + $(filter-out %.stripped.pdb,$(dbgfiles)),$(dbgfiles)) \ + ) \ $(eval $(call SetupCopyFiles, COPY_$1_CMDS_DEBUGINFO_$m, \ SRC := $(SUPPORT_OUTPUTDIR)/modules_cmds/$m, \ DEST := $($1_IMAGE_DIR)/$(CMDS_TARGET_SUBDIR), \ - FILES := $(call FindDebuginfoFiles, \ - $(SUPPORT_OUTPUTDIR)/modules_cmds/$m), \ + FILES := $(dbgfiles), \ )) \ $(eval $1_TARGETS += $$(COPY_$1_CMDS_DEBUGINFO_$m)) \ ) -# No space before argument to avoid having to put $(strip ) everywhere in -# implementation above. -$(call SetupCopyDebuginfo,JDK) -$(call SetupCopyDebuginfo,JRE) +# No space before argument to avoid having to put $(strip ) everywhere in implementation above. $(call SetupCopyDebuginfo,SYMBOLS) ################################################################################ diff --git a/make/RunTests.gmk b/make/RunTests.gmk index 947389f64f9..946b1332edc 100644 --- a/make/RunTests.gmk +++ b/make/RunTests.gmk @@ -206,7 +206,7 @@ $(eval $(call ParseKeywordVariable, JTREG, \ SINGLE_KEYWORDS := JOBS TIMEOUT_FACTOR FAILURE_HANDLER_TIMEOUT \ 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), \ + AOT_JDK MANUAL $(CUSTOM_JTREG_SINGLE_KEYWORDS), \ STRING_KEYWORDS := OPTIONS JAVA_OPTIONS VM_OPTIONS KEYWORDS \ EXTRA_PROBLEM_LISTS LAUNCHER_OPTIONS \ $(CUSTOM_JTREG_STRING_KEYWORDS), \ @@ -873,7 +873,7 @@ define SetupRunJtregTestBody $1_JTREG_BASIC_OPTIONS += -testThreadFactoryPath:$$(JTREG_TEST_THREAD_FACTORY_JAR) $1_JTREG_BASIC_OPTIONS += -testThreadFactory:$$(JTREG_TEST_THREAD_FACTORY) $1_JTREG_BASIC_OPTIONS += $$(addprefix $$(JTREG_PROBLEM_LIST_PREFIX), $$(wildcard \ - $$(addprefix $$($1_TEST_ROOT)/, ProblemList-$$(JTREG_TEST_THREAD_FACTORY).txt) \ + $$(addprefix $$($1_TEST_ROOT)/, ProblemList-$$(JTREG_TEST_THREAD_FACTORY).txt) \ )) endif @@ -881,8 +881,8 @@ define SetupRunJtregTestBody 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) \ - )) + $$(addprefix $$($1_TEST_ROOT)/, ProblemList-jvmti-stress-agent.txt) \ + )) endif @@ -911,7 +911,13 @@ define SetupRunJtregTestBody -vmoption:-Dtest.boot.jdk="$$(BOOT_JDK)" \ -vmoption:-Djava.io.tmpdir="$$($1_TEST_TMP_DIR)" - $1_JTREG_BASIC_OPTIONS += -automatic -ignore:quiet + $1_JTREG_BASIC_OPTIONS += -ignore:quiet + + ifeq ($$(JTREG_MANUAL), true) + $1_JTREG_BASIC_OPTIONS += -manual + else + $1_JTREG_BASIC_OPTIONS += -automatic + endif # Make it possible to specify the JIB_DATA_DIR for tests using the # JIB Artifact resolver @@ -1086,7 +1092,7 @@ define SetupRunJtregTestBody $$(call MakeDir, $$($1_TEST_RESULTS_DIR) $$($1_TEST_SUPPORT_DIR) \ $$($1_TEST_TMP_DIR)) $$(call ExecuteWithLog, $$($1_TEST_SUPPORT_DIR)/jtreg, \ - $$(COV_ENVIRONMENT) $$($1_COMMAND_LINE) \ + $$(COV_ENVIRONMENT) $$($1_COMMAND_LINE) \ ) $1_RESULT_FILE := $$($1_TEST_RESULTS_DIR)/text/stats.txt @@ -1096,11 +1102,11 @@ define SetupRunJtregTestBody $$(call LogWarn, Test report is stored in $$(strip \ $$(subst $$(TOPDIR)/, , $$($1_TEST_RESULTS_DIR)))) - # Read jtreg documentation to learn on the test stats categories: - # https://github.com/openjdk/jtreg/blob/master/src/share/doc/javatest/regtest/faq.md#what-do-all-those-numbers-in-the-test-results-line-mean - # In jtreg, "skipped:" category accounts for tests that threw jtreg.SkippedException at runtime. - # At the same time these tests contribute to "passed:" tests. - # In here we don't want that and so we substract number of "skipped:" from "passed:". + # Read jtreg documentation to learn on the test stats categories: + # https://github.com/openjdk/jtreg/blob/master/src/share/doc/javatest/regtest/faq.md#what-do-all-those-numbers-in-the-test-results-line-mean + # In jtreg, "skipped:" category accounts for tests that threw jtreg.SkippedException at runtime. + # At the same time these tests contribute to "passed:" tests. + # In here we don't want that and so we substract number of "skipped:" from "passed:". $$(if $$(wildcard $$($1_RESULT_FILE)), \ $$(eval $1_PASSED_AND_RUNTIME_SKIPPED := $$(shell $$(AWK) '{ gsub(/[,;]/, ""); \ @@ -1151,6 +1157,7 @@ define SetupRunJtregTestBody $$(EXPR) $$($1_PASSED) + $$($1_FAILED) + $$($1_ERROR) + $$($1_SKIPPED))) \ , \ $$(eval $1_PASSED_AND_RUNTIME_SKIPPED := 0) \ + $$(eval $1_PASSED := 0) \ $$(eval $1_RUNTIME_SKIPPED := 0) \ $$(eval $1_SKIPPED := 0) \ $$(eval $1_FAILED := 0) \ diff --git a/make/RunTestsPrebuiltSpec.gmk b/make/RunTestsPrebuiltSpec.gmk index e9fd901c9e1..5fe559eafad 100644 --- a/make/RunTestsPrebuiltSpec.gmk +++ b/make/RunTestsPrebuiltSpec.gmk @@ -1,5 +1,5 @@ # -# Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it diff --git a/make/ToolsJdk.gmk b/make/ToolsJdk.gmk index 629cadbf83a..b04d7820c91 100644 --- a/make/ToolsJdk.gmk +++ b/make/ToolsJdk.gmk @@ -79,7 +79,7 @@ TOOL_GENERATEEXTRAPROPERTIES = $(JAVA_SMALL) -cp $(BUILDTOOLS_OUTPUTDIR)/jdk_too build.tools.generateextraproperties.GenerateExtraProperties TOOL_GENERATECASEFOLDING = $(JAVA_SMALL) -cp $(BUILDTOOLS_OUTPUTDIR)/jdk_tools_classes \ - build.tools.generatecharacter.CaseFolding + build.tools.generatecharacter.GenerateCaseFolding TOOL_MAKEZIPREPRODUCIBLE = $(JAVA_SMALL) -cp $(BUILDTOOLS_OUTPUTDIR)/jdk_tools_classes \ build.tools.makezipreproducible.MakeZipReproducible diff --git a/make/autoconf/basic.m4 b/make/autoconf/basic.m4 index 316bfc5037d..bb6908d9194 100644 --- a/make/autoconf/basic.m4 +++ b/make/autoconf/basic.m4 @@ -353,7 +353,12 @@ AC_DEFUN_ONCE([BASIC_SETUP_DEVKIT], [set up toolchain on Mac OS using a path to an Xcode installation])]) UTIL_DEPRECATED_ARG_WITH(sys-root) - UTIL_DEPRECATED_ARG_WITH(tools-dir) + + AC_ARG_WITH([tools-dir], [AS_HELP_STRING([--with-tools-dir], + [Point to a nonstandard Visual Studio installation location on Windows by + specifying any existing directory 2 or 3 levels below the installation + root.])] + ) if test "x$with_xcode_path" != x; then if test "x$OPENJDK_BUILD_OS" = "xmacosx"; then diff --git a/make/autoconf/bootcycle-spec.gmk.template b/make/autoconf/bootcycle-spec.gmk.template index d17a95e190a..eb564f40e3f 100644 --- a/make/autoconf/bootcycle-spec.gmk.template +++ b/make/autoconf/bootcycle-spec.gmk.template @@ -1,5 +1,5 @@ # -# Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it diff --git a/make/autoconf/buildjdk-spec.gmk.template b/make/autoconf/buildjdk-spec.gmk.template index 924389b94e8..bb020842d59 100644 --- a/make/autoconf/buildjdk-spec.gmk.template +++ b/make/autoconf/buildjdk-spec.gmk.template @@ -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 diff --git a/make/autoconf/compare.sh.template b/make/autoconf/compare.sh.template index 84421035ab9..b17ddc16827 100644 --- a/make/autoconf/compare.sh.template +++ b/make/autoconf/compare.sh.template @@ -1,6 +1,6 @@ #!/bin/bash # -# Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. # 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/make/autoconf/flags-cflags.m4 b/make/autoconf/flags-cflags.m4 index 9d58a280998..5a9fdc57c74 100644 --- a/make/autoconf/flags-cflags.m4 +++ b/make/autoconf/flags-cflags.m4 @@ -69,6 +69,23 @@ AC_DEFUN([FLAGS_SETUP_DEBUG_SYMBOLS], # Debug prefix mapping if supported by compiler DEBUG_PREFIX_CFLAGS= + UTIL_ARG_WITH(NAME: native-debug-symbols-level, TYPE: string, + DEFAULT: "", + RESULT: DEBUG_SYMBOLS_LEVEL, + DESC: [set the native debug symbol level (GCC and Clang only)], + DEFAULT_DESC: [toolchain default]) + AC_SUBST(DEBUG_SYMBOLS_LEVEL) + + if test "x${TOOLCHAIN_TYPE}" = xgcc || \ + test "x${TOOLCHAIN_TYPE}" = xclang; then + DEBUG_SYMBOLS_LEVEL_FLAGS="-g" + if test "x${DEBUG_SYMBOLS_LEVEL}" != "x"; then + DEBUG_SYMBOLS_LEVEL_FLAGS="-g${DEBUG_SYMBOLS_LEVEL}" + FLAGS_COMPILER_CHECK_ARGUMENTS(ARGUMENT: [${DEBUG_SYMBOLS_LEVEL_FLAGS}], + IF_FALSE: AC_MSG_ERROR("Debug info level ${DEBUG_SYMBOLS_LEVEL} is not supported")) + fi + fi + # Debug symbols if test "x$TOOLCHAIN_TYPE" = xgcc; then if test "x$ALLOW_ABSOLUTE_PATHS_IN_OUTPUT" = "xfalse"; then @@ -93,8 +110,9 @@ AC_DEFUN([FLAGS_SETUP_DEBUG_SYMBOLS], ) fi - CFLAGS_DEBUG_SYMBOLS="-g -gdwarf-4" - ASFLAGS_DEBUG_SYMBOLS="-g" + # Debug info level should follow the debug format to be effective. + CFLAGS_DEBUG_SYMBOLS="-gdwarf-4 ${DEBUG_SYMBOLS_LEVEL_FLAGS}" + ASFLAGS_DEBUG_SYMBOLS="${DEBUG_SYMBOLS_LEVEL_FLAGS}" elif test "x$TOOLCHAIN_TYPE" = xclang; then if test "x$ALLOW_ABSOLUTE_PATHS_IN_OUTPUT" = "xfalse"; then # Check if compiler supports -fdebug-prefix-map. If so, use that to make @@ -113,8 +131,9 @@ AC_DEFUN([FLAGS_SETUP_DEBUG_SYMBOLS], FLAGS_COMPILER_CHECK_ARGUMENTS(ARGUMENT: [${GDWARF_FLAGS}], IF_FALSE: [GDWARF_FLAGS=""]) - CFLAGS_DEBUG_SYMBOLS="-g ${GDWARF_FLAGS}" - ASFLAGS_DEBUG_SYMBOLS="-g" + # Debug info level should follow the debug format to be effective. + CFLAGS_DEBUG_SYMBOLS="${GDWARF_FLAGS} ${DEBUG_SYMBOLS_LEVEL_FLAGS}" + ASFLAGS_DEBUG_SYMBOLS="${DEBUG_SYMBOLS_LEVEL_FLAGS}" elif test "x$TOOLCHAIN_TYPE" = xmicrosoft; then CFLAGS_DEBUG_SYMBOLS="-Z7" fi @@ -282,10 +301,17 @@ AC_DEFUN([FLAGS_SETUP_OPTIMIZATION], C_O_FLAG_DEBUG_JVM="-O0" C_O_FLAG_NONE="-O0" + if test "x$TOOLCHAIN_TYPE" = xgcc; then + C_O_FLAG_LTO="-flto=auto -fuse-linker-plugin -fno-strict-aliasing -fno-fat-lto-objects" + else + C_O_FLAG_LTO="-flto -fno-strict-aliasing" + fi + if test "x$TOOLCHAIN_TYPE" = xclang && test "x$OPENJDK_TARGET_OS" = xaix; then C_O_FLAG_HIGHEST_JVM="${C_O_FLAG_HIGHEST_JVM} -finline-functions" C_O_FLAG_HIGHEST="${C_O_FLAG_HIGHEST} -finline-functions" C_O_FLAG_HI="${C_O_FLAG_HI} -finline-functions" + C_O_FLAG_LTO="${C_O_FLAG_LTO} -ffat-lto-objects" fi # -D_FORTIFY_SOURCE=2 hardening option needs optimization (at least -O1) enabled @@ -317,6 +343,7 @@ AC_DEFUN([FLAGS_SETUP_OPTIMIZATION], C_O_FLAG_DEBUG_JVM="" C_O_FLAG_NONE="-Od" C_O_FLAG_SIZE="-O1" + C_O_FLAG_LTO="-GL" fi # Now copy to C++ flags @@ -328,6 +355,7 @@ AC_DEFUN([FLAGS_SETUP_OPTIMIZATION], CXX_O_FLAG_DEBUG_JVM="$C_O_FLAG_DEBUG_JVM" CXX_O_FLAG_NONE="$C_O_FLAG_NONE" CXX_O_FLAG_SIZE="$C_O_FLAG_SIZE" + CXX_O_FLAG_LTO="$C_O_FLAG_LTO" # Adjust optimization flags according to debug level. case $DEBUG_LEVEL in @@ -360,12 +388,15 @@ AC_DEFUN([FLAGS_SETUP_OPTIMIZATION], AC_SUBST(C_O_FLAG_NORM) AC_SUBST(C_O_FLAG_NONE) AC_SUBST(C_O_FLAG_SIZE) + AC_SUBST(C_O_FLAG_LTO) + AC_SUBST(CXX_O_FLAG_HIGHEST_JVM) AC_SUBST(CXX_O_FLAG_HIGHEST) AC_SUBST(CXX_O_FLAG_HI) AC_SUBST(CXX_O_FLAG_NORM) AC_SUBST(CXX_O_FLAG_NONE) AC_SUBST(CXX_O_FLAG_SIZE) + AC_SUBST(CXX_O_FLAG_LTO) ]) AC_DEFUN([FLAGS_SETUP_CFLAGS], diff --git a/make/autoconf/flags-ldflags.m4 b/make/autoconf/flags-ldflags.m4 index 66f8904db89..466ff1beaf4 100644 --- a/make/autoconf/flags-ldflags.m4 +++ b/make/autoconf/flags-ldflags.m4 @@ -34,7 +34,7 @@ AC_DEFUN([FLAGS_SETUP_LDFLAGS], FLAGS_SETUP_LDFLAGS_CPU_DEP([TARGET]) # Setup the build toolchain - FLAGS_SETUP_LDFLAGS_CPU_DEP([BUILD], [OPENJDK_BUILD_]) + FLAGS_SETUP_LDFLAGS_CPU_DEP([BUILD], [OPENJDK_BUILD_], [BUILD_]) AC_SUBST(ADLC_LDFLAGS) ]) @@ -50,7 +50,9 @@ AC_DEFUN([FLAGS_SETUP_LDFLAGS_HELPER], # add -z,relro (mark relocations read only) for all libs # add -z,now ("full relro" - more of the Global Offset Table GOT is marked read only) # add --no-as-needed to disable default --as-needed link flag on some GCC toolchains + # add --icf=all (Identical Code Folding — merges identical functions) BASIC_LDFLAGS="-Wl,-z,defs -Wl,-z,relro -Wl,-z,now -Wl,--no-as-needed -Wl,--exclude-libs,ALL" + # Linux : remove unused code+data in link step if test "x$ENABLE_LINKTIME_GC" = xtrue; then if test "x$OPENJDK_TARGET_CPU" = xs390x; then @@ -61,6 +63,7 @@ AC_DEFUN([FLAGS_SETUP_LDFLAGS_HELPER], fi BASIC_LDFLAGS_JVM_ONLY="" + LDFLAGS_LTO="-flto=auto -fuse-linker-plugin -fno-strict-aliasing $DEBUG_PREFIX_CFLAGS" LDFLAGS_CXX_PARTIAL_LINKING="$MACHINE_FLAG -r" @@ -68,6 +71,7 @@ AC_DEFUN([FLAGS_SETUP_LDFLAGS_HELPER], BASIC_LDFLAGS_JVM_ONLY="-mno-omit-leaf-frame-pointer -mstack-alignment=16 \ -fPIC" + LDFLAGS_LTO="-flto=auto -fuse-linker-plugin -fno-strict-aliasing $DEBUG_PREFIX_CFLAGS" LDFLAGS_CXX_PARTIAL_LINKING="$MACHINE_FLAG -r" if test "x$OPENJDK_TARGET_OS" = xlinux; then @@ -87,6 +91,7 @@ AC_DEFUN([FLAGS_SETUP_LDFLAGS_HELPER], BASIC_LDFLAGS="-opt:ref" BASIC_LDFLAGS_JDK_ONLY="-incremental:no" BASIC_LDFLAGS_JVM_ONLY="-opt:icf,8 -subsystem:windows" + LDFLAGS_LTO="-LTCG:INCREMENTAL" fi if (test "x$TOOLCHAIN_TYPE" = xgcc || test "x$TOOLCHAIN_TYPE" = xclang) \ @@ -98,6 +103,9 @@ AC_DEFUN([FLAGS_SETUP_LDFLAGS_HELPER], # Setup OS-dependent LDFLAGS if test "x$OPENJDK_TARGET_OS" = xmacosx && test "x$TOOLCHAIN_TYPE" = xclang; then + if test x$DEBUG_LEVEL = xrelease; then + BASIC_LDFLAGS_JDK_ONLY="$BASIC_LDFLAGS_JDK_ONLY -Wl,-dead_strip" + fi # FIXME: We should really generalize SetSharedLibraryOrigin instead. OS_LDFLAGS_JVM_ONLY="-Wl,-rpath,@loader_path/. -Wl,-rpath,@loader_path/.." OS_LDFLAGS="-mmacosx-version-min=$MACOSX_VERSION_MIN -Wl,-reproducible" @@ -148,6 +156,7 @@ AC_DEFUN([FLAGS_SETUP_LDFLAGS_HELPER], # Export some intermediate variables for compatibility LDFLAGS_CXX_JDK="$DEBUGLEVEL_LDFLAGS_JDK_ONLY" + AC_SUBST(LDFLAGS_LTO) AC_SUBST(LDFLAGS_CXX_JDK) AC_SUBST(LDFLAGS_CXX_PARTIAL_LINKING) ]) @@ -155,7 +164,8 @@ AC_DEFUN([FLAGS_SETUP_LDFLAGS_HELPER], ################################################################################ # $1 - Either BUILD or TARGET to pick the correct OS/CPU variables to check # conditionals against. -# $2 - Optional prefix for each variable defined. +# $2 - Optional prefix for each variable defined (OPENJDK_BUILD_ or nothing). +# $3 - Optional prefix for toolchain variables (BUILD_ or nothing). AC_DEFUN([FLAGS_SETUP_LDFLAGS_CPU_DEP], [ # Setup CPU-dependent basic LDFLAGS. These can differ between the target and @@ -189,6 +199,12 @@ AC_DEFUN([FLAGS_SETUP_LDFLAGS_CPU_DEP], fi fi + if test "x${$3LD_TYPE}" = "xgold"; then + if test x$DEBUG_LEVEL = xrelease; then + $1_CPU_LDFLAGS="${$1_CPU_LDFLAGS} -Wl,--icf=all" + fi + fi + # Export variables according to old definitions, prefix with $2 if present. LDFLAGS_JDK_COMMON="$BASIC_LDFLAGS $BASIC_LDFLAGS_JDK_ONLY \ $OS_LDFLAGS $DEBUGLEVEL_LDFLAGS_JDK_ONLY ${$2EXTRA_LDFLAGS}" diff --git a/make/autoconf/hotspot.m4 b/make/autoconf/hotspot.m4 index 6dc46d17aa3..3adb1333fe5 100644 --- a/make/autoconf/hotspot.m4 +++ b/make/autoconf/hotspot.m4 @@ -1,5 +1,5 @@ # -# Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it diff --git a/make/autoconf/jdk-options.m4 b/make/autoconf/jdk-options.m4 index bb188778001..87d147d4f07 100644 --- a/make/autoconf/jdk-options.m4 +++ b/make/autoconf/jdk-options.m4 @@ -316,23 +316,36 @@ AC_DEFUN_ONCE([JDKOPT_SETUP_DEBUG_SYMBOLS], AC_MSG_CHECKING([if we should add external native debug symbols to the shipped bundles]) AC_ARG_WITH([external-symbols-in-bundles], [AS_HELP_STRING([--with-external-symbols-in-bundles], - [which type of external native debug symbol information shall be shipped in product bundles (none, public, full) - (e.g. ship full/stripped pdbs on Windows) @<:@none@:>@])]) + [which type of external native debug symbol information shall be shipped with bundles/images (none, public, full). + @<:@none in release builds, full otherwise. --with-native-debug-symbols=external/zipped is a prerequisite. public is only supported on Windows@:>@])], + [], + [with_external_symbols_in_bundles=default]) if test "x$with_external_symbols_in_bundles" = x || test "x$with_external_symbols_in_bundles" = xnone ; then AC_MSG_RESULT([no]) elif test "x$with_external_symbols_in_bundles" = xfull || test "x$with_external_symbols_in_bundles" = xpublic ; then - if test "x$OPENJDK_TARGET_OS" != xwindows ; then - AC_MSG_ERROR([--with-external-symbols-in-bundles currently only works on windows!]) - elif test "x$COPY_DEBUG_SYMBOLS" != xtrue ; then - AC_MSG_ERROR([--with-external-symbols-in-bundles only works when --with-native-debug-symbols=external is used!]) - elif test "x$with_external_symbols_in_bundles" = xfull ; then + if test "x$COPY_DEBUG_SYMBOLS" != xtrue ; then + AC_MSG_ERROR([--with-external-symbols-in-bundles only works when --with-native-debug-symbols=external/zipped is used!]) + elif test "x$with_external_symbols_in_bundles" = xpublic && test "x$OPENJDK_TARGET_OS" != xwindows ; then + AC_MSG_ERROR([--with-external-symbols-in-bundles=public is only supported on Windows!]) + fi + + if test "x$with_external_symbols_in_bundles" = xfull ; then AC_MSG_RESULT([full]) SHIP_DEBUG_SYMBOLS=full else AC_MSG_RESULT([public]) SHIP_DEBUG_SYMBOLS=public fi + elif test "x$with_external_symbols_in_bundles" = xdefault ; then + if test "x$DEBUG_LEVEL" = xrelease ; then + AC_MSG_RESULT([no (default)]) + elif test "x$COPY_DEBUG_SYMBOLS" = xtrue ; then + AC_MSG_RESULT([full (default)]) + SHIP_DEBUG_SYMBOLS=full + else + AC_MSG_RESULT([no (default, native debug symbols are not external/zipped)]) + fi else AC_MSG_ERROR([$with_external_symbols_in_bundles is an unknown value for --with-external-symbols-in-bundles]) fi diff --git a/make/autoconf/lib-bundled.m4 b/make/autoconf/lib-bundled.m4 index 3246697663c..a6266bec014 100644 --- a/make/autoconf/lib-bundled.m4 +++ b/make/autoconf/lib-bundled.m4 @@ -1,5 +1,5 @@ # -# Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it diff --git a/make/autoconf/platform.m4 b/make/autoconf/platform.m4 index 31451d0c37f..1b247e159ac 100644 --- a/make/autoconf/platform.m4 +++ b/make/autoconf/platform.m4 @@ -1,5 +1,5 @@ # -# Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it diff --git a/make/autoconf/spec.gmk.template b/make/autoconf/spec.gmk.template index 0b336721d65..b3d58704c50 100644 --- a/make/autoconf/spec.gmk.template +++ b/make/autoconf/spec.gmk.template @@ -513,12 +513,14 @@ C_O_FLAG_HI := @C_O_FLAG_HI@ C_O_FLAG_NORM := @C_O_FLAG_NORM@ C_O_FLAG_NONE := @C_O_FLAG_NONE@ C_O_FLAG_SIZE := @C_O_FLAG_SIZE@ +C_O_FLAG_LTO := @C_O_FLAG_LTO@ CXX_O_FLAG_HIGHEST_JVM := @CXX_O_FLAG_HIGHEST_JVM@ CXX_O_FLAG_HIGHEST := @CXX_O_FLAG_HIGHEST@ CXX_O_FLAG_HI := @CXX_O_FLAG_HI@ CXX_O_FLAG_NORM := @CXX_O_FLAG_NORM@ CXX_O_FLAG_NONE := @CXX_O_FLAG_NONE@ CXX_O_FLAG_SIZE := @CXX_O_FLAG_SIZE@ +CXX_O_FLAG_LTO := @CXX_O_FLAG_LTO@ GENDEPS_FLAGS := @GENDEPS_FLAGS@ @@ -587,6 +589,9 @@ LDFLAGS_CXX_JDK := @LDFLAGS_CXX_JDK@ # LDFLAGS specific to partial linking. LDFLAGS_CXX_PARTIAL_LINKING := @LDFLAGS_CXX_PARTIAL_LINKING@ +# LDFLAGS specific to link time optimization +LDFLAGS_LTO := @LDFLAGS_LTO@ + # Sometimes a different linker is needed for c++ libs LDCXX := @LDCXX@ # The flags for linking libstdc++ linker. diff --git a/make/autoconf/toolchain.m4 b/make/autoconf/toolchain.m4 index 4662c62d901..c882deb10a7 100644 --- a/make/autoconf/toolchain.m4 +++ b/make/autoconf/toolchain.m4 @@ -516,6 +516,7 @@ AC_DEFUN([TOOLCHAIN_EXTRACT_LD_VERSION], if [ [[ "$LINKER_VERSION_STRING" == *gold* ]] ]; then [ LINKER_VERSION_NUMBER=`$ECHO $LINKER_VERSION_STRING | \ $SED -e 's/.* \([0-9][0-9]*\(\.[0-9][0-9]*\)*\).*) .*/\1/'` ] + $1_TYPE=gold else [ LINKER_VERSION_NUMBER=`$ECHO $LINKER_VERSION_STRING | \ $SED -e 's/.* \([0-9][0-9]*\(\.[0-9][0-9]*\)*\).*/\1/'` ] diff --git a/make/common/NativeCompilation.gmk b/make/common/NativeCompilation.gmk index 9721f1c0aca..28e186adf5f 100644 --- a/make/common/NativeCompilation.gmk +++ b/make/common/NativeCompilation.gmk @@ -98,6 +98,7 @@ include native/Paths.gmk # SYSROOT_CFLAGS the compiler flags for using the specific sysroot # SYSROOT_LDFLAGS the linker flags for using the specific sysroot # OPTIMIZATION sets optimization level to NONE, LOW, HIGH, HIGHEST, HIGHEST_JVM, SIZE +# LINK_TIME_OPTIMIZATION if set to true, enables link time optimization # DISABLED_WARNINGS_ Disable the given warnings for the specified toolchain # DISABLED_WARNINGS__ Disable the given warnings for the specified # toolchain and target OS diff --git a/make/common/Utils.gmk b/make/common/Utils.gmk index c0ebabca3f7..b4bb949d3ee 100644 --- a/make/common/Utils.gmk +++ b/make/common/Utils.gmk @@ -114,7 +114,7 @@ EscapeDollar = $(subst $$,\$$,$(subst \$$,$$,$(strip $1))) ################################################################################ # This macro works just like EscapeDollar above, but for #. -EscapeHash = $(subst \#,\\\#,$(subst \\\#,\#,$(strip $1))) +EscapeHash = $(subst $(HASH),\$(HASH),$(subst \$(HASH),$(HASH),$(strip $1))) ################################################################################ # This macro translates $ into $$ to protect the string from make itself. diff --git a/make/common/native/Flags.gmk b/make/common/native/Flags.gmk index 747e090b816..6353b490654 100644 --- a/make/common/native/Flags.gmk +++ b/make/common/native/Flags.gmk @@ -194,6 +194,11 @@ define SetupCompilerFlags $1_EXTRA_CXXFLAGS += $(CFLAGS_WARNINGS_ARE_ERRORS) endif + ifeq (true, $$($1_LINK_TIME_OPTIMIZATION)) + $1_EXTRA_CFLAGS += $(C_O_FLAG_LTO) + $1_EXTRA_CXXFLAGS += $(CXX_O_FLAG_LTO) + endif + ifeq (NONE, $$($1_OPTIMIZATION)) $1_OPT_CFLAGS := $(C_O_FLAG_NONE) $1_OPT_CXXFLAGS := $(CXX_O_FLAG_NONE) @@ -222,6 +227,18 @@ define SetupLinkerFlags # Pickup extra OPENJDK_TARGET_OS_TYPE, OPENJDK_TARGET_OS and TOOLCHAIN_TYPE # dependent variables for LDFLAGS and LIBS, and additionally the pair dependent # TOOLCHAIN_TYPE plus OPENJDK_TARGET_OS + ifeq ($$($1_LINK_TIME_OPTIMIZATION), true) + $1_EXTRA_LDFLAGS += $(LDFLAGS_LTO) + # Instruct the ld64 linker not to delete the temporary object file + # generated during Link Time Optimization + ifeq ($(call isTargetOs, macosx), true) + $1_EXTRA_LDFLAGS += -Wl,-object_path_lto,$$($1_OBJECT_DIR)/$$($1_NAME)_lto_helper.o + endif + ifeq ($(TOOLCHAIN_TYPE), microsoft) + $1_EXTRA_LDFLAGS += -LTCGOUT:$$($1_OBJECT_DIR)/$$($1_NAME).iobj + endif + endif + $1_EXTRA_LDFLAGS += $$($1_LDFLAGS_$(OPENJDK_TARGET_OS_TYPE)) $$($1_LDFLAGS_$(OPENJDK_TARGET_OS)) \ $$($1_LDFLAGS_$(TOOLCHAIN_TYPE)) $$($1_LDFLAGS_$(TOOLCHAIN_TYPE)_$(OPENJDK_TARGET_OS)) $1_EXTRA_LIBS += $$($1_LIBS_$(OPENJDK_TARGET_OS_TYPE)) $$($1_LIBS_$(OPENJDK_TARGET_OS)) \ diff --git a/make/conf/jib-profiles.js b/make/conf/jib-profiles.js index 795335d7c3c..93aeebc0dd6 100644 --- a/make/conf/jib-profiles.js +++ b/make/conf/jib-profiles.js @@ -1192,8 +1192,8 @@ var getJibProfilesDependencies = function (input, common) { server: "jpg", product: "jcov", version: "3.0", - build_number: "3", - file: "bundles/jcov-3.0+3.zip", + build_number: "5", + file: "bundles/jcov-3.0+5.zip", environment_name: "JCOV_HOME", }, diff --git a/make/conf/version-numbers.conf b/make/conf/version-numbers.conf index 977809535ba..4392d86ac33 100644 --- a/make/conf/version-numbers.conf +++ b/make/conf/version-numbers.conf @@ -26,17 +26,17 @@ # Default version, product, and vendor information to use, # unless overridden by configure -DEFAULT_VERSION_FEATURE=26 +DEFAULT_VERSION_FEATURE=27 DEFAULT_VERSION_INTERIM=0 DEFAULT_VERSION_UPDATE=0 DEFAULT_VERSION_PATCH=0 DEFAULT_VERSION_EXTRA1=0 DEFAULT_VERSION_EXTRA2=0 DEFAULT_VERSION_EXTRA3=0 -DEFAULT_VERSION_DATE=2026-03-17 -DEFAULT_VERSION_CLASSFILE_MAJOR=70 # "`$EXPR $DEFAULT_VERSION_FEATURE + 44`" +DEFAULT_VERSION_DATE=2026-09-15 +DEFAULT_VERSION_CLASSFILE_MAJOR=71 # "`$EXPR $DEFAULT_VERSION_FEATURE + 44`" DEFAULT_VERSION_CLASSFILE_MINOR=0 DEFAULT_VERSION_DOCS_API_SINCE=11 -DEFAULT_ACCEPTABLE_BOOT_VERSIONS="25 26" -DEFAULT_JDK_SOURCE_TARGET_VERSION=26 +DEFAULT_ACCEPTABLE_BOOT_VERSIONS="25 26 27" +DEFAULT_JDK_SOURCE_TARGET_VERSION=27 DEFAULT_PROMOTED_VERSION_PRE=ea diff --git a/make/devkit/createWindowsDevkit.sh b/make/devkit/createWindowsDevkit.sh index 7c55605c776..b21557ed498 100644 --- a/make/devkit/createWindowsDevkit.sh +++ b/make/devkit/createWindowsDevkit.sh @@ -1,6 +1,6 @@ #!/bin/bash # -# Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it diff --git a/make/hotspot/gensrc/GensrcAdlc.gmk b/make/hotspot/gensrc/GensrcAdlc.gmk index 4cecc3070e7..14117421d1b 100644 --- a/make/hotspot/gensrc/GensrcAdlc.gmk +++ b/make/hotspot/gensrc/GensrcAdlc.gmk @@ -170,6 +170,7 @@ ifeq ($(call check-jvm-feature, compiler2), true) ifeq ($(HOTSPOT_TARGET_CPU_ARCH), aarch64) AD_SRC_FILES += $(call uniq, $(wildcard $(foreach d, $(AD_SRC_ROOTS), \ $d/cpu/$(HOTSPOT_TARGET_CPU_ARCH)/$(HOTSPOT_TARGET_CPU_ARCH)_vector.ad \ + $d/cpu/$(HOTSPOT_TARGET_CPU_ARCH)/$(HOTSPOT_TARGET_CPU_ARCH)_atomic.ad \ ))) endif diff --git a/make/hotspot/lib/CompileJvm.gmk b/make/hotspot/lib/CompileJvm.gmk index a8b90c92e4d..39a549b7db0 100644 --- a/make/hotspot/lib/CompileJvm.gmk +++ b/make/hotspot/lib/CompileJvm.gmk @@ -151,6 +151,12 @@ JVM_STRIPFLAGS ?= $(STRIPFLAGS) # This source set is reused so save in cache. $(call FillFindCache, $(JVM_SRC_DIRS)) +ifeq ($(SHIP_DEBUG_SYMBOLS), full) + CFLAGS_SHIP_DEBUGINFO := -DSHIP_DEBUGINFO_FULL +else ifeq ($(SHIP_DEBUG_SYMBOLS), public) + CFLAGS_SHIP_DEBUGINFO := -DSHIP_DEBUGINFO_PUBLIC +endif + ifeq ($(call isTargetOs, windows), true) ifeq ($(STATIC_LIBS), true) WIN_EXPORT_FILE := $(JVM_OUTPUTDIR)/static-win-exports.def @@ -158,10 +164,6 @@ ifeq ($(call isTargetOs, windows), true) WIN_EXPORT_FILE := $(JVM_OUTPUTDIR)/win-exports.def endif - ifeq ($(SHIP_DEBUG_SYMBOLS), public) - CFLAGS_STRIPPED_DEBUGINFO := -DHAS_STRIPPED_DEBUGINFO - endif - JVM_LDFLAGS += -def:$(WIN_EXPORT_FILE) endif @@ -187,7 +189,7 @@ $(eval $(call SetupJdkLibrary, BUILD_LIBJVM, \ CFLAGS := $(JVM_CFLAGS), \ abstract_vm_version.cpp_CXXFLAGS := $(CFLAGS_VM_VERSION), \ arguments.cpp_CXXFLAGS := $(CFLAGS_VM_VERSION), \ - whitebox.cpp_CXXFLAGS := $(CFLAGS_STRIPPED_DEBUGINFO), \ + whitebox.cpp_CXXFLAGS := $(CFLAGS_SHIP_DEBUGINFO), \ DISABLED_WARNINGS_gcc := $(DISABLED_WARNINGS_gcc), \ DISABLED_WARNINGS_gcc_ad_$(HOTSPOT_TARGET_CPU_ARCH).cpp := nonnull, \ DISABLED_WARNINGS_gcc_bytecodeInterpreter.cpp := unused-label, \ @@ -234,6 +236,7 @@ $(eval $(call SetupJdkLibrary, BUILD_LIBJVM, \ LDFLAGS := $(JVM_LDFLAGS), \ LIBS := $(JVM_LIBS), \ OPTIMIZATION := $(JVM_OPTIMIZATION), \ + LINK_TIME_OPTIMIZATION := $(JVM_LTO), \ OBJECT_DIR := $(JVM_OUTPUTDIR)/objs, \ STRIPFLAGS := $(JVM_STRIPFLAGS), \ EMBED_MANIFEST := true, \ diff --git a/make/hotspot/lib/JvmFeatures.gmk b/make/hotspot/lib/JvmFeatures.gmk index 79bbd6a4106..90ea8a985e3 100644 --- a/make/hotspot/lib/JvmFeatures.gmk +++ b/make/hotspot/lib/JvmFeatures.gmk @@ -175,22 +175,12 @@ ifeq ($(call check-jvm-feature, link-time-opt), true) # Set JVM_OPTIMIZATION directly so other jvm-feature flags can override it # later on if desired JVM_OPTIMIZATION := HIGHEST_JVM - ifeq ($(call isCompiler, gcc), true) - JVM_CFLAGS_FEATURES += -flto=auto -fuse-linker-plugin -fno-strict-aliasing \ - -fno-fat-lto-objects - JVM_LDFLAGS_FEATURES += $(CXX_O_FLAG_HIGHEST_JVM) -flto=auto \ - -fuse-linker-plugin -fno-strict-aliasing - else ifeq ($(call isCompiler, clang), true) - JVM_CFLAGS_FEATURES += -flto -fno-strict-aliasing - ifeq ($(call isBuildOs, aix), true) - JVM_CFLAGS_FEATURES += -ffat-lto-objects - endif - JVM_LDFLAGS_FEATURES += $(CXX_O_FLAG_HIGHEST_JVM) -flto -fno-strict-aliasing - else ifeq ($(call isCompiler, microsoft), true) - JVM_CFLAGS_FEATURES += -GL - JVM_LDFLAGS_FEATURES += -LTCG:INCREMENTAL + JVM_LTO := true + ifneq ($(call isCompiler, microsoft), true) + JVM_LDFLAGS_FEATURES += $(CXX_O_FLAG_HIGHEST_JVM) endif else + JVM_LTO := false ifeq ($(call isCompiler, gcc), true) JVM_LDFLAGS_FEATURES += -O1 endif diff --git a/make/jdk/src/classes/build/tools/generatecharacter/CaseFolding.java b/make/jdk/src/classes/build/tools/generatecharacter/CaseFolding.java deleted file mode 100644 index 9abc2059b6a..00000000000 --- a/make/jdk/src/classes/build/tools/generatecharacter/CaseFolding.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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 build.tools.generatecharacter; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public class CaseFolding { - - public static void main(String[] args) throws Throwable { - if (args.length != 3) { - System.err.println("Usage: java CaseFolding TemplateFile CaseFolding.txt CaseFolding.java"); - System.exit(1); - } - var templateFile = Paths.get(args[0]); - var caseFoldingTxt = Paths.get(args[1]); - var genSrcFile = Paths.get(args[2]); - var supportedTypes = "^.*; [CTS]; .*$"; - var caseFoldingEntries = Files.lines(caseFoldingTxt) - .filter(line -> !line.startsWith("#") && line.matches(supportedTypes)) - .map(line -> { - String[] cols = line.split("; "); - return new String[] {cols[0], cols[1], cols[2]}; - }) - .filter(cols -> { - // the folding case doesn't map back to the original char. - var cp1 = Integer.parseInt(cols[0], 16); - var cp2 = Integer.parseInt(cols[2], 16); - return Character.toUpperCase(cp2) != cp1 && Character.toLowerCase(cp2) != cp1; - }) - .map(cols -> String.format(" entry(0x%s, 0x%s)", cols[0], cols[2])) - .collect(Collectors.joining(",\n", "", "")); - - // hack, hack, hack! the logic does not pick 0131. just add manually to support 'I's. - // 0049; T; 0131; # LATIN CAPITAL LETTER I - final String T_0x0131_0x49 = String.format(" entry(0x%04x, 0x%04x),\n", 0x0131, 0x49); - - // Generate .java file - Files.write( - genSrcFile, - Files.lines(templateFile) - .map(line -> line.contains("%%%Entries") ? T_0x0131_0x49 + caseFoldingEntries : line) - .collect(Collectors.toList()), - StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); - } -} diff --git a/make/jdk/src/classes/build/tools/generatecharacter/GenerateCaseFolding.java b/make/jdk/src/classes/build/tools/generatecharacter/GenerateCaseFolding.java new file mode 100644 index 00000000000..2f6a9add5cb --- /dev/null +++ b/make/jdk/src/classes/build/tools/generatecharacter/GenerateCaseFolding.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. 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 build.tools.generatecharacter; + +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.Arrays; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public class GenerateCaseFolding { + + public static void main(String[] args) throws Throwable { + if (args.length != 3) { + System.err.println("Usage: java GenerateCaseFolding TemplateFile CaseFolding.txt CaseFolding.java"); + System.exit(1); + } + var templateFile = Paths.get(args[0]); + var caseFoldingTxt = Paths.get(args[1]); + var genSrcFile = Paths.get(args[2]); + + // java.lang + var supportedTypes = "^.*; [CF]; .*$"; // full/1:M case folding + String[][] caseFoldings = Files.lines(caseFoldingTxt) + .filter(line -> !line.startsWith("#") && line.matches(supportedTypes)) + .map(line -> { + var fields = line.split("; "); + var cp = fields[0]; + fields = fields[2].trim().split(" "); + var folding = new String[fields.length + 1]; + folding[0] = cp; + System.arraycopy(fields, 0, folding, 1, fields.length); + return folding; + }) + .toArray(size -> new String[size][]); + + // util.regex + var expandedSupportedTypes = "^.*; [CTS]; .*$"; + var expanded_caseFoldingEntries = Files.lines(caseFoldingTxt) + .filter(line -> !line.startsWith("#") && line.matches(expandedSupportedTypes)) + .map(line -> { + String[] cols = line.split("; "); + return new String[]{cols[0], cols[1], cols[2]}; + }) + .filter(cols -> { + // the folding case doesn't map back to the original char. + var cp1 = Integer.parseInt(cols[0], 16); + var cp2 = Integer.parseInt(cols[2], 16); + return Character.toUpperCase(cp2) != cp1 && Character.toLowerCase(cp2) != cp1; + }) + .map(cols -> String.format(" entry(0x%s, 0x%s)", cols[0], cols[2])) + .collect(Collectors.joining(",\n", "", "")); + + // hack, hack, hack! the logic does not pick 0131. just add manually to support 'I's. + // 0049; T; 0131; # LATIN CAPITAL LETTER I + final String T_0x0131_0x49 = String.format(" entry(0x%04x, 0x%04x),\n", 0x0131, 0x49); + + Files.write( + genSrcFile, + Files.lines(templateFile) + .map(line -> line.contains("%%%Entries") ? genFoldingEntries(caseFoldings) : line) + .map(line -> line.contains("%%%Expanded_Case_Map_Entries") ? T_0x0131_0x49 + expanded_caseFoldingEntries : line) + .collect(Collectors.toList()), + StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + } + + private static long foldingToLong(String[] folding) { + int cp = Integer.parseInt(folding[0], 16); + long value = (long)Integer.parseInt(folding[1], 16); + if (!Character.isSupplementaryCodePoint(cp) && folding.length != 2) { + var shift = 16; + for (int j = 2; j < folding.length; j++) { + value |= (long)Integer.parseInt(folding[j], 16) << shift; + shift <<= 1; + } + value = value | (long) (folding.length - 1) << 48; + } + return value; + } + + private static String genFoldingEntries(String[][] foldings) { + StringBuilder sb = new StringBuilder(); + sb.append(" private static final int[] CASE_FOLDING_CPS = {\n"); + int width = 10; + for (int i = 0; i < foldings.length; i++) { + if (i % width == 0) + sb.append(" "); + sb.append(String.format("0X%s", foldings[i][0])); + if (i < foldings.length - 1) + sb.append(", "); + if (i % width == width - 1 || i == foldings.length - 1) + sb.append("\n"); + } + sb.append(" };\n\n"); + + sb.append(" private static final long[] CASE_FOLDING_VALUES = {\n"); + width = 6; + for (int i = 0; i < foldings.length; i++) { + if (i % width == 0) + sb.append(" "); // indent + sb.append(String.format("0x%013xL", foldingToLong(foldings[i]))); + if (i < foldings.length - 1) + sb.append(", "); + if (i % width == width - 1 || i == foldings.length - 1) { + sb.append("\n"); + } + } + sb.append(" };\n"); + return sb.toString(); + } +} diff --git a/make/jdk/src/classes/build/tools/pandocfilter/PandocFilter.java b/make/jdk/src/classes/build/tools/pandocfilter/PandocFilter.java index ebb49613e53..9f4d84ac95b 100644 --- a/make/jdk/src/classes/build/tools/pandocfilter/PandocFilter.java +++ b/make/jdk/src/classes/build/tools/pandocfilter/PandocFilter.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 diff --git a/make/jdk/src/classes/build/tools/taglet/JSpec.java b/make/jdk/src/classes/build/tools/taglet/JSpec.java index ca45104e086..1c301e94a8a 100644 --- a/make/jdk/src/classes/build/tools/taglet/JSpec.java +++ b/make/jdk/src/classes/build/tools/taglet/JSpec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,10 +31,9 @@ import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; +import java.lang.reflect.Field; import javax.lang.model.element.Element; -import javax.lang.model.element.PackageElement; -import javax.lang.model.element.TypeElement; import com.sun.source.doctree.DocTree; import com.sun.source.doctree.LiteralTree; @@ -160,9 +159,10 @@ public class JSpec implements Taglet { if (m.find()) { String chapter = m.group("chapter"); String section = m.group("section"); + String rootParent = currentPath().replaceAll("[^/]+", ".."); - String url = String.format("%1$s/../specs/%2$s/%2$s-%3$s.html#%2$s-%3$s%4$s", - docRoot(elem), idPrefix, chapter, section); + String url = String.format("%1$s/specs/%2$s/%2$s-%3$s.html#%2$s-%3$s%4$s", + rootParent, idPrefix, chapter, section); sb.append(" CURRENT_PATH = null; + + private String currentPath() { + if (CURRENT_PATH == null) { + try { + Field f = Class.forName("jdk.javadoc.internal.doclets.formats.html.HtmlDocletWriter") + .getField("CURRENT_PATH"); + @SuppressWarnings("unchecked") + ThreadLocal tl = (ThreadLocal) f.get(null); + CURRENT_PATH = tl; + } catch (ReflectiveOperationException e) { + throw new RuntimeException("Cannot determine current path", e); + } + } + return CURRENT_PATH.get(); + } private String expand(List trees) { return (new SimpleDocTreeVisitor() { @@ -209,34 +225,4 @@ public class JSpec implements Taglet { }).visit(trees, new StringBuilder()).toString(); } - private String docRoot(Element elem) { - switch (elem.getKind()) { - case MODULE: - return ".."; - - case PACKAGE: - PackageElement pe = (PackageElement)elem; - String pkgPart = pe.getQualifiedName() - .toString() - .replace('.', '/') - .replaceAll("[^/]+", ".."); - return pe.getEnclosingElement() != null - ? "../" + pkgPart - : pkgPart; - - case CLASS, ENUM, RECORD, INTERFACE, ANNOTATION_TYPE: - TypeElement te = (TypeElement)elem; - return te.getQualifiedName() - .toString() - .replace('.', '/') - .replaceAll("[^/]+", ".."); - - default: - var enclosing = elem.getEnclosingElement(); - if (enclosing == null) - throw new IllegalArgumentException(elem.getKind().toString()); - return docRoot(enclosing); - } - } - } diff --git a/make/jdk/src/classes/build/tools/taglet/ToolGuide.java b/make/jdk/src/classes/build/tools/taglet/ToolGuide.java index 72a8ab05b3b..8db2aee3092 100644 --- a/make/jdk/src/classes/build/tools/taglet/ToolGuide.java +++ b/make/jdk/src/classes/build/tools/taglet/ToolGuide.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,10 +31,9 @@ import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; +import java.lang.reflect.Field; import javax.lang.model.element.Element; -import javax.lang.model.element.PackageElement; -import javax.lang.model.element.TypeElement; import com.sun.source.doctree.DocTree; import com.sun.source.doctree.UnknownBlockTagTree; @@ -68,7 +67,7 @@ public class ToolGuide implements Taglet { static final String TAG_NAME = "toolGuide"; - static final String BASE_URL = "../specs/man"; + static final String BASE_URL = "specs/man"; static final Pattern TAG_PATTERN = Pattern.compile("(?s)(?[A-Za-z0-9]+)\\s*(?

Array class creation does not generate a class load event. The creation of a primitive class (for example, java.lang.Integer.TYPE) diff --git a/src/hotspot/share/prims/jvmtiAgent.hpp b/src/hotspot/share/prims/jvmtiAgent.hpp index b2e312c8030..f3873ff9559 100644 --- a/src/hotspot/share/prims/jvmtiAgent.hpp +++ b/src/hotspot/share/prims/jvmtiAgent.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. * 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/prims/jvmtiClassFileReconstituter.cpp b/src/hotspot/share/prims/jvmtiClassFileReconstituter.cpp index a441d405f8d..5077a1743b9 100644 --- a/src/hotspot/share/prims/jvmtiClassFileReconstituter.cpp +++ b/src/hotspot/share/prims/jvmtiClassFileReconstituter.cpp @@ -25,6 +25,7 @@ #include "classfile/symbolTable.hpp" #include "interpreter/bytecodeStream.hpp" #include "memory/universe.hpp" +#include "oops/bsmAttribute.inline.hpp" #include "oops/constantPool.inline.hpp" #include "oops/fieldStreams.inline.hpp" #include "oops/instanceKlass.inline.hpp" @@ -389,20 +390,13 @@ void JvmtiClassFileReconstituter::write_annotations_attribute(const char* attr_n // } bootstrap_methods[num_bootstrap_methods]; // } void JvmtiClassFileReconstituter::write_bootstrapmethod_attribute() { - Array* operands = cpool()->operands(); write_attribute_name_index("BootstrapMethods"); - int num_bootstrap_methods = ConstantPool::operand_array_length(operands); - - // calculate length of attribute - u4 length = sizeof(u2); // num_bootstrap_methods - for (int n = 0; n < num_bootstrap_methods; n++) { - u2 num_bootstrap_arguments = cpool()->bsm_attribute_entry(n)->argument_count(); - length += sizeof(u2); // bootstrap_method_ref - length += sizeof(u2); // num_bootstrap_arguments - length += (u4)sizeof(u2) * num_bootstrap_arguments; // bootstrap_arguments[num_bootstrap_arguments] - } + u4 length = sizeof(u2) + // Size of num_bootstrap_methods + // The rest of the data for the attribute is exactly the u2s in the data array. + sizeof(u2) * cpool()->bsm_entries().array_length(); write_u4(length); + int num_bootstrap_methods = cpool()->bsm_entries().number_of_entries(); // write attribute write_u2(checked_cast(num_bootstrap_methods)); for (int n = 0; n < num_bootstrap_methods; n++) { @@ -411,7 +405,7 @@ void JvmtiClassFileReconstituter::write_bootstrapmethod_attribute() { write_u2(bsme->bootstrap_method_index()); write_u2(num_bootstrap_arguments); for (int arg = 0; arg < num_bootstrap_arguments; arg++) { - u2 bootstrap_argument = bsme->argument_index(arg); + u2 bootstrap_argument = bsme->argument(arg); write_u2(bootstrap_argument); } } @@ -798,7 +792,7 @@ void JvmtiClassFileReconstituter::write_class_attributes() { if (type_anno != nullptr) { ++attr_count; // has RuntimeVisibleTypeAnnotations attribute } - if (cpool()->operands() != nullptr) { + if (!cpool()->bsm_entries().is_empty()) { ++attr_count; } if (ik()->nest_host_index() != 0) { @@ -843,7 +837,7 @@ void JvmtiClassFileReconstituter::write_class_attributes() { if (ik()->record_components() != nullptr) { write_record_attribute(); } - if (cpool()->operands() != nullptr) { + if (!cpool()->bsm_entries().is_empty()) { write_bootstrapmethod_attribute(); } if (inner_classes_length > 0) { diff --git a/src/hotspot/share/prims/jvmtiEnv.cpp b/src/hotspot/share/prims/jvmtiEnv.cpp index 5642cd9ff8f..e0863c07f4f 100644 --- a/src/hotspot/share/prims/jvmtiEnv.cpp +++ b/src/hotspot/share/prims/jvmtiEnv.cpp @@ -66,6 +66,7 @@ #include "runtime/javaThread.inline.hpp" #include "runtime/jfieldIDWorkaround.hpp" #include "runtime/jniHandles.inline.hpp" +#include "runtime/mountUnmountDisabler.hpp" #include "runtime/objectMonitor.inline.hpp" #include "runtime/os.hpp" #include "runtime/osThread.hpp" @@ -147,7 +148,7 @@ jvmtiError JvmtiEnv::SetThreadLocalStorage(jthread thread, const void* data) { JavaThread* current = JavaThread::current(); JvmtiThreadState* state = nullptr; - JvmtiVTMSTransitionDisabler disabler(thread); + MountUnmountDisabler disabler(thread); ThreadsListHandle tlh(current); JavaThread* java_thread = nullptr; @@ -200,7 +201,7 @@ JvmtiEnv::GetThreadLocalStorage(jthread thread, void** data_ptr) { VM_ENTRY_BASE(jvmtiError, JvmtiEnv::GetThreadLocalStorage , current_thread) DEBUG_ONLY(VMNativeEntryWrapper __vew;) - JvmtiVTMSTransitionDisabler disabler(thread); + MountUnmountDisabler disabler(thread); ThreadsListHandle tlh(current_thread); JavaThread* java_thread = nullptr; @@ -561,7 +562,7 @@ JvmtiEnv::SetNativeMethodPrefixes(jint prefix_count, char** prefixes) { // size_of_callbacks - pre-checked to be greater than or equal to 0 jvmtiError JvmtiEnv::SetEventCallbacks(const jvmtiEventCallbacks* callbacks, jint size_of_callbacks) { - JvmtiVTMSTransitionDisabler disabler; + MountUnmountDisabler disabler; JvmtiEventController::set_event_callbacks(this, callbacks, size_of_callbacks); return JVMTI_ERROR_NONE; } /* end SetEventCallbacks */ @@ -585,7 +586,7 @@ JvmtiEnv::SetEventNotificationMode(jvmtiEventMode mode, jvmtiEvent event_type, j if (event_type == JVMTI_EVENT_CLASS_FILE_LOAD_HOOK && enabled) { record_class_file_load_hook_enabled(); } - JvmtiVTMSTransitionDisabler disabler; + MountUnmountDisabler disabler; if (event_thread == nullptr) { // Can be called at Agent_OnLoad() time with event_thread == nullptr @@ -867,7 +868,7 @@ JvmtiEnv::GetJLocationFormat(jvmtiJlocationFormat* format_ptr) { jvmtiError JvmtiEnv::GetThreadState(jthread thread, jint* thread_state_ptr) { JavaThread* current_thread = JavaThread::current(); - JvmtiVTMSTransitionDisabler disabler(thread); + MountUnmountDisabler disabler(thread); ThreadsListHandle tlh(current_thread); JavaThread* java_thread = nullptr; @@ -939,7 +940,7 @@ JvmtiEnv::SuspendThread(jthread thread) { jvmtiError err; { - JvmtiVTMSTransitionDisabler disabler(true); + MountUnmountDisabler disabler(true); ThreadsListHandle tlh(current); JavaThread* java_thread = nullptr; oop thread_oop = nullptr; @@ -949,7 +950,7 @@ JvmtiEnv::SuspendThread(jthread thread) { return err; } - // Do not use JvmtiVTMSTransitionDisabler in context of self suspend to avoid deadlocks. + // Do not use MountUnmountDisabler in context of self suspend to avoid deadlocks. if (java_thread != current) { err = suspend_thread(thread_oop, java_thread, /* single_suspend */ true); return err; @@ -974,7 +975,7 @@ JvmtiEnv::SuspendThreadList(jint request_count, const jthread* request_list, jvm int self_idx = -1; { - JvmtiVTMSTransitionDisabler disabler(true); + MountUnmountDisabler disabler(true); ThreadsListHandle tlh(current); for (int i = 0; i < request_count; i++) { @@ -1007,7 +1008,7 @@ JvmtiEnv::SuspendThreadList(jint request_count, const jthread* request_list, jvm } } // Self suspend after all other suspends if necessary. - // Do not use JvmtiVTMSTransitionDisabler in context of self suspend to avoid deadlocks. + // Do not use MountUnmountDisabler in context of self suspend to avoid deadlocks. if (self_tobj() != nullptr) { // there should not be any error for current java_thread results[self_idx] = suspend_thread(self_tobj(), current, /* single_suspend */ true); @@ -1028,7 +1029,7 @@ JvmtiEnv::SuspendAllVirtualThreads(jint except_count, const jthread* except_list { ResourceMark rm(current); - JvmtiVTMSTransitionDisabler disabler(true); + MountUnmountDisabler disabler(true); ThreadsListHandle tlh(current); GrowableArray* elist = new GrowableArray(except_count); @@ -1078,7 +1079,7 @@ JvmtiEnv::SuspendAllVirtualThreads(jint except_count, const jthread* except_list } } // Self suspend after all other suspends if necessary. - // Do not use JvmtiVTMSTransitionDisabler in context of self suspend to avoid deadlocks. + // Do not use MountUnmountDisabler in context of self suspend to avoid deadlocks. if (self_tobj() != nullptr) { suspend_thread(self_tobj(), current, /* single_suspend */ false); } @@ -1089,7 +1090,7 @@ JvmtiEnv::SuspendAllVirtualThreads(jint except_count, const jthread* except_list // thread - NOT protected by ThreadsListHandle and NOT pre-checked jvmtiError JvmtiEnv::ResumeThread(jthread thread) { - JvmtiVTMSTransitionDisabler disabler(true); + MountUnmountDisabler disabler(true); JavaThread* current = JavaThread::current(); ThreadsListHandle tlh(current); @@ -1111,7 +1112,7 @@ jvmtiError JvmtiEnv::ResumeThreadList(jint request_count, const jthread* request_list, jvmtiError* results) { oop thread_oop = nullptr; JavaThread* java_thread = nullptr; - JvmtiVTMSTransitionDisabler disabler(true); + MountUnmountDisabler disabler(true); ThreadsListHandle tlh; for (int i = 0; i < request_count; i++) { @@ -1150,7 +1151,7 @@ JvmtiEnv::ResumeAllVirtualThreads(jint except_count, const jthread* except_list) return err; } ResourceMark rm; - JvmtiVTMSTransitionDisabler disabler(true); + MountUnmountDisabler disabler(true); GrowableArray* elist = new GrowableArray(except_count); // Collect threads from except_list for which suspended status must be restored (only for VirtualThread case) @@ -1196,7 +1197,7 @@ jvmtiError JvmtiEnv::StopThread(jthread thread, jobject exception) { JavaThread* current_thread = JavaThread::current(); - JvmtiVTMSTransitionDisabler disabler(thread); + MountUnmountDisabler disabler(thread); ThreadsListHandle tlh(current_thread); JavaThread* java_thread = nullptr; oop thread_oop = nullptr; @@ -1234,7 +1235,7 @@ JvmtiEnv::InterruptThread(jthread thread) { JavaThread* current_thread = JavaThread::current(); HandleMark hm(current_thread); - JvmtiVTMSTransitionDisabler disabler(thread); + MountUnmountDisabler disabler(thread); ThreadsListHandle tlh(current_thread); JavaThread* java_thread = nullptr; @@ -1280,7 +1281,7 @@ JvmtiEnv::GetThreadInfo(jthread thread, jvmtiThreadInfo* info_ptr) { JavaThread* java_thread = nullptr; oop thread_oop = nullptr; - JvmtiVTMSTransitionDisabler disabler(thread); + MountUnmountDisabler disabler(thread); ThreadsListHandle tlh(current_thread); // if thread is null the current thread is used @@ -1369,7 +1370,7 @@ JvmtiEnv::GetOwnedMonitorInfo(jthread thread, jint* owned_monitor_count_ptr, job JavaThread* calling_thread = JavaThread::current(); HandleMark hm(calling_thread); - JvmtiVTMSTransitionDisabler disabler(thread); + MountUnmountDisabler disabler(thread); ThreadsListHandle tlh(calling_thread); JavaThread* java_thread = nullptr; @@ -1424,7 +1425,7 @@ JvmtiEnv::GetOwnedMonitorStackDepthInfo(jthread thread, jint* monitor_info_count JavaThread* calling_thread = JavaThread::current(); HandleMark hm(calling_thread); - JvmtiVTMSTransitionDisabler disabler(thread); + MountUnmountDisabler disabler(thread); ThreadsListHandle tlh(calling_thread); JavaThread* java_thread = nullptr; @@ -1707,7 +1708,7 @@ JvmtiEnv::GetThreadListStackTraces(jint thread_count, const jthread* thread_list *stack_info_ptr = op.stack_info(); } } else { - JvmtiVTMSTransitionDisabler disabler; + MountUnmountDisabler disabler; // JVMTI get stack traces at safepoint. VM_GetThreadListStackTraces op(this, thread_count, thread_list, max_frame_count); @@ -1740,7 +1741,7 @@ JvmtiEnv::PopFrame(jthread thread) { if (thread == nullptr) { return JVMTI_ERROR_INVALID_THREAD; } - JvmtiVTMSTransitionDisabler disabler(thread); + MountUnmountDisabler disabler(thread); ThreadsListHandle tlh(current_thread); JavaThread* java_thread = nullptr; @@ -1795,7 +1796,7 @@ JvmtiEnv::GetFrameLocation(jthread thread, jint depth, jmethodID* method_ptr, jl jvmtiError JvmtiEnv::NotifyFramePop(jthread thread, jint depth) { ResourceMark rm; - JvmtiVTMSTransitionDisabler disabler(thread); + MountUnmountDisabler disabler(thread); JavaThread* current = JavaThread::current(); ThreadsListHandle tlh(current); @@ -1823,7 +1824,7 @@ JvmtiEnv::NotifyFramePop(jthread thread, jint depth) { jvmtiError JvmtiEnv::ClearAllFramePops(jthread thread) { ResourceMark rm; - JvmtiVTMSTransitionDisabler disabler(thread); + MountUnmountDisabler disabler(thread); JavaThread* current = JavaThread::current(); ThreadsListHandle tlh(current); @@ -2084,7 +2085,7 @@ JvmtiEnv::GetLocalObject(jthread thread, jint depth, jint slot, jobject* value_p // doit_prologue(), but after doit() is finished with it. ResourceMark rm(current_thread); HandleMark hm(current_thread); - JvmtiVTMSTransitionDisabler disabler(thread); + MountUnmountDisabler disabler(thread); ThreadsListHandle tlh(current_thread); JavaThread* java_thread = nullptr; @@ -2125,7 +2126,7 @@ JvmtiEnv::GetLocalInstance(jthread thread, jint depth, jobject* value_ptr){ // doit_prologue(), but after doit() is finished with it. ResourceMark rm(current_thread); HandleMark hm(current_thread); - JvmtiVTMSTransitionDisabler disabler(thread); + MountUnmountDisabler disabler(thread); ThreadsListHandle tlh(current_thread); JavaThread* java_thread = nullptr; @@ -2167,7 +2168,7 @@ JvmtiEnv::GetLocalInt(jthread thread, jint depth, jint slot, jint* value_ptr) { // doit_prologue(), but after doit() is finished with it. ResourceMark rm(current_thread); HandleMark hm(current_thread); - JvmtiVTMSTransitionDisabler disabler(thread); + MountUnmountDisabler disabler(thread); ThreadsListHandle tlh(current_thread); JavaThread* java_thread = nullptr; @@ -2209,7 +2210,7 @@ JvmtiEnv::GetLocalLong(jthread thread, jint depth, jint slot, jlong* value_ptr) // doit_prologue(), but after doit() is finished with it. ResourceMark rm(current_thread); HandleMark hm(current_thread); - JvmtiVTMSTransitionDisabler disabler(thread); + MountUnmountDisabler disabler(thread); ThreadsListHandle tlh(current_thread); JavaThread* java_thread = nullptr; @@ -2251,7 +2252,7 @@ JvmtiEnv::GetLocalFloat(jthread thread, jint depth, jint slot, jfloat* value_ptr // doit_prologue(), but after doit() is finished with it. ResourceMark rm(current_thread); HandleMark hm(current_thread); - JvmtiVTMSTransitionDisabler disabler(thread); + MountUnmountDisabler disabler(thread); ThreadsListHandle tlh(current_thread); JavaThread* java_thread = nullptr; @@ -2293,7 +2294,7 @@ JvmtiEnv::GetLocalDouble(jthread thread, jint depth, jint slot, jdouble* value_p // doit_prologue(), but after doit() is finished with it. ResourceMark rm(current_thread); HandleMark hm(current_thread); - JvmtiVTMSTransitionDisabler disabler(thread); + MountUnmountDisabler disabler(thread); ThreadsListHandle tlh(current_thread); JavaThread* java_thread = nullptr; @@ -2334,7 +2335,7 @@ JvmtiEnv::SetLocalObject(jthread thread, jint depth, jint slot, jobject value) { // doit_prologue(), but after doit() is finished with it. ResourceMark rm(current_thread); HandleMark hm(current_thread); - JvmtiVTMSTransitionDisabler disabler(thread); + MountUnmountDisabler disabler(thread); ThreadsListHandle tlh(current_thread); JavaThread* java_thread = nullptr; @@ -2371,7 +2372,7 @@ JvmtiEnv::SetLocalInt(jthread thread, jint depth, jint slot, jint value) { // doit_prologue(), but after doit() is finished with it. ResourceMark rm(current_thread); HandleMark hm(current_thread); - JvmtiVTMSTransitionDisabler disabler(thread); + MountUnmountDisabler disabler(thread); ThreadsListHandle tlh(current_thread); JavaThread* java_thread = nullptr; @@ -2408,7 +2409,7 @@ JvmtiEnv::SetLocalLong(jthread thread, jint depth, jint slot, jlong value) { // doit_prologue(), but after doit() is finished with it. ResourceMark rm(current_thread); HandleMark hm(current_thread); - JvmtiVTMSTransitionDisabler disabler(thread); + MountUnmountDisabler disabler(thread); ThreadsListHandle tlh(current_thread); JavaThread* java_thread = nullptr; @@ -2445,7 +2446,7 @@ JvmtiEnv::SetLocalFloat(jthread thread, jint depth, jint slot, jfloat value) { // doit_prologue(), but after doit() is finished with it. ResourceMark rm(current_thread); HandleMark hm(current_thread); - JvmtiVTMSTransitionDisabler disabler(thread); + MountUnmountDisabler disabler(thread); ThreadsListHandle tlh(current_thread); JavaThread* java_thread = nullptr; @@ -2482,7 +2483,7 @@ JvmtiEnv::SetLocalDouble(jthread thread, jint depth, jint slot, jdouble value) { // doit_prologue(), but after doit() is finished with it. ResourceMark rm(current_thread); HandleMark hm(current_thread); - JvmtiVTMSTransitionDisabler disabler(thread); + MountUnmountDisabler disabler(thread); ThreadsListHandle tlh(current_thread); JavaThread* java_thread = nullptr; @@ -2574,7 +2575,7 @@ JvmtiEnv::ClearBreakpoint(Method* method, jlocation location) { jvmtiError JvmtiEnv::SetFieldAccessWatch(fieldDescriptor* fdesc_ptr) { - JvmtiVTMSTransitionDisabler disabler; + MountUnmountDisabler disabler; // make sure we haven't set this watch before if (fdesc_ptr->is_field_access_watched()) return JVMTI_ERROR_DUPLICATE; fdesc_ptr->set_is_field_access_watched(true); @@ -2587,7 +2588,7 @@ JvmtiEnv::SetFieldAccessWatch(fieldDescriptor* fdesc_ptr) { jvmtiError JvmtiEnv::ClearFieldAccessWatch(fieldDescriptor* fdesc_ptr) { - JvmtiVTMSTransitionDisabler disabler; + MountUnmountDisabler disabler; // make sure we have a watch to clear if (!fdesc_ptr->is_field_access_watched()) return JVMTI_ERROR_NOT_FOUND; fdesc_ptr->set_is_field_access_watched(false); @@ -2600,7 +2601,7 @@ JvmtiEnv::ClearFieldAccessWatch(fieldDescriptor* fdesc_ptr) { jvmtiError JvmtiEnv::SetFieldModificationWatch(fieldDescriptor* fdesc_ptr) { - JvmtiVTMSTransitionDisabler disabler; + MountUnmountDisabler disabler; // make sure we haven't set this watch before if (fdesc_ptr->is_field_modification_watched()) return JVMTI_ERROR_DUPLICATE; fdesc_ptr->set_is_field_modification_watched(true); @@ -2613,7 +2614,7 @@ JvmtiEnv::SetFieldModificationWatch(fieldDescriptor* fdesc_ptr) { jvmtiError JvmtiEnv::ClearFieldModificationWatch(fieldDescriptor* fdesc_ptr) { - JvmtiVTMSTransitionDisabler disabler; + MountUnmountDisabler disabler; // make sure we have a watch to clear if (!fdesc_ptr->is_field_modification_watched()) return JVMTI_ERROR_NOT_FOUND; fdesc_ptr->set_is_field_modification_watched(false); diff --git a/src/hotspot/share/prims/jvmtiEnvBase.cpp b/src/hotspot/share/prims/jvmtiEnvBase.cpp index 2b17eddbe94..4894a4dd21a 100644 --- a/src/hotspot/share/prims/jvmtiEnvBase.cpp +++ b/src/hotspot/share/prims/jvmtiEnvBase.cpp @@ -51,6 +51,7 @@ #include "runtime/javaThread.inline.hpp" #include "runtime/jfieldIDWorkaround.hpp" #include "runtime/jniHandles.inline.hpp" +#include "runtime/mountUnmountDisabler.hpp" #include "runtime/objectMonitor.inline.hpp" #include "runtime/osThread.hpp" #include "runtime/signature.hpp" @@ -697,7 +698,7 @@ JvmtiEnvBase::check_and_skip_hidden_frames(bool is_in_VTMS_transition, javaVFram javaVFrame* JvmtiEnvBase::check_and_skip_hidden_frames(JavaThread* jt, javaVFrame* jvf) { - jvf = check_and_skip_hidden_frames(jt->is_in_VTMS_transition(), jvf); + jvf = check_and_skip_hidden_frames(jt->is_in_vthread_transition(), jvf); return jvf; } @@ -719,7 +720,7 @@ JvmtiEnvBase::get_vthread_jvf(oop vthread) { return nullptr; } vframeStream vfs(java_thread); - assert(!java_thread->is_in_VTMS_transition(), "invariant"); + assert(!java_thread->is_in_vthread_transition(), "invariant"); jvf = vfs.at_end() ? nullptr : vfs.asJavaVFrame(); jvf = check_and_skip_hidden_frames(false, jvf); } else { @@ -1693,8 +1694,7 @@ private: // jt->jvmti_vthread() for VTMS transition protocol. void correct_jvmti_thread_states() { for (JavaThread* jt : ThreadsListHandle()) { - if (jt->is_in_VTMS_transition()) { - jt->set_VTMS_transition_mark(true); + if (jt->is_in_vthread_transition()) { continue; // no need in JvmtiThreadState correction below if in transition } correct_jvmti_thread_state(jt); @@ -1711,7 +1711,7 @@ public: if (_enable) { correct_jvmti_thread_states(); } - JvmtiVTMSTransitionDisabler::set_VTMS_notify_jvmti_events(_enable); + MountUnmountDisabler::set_notify_jvmti_events(_enable); } }; @@ -1722,7 +1722,7 @@ JvmtiEnvBase::enable_virtual_threads_notify_jvmti() { if (!Continuations::enabled()) { return false; } - if (JvmtiVTMSTransitionDisabler::VTMS_notify_jvmti_events()) { + if (MountUnmountDisabler::notify_jvmti_events()) { return false; // already enabled } VM_SetNotifyJvmtiEventsMode op(true); @@ -1738,10 +1738,10 @@ JvmtiEnvBase::disable_virtual_threads_notify_jvmti() { if (!Continuations::enabled()) { return false; } - if (!JvmtiVTMSTransitionDisabler::VTMS_notify_jvmti_events()) { + if (!MountUnmountDisabler::notify_jvmti_events()) { return false; // already disabled } - JvmtiVTMSTransitionDisabler disabler(true); // ensure there are no other disablers + MountUnmountDisabler disabler(true); // ensure there are no other disablers VM_SetNotifyJvmtiEventsMode op(false); VMThread::execute(&op); return true; @@ -1769,7 +1769,6 @@ JvmtiEnvBase::suspend_thread(oop thread_oop, JavaThread* java_thread, bool singl // Platform thread or mounted vthread cases. assert(java_thread != nullptr, "sanity check"); - assert(!java_thread->is_in_VTMS_transition(), "sanity check"); // Don't allow hidden thread suspend request. if (java_thread->is_hidden_from_external_view()) { @@ -1828,7 +1827,6 @@ JvmtiEnvBase::resume_thread(oop thread_oop, JavaThread* java_thread, bool single // Platform thread or mounted vthread cases. assert(java_thread != nullptr, "sanity check"); - assert(!java_thread->is_in_VTMS_transition(), "sanity check"); // Don't allow hidden thread resume request. if (java_thread->is_hidden_from_external_view()) { @@ -2008,12 +2006,12 @@ class AdapterClosure : public HandshakeClosure { }; // Supports platform and virtual threads. -// JvmtiVTMSTransitionDisabler is always set by this function. +// MountUnmountDisabler is always set by this function. void JvmtiHandshake::execute(JvmtiUnitedHandshakeClosure* hs_cl, jthread target) { JavaThread* current = JavaThread::current(); HandleMark hm(current); - JvmtiVTMSTransitionDisabler disabler(target); + MountUnmountDisabler disabler(target); ThreadsListHandle tlh(current); JavaThread* java_thread = nullptr; oop thread_obj = nullptr; @@ -2030,7 +2028,7 @@ JvmtiHandshake::execute(JvmtiUnitedHandshakeClosure* hs_cl, jthread target) { // Supports platform and virtual threads. // A virtual thread is always identified by the target_h oop handle. // The target_jt is always nullptr for an unmounted virtual thread. -// JvmtiVTMSTransitionDisabler has to be set before call to this function. +// MountUnmountDisabler has to be set before call to this function. void JvmtiHandshake::execute(JvmtiUnitedHandshakeClosure* hs_cl, ThreadsListHandle* tlh, JavaThread* target_jt, Handle target_h) { @@ -2038,7 +2036,7 @@ JvmtiHandshake::execute(JvmtiUnitedHandshakeClosure* hs_cl, ThreadsListHandle* t bool is_virtual = java_lang_VirtualThread::is_instance(target_h()); bool self = target_jt == current; - assert(!Continuations::enabled() || self || !is_virtual || current->is_VTMS_transition_disabler(), "sanity check"); + assert(!Continuations::enabled() || self || !is_virtual || current->is_vthread_transition_disabler(), "sanity check"); hs_cl->set_target_jt(target_jt); // can be needed in the virtual thread case hs_cl->set_is_virtual(is_virtual); // can be needed in the virtual thread case @@ -2211,7 +2209,7 @@ JvmtiEnvBase::force_early_return(jthread thread, jvalue value, TosState tos) { JavaThread* current_thread = JavaThread::current(); HandleMark hm(current_thread); - JvmtiVTMSTransitionDisabler disabler(thread); + MountUnmountDisabler disabler(thread); ThreadsListHandle tlh(current_thread); JavaThread* java_thread = nullptr; @@ -2612,7 +2610,7 @@ PrintStackTraceClosure::do_thread_impl(Thread *target) { "is_VTMS_transition_disabler: %d, is_in_VTMS_transition = %d\n", tname, java_thread->name(), java_thread->is_exiting(), java_thread->is_suspended(), java_thread->is_carrier_thread_suspended(), is_vt_suspended, - java_thread->is_VTMS_transition_disabler(), java_thread->is_in_VTMS_transition()); + java_thread->is_vthread_transition_disabler(), java_thread->is_in_vthread_transition()); if (java_thread->has_last_Java_frame()) { RegisterMap reg_map(java_thread, diff --git a/src/hotspot/share/prims/jvmtiEventController.cpp b/src/hotspot/share/prims/jvmtiEventController.cpp index 169ccbe035f..9df3bbb4b3e 100644 --- a/src/hotspot/share/prims/jvmtiEventController.cpp +++ b/src/hotspot/share/prims/jvmtiEventController.cpp @@ -1077,10 +1077,6 @@ JvmtiEventController::is_global_event(jvmtiEvent event_type) { void JvmtiEventController::set_user_enabled(JvmtiEnvBase *env, JavaThread *thread, oop thread_oop, jvmtiEvent event_type, bool enabled) { - if (event_type == JVMTI_EVENT_OBJECT_FREE) { - JvmtiEventControllerPrivate::flush_object_free_events(env); - } - if (Threads::number_of_threads() == 0) { // during early VM start-up locks don't exist, but we are safely single threaded, // call the functionality without holding the JvmtiThreadState_lock. @@ -1089,6 +1085,11 @@ JvmtiEventController::set_user_enabled(JvmtiEnvBase *env, JavaThread *thread, oo Thread* current = Thread::current(); HandleMark hmi(current); Handle thread_oop_h = Handle(current, thread_oop); + + if (event_type == JVMTI_EVENT_OBJECT_FREE) { + JvmtiEventControllerPrivate::flush_object_free_events(env); + } + MutexLocker mu(JvmtiThreadState_lock); JvmtiEventControllerPrivate::set_user_enabled(env, thread, thread_oop_h, event_type, enabled); } @@ -1238,4 +1239,4 @@ JvmtiEventController::vm_death() { break; } } -} \ No newline at end of file +} diff --git a/src/hotspot/share/prims/jvmtiEventController.inline.hpp b/src/hotspot/share/prims/jvmtiEventController.inline.hpp index c5dbdcc5f8e..8c00df99838 100644 --- a/src/hotspot/share/prims/jvmtiEventController.inline.hpp +++ b/src/hotspot/share/prims/jvmtiEventController.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2019, 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 diff --git a/src/hotspot/share/prims/jvmtiExport.cpp b/src/hotspot/share/prims/jvmtiExport.cpp index 69d1fa2c974..02f39460ff6 100644 --- a/src/hotspot/share/prims/jvmtiExport.cpp +++ b/src/hotspot/share/prims/jvmtiExport.cpp @@ -61,6 +61,7 @@ #include "runtime/javaThread.hpp" #include "runtime/jniHandles.inline.hpp" #include "runtime/keepStackGCProcessed.hpp" +#include "runtime/mountUnmountDisabler.hpp" #include "runtime/objectMonitor.inline.hpp" #include "runtime/os.hpp" #include "runtime/osThread.hpp" @@ -412,7 +413,7 @@ JvmtiExport::get_jvmti_interface(JavaVM *jvm, void **penv, jint version) { if (Continuations::enabled()) { // Virtual threads support for agents loaded into running VM. // There is a performance impact when VTMS transitions are enabled. - if (!JvmtiVTMSTransitionDisabler::VTMS_notify_jvmti_events()) { + if (!MountUnmountDisabler::notify_jvmti_events()) { JvmtiEnvBase::enable_virtual_threads_notify_jvmti(); } } @@ -426,7 +427,7 @@ JvmtiExport::get_jvmti_interface(JavaVM *jvm, void **penv, jint version) { if (Continuations::enabled()) { // Virtual threads support for agents loaded at startup. // There is a performance impact when VTMS transitions are enabled. - JvmtiVTMSTransitionDisabler::set_VTMS_notify_jvmti_events(true); + MountUnmountDisabler::set_notify_jvmti_events(true, true /*is_onload*/); } return JNI_OK; @@ -1639,7 +1640,7 @@ void JvmtiExport::post_vthread_end(jobject vthread) { JVMTI_JAVA_THREAD_EVENT_CALLBACK_BLOCK(thread) jvmtiEventVirtualThreadEnd callback = env->callbacks()->VirtualThreadEnd; if (callback != nullptr) { - (*callback)(env->jvmti_external(), jem.jni_env(), vthread); + (*callback)(env->jvmti_external(), jem.jni_env(), jem.jni_thread()); } } } @@ -2924,13 +2925,13 @@ void JvmtiExport::vthread_post_monitor_waited(JavaThread *current, ObjectMonitor Handle vthread(current, current->vthread()); // Finish the VTMS transition temporarily to post the event. - JvmtiVTMSTransitionDisabler::VTMS_vthread_mount((jthread)vthread.raw_value(), false); + MountUnmountDisabler::end_transition(current, vthread(), true /*is_mount*/, false /*is_thread_start*/); // Post event. JvmtiExport::post_monitor_waited(current, obj_mntr, timed_out); // Go back to VTMS transition state. - JvmtiVTMSTransitionDisabler::VTMS_vthread_unmount((jthread)vthread.raw_value(), true); + MountUnmountDisabler::start_transition(current, vthread(), false /*is_mount*/, false /*is_thread_start*/); } void JvmtiExport::post_vm_object_alloc(JavaThread *thread, oop object) { @@ -3159,31 +3160,19 @@ void JvmtiObjectAllocEventCollector::record_allocation(oop obj) { _allocated->push(OopHandle(JvmtiExport::jvmti_oop_storage(), obj)); } -// Disable collection of VMObjectAlloc events -NoJvmtiVMObjectAllocMark::NoJvmtiVMObjectAllocMark() : _collector(nullptr) { - // a no-op if VMObjectAlloc event is not enabled - if (!JvmtiExport::should_post_vm_object_alloc()) { - return; - } +NoJvmtiEventsMark::NoJvmtiEventsMark() { Thread* thread = Thread::current_or_null(); if (thread != nullptr && thread->is_Java_thread()) { JavaThread* current_thread = JavaThread::cast(thread); - JvmtiThreadState *state = current_thread->jvmti_thread_state(); - if (state != nullptr) { - JvmtiVMObjectAllocEventCollector *collector; - collector = state->get_vm_object_alloc_event_collector(); - if (collector != nullptr && collector->is_enabled()) { - _collector = collector; - _collector->set_enabled(false); - } - } + current_thread->disable_jvmti_events(); } } -// Re-Enable collection of VMObjectAlloc events (if previously enabled) -NoJvmtiVMObjectAllocMark::~NoJvmtiVMObjectAllocMark() { - if (was_enabled()) { - _collector->set_enabled(true); +NoJvmtiEventsMark::~NoJvmtiEventsMark() { + Thread* thread = Thread::current_or_null(); + if (thread != nullptr && thread->is_Java_thread()) { + JavaThread* current_thread = JavaThread::cast(thread); + current_thread->enable_jvmti_events(); } }; diff --git a/src/hotspot/share/prims/jvmtiExport.hpp b/src/hotspot/share/prims/jvmtiExport.hpp index 66f5413c8f6..f6c9f3a74d5 100644 --- a/src/hotspot/share/prims/jvmtiExport.hpp +++ b/src/hotspot/share/prims/jvmtiExport.hpp @@ -585,29 +585,12 @@ class JvmtiSampledObjectAllocEventCollector : public JvmtiObjectAllocEventCollec static bool object_alloc_is_safe_to_sample() NOT_JVMTI_RETURN_(false); }; -// Marker class to disable the posting of VMObjectAlloc events -// within its scope. -// -// Usage :- -// -// { -// NoJvmtiVMObjectAllocMark njm; -// : -// // VMObjAlloc event will not be posted -// JvmtiExport::vm_object_alloc_event_collector(obj); -// : -// } - -class NoJvmtiVMObjectAllocMark : public StackObj { - private: - // enclosing collector if enabled, null otherwise - JvmtiVMObjectAllocEventCollector *_collector; - - bool was_enabled() { return _collector != nullptr; } +// Marker class to temporary disable posting of jvmti events. +class NoJvmtiEventsMark : public StackObj { public: - NoJvmtiVMObjectAllocMark() NOT_JVMTI_RETURN; - ~NoJvmtiVMObjectAllocMark() NOT_JVMTI_RETURN; + NoJvmtiEventsMark() NOT_JVMTI_RETURN; + ~NoJvmtiEventsMark() NOT_JVMTI_RETURN; }; diff --git a/src/hotspot/share/prims/jvmtiExtensions.cpp b/src/hotspot/share/prims/jvmtiExtensions.cpp index 603d62eff85..855c7bd4eba 100644 --- a/src/hotspot/share/prims/jvmtiExtensions.cpp +++ b/src/hotspot/share/prims/jvmtiExtensions.cpp @@ -29,6 +29,7 @@ #include "runtime/handles.inline.hpp" #include "runtime/interfaceSupport.inline.hpp" #include "runtime/jniHandles.inline.hpp" +#include "runtime/mountUnmountDisabler.hpp" // the list of extension functions GrowableArray* JvmtiExtensions::_ext_functions; @@ -77,7 +78,7 @@ static jvmtiError JNICALL GetVirtualThread(const jvmtiEnv* env, ...) { va_end(ap); ThreadInVMfromNative tiv(current_thread); - JvmtiVTMSTransitionDisabler disabler; + MountUnmountDisabler disabler; ThreadsListHandle tlh(current_thread); jvmtiError err; @@ -135,7 +136,7 @@ static jvmtiError JNICALL GetCarrierThread(const jvmtiEnv* env, ...) { MACOS_AARCH64_ONLY(ThreadWXEnable __wx(WXWrite, current_thread)); ThreadInVMfromNative tiv(current_thread); - JvmtiVTMSTransitionDisabler disabler; + MountUnmountDisabler disabler; ThreadsListHandle tlh(current_thread); JavaThread* java_thread; diff --git a/src/hotspot/share/prims/jvmtiImpl.hpp b/src/hotspot/share/prims/jvmtiImpl.hpp index c359b2fcbc3..0fd63556a78 100644 --- a/src/hotspot/share/prims/jvmtiImpl.hpp +++ b/src/hotspot/share/prims/jvmtiImpl.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/hotspot/share/prims/jvmtiRedefineClasses.cpp b/src/hotspot/share/prims/jvmtiRedefineClasses.cpp index ef8875d582e..13b239b4df0 100644 --- a/src/hotspot/share/prims/jvmtiRedefineClasses.cpp +++ b/src/hotspot/share/prims/jvmtiRedefineClasses.cpp @@ -45,7 +45,8 @@ #include "memory/resourceArea.hpp" #include "memory/universe.hpp" #include "oops/annotations.hpp" -#include "oops/constantPool.hpp" +#include "oops/bsmAttribute.inline.hpp" +#include "oops/constantPool.inline.hpp" #include "oops/fieldStreams.inline.hpp" #include "oops/klass.inline.hpp" #include "oops/klassVtable.hpp" @@ -573,9 +574,9 @@ void VM_RedefineClasses::append_entry(const constantPoolHandle& scratch_cp, case JVM_CONSTANT_Dynamic: // fall through case JVM_CONSTANT_InvokeDynamic: { - // Index of the bootstrap specifier in the operands array + // Index of the bootstrap specifier in the BSM array int old_bs_i = scratch_cp->bootstrap_methods_attribute_index(scratch_i); - int new_bs_i = find_or_append_operand(scratch_cp, old_bs_i, merge_cp_p, + int new_bs_i = find_or_append_bsm_entry(scratch_cp, old_bs_i, merge_cp_p, merge_cp_length_p); // The bootstrap method NameAndType_info index int old_ref_i = scratch_cp->bootstrap_name_and_type_ref_index_at(scratch_i); @@ -591,10 +592,11 @@ void VM_RedefineClasses::append_entry(const constantPoolHandle& scratch_cp, ("Dynamic entry@%d name_and_type_index change: %d to %d", *merge_cp_length_p, old_ref_i, new_ref_i); } - if (scratch_cp->tag_at(scratch_i).is_dynamic_constant()) + if (scratch_cp->tag_at(scratch_i).is_dynamic_constant()) { (*merge_cp_p)->dynamic_constant_at_put(*merge_cp_length_p, new_bs_i, new_ref_i); - else + } else { (*merge_cp_p)->invoke_dynamic_at_put(*merge_cp_length_p, new_bs_i, new_ref_i); + } if (scratch_i != *merge_cp_length_p) { // The new entry in *merge_cp_p is at a different index than // the new entry in scratch_cp so we need to map the index values. @@ -660,10 +662,10 @@ u2 VM_RedefineClasses::find_or_append_indirect_entry(const constantPoolHandle& s } // end find_or_append_indirect_entry() -// Append a bootstrap specifier into the merge_cp operands that is semantically equal -// to the scratch_cp operands bootstrap specifier passed by the old_bs_i index. +// Append a bootstrap specifier into the merge_cp BSM entries that is semantically equal +// to the scratch_cp BSM entries' bootstrap specifier passed by the old_bs_i index. // Recursively append new merge_cp entries referenced by the new bootstrap specifier. -void VM_RedefineClasses::append_operand(const constantPoolHandle& scratch_cp, const int old_bs_i, +int VM_RedefineClasses::append_bsm_entry(const constantPoolHandle& scratch_cp, const int old_bs_i, constantPoolHandle *merge_cp_p, int *merge_cp_length_p) { BSMAttributeEntry* old_bsme = scratch_cp->bsm_attribute_entry(old_bs_i); @@ -672,90 +674,82 @@ void VM_RedefineClasses::append_operand(const constantPoolHandle& scratch_cp, co merge_cp_length_p); if (new_ref_i != old_ref_i) { log_trace(redefine, class, constantpool) - ("operands entry@%d bootstrap method ref_index change: %d to %d", _operands_cur_length, old_ref_i, new_ref_i); + ("BSM attribute entry@%d bootstrap method ref_index change: %d to %d", _bsmae_iter.current_offset() - 1, old_ref_i, new_ref_i); } - Array* merge_ops = (*merge_cp_p)->operands(); - int new_bs_i = _operands_cur_length; - // We have _operands_cur_length == 0 when the merge_cp operands is empty yet. - // However, the operand_offset_at(0) was set in the extend_operands() call. - int new_base = (new_bs_i == 0) ? (*merge_cp_p)->operand_offset_at(0) - : (*merge_cp_p)->operand_next_offset_at(new_bs_i - 1); - u2 argc = old_bsme->argument_count(); - - ConstantPool::operand_offset_at_put(merge_ops, _operands_cur_length, new_base); - merge_ops->at_put(new_base++, new_ref_i); - merge_ops->at_put(new_base++, argc); - - for (int i = 0; i < argc; i++) { - u2 old_arg_ref_i = old_bsme->argument_index(i); + const int new_bs_i = _bsmae_iter.current_offset(); + BSMAttributeEntry* new_bsme = + _bsmae_iter.reserve_new_entry(new_ref_i, old_bsme->argument_count()); + assert(new_bsme != nullptr, "must be"); + for (int i = 0; i < new_bsme->argument_count(); i++) { + u2 old_arg_ref_i = old_bsme->argument(i); u2 new_arg_ref_i = find_or_append_indirect_entry(scratch_cp, old_arg_ref_i, merge_cp_p, merge_cp_length_p); - merge_ops->at_put(new_base++, new_arg_ref_i); + new_bsme->set_argument(i, new_arg_ref_i); + if (new_arg_ref_i != old_arg_ref_i) { log_trace(redefine, class, constantpool) - ("operands entry@%d bootstrap method argument ref_index change: %d to %d", - _operands_cur_length, old_arg_ref_i, new_arg_ref_i); + ("BSM attribute entry@%d bootstrap method argument ref_index change: %d to %d", + _bsmae_iter.current_offset() - 1, old_arg_ref_i, new_arg_ref_i); } } - if (old_bs_i != _operands_cur_length) { - // The bootstrap specifier in *merge_cp_p is at a different index than - // that in scratch_cp so we need to map the index values. - map_operand_index(old_bs_i, new_bs_i); - } - _operands_cur_length++; -} // end append_operand() + // This is only for the logging + map_bsm_index(old_bs_i, new_bs_i); + return new_bs_i; +} // end append_bsm_entry() -int VM_RedefineClasses::find_or_append_operand(const constantPoolHandle& scratch_cp, +int VM_RedefineClasses::find_or_append_bsm_entry(const constantPoolHandle& scratch_cp, int old_bs_i, constantPoolHandle *merge_cp_p, int *merge_cp_length_p) { + const int max_offset_in_merge = _bsmae_iter.current_offset(); int new_bs_i = old_bs_i; // bootstrap specifier index - bool match = (old_bs_i < _operands_cur_length) && - scratch_cp->compare_operand_to(old_bs_i, *merge_cp_p, old_bs_i); + // Has the old_bs_i index been used already? Check if it's the same so we know + // whether or not a remapping is required. + bool match = (old_bs_i < max_offset_in_merge) && + scratch_cp->compare_bootstrap_entry_to(old_bs_i, *merge_cp_p, old_bs_i); if (!match) { // forward reference in *merge_cp_p or not a direct match - int found_i = scratch_cp->find_matching_operand(old_bs_i, *merge_cp_p, - _operands_cur_length); + int found_i = scratch_cp->find_matching_bsm_entry(old_bs_i, *merge_cp_p, + max_offset_in_merge); if (found_i != -1) { - guarantee(found_i != old_bs_i, "compare_operand_to() and find_matching_operand() disagree"); - // found a matching operand somewhere else in *merge_cp_p so just need a mapping + guarantee(found_i != old_bs_i, "compare_bootstrap_entry_to() and find_matching_bsm_entry() disagree"); + // found a matching BSM entry somewhere else in *merge_cp_p so just need a mapping new_bs_i = found_i; - map_operand_index(old_bs_i, found_i); + map_bsm_index(old_bs_i, found_i); } else { // no match found so we have to append this bootstrap specifier to *merge_cp_p - append_operand(scratch_cp, old_bs_i, merge_cp_p, merge_cp_length_p); - new_bs_i = _operands_cur_length - 1; + new_bs_i = append_bsm_entry(scratch_cp, old_bs_i, merge_cp_p, merge_cp_length_p); } } return new_bs_i; -} // end find_or_append_operand() +} // end find_or_append_bsm_entry() -void VM_RedefineClasses::finalize_operands_merge(const constantPoolHandle& merge_cp, TRAPS) { - if (merge_cp->operands() == nullptr) { +void VM_RedefineClasses::finalize_bsm_entries_merge(const constantPoolHandle& merge_cp, TRAPS) { + if (merge_cp->bsm_entries().number_of_entries() == 0) { return; } - // Shrink the merge_cp operands - merge_cp->shrink_operands(_operands_cur_length, CHECK); + // Finished extending the BSMAEs + merge_cp->end_extension(_bsmae_iter, CHECK); if (log_is_enabled(Trace, redefine, class, constantpool)) { // don't want to loop unless we are tracing int count = 0; - for (int i = 1; i < _operands_index_map_p->length(); i++) { - int value = _operands_index_map_p->at(i); + for (int i = 1; i < _bsm_index_map_p->length(); i++) { + int value = _bsm_index_map_p->at(i); if (value != -1) { - log_trace(redefine, class, constantpool)("operands_index_map[%d]: old=%d new=%d", count, i, value); + log_trace(redefine, class, constantpool)("bsm_index_map[%d]: old=%d new=%d", count, i, value); count++; } } } // Clean-up - _operands_index_map_p = nullptr; - _operands_cur_length = 0; - _operands_index_map_count = 0; -} // end finalize_operands_merge() + _bsm_index_map_p = nullptr; + _bsm_index_map_count = 0; + _bsmae_iter = BSMAttributeEntries::InsertionIterator(); +} // end finalize_bsmentries_merge() // Symbol* comparator for qsort // The caller must have an active ResourceMark. @@ -1272,26 +1266,26 @@ u2 VM_RedefineClasses::find_new_index(int old_index) { // Find new bootstrap specifier index value for old bootstrap specifier index // value by searching the index map. Returns unused index (-1) if there is // no mapped value for the old bootstrap specifier index. -int VM_RedefineClasses::find_new_operand_index(int old_index) { - if (_operands_index_map_count == 0) { +int VM_RedefineClasses::find_new_bsm_index(int old_index) { + if (_bsm_index_map_count == 0) { // map is empty so nothing can be found return -1; } - if (old_index == -1 || old_index >= _operands_index_map_p->length()) { + if (old_index == -1 || old_index >= _bsm_index_map_p->length()) { // The old_index is out of range so it is not mapped. // This should not happen in regular constant pool merging use. return -1; } - int value = _operands_index_map_p->at(old_index); + int value = _bsm_index_map_p->at(old_index); if (value == -1) { // the old_index is not mapped return -1; } return value; -} // end find_new_operand_index() +} // end find_new_bsm_index() // The bug 6214132 caused the verification to fail. @@ -1560,22 +1554,15 @@ void VM_RedefineClasses::map_index(const constantPoolHandle& scratch_cp, // Map old_index to new_index as needed. -void VM_RedefineClasses::map_operand_index(int old_index, int new_index) { - if (find_new_operand_index(old_index) != -1) { - // old_index is already mapped - return; - } - +void VM_RedefineClasses::map_bsm_index(int old_index, int new_index) { if (old_index == new_index) { // no mapping is needed return; } - - _operands_index_map_p->at_put(old_index, new_index); - _operands_index_map_count++; - + _bsm_index_map_p->at_put(old_index, new_index); + _bsm_index_map_count++; log_trace(redefine, class, constantpool)("mapped bootstrap specifier at index %d to %d", old_index, new_index); -} // end map_index() +} // end map_bsm_index() // Merge old_cp and scratch_cp and return the results of the merge via @@ -1639,8 +1626,8 @@ bool VM_RedefineClasses::merge_constant_pools(const constantPoolHandle& old_cp, } } // end for each old_cp entry - ConstantPool::copy_operands(old_cp, merge_cp_p, CHECK_false); - merge_cp_p->extend_operands(scratch_cp, CHECK_false); + ConstantPool::copy_bsm_entries(old_cp, merge_cp_p, CHECK_false); + _bsmae_iter = merge_cp_p->start_extension(scratch_cp, CHECK_false); // We don't need to sanity check that *merge_cp_length_p is within // *merge_cp_p bounds since we have the minimum on-entry check above. @@ -1737,7 +1724,7 @@ bool VM_RedefineClasses::merge_constant_pools(const constantPoolHandle& old_cp, ("after pass 1b: merge_cp_len=%d, scratch_i=%d, index_map_len=%d", merge_cp_length_p, scratch_i, _index_map_count); } - finalize_operands_merge(merge_cp_p, CHECK_false); + finalize_bsm_entries_merge(merge_cp_p, CHECK_false); return true; } // end merge_constant_pools() @@ -1807,12 +1794,11 @@ jvmtiError VM_RedefineClasses::merge_cp_and_rewrite( _index_map_count = 0; _index_map_p = new intArray(scratch_cp->length(), scratch_cp->length(), -1); - _operands_cur_length = ConstantPool::operand_array_length(old_cp->operands()); - _operands_index_map_count = 0; - int operands_index_map_len = ConstantPool::operand_array_length(scratch_cp->operands()); - _operands_index_map_p = new intArray(operands_index_map_len, operands_index_map_len, -1); + _bsm_index_map_count = 0; + int bsm_data_len = scratch_cp->bsm_entries().array_length(); + _bsm_index_map_p = new intArray(bsm_data_len, bsm_data_len, -1); - // reference to the cp holder is needed for copy_operands() + // reference to the cp holder is needed for reallocating the BSM attribute merge_cp->set_pool_holder(scratch_class); bool result = merge_constant_pools(old_cp, scratch_cp, merge_cp, merge_cp_length, THREAD); @@ -3500,7 +3486,7 @@ void VM_RedefineClasses::set_new_constant_pool( smaller_cp->set_version(version); // attach klass to new constant pool - // reference to the cp holder is needed for copy_operands() + // reference to the cp holder is needed for reallocating the BSM attribute smaller_cp->set_pool_holder(scratch_class); smaller_cp->copy_fields(scratch_cp()); diff --git a/src/hotspot/share/prims/jvmtiRedefineClasses.hpp b/src/hotspot/share/prims/jvmtiRedefineClasses.hpp index d2eda1f3eed..3f1b555b175 100644 --- a/src/hotspot/share/prims/jvmtiRedefineClasses.hpp +++ b/src/hotspot/share/prims/jvmtiRedefineClasses.hpp @@ -363,11 +363,16 @@ class VM_RedefineClasses: public VM_Operation { int _index_map_count; intArray * _index_map_p; - // _operands_index_map_count is just an optimization for knowing if - // _operands_index_map_p contains any entries. - int _operands_cur_length; - int _operands_index_map_count; - intArray * _operands_index_map_p; + // _bsm_index_map_count is just an optimization for knowing if + // _bsm_index_map_p contains any entries. + int _bsm_index_map_count; + intArray * _bsm_index_map_p; + + // After merge_constant_pools "Pass 0", the BSMAttribute entries of merge_cp_p will have been expanded to fit + // scratch_cp's BSMAttribute entries as well. + // However, the newly acquired space will not have been filled in yet. + // To append to this new space, the iterator is used. + BSMAttributeEntries::InsertionIterator _bsmae_iter; // ptr to _class_count scratch_classes InstanceKlass** _scratch_classes; @@ -429,17 +434,18 @@ class VM_RedefineClasses: public VM_Operation { // Support for constant pool merging (these routines are in alpha order): void append_entry(const constantPoolHandle& scratch_cp, int scratch_i, constantPoolHandle *merge_cp_p, int *merge_cp_length_p); - void append_operand(const constantPoolHandle& scratch_cp, int scratch_bootstrap_spec_index, + // Returns the index of the appended BSM + int append_bsm_entry(const constantPoolHandle& scratch_cp, int scratch_bootstrap_spec_index, constantPoolHandle *merge_cp_p, int *merge_cp_length_p); - void finalize_operands_merge(const constantPoolHandle& merge_cp, TRAPS); + void finalize_bsm_entries_merge(const constantPoolHandle& merge_cp, TRAPS); u2 find_or_append_indirect_entry(const constantPoolHandle& scratch_cp, int scratch_i, constantPoolHandle *merge_cp_p, int *merge_cp_length_p); - int find_or_append_operand(const constantPoolHandle& scratch_cp, int scratch_bootstrap_spec_index, + int find_or_append_bsm_entry(const constantPoolHandle& scratch_cp, int scratch_bootstrap_spec_index, constantPoolHandle *merge_cp_p, int *merge_cp_length_p); u2 find_new_index(int old_index); - int find_new_operand_index(int old_bootstrap_spec_index); + int find_new_bsm_index(int old_bootstrap_spec_index); void map_index(const constantPoolHandle& scratch_cp, int old_index, int new_index); - void map_operand_index(int old_bootstrap_spec_index, int new_bootstrap_spec_index); + void map_bsm_index(int old_bootstrap_spec_index, int new_bootstrap_spec_index); bool merge_constant_pools(const constantPoolHandle& old_cp, const constantPoolHandle& scratch_cp, constantPoolHandle& merge_cp_p, int& merge_cp_length_p, TRAPS); diff --git a/src/hotspot/share/prims/jvmtiTagMap.cpp b/src/hotspot/share/prims/jvmtiTagMap.cpp index c923f91f69d..04cb70863cd 100644 --- a/src/hotspot/share/prims/jvmtiTagMap.cpp +++ b/src/hotspot/share/prims/jvmtiTagMap.cpp @@ -57,6 +57,7 @@ #include "runtime/javaCalls.hpp" #include "runtime/javaThread.inline.hpp" #include "runtime/jniHandles.inline.hpp" +#include "runtime/mountUnmountDisabler.hpp" #include "runtime/mutex.hpp" #include "runtime/mutexLocker.hpp" #include "runtime/safepoint.hpp" @@ -1203,8 +1204,10 @@ void JvmtiTagMap::flush_object_free_events() { assert_not_at_safepoint(); if (env()->is_enabled(JVMTI_EVENT_OBJECT_FREE)) { { + // The other thread can block for safepoints during event callbacks, so ensure we + // are safepoint-safe while waiting. + ThreadBlockInVM tbivm(JavaThread::current()); MonitorLocker ml(lock(), Mutex::_no_safepoint_check_flag); - // If another thread is posting events, let it finish while (_posting_events) { ml.wait(); } @@ -3028,7 +3031,7 @@ void JvmtiTagMap::iterate_over_reachable_objects(jvmtiHeapRootCallback heap_root jvmtiObjectReferenceCallback object_ref_callback, const void* user_data) { // VTMS transitions must be disabled before the EscapeBarrier. - JvmtiVTMSTransitionDisabler disabler; + MountUnmountDisabler disabler; JavaThread* jt = JavaThread::current(); EscapeBarrier eb(true, jt); @@ -3056,7 +3059,7 @@ void JvmtiTagMap::iterate_over_objects_reachable_from_object(jobject object, Arena dead_object_arena(mtServiceability); GrowableArray dead_objects(&dead_object_arena, 10, 0, 0); - JvmtiVTMSTransitionDisabler disabler; + MountUnmountDisabler disabler; { MutexLocker ml(Heap_lock); @@ -3076,7 +3079,7 @@ void JvmtiTagMap::follow_references(jint heap_filter, const void* user_data) { // VTMS transitions must be disabled before the EscapeBarrier. - JvmtiVTMSTransitionDisabler disabler; + MountUnmountDisabler disabler; oop obj = JNIHandles::resolve(object); JavaThread* jt = JavaThread::current(); diff --git a/src/hotspot/share/prims/jvmtiTagMapTable.hpp b/src/hotspot/share/prims/jvmtiTagMapTable.hpp index f717552da2b..e408d9dac86 100644 --- a/src/hotspot/share/prims/jvmtiTagMapTable.hpp +++ b/src/hotspot/share/prims/jvmtiTagMapTable.hpp @@ -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 diff --git a/src/hotspot/share/prims/jvmtiThreadState.cpp b/src/hotspot/share/prims/jvmtiThreadState.cpp index 00a48dec111..fc965e568f7 100644 --- a/src/hotspot/share/prims/jvmtiThreadState.cpp +++ b/src/hotspot/share/prims/jvmtiThreadState.cpp @@ -209,479 +209,6 @@ JvmtiThreadState::periodic_clean_up() { } } -// -// Virtual Threads Mount State transition (VTMS transition) mechanism -// - -// VTMS transitions for one virtual thread are disabled while it is positive -volatile int JvmtiVTMSTransitionDisabler::_VTMS_transition_disable_for_one_count = 0; - -// VTMS transitions for all virtual threads are disabled while it is positive -volatile int JvmtiVTMSTransitionDisabler::_VTMS_transition_disable_for_all_count = 0; - -// There is an active suspender or resumer. -volatile bool JvmtiVTMSTransitionDisabler::_SR_mode = false; - -// Notifications from VirtualThread about VTMS events are enabled. -bool JvmtiVTMSTransitionDisabler::_VTMS_notify_jvmti_events = false; - -// The JvmtiVTMSTransitionDisabler sync protocol is enabled if this count > 0. -volatile int JvmtiVTMSTransitionDisabler::_sync_protocol_enabled_count = 0; - -// JvmtiVTMSTraansitionDisabler sync protocol is enabled permanently after seeing a suspender. -volatile bool JvmtiVTMSTransitionDisabler::_sync_protocol_enabled_permanently = false; - -#ifdef ASSERT -void -JvmtiVTMSTransitionDisabler::print_info() { - log_error(jvmti)("_VTMS_transition_disable_for_one_count: %d\n", _VTMS_transition_disable_for_one_count); - log_error(jvmti)("_VTMS_transition_disable_for_all_count: %d\n\n", _VTMS_transition_disable_for_all_count); - int attempts = 10000; - for (JavaThreadIteratorWithHandle jtiwh; JavaThread *java_thread = jtiwh.next(); ) { - if (java_thread->VTMS_transition_mark()) { - log_error(jvmti)("jt: %p VTMS_transition_mark: %d\n", - (void*)java_thread, java_thread->VTMS_transition_mark()); - } - ResourceMark rm; - // Handshake with target. - PrintStackTraceClosure pstc; - Handshake::execute(&pstc, java_thread); - } -} -#endif - -// disable VTMS transitions for one virtual thread -// disable VTMS transitions for all threads if thread is nullptr or a platform thread -JvmtiVTMSTransitionDisabler::JvmtiVTMSTransitionDisabler(jthread thread) - : _is_SR(false), - _is_virtual(false), - _is_self(false), - _thread(thread) -{ - if (!Continuations::enabled()) { - return; // JvmtiVTMSTransitionDisabler is no-op without virtual threads - } - if (Thread::current_or_null() == nullptr) { - return; // Detached thread, can be a call from Agent_OnLoad. - } - JavaThread* current = JavaThread::current(); - oop thread_oop = JNIHandles::resolve_external_guard(thread); - _is_virtual = java_lang_VirtualThread::is_instance(thread_oop); - - if (thread == nullptr || - (!_is_virtual && thread_oop == current->threadObj()) || - (_is_virtual && thread_oop == current->vthread())) { - _is_self = true; - return; // no need for current thread to disable and enable transitions for itself - } - if (!sync_protocol_enabled_permanently()) { - JvmtiVTMSTransitionDisabler::inc_sync_protocol_enabled_count(); - } - - // Target can be virtual or platform thread. - // If target is a platform thread then we have to disable VTMS transitions for all threads. - // It is by several reasons: - // - carrier threads can mount virtual threads which may cause incorrect behavior - // - there is no mechanism to disable transitions for a specific carrier thread yet - if (_is_virtual) { - VTMS_transition_disable_for_one(); // disable VTMS transitions for one virtual thread - } else { - VTMS_transition_disable_for_all(); // disable VTMS transitions for all virtual threads - } -} - -// disable VTMS transitions for all virtual threads -JvmtiVTMSTransitionDisabler::JvmtiVTMSTransitionDisabler(bool is_SR) - : _is_SR(is_SR), - _is_virtual(false), - _is_self(false), - _thread(nullptr) -{ - if (!Continuations::enabled()) { - return; // JvmtiVTMSTransitionDisabler is no-op without virtual threads - } - if (Thread::current_or_null() == nullptr) { - return; // Detached thread, can be a call from Agent_OnLoad. - } - if (!sync_protocol_enabled_permanently()) { - JvmtiVTMSTransitionDisabler::inc_sync_protocol_enabled_count(); - if (is_SR) { - AtomicAccess::store(&_sync_protocol_enabled_permanently, true); - } - } - VTMS_transition_disable_for_all(); -} - -JvmtiVTMSTransitionDisabler::~JvmtiVTMSTransitionDisabler() { - if (!Continuations::enabled()) { - return; // JvmtiVTMSTransitionDisabler is a no-op without virtual threads - } - if (Thread::current_or_null() == nullptr) { - return; // Detached thread, can be a call from Agent_OnLoad. - } - if (_is_self) { - return; // no need for current thread to disable and enable transitions for itself - } - if (_is_virtual) { - VTMS_transition_enable_for_one(); // enable VTMS transitions for one virtual thread - } else { - VTMS_transition_enable_for_all(); // enable VTMS transitions for all virtual threads - } - if (!sync_protocol_enabled_permanently()) { - JvmtiVTMSTransitionDisabler::dec_sync_protocol_enabled_count(); - } -} - -// disable VTMS transitions for one virtual thread -void -JvmtiVTMSTransitionDisabler::VTMS_transition_disable_for_one() { - assert(_thread != nullptr, "sanity check"); - JavaThread* thread = JavaThread::current(); - HandleMark hm(thread); - Handle vth = Handle(thread, JNIHandles::resolve_external_guard(_thread)); - assert(java_lang_VirtualThread::is_instance(vth()), "sanity check"); - - MonitorLocker ml(JvmtiVTMSTransition_lock); - - while (_SR_mode) { // suspender or resumer is a JvmtiVTMSTransitionDisabler monopolist - ml.wait(10); // wait while there is an active suspender or resumer - } - AtomicAccess::inc(&_VTMS_transition_disable_for_one_count); - java_lang_Thread::inc_VTMS_transition_disable_count(vth()); - - while (java_lang_Thread::is_in_VTMS_transition(vth())) { - ml.wait(10); // wait while the virtual thread is in transition - } -#ifdef ASSERT - thread->set_is_VTMS_transition_disabler(true); -#endif -} - -// disable VTMS transitions for all virtual threads -void -JvmtiVTMSTransitionDisabler::VTMS_transition_disable_for_all() { - JavaThread* thread = JavaThread::current(); - int attempts = 50000; - { - MonitorLocker ml(JvmtiVTMSTransition_lock); - - assert(!thread->is_in_VTMS_transition(), "VTMS_transition sanity check"); - while (_SR_mode) { // Suspender or resumer is a JvmtiVTMSTransitionDisabler monopolist. - ml.wait(10); // Wait while there is an active suspender or resumer. - } - if (_is_SR) { - _SR_mode = true; - while (_VTMS_transition_disable_for_all_count > 0 || - _VTMS_transition_disable_for_one_count > 0) { - ml.wait(10); // Wait while there is any active jvmtiVTMSTransitionDisabler. - } - } - AtomicAccess::inc(&_VTMS_transition_disable_for_all_count); - - // Block while some mount/unmount transitions are in progress. - // Debug version fails and prints diagnostic information. - for (JavaThreadIteratorWithHandle jtiwh; JavaThread *jt = jtiwh.next(); ) { - while (jt->VTMS_transition_mark()) { - if (ml.wait(10)) { - attempts--; - } - DEBUG_ONLY(if (attempts == 0) break;) - } - } - assert(!thread->is_VTMS_transition_disabler(), "VTMS_transition sanity check"); -#ifdef ASSERT - if (attempts > 0) { - thread->set_is_VTMS_transition_disabler(true); - } -#endif - } -#ifdef ASSERT - if (attempts == 0) { - print_info(); - fatal("stuck in JvmtiVTMSTransitionDisabler::VTMS_transition_disable"); - } -#endif -} - -// enable VTMS transitions for one virtual thread -void -JvmtiVTMSTransitionDisabler::VTMS_transition_enable_for_one() { - JavaThread* thread = JavaThread::current(); - HandleMark hm(thread); - Handle vth = Handle(thread, JNIHandles::resolve_external_guard(_thread)); - if (!java_lang_VirtualThread::is_instance(vth())) { - return; // no-op if _thread is not a virtual thread - } - MonitorLocker ml(JvmtiVTMSTransition_lock); - java_lang_Thread::dec_VTMS_transition_disable_count(vth()); - AtomicAccess::dec(&_VTMS_transition_disable_for_one_count); - if (_VTMS_transition_disable_for_one_count == 0) { - ml.notify_all(); - } -#ifdef ASSERT - thread->set_is_VTMS_transition_disabler(false); -#endif -} - -// enable VTMS transitions for all virtual threads -void -JvmtiVTMSTransitionDisabler::VTMS_transition_enable_for_all() { - JavaThread* current = JavaThread::current(); - { - MonitorLocker ml(JvmtiVTMSTransition_lock); - assert(_VTMS_transition_disable_for_all_count > 0, "VTMS_transition sanity check"); - - if (_is_SR) { // Disabler is suspender or resumer. - _SR_mode = false; - } - AtomicAccess::dec(&_VTMS_transition_disable_for_all_count); - if (_VTMS_transition_disable_for_all_count == 0 || _is_SR) { - ml.notify_all(); - } -#ifdef ASSERT - current->set_is_VTMS_transition_disabler(false); -#endif - } -} - -void -JvmtiVTMSTransitionDisabler::start_VTMS_transition(jthread vthread, bool is_mount) { - JavaThread* thread = JavaThread::current(); - oop vt = JNIHandles::resolve_external_guard(vthread); - assert(!thread->is_in_VTMS_transition(), "VTMS_transition sanity check"); - - // Avoid using MonitorLocker on performance critical path, use - // two-level synchronization with lock-free operations on state bits. - assert(!thread->VTMS_transition_mark(), "sanity check"); - thread->set_VTMS_transition_mark(true); // Try to enter VTMS transition section optmistically. - java_lang_Thread::set_is_in_VTMS_transition(vt, true); - - if (!sync_protocol_enabled()) { - thread->set_is_in_VTMS_transition(true); - return; - } - HandleMark hm(thread); - Handle vth = Handle(thread, vt); - int attempts = 50000; - - // Do not allow suspends inside VTMS transitions. - // Block while transitions are disabled or there are suspend requests. - int64_t thread_id = java_lang_Thread::thread_id(vth()); // Cannot use oops while blocked. - - if (_VTMS_transition_disable_for_all_count > 0 || - java_lang_Thread::VTMS_transition_disable_count(vth()) > 0 || - thread->is_suspended() || - JvmtiVTSuspender::is_vthread_suspended(thread_id) - ) { - // Slow path: undo unsuccessful optimistic set of the VTMS_transition_mark. - // It can cause an extra waiting cycle for VTMS transition disablers. - thread->set_VTMS_transition_mark(false); - java_lang_Thread::set_is_in_VTMS_transition(vth(), false); - - while (true) { - MonitorLocker ml(JvmtiVTMSTransition_lock); - - // Do not allow suspends inside VTMS transitions. - // Block while transitions are disabled or there are suspend requests. - if (_VTMS_transition_disable_for_all_count > 0 || - java_lang_Thread::VTMS_transition_disable_count(vth()) > 0 || - thread->is_suspended() || - JvmtiVTSuspender::is_vthread_suspended(thread_id) - ) { - // Block while transitions are disabled or there are suspend requests. - if (ml.wait(200)) { - attempts--; - } - DEBUG_ONLY(if (attempts == 0) break;) - continue; // ~ThreadBlockInVM has handshake-based suspend point. - } - thread->set_VTMS_transition_mark(true); - java_lang_Thread::set_is_in_VTMS_transition(vth(), true); - break; - } - } -#ifdef ASSERT - if (attempts == 0) { - log_error(jvmti)("start_VTMS_transition: thread->is_suspended: %d is_vthread_suspended: %d\n\n", - thread->is_suspended(), JvmtiVTSuspender::is_vthread_suspended(thread_id)); - print_info(); - fatal("stuck in JvmtiVTMSTransitionDisabler::start_VTMS_transition"); - } -#endif - // Enter VTMS transition section. - thread->set_is_in_VTMS_transition(true); -} - -void -JvmtiVTMSTransitionDisabler::finish_VTMS_transition(jthread vthread, bool is_mount) { - JavaThread* thread = JavaThread::current(); - - assert(thread->is_in_VTMS_transition(), "sanity check"); - thread->set_is_in_VTMS_transition(false); - oop vt = JNIHandles::resolve_external_guard(vthread); - java_lang_Thread::set_is_in_VTMS_transition(vt, false); - assert(thread->VTMS_transition_mark(), "sanity check"); - thread->set_VTMS_transition_mark(false); - - if (!sync_protocol_enabled()) { - return; - } - int64_t thread_id = java_lang_Thread::thread_id(vt); - - // Unblock waiting VTMS transition disablers. - if (_VTMS_transition_disable_for_one_count > 0 || - _VTMS_transition_disable_for_all_count > 0) { - MonitorLocker ml(JvmtiVTMSTransition_lock); - ml.notify_all(); - } - // In unmount case the carrier thread is attached after unmount transition. - // Check and block it if there was external suspend request. - int attempts = 10000; - if (!is_mount && thread->is_carrier_thread_suspended()) { - while (true) { - MonitorLocker ml(JvmtiVTMSTransition_lock); - - // Block while there are suspend requests. - if ((!is_mount && thread->is_carrier_thread_suspended()) || - (is_mount && JvmtiVTSuspender::is_vthread_suspended(thread_id)) - ) { - // Block while there are suspend requests. - if (ml.wait(200)) { - attempts--; - } - DEBUG_ONLY(if (attempts == 0) break;) - continue; - } - break; - } - } -#ifdef ASSERT - if (attempts == 0) { - log_error(jvmti)("finish_VTMS_transition: thread->is_suspended: %d is_vthread_suspended: %d\n\n", - thread->is_suspended(), JvmtiVTSuspender::is_vthread_suspended(thread_id)); - print_info(); - fatal("stuck in JvmtiVTMSTransitionDisabler::finish_VTMS_transition"); - } -#endif -} - -// set VTMS transition bit value in JavaThread and java.lang.VirtualThread object -void JvmtiVTMSTransitionDisabler::set_is_in_VTMS_transition(JavaThread* thread, jobject vthread, bool in_trans) { - oop vt = JNIHandles::resolve_external_guard(vthread); - java_lang_Thread::set_is_in_VTMS_transition(vt, in_trans); - thread->set_is_in_VTMS_transition(in_trans); -} - -void -JvmtiVTMSTransitionDisabler::VTMS_vthread_start(jobject vthread) { - VTMS_mount_end(vthread); - JavaThread* thread = JavaThread::current(); - - assert(!thread->is_in_VTMS_transition(), "sanity check"); - - // If interp_only_mode has been enabled then we must eagerly create JvmtiThreadState - // objects for globally enabled virtual thread filtered events. Otherwise, - // it is an important optimization to create JvmtiThreadState objects lazily. - // This optimization is disabled when watchpoint capabilities are present. It is to - // work around a bug with virtual thread frames which can be not deoptimized in time. - if (JvmtiThreadState::seen_interp_only_mode() || - JvmtiExport::should_post_field_access() || - JvmtiExport::should_post_field_modification()){ - JvmtiEventController::thread_started(thread); - } - if (JvmtiExport::should_post_vthread_start()) { - JvmtiExport::post_vthread_start(vthread); - } - // post VirtualThreadMount event after VirtualThreadStart - if (JvmtiExport::should_post_vthread_mount()) { - JvmtiExport::post_vthread_mount(vthread); - } -} - -void -JvmtiVTMSTransitionDisabler::VTMS_vthread_end(jobject vthread) { - JavaThread* thread = JavaThread::current(); - - assert(!thread->is_in_VTMS_transition(), "sanity check"); - - // post VirtualThreadUnmount event before VirtualThreadEnd - if (JvmtiExport::should_post_vthread_unmount()) { - JvmtiExport::post_vthread_unmount(vthread); - } - if (JvmtiExport::should_post_vthread_end()) { - JvmtiExport::post_vthread_end(vthread); - } - VTMS_unmount_begin(vthread, /* last_unmount */ true); - if (thread->jvmti_thread_state() != nullptr) { - JvmtiExport::cleanup_thread(thread); - assert(thread->jvmti_thread_state() == nullptr, "should be null"); - assert(java_lang_Thread::jvmti_thread_state(JNIHandles::resolve(vthread)) == nullptr, "should be null"); - } - thread->rebind_to_jvmti_thread_state_of(thread->threadObj()); -} - -void -JvmtiVTMSTransitionDisabler::VTMS_vthread_mount(jobject vthread, bool hide) { - if (hide) { - VTMS_mount_begin(vthread); - } else { - VTMS_mount_end(vthread); - if (JvmtiExport::should_post_vthread_mount()) { - JvmtiExport::post_vthread_mount(vthread); - } - } -} - -void -JvmtiVTMSTransitionDisabler::VTMS_vthread_unmount(jobject vthread, bool hide) { - if (hide) { - if (JvmtiExport::should_post_vthread_unmount()) { - JvmtiExport::post_vthread_unmount(vthread); - } - VTMS_unmount_begin(vthread, /* last_unmount */ false); - } else { - VTMS_unmount_end(vthread); - } -} - -void -JvmtiVTMSTransitionDisabler::VTMS_mount_begin(jobject vthread) { - JavaThread* thread = JavaThread::current(); - assert(!thread->is_in_VTMS_transition(), "sanity check"); - start_VTMS_transition(vthread, /* is_mount */ true); -} - -void -JvmtiVTMSTransitionDisabler::VTMS_mount_end(jobject vthread) { - JavaThread* thread = JavaThread::current(); - oop vt = JNIHandles::resolve(vthread); - - thread->rebind_to_jvmti_thread_state_of(vt); - - assert(thread->is_in_VTMS_transition(), "sanity check"); - finish_VTMS_transition(vthread, /* is_mount */ true); -} - -void -JvmtiVTMSTransitionDisabler::VTMS_unmount_begin(jobject vthread, bool last_unmount) { - JavaThread* thread = JavaThread::current(); - - assert(!thread->is_in_VTMS_transition(), "sanity check"); - - start_VTMS_transition(vthread, /* is_mount */ false); - if (!last_unmount) { - thread->rebind_to_jvmti_thread_state_of(thread->threadObj()); - } -} - -void -JvmtiVTMSTransitionDisabler::VTMS_unmount_end(jobject vthread) { - JavaThread* thread = JavaThread::current(); - assert(thread->is_in_VTMS_transition(), "sanity check"); - finish_VTMS_transition(vthread, /* is_mount */ false); -} - - // // Virtual Threads Suspend/Resume management // diff --git a/src/hotspot/share/prims/jvmtiThreadState.hpp b/src/hotspot/share/prims/jvmtiThreadState.hpp index 17bdae4662e..43b568cf1fc 100644 --- a/src/hotspot/share/prims/jvmtiThreadState.hpp +++ b/src/hotspot/share/prims/jvmtiThreadState.hpp @@ -72,67 +72,6 @@ class JvmtiEnvThreadStateIterator : public StackObj { JvmtiEnvThreadState* next(JvmtiEnvThreadState* ets); }; -/////////////////////////////////////////////////////////////// -// -// class JvmtiVTMSTransitionDisabler -// -// Virtual Thread Mount State Transition (VTMS transition) mechanism -// -class JvmtiVTMSTransitionDisabler : public AnyObj { - private: - static volatile int _VTMS_transition_disable_for_one_count; // transitions for one virtual thread are disabled while it is positive - static volatile int _VTMS_transition_disable_for_all_count; // transitions for all virtual threads are disabled while it is positive - static volatile bool _SR_mode; // there is an active suspender or resumer - static volatile int _sync_protocol_enabled_count; // current number of JvmtiVTMSTransitionDisablers enabled sync protocol - static volatile bool _sync_protocol_enabled_permanently; // seen a suspender: JvmtiVTMSTransitionDisabler protocol is enabled permanently - - bool _is_SR; // is suspender or resumer - bool _is_virtual; // target thread is virtual - bool _is_self; // JvmtiVTMSTransitionDisabler is a no-op for current platform, carrier or virtual thread - jthread _thread; // virtual thread to disable transitions for, no-op if it is a platform thread - - DEBUG_ONLY(static void print_info();) - void VTMS_transition_disable_for_one(); - void VTMS_transition_disable_for_all(); - void VTMS_transition_enable_for_one(); - void VTMS_transition_enable_for_all(); - - public: - static bool _VTMS_notify_jvmti_events; // enable notifications from VirtualThread about VTMS events - static bool VTMS_notify_jvmti_events() { return _VTMS_notify_jvmti_events; } - static void set_VTMS_notify_jvmti_events(bool val) { _VTMS_notify_jvmti_events = val; } - - static void inc_sync_protocol_enabled_count() { AtomicAccess::inc(&_sync_protocol_enabled_count); } - static void dec_sync_protocol_enabled_count() { AtomicAccess::dec(&_sync_protocol_enabled_count); } - static int sync_protocol_enabled_count() { return AtomicAccess::load(&_sync_protocol_enabled_count); } - static bool sync_protocol_enabled_permanently() { return AtomicAccess::load(&_sync_protocol_enabled_permanently); } - - static bool sync_protocol_enabled() { return sync_protocol_enabled_permanently() || sync_protocol_enabled_count() > 0; } - - // parameter is_SR: suspender or resumer - JvmtiVTMSTransitionDisabler(bool is_SR = false); - JvmtiVTMSTransitionDisabler(jthread thread); - ~JvmtiVTMSTransitionDisabler(); - - // set VTMS transition bit value in JavaThread and java.lang.VirtualThread object - static void set_is_in_VTMS_transition(JavaThread* thread, jobject vthread, bool in_trans); - - static void start_VTMS_transition(jthread vthread, bool is_mount); - static void finish_VTMS_transition(jthread vthread, bool is_mount); - - static void VTMS_vthread_start(jobject vthread); - static void VTMS_vthread_end(jobject vthread); - - static void VTMS_vthread_mount(jobject vthread, bool hide); - static void VTMS_vthread_unmount(jobject vthread, bool hide); - - static void VTMS_mount_begin(jobject vthread); - static void VTMS_mount_end(jobject vthread); - - static void VTMS_unmount_begin(jobject vthread, bool last_unmount); - static void VTMS_unmount_end(jobject vthread); -}; - /////////////////////////////////////////////////////////////// // // class VirtualThreadList diff --git a/src/hotspot/share/prims/jvmtiThreadState.inline.hpp b/src/hotspot/share/prims/jvmtiThreadState.inline.hpp index 5f9aa842d15..2b060f1a2e4 100644 --- a/src/hotspot/share/prims/jvmtiThreadState.inline.hpp +++ b/src/hotspot/share/prims/jvmtiThreadState.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2006, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/hotspot/share/prims/methodHandles.hpp b/src/hotspot/share/prims/methodHandles.hpp index 1a419cbb53d..73da28a6cf5 100644 --- a/src/hotspot/share/prims/methodHandles.hpp +++ b/src/hotspot/share/prims/methodHandles.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2023, 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 diff --git a/src/hotspot/share/prims/vectorSupport.hpp b/src/hotspot/share/prims/vectorSupport.hpp index 9ec6500543c..5dd06f31b16 100644 --- a/src/hotspot/share/prims/vectorSupport.hpp +++ b/src/hotspot/share/prims/vectorSupport.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/hotspot/share/prims/vmstorage.hpp b/src/hotspot/share/prims/vmstorage.hpp index f605dc28a9f..ea9aa3c5423 100644 --- a/src/hotspot/share/prims/vmstorage.hpp +++ b/src/hotspot/share/prims/vmstorage.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023, 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 diff --git a/src/hotspot/share/prims/whitebox.cpp b/src/hotspot/share/prims/whitebox.cpp index 5514f7d3260..1c2f5527ff9 100644 --- a/src/hotspot/share/prims/whitebox.cpp +++ b/src/hotspot/share/prims/whitebox.cpp @@ -199,6 +199,10 @@ WB_ENTRY(jint, WB_TakeLockAndHangInSafepoint(JNIEnv* env, jobject wb)) return 0; WB_END +WB_ENTRY(jlong, WB_GetMinimumJavaStackSize(JNIEnv* env, jobject o)) + return os::get_minimum_java_stack_size(); +WB_END + class WBIsKlassAliveClosure : public LockedClassesDo { Symbol* _name; int _count; @@ -508,8 +512,16 @@ WB_ENTRY(jboolean, WB_ConcurrentGCRunTo(JNIEnv* env, jobject o, jobject at)) return ConcurrentGCBreakpoints::run_to(c_name); WB_END -WB_ENTRY(jboolean, WB_HasExternalSymbolsStripped(JNIEnv* env, jobject o)) -#if defined(HAS_STRIPPED_DEBUGINFO) +WB_ENTRY(jboolean, WB_ShipDebugInfoFull(JNIEnv* env, jobject o)) +#if defined(SHIP_DEBUGINFO_FULL) + return true; +#else + return false; +#endif +WB_END + +WB_ENTRY(jboolean, WB_ShipDebugInfoPublic(JNIEnv* env, jobject o)) +#if defined(SHIP_DEBUGINFO_PUBLIC) return true; #else return false; @@ -1678,7 +1690,7 @@ WB_ENTRY(void, WB_RelocateNMethodFromAddr(JNIEnv* env, jobject o, jlong addr, ji CodeBlob* blob = CodeCache::find_blob(address); if (blob != nullptr && blob->is_nmethod()) { nmethod* code = blob->as_nmethod(); - if (code->is_in_use()) { + if (code->is_in_use() && !code->is_unloading()) { CompiledICLocker ic_locker(code); code->relocate(static_cast(blob_type)); } @@ -2840,7 +2852,8 @@ static JNINativeMethod methods[] = { {CC"getVMLargePageSize", CC"()J", (void*)&WB_GetVMLargePageSize}, {CC"getHeapSpaceAlignment", CC"()J", (void*)&WB_GetHeapSpaceAlignment}, {CC"getHeapAlignment", CC"()J", (void*)&WB_GetHeapAlignment}, - {CC"hasExternalSymbolsStripped", CC"()Z", (void*)&WB_HasExternalSymbolsStripped}, + {CC"shipsFullDebugInfo", CC"()Z", (void*)&WB_ShipDebugInfoFull}, + {CC"shipsPublicDebugInfo", CC"()Z", (void*)&WB_ShipDebugInfoPublic}, {CC"countAliveClasses0", CC"(Ljava/lang/String;)I", (void*)&WB_CountAliveClasses }, {CC"getSymbolRefcount", CC"(Ljava/lang/String;)I", (void*)&WB_GetSymbolRefcount }, {CC"parseCommandLine0", @@ -3124,7 +3137,8 @@ static JNINativeMethod methods[] = { {CC"cleanMetaspaces", CC"()V", (void*)&WB_CleanMetaspaces}, {CC"rss", CC"()J", (void*)&WB_Rss}, {CC"printString", CC"(Ljava/lang/String;I)Ljava/lang/String;", (void*)&WB_PrintString}, - {CC"lockAndStuckInSafepoint", CC"()V", (void*)&WB_TakeLockAndHangInSafepoint}, + {CC"lockAndStuckInSafepoint", CC"()V", (void*)&WB_TakeLockAndHangInSafepoint}, + {CC"getMinimumJavaStackSize", CC"()J", (void*)&WB_GetMinimumJavaStackSize}, {CC"wordSize", CC"()J", (void*)&WB_WordSize}, {CC"rootChunkWordSize", CC"()J", (void*)&WB_RootChunkWordSize}, {CC"isStatic", CC"()Z", (void*)&WB_IsStaticallyLinked}, diff --git a/src/hotspot/share/runtime/abstract_vm_version.cpp b/src/hotspot/share/runtime/abstract_vm_version.cpp index 3860307f8df..4051ba3f9d6 100644 --- a/src/hotspot/share/runtime/abstract_vm_version.cpp +++ b/src/hotspot/share/runtime/abstract_vm_version.cpp @@ -26,6 +26,7 @@ #include "compiler/compilerDefinitions.hpp" #include "jvm_io.h" #include "runtime/arguments.hpp" +#include "runtime/os.hpp" #include "runtime/vm_version.hpp" #include "utilities/globalDefinitions.hpp" @@ -282,6 +283,8 @@ const char* Abstract_VM_Version::internal_vm_info_string() { #define HOTSPOT_BUILD_COMPILER "MS VC++ 17.13 (VS2022)" #elif _MSC_VER == 1944 #define HOTSPOT_BUILD_COMPILER "MS VC++ 17.14 (VS2022)" + #elif _MSC_VER == 1950 + #define HOTSPOT_BUILD_COMPILER "MS VC++ 18.0 (VS2026)" #else #define HOTSPOT_BUILD_COMPILER "unknown MS VC++:" XSTR(_MSC_VER) #endif diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index 55ee7641a5f..cf0a1ab9757 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -534,13 +534,11 @@ static SpecialFlag const special_jvm_flags[] = { { "DynamicDumpSharedSpaces", JDK_Version::jdk(18), JDK_Version::jdk(19), JDK_Version::undefined() }, { "RequireSharedSpaces", JDK_Version::jdk(18), JDK_Version::jdk(19), JDK_Version::undefined() }, { "UseSharedSpaces", JDK_Version::jdk(18), JDK_Version::jdk(19), JDK_Version::undefined() }, - { "LockingMode", JDK_Version::jdk(24), JDK_Version::jdk(26), JDK_Version::jdk(27) }, #ifdef _LP64 { "UseCompressedClassPointers", JDK_Version::jdk(25), JDK_Version::jdk(27), JDK_Version::undefined() }, #endif { "ParallelRefProcEnabled", JDK_Version::jdk(26), JDK_Version::jdk(27), JDK_Version::jdk(28) }, { "ParallelRefProcBalancingEnabled", JDK_Version::jdk(26), JDK_Version::jdk(27), JDK_Version::jdk(28) }, - { "PSChunkLargeArrays", JDK_Version::jdk(26), JDK_Version::jdk(27), JDK_Version::jdk(28) }, { "MaxRAM", JDK_Version::jdk(26), JDK_Version::jdk(27), JDK_Version::jdk(28) }, { "AggressiveHeap", JDK_Version::jdk(26), JDK_Version::jdk(27), JDK_Version::jdk(28) }, { "NeverActAsServerClassMachine", JDK_Version::jdk(26), JDK_Version::jdk(27), JDK_Version::jdk(28) }, @@ -550,35 +548,12 @@ static SpecialFlag const special_jvm_flags[] = { // -------------- Obsolete Flags - sorted by expired_in -------------- -#ifdef LINUX - { "UseOprofile", JDK_Version::jdk(25), JDK_Version::jdk(26), JDK_Version::jdk(27) }, -#endif { "MetaspaceReclaimPolicy", JDK_Version::undefined(), JDK_Version::jdk(21), JDK_Version::undefined() }, - { "G1UpdateBufferSize", JDK_Version::undefined(), JDK_Version::jdk(26), JDK_Version::jdk(27) }, - { "ShenandoahPacing", JDK_Version::jdk(25), JDK_Version::jdk(26), JDK_Version::jdk(27) }, #if defined(AARCH64) { "NearCpool", JDK_Version::undefined(), JDK_Version::jdk(25), JDK_Version::undefined() }, #endif - { "AdaptiveSizeMajorGCDecayTimeScale", JDK_Version::undefined(), JDK_Version::jdk(26), JDK_Version::jdk(27) }, - { "AdaptiveSizePolicyInitializingSteps", JDK_Version::undefined(), JDK_Version::jdk(26), JDK_Version::jdk(27) }, - { "AdaptiveSizePolicyOutputInterval", JDK_Version::undefined(), JDK_Version::jdk(26), JDK_Version::jdk(27) }, - { "AdaptiveSizeThroughPutPolicy", JDK_Version::undefined(), JDK_Version::jdk(26), JDK_Version::jdk(27) }, - { "AdaptiveTimeWeight", JDK_Version::undefined(), JDK_Version::jdk(26), JDK_Version::jdk(27) }, - { "PausePadding", JDK_Version::undefined(), JDK_Version::jdk(26), JDK_Version::jdk(27) }, - { "SurvivorPadding", JDK_Version::undefined(), JDK_Version::jdk(26), JDK_Version::jdk(27) }, - { "TenuredGenerationSizeIncrement", JDK_Version::undefined(), JDK_Version::jdk(26), JDK_Version::jdk(27) }, - { "TenuredGenerationSizeSupplement", JDK_Version::undefined(), JDK_Version::jdk(26), JDK_Version::jdk(27) }, - { "TenuredGenerationSizeSupplementDecay", JDK_Version::undefined(), JDK_Version::jdk(26), JDK_Version::jdk(27) }, - { "UseAdaptiveGenerationSizePolicyAtMajorCollection", JDK_Version::undefined(), JDK_Version::jdk(26), JDK_Version::jdk(27) }, - { "UseAdaptiveGenerationSizePolicyAtMinorCollection", JDK_Version::undefined(), JDK_Version::jdk(26), JDK_Version::jdk(27) }, - { "UseAdaptiveSizeDecayMajorGCCost", JDK_Version::undefined(), JDK_Version::jdk(26), JDK_Version::jdk(27) }, - { "UseAdaptiveSizePolicyFootprintGoal", JDK_Version::undefined(), JDK_Version::jdk(26), JDK_Version::jdk(27) }, - { "UseAdaptiveSizePolicyWithSystemGC", JDK_Version::undefined(), JDK_Version::jdk(26), JDK_Version::jdk(27) }, - { "UsePSAdaptiveSurvivorSizePolicy", JDK_Version::undefined(), JDK_Version::jdk(26), JDK_Version::jdk(27) }, - - { "PretenureSizeThreshold", JDK_Version::undefined(), JDK_Version::jdk(26), JDK_Version::jdk(27) }, - { "HeapMaximumCompactionInterval",JDK_Version::undefined(), JDK_Version::jdk(26), JDK_Version::jdk(27) }, + { "PSChunkLargeArrays", JDK_Version::jdk(26), JDK_Version::jdk(27), JDK_Version::jdk(28) }, #ifdef ASSERT { "DummyObsoleteTestFlag", JDK_Version::undefined(), JDK_Version::jdk(18), JDK_Version::undefined() }, @@ -1117,6 +1092,13 @@ void Arguments::print_summary_on(outputStream* st) { st->cr(); } +void Arguments::set_jvm_flags_file(const char *value) { + if (_jvm_flags_file != nullptr) { + os::free(_jvm_flags_file); + } + _jvm_flags_file = os::strdup_check_oom(value); +} + void Arguments::print_jvm_flags_on(outputStream* st) { if (_num_jvm_flags > 0) { for (int i=0; i < _num_jvm_flags; i++) { @@ -1478,10 +1460,10 @@ void Arguments::set_conservative_max_heap_alignment() { // the alignments imposed by several sources: any requirements from the heap // itself and the maximum page size we may run the VM with. size_t heap_alignment = GCConfig::arguments()->conservative_max_heap_alignment(); - _conservative_max_heap_alignment = MAX4(heap_alignment, + _conservative_max_heap_alignment = MAX3(heap_alignment, os::vm_allocation_granularity(), - os::max_page_size(), - GCArguments::compute_heap_alignment()); + os::max_page_size()); + assert(is_power_of_2(_conservative_max_heap_alignment), "Expected to be a power-of-2"); } jint Arguments::set_ergonomics_flags() { @@ -1589,8 +1571,8 @@ void Arguments::set_heap_size() { } if (UseCompressedOops) { - size_t heap_end = HeapBaseMinAddress + MaxHeapSize; - size_t max_coop_heap = max_heap_for_compressed_oops(); + uintptr_t heap_end = HeapBaseMinAddress + MaxHeapSize; + uintptr_t max_coop_heap = max_heap_for_compressed_oops(); // Limit the heap size to the maximum possible when using compressed oops if (heap_end < max_coop_heap) { @@ -1604,10 +1586,10 @@ void Arguments::set_heap_size() { // and UseCompressedOops was not specified. if (reasonable_max > max_coop_heap) { if (FLAG_IS_ERGO(UseCompressedOops) && has_ram_limit) { - aot_log_info(aot)("UseCompressedOops disabled due to " - "max heap %zu > compressed oop heap %zu. " - "Please check the setting of MaxRAMPercentage %5.2f.", - reasonable_max, max_coop_heap, MaxRAMPercentage); + log_debug(gc, heap, coops)("UseCompressedOops disabled due to " + "max heap %zu > compressed oop heap %zu. " + "Please check the setting of MaxRAMPercentage %5.2f.", + reasonable_max, (size_t)max_coop_heap, MaxRAMPercentage); FLAG_SET_ERGO(UseCompressedOops, false); } else { reasonable_max = max_coop_heap; @@ -2869,6 +2851,10 @@ jint Arguments::parse_each_vm_init_arg(const JavaVMInitArgs* args, JVMFlagOrigin return JNI_OK; } +void Arguments::set_ext_dirs(char *value) { + _ext_dirs = os::strdup_check_oom(value); +} + void Arguments::add_patch_mod_prefix(const char* module_name, const char* path) { // For java.base check for duplicate --patch-module options being specified on the command line. // This check is only required for java.base, all other duplicate module specifications diff --git a/src/hotspot/share/runtime/arguments.hpp b/src/hotspot/share/runtime/arguments.hpp index 3d83959d76a..f2bcc21e123 100644 --- a/src/hotspot/share/runtime/arguments.hpp +++ b/src/hotspot/share/runtime/arguments.hpp @@ -25,18 +25,17 @@ #ifndef SHARE_RUNTIME_ARGUMENTS_HPP #define SHARE_RUNTIME_ARGUMENTS_HPP -#include "logging/logLevel.hpp" -#include "logging/logTag.hpp" +#include "jni.h" #include "memory/allocation.hpp" #include "memory/allStatic.hpp" -#include "runtime/globals.hpp" +#include "runtime/flags/jvmFlag.hpp" #include "runtime/java.hpp" -#include "runtime/os.hpp" -#include "utilities/debug.hpp" -#include "utilities/vmEnums.hpp" +#include "utilities/globalDefinitions.hpp" // Arguments parses the command line and recognizes options +template +class GrowableArray; class JVMFlag; // Invocation API hook typedefs (these should really be defined in jni.h) @@ -412,12 +411,8 @@ class Arguments : AllStatic { // convenient methods to get and set jvm_flags_file static const char* get_jvm_flags_file() { return _jvm_flags_file; } - static void set_jvm_flags_file(const char *value) { - if (_jvm_flags_file != nullptr) { - os::free(_jvm_flags_file); - } - _jvm_flags_file = os::strdup_check_oom(value); - } + static void set_jvm_flags_file(const char *value); + // convenient methods to obtain / print jvm_flags and jvm_args static const char* jvm_flags() { return build_resource_string(_jvm_flags_array, _num_jvm_flags); } static const char* jvm_args() { return build_resource_string(_jvm_args_array, _num_jvm_args); } @@ -479,7 +474,7 @@ class Arguments : AllStatic { static void set_dll_dir(const char *value) { _sun_boot_library_path->set_value(value); } static void set_java_home(const char *value) { _java_home->set_value(value); } static void set_library_path(const char *value) { _java_library_path->set_value(value); } - static void set_ext_dirs(char *value) { _ext_dirs = os::strdup_check_oom(value); } + static void set_ext_dirs(char *value); // Set up the underlying pieces of the boot class path static void add_patch_mod_prefix(const char *module_name, const char *path); diff --git a/src/hotspot/share/runtime/atomic.hpp b/src/hotspot/share/runtime/atomic.hpp index 5b4d7d8659f..f708e9c18ca 100644 --- a/src/hotspot/share/runtime/atomic.hpp +++ b/src/hotspot/share/runtime/atomic.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,7 +26,7 @@ #define SHARE_RUNTIME_ATOMIC_HPP #include "cppstdlib/type_traits.hpp" -#include "metaprogramming/enableIf.hpp" +#include "metaprogramming/dependentAlwaysFalse.hpp" #include "metaprogramming/primitiveConversions.hpp" #include "runtime/atomicAccess.hpp" #include "utilities/globalDefinitions.hpp" @@ -75,6 +75,8 @@ // v.release_store(x) -> void // v.release_store_fence(x) -> void // v.compare_exchange(x, y [, o]) -> T +// v.compare_set(x, y [, o]) -> bool +// v.exchange(x [, o]) -> T // // (2) All atomic types are default constructible. // @@ -88,11 +90,14 @@ // value will be initialized as if by translating the value that would be // provided by default constructing an atomic type for the value type's // decayed type. - -// (3) Atomic pointers and atomic integers additionally provide +// +// (3) Constructors for all atomic types are constexpr, to ensure non-local +// atomic variables are constant initialized (C++17 6.6.2) when initialized +// with suitable arguments. +// +// (4) Atomic pointers and atomic integers additionally provide // // member functions: -// v.exchange(x [, o]) -> T // v.add_then_fetch(i [, o]) -> T // v.sub_then_fetch(i [, o]) -> T // v.fetch_then_add(i [, o]) -> T @@ -102,9 +107,6 @@ // type of i must be signed, or both must be unsigned. Atomic pointers perform // element arithmetic. // -// (4) An atomic translated type additionally provides the exchange -// function if its associated atomic decayed type provides that function. -// // (5) Atomic integers additionally provide // // member functions: @@ -127,9 +129,6 @@ // stand out a little more when used in surrounding non-atomic code. Without // the "AtomicAccess::" qualifier, some of those names are easily overlooked. // -// Atomic bytes don't provide exchange(). This is because that operation -// hasn't been implemented for 1 byte values. That could be changed if needed. -// // Atomic for 2 byte integers is not supported. This is because atomic // operations of that size have not been implemented. There haven't been // required use-cases. Many platforms don't provide hardware support. @@ -184,15 +183,8 @@ private: // Helper base classes, providing various parts of the APIs. template class CommonCore; - template class SupportsExchange; template class SupportsArithmetic; - // Support conditional exchange() for atomic translated types. - template class HasExchange; - template class DecayedHasExchange; - template::value> - class TranslatedExchange; - public: template()> class Atomic; @@ -230,7 +222,7 @@ class AtomicImpl::CommonCore { T volatile _value; protected: - explicit CommonCore(T value) : _value(value) {} + explicit constexpr CommonCore(T value) : _value(value) {} ~CommonCore() = default; T volatile* value_ptr() { return &_value; } @@ -275,15 +267,12 @@ public: atomic_memory_order order = memory_order_conservative) { return AtomicAccess::cmpxchg(value_ptr(), compare_value, new_value, order); } -}; -template -class AtomicImpl::SupportsExchange : public CommonCore { -protected: - explicit SupportsExchange(T value) : CommonCore(value) {} - ~SupportsExchange() = default; + bool compare_set(T compare_value, T new_value, + atomic_memory_order order = memory_order_conservative) { + return compare_exchange(compare_value, new_value, order) == compare_value; + } -public: T exchange(T new_value, atomic_memory_order order = memory_order_conservative) { return AtomicAccess::xchg(this->value_ptr(), new_value, order); @@ -291,7 +280,7 @@ public: }; template -class AtomicImpl::SupportsArithmetic : public SupportsExchange { +class AtomicImpl::SupportsArithmetic : public CommonCore { // Guarding the AtomicAccess calls with constexpr checking of Offset produces // better compile-time error messages. template @@ -311,7 +300,7 @@ class AtomicImpl::SupportsArithmetic : public SupportsExchange { } protected: - explicit SupportsArithmetic(T value) : SupportsExchange(value) {} + explicit constexpr SupportsArithmetic(T value) : CommonCore(value) {} ~SupportsArithmetic() = default; public: @@ -354,7 +343,7 @@ class AtomicImpl::Atomic : public SupportsArithmetic { public: - explicit Atomic(T value = 0) : SupportsArithmetic(value) {} + explicit constexpr Atomic(T value = 0) : SupportsArithmetic(value) {} NONCOPYABLE(Atomic); @@ -394,7 +383,7 @@ class AtomicImpl::Atomic : public CommonCore { public: - explicit Atomic(T value = 0) : CommonCore(value) {} + explicit constexpr Atomic(T value = 0) : CommonCore(value) {} NONCOPYABLE(Atomic); @@ -410,7 +399,7 @@ class AtomicImpl::Atomic : public SupportsArithmetic { public: - explicit Atomic(T value = nullptr) : SupportsArithmetic(value) {} + explicit constexpr Atomic(T value = nullptr) : SupportsArithmetic(value) {} NONCOPYABLE(Atomic); @@ -424,65 +413,28 @@ public: // Atomic translated type -// Test whether Atomic has exchange(). template -class AtomicImpl::HasExchange { - template static void* test(decltype(&Check::exchange)); - template static int test(...); - using test_type = decltype(test>(nullptr)); -public: - static constexpr bool value = std::is_pointer_v; -}; - -// Test whether the atomic decayed type associated with T has exchange(). -template -class AtomicImpl::DecayedHasExchange { - using Translator = PrimitiveConversions::Translate; - using Decayed = typename Translator::Decayed; - - // "Unit test" HasExchange<>. - static_assert(HasExchange::value); - static_assert(HasExchange::value); - static_assert(!HasExchange::value); - -public: - static constexpr bool value = HasExchange::value; -}; - -// Base class for atomic translated type if atomic decayed type doesn't have -// exchange(). -template -class AtomicImpl::TranslatedExchange {}; - -// Base class for atomic translated type if atomic decayed type does have -// exchange(). -template -class AtomicImpl::TranslatedExchange { -public: - T exchange(T new_value, - atomic_memory_order order = memory_order_conservative) { - return static_cast(this)->exchange_impl(new_value, order); - } -}; - -template -class AtomicImpl::Atomic - : public TranslatedExchange, T> -{ - // Give TranslatedExchange<> access to exchange_impl() if needed. - friend class TranslatedExchange, T>; - +class AtomicImpl::Atomic { using Translator = PrimitiveConversions::Translate; using Decayed = typename Translator::Decayed; Atomic _value; - static Decayed decay(T x) { return Translator::decay(x); } + // The decay function and the constructors are constexpr so that a non-local + // atomic object constructed with constant arguments will be a constant + // initialization. One might ask why it's not a problem that some + // specializations of these functions are not constant expressions. The + // answer lies in C++17 10.1.5/6, along with us having *some* constexpr + // translator decay functions, constexpr ctors for some translated types, + // and constexpr ctors for some decayed types. Also, C++23 removes those + // restrictions on constexpr functions and ctors. + + static constexpr Decayed decay(T x) { return Translator::decay(x); } static T recover(Decayed x) { return Translator::recover(x); } // Support for default construction via the default construction of _value. struct UseDecayedCtor {}; - explicit Atomic(UseDecayedCtor) : _value() {} + explicit constexpr Atomic(UseDecayedCtor) : _value() {} using DefaultCtorSelect = std::conditional_t, T, UseDecayedCtor>; @@ -491,9 +443,9 @@ public: // If T is default constructible, construct from a default constructed T. // Otherwise, default construct the underlying Atomic. - Atomic() : Atomic(DefaultCtorSelect()) {} + constexpr Atomic() : Atomic(DefaultCtorSelect()) {} - explicit Atomic(T value) : _value(decay(value)) {} + explicit constexpr Atomic(T value) : _value(decay(value)) {} NONCOPYABLE(Atomic); @@ -533,12 +485,14 @@ public: order)); } -private: - // Implementation of exchange() if needed. - // Exclude when not needed, to prevent reference to non-existent function - // of atomic decayed type if someone explicitly instantiates Atomic. - template::value)> - T exchange_impl(T new_value, atomic_memory_order order) { + bool compare_set(T compare_value, T new_value, + atomic_memory_order order = memory_order_conservative) { + return _value.compare_set(decay(compare_value), + decay(new_value), + order); + } + + T exchange(T new_value, atomic_memory_order order = memory_order_conservative) { return recover(_value.exchange(decay(new_value), order)); } }; diff --git a/src/hotspot/share/runtime/atomicAccess.hpp b/src/hotspot/share/runtime/atomicAccess.hpp index 72b7f92cf04..c9a2dfb9383 100644 --- a/src/hotspot/share/runtime/atomicAccess.hpp +++ b/src/hotspot/share/runtime/atomicAccess.hpp @@ -419,8 +419,8 @@ private: struct XchgImpl; // Platform-specific implementation of xchg. Support for sizes - // of 4, and sizeof(intptr_t) are required. The class is a function - // object that must be default constructable, with these requirements: + // of 1, 4, and 8 are required. The class is a function object + // that must be default constructable, with these requirements: // // - dest is of type T*. // - exchange_value is of type T. @@ -635,7 +635,6 @@ inline void AtomicAccess::dec(D volatile* dest, atomic_memory_order order) { STATIC_ASSERT(std::is_pointer::value || std::is_integral::value); using I = std::conditional_t::value, ptrdiff_t, D>; // Assumes two's complement integer representation. - #pragma warning(suppress: 4146) AtomicAccess::add(dest, I(-1), order); } @@ -652,7 +651,6 @@ inline D AtomicAccess::sub(D volatile* dest, I sub_value, atomic_memory_order or STATIC_ASSERT(sizeof(I) <= sizeof(AddendType)); AddendType addend = sub_value; // Assumes two's complement integer representation. - #pragma warning(suppress: 4146) // In case AddendType is not signed. return AtomicAccess::add(dest, -addend, order); } diff --git a/src/hotspot/share/runtime/continuation.cpp b/src/hotspot/share/runtime/continuation.cpp index 8f1cbe39640..935f304a751 100644 --- a/src/hotspot/share/runtime/continuation.cpp +++ b/src/hotspot/share/runtime/continuation.cpp @@ -35,6 +35,7 @@ #include "runtime/interfaceSupport.inline.hpp" #include "runtime/javaThread.inline.hpp" #include "runtime/jniHandles.inline.hpp" +#include "runtime/mountUnmountDisabler.hpp" #include "runtime/osThread.hpp" #include "runtime/vframe.inline.hpp" #include "runtime/vframe_hp.hpp" @@ -56,66 +57,50 @@ JVM_ENTRY(void, CONT_unpin(JNIEnv* env, jclass cls)) { } JVM_END -#if INCLUDE_JVMTI -class JvmtiUnmountBeginMark : public StackObj { +class UnmountBeginMark : public StackObj { Handle _vthread; JavaThread* _current; freeze_result _result; bool _failed; public: - JvmtiUnmountBeginMark(JavaThread* t) : + UnmountBeginMark(JavaThread* t) : _vthread(t, t->vthread()), _current(t), _result(freeze_pinned_native), _failed(false) { - assert(!_current->is_in_VTMS_transition(), "must be"); + assert(!_current->is_in_vthread_transition(), "must be"); - if (JvmtiVTMSTransitionDisabler::VTMS_notify_jvmti_events()) { - JvmtiVTMSTransitionDisabler::VTMS_vthread_unmount((jthread)_vthread.raw_value(), true); + MountUnmountDisabler::start_transition(_current, _vthread(), false /*is_mount*/, false /*is_thread_start*/); - // Don't preempt if there is a pending popframe or earlyret operation. This can - // be installed in start_VTMS_transition() so we need to check it here. - if (JvmtiExport::can_pop_frame() || JvmtiExport::can_force_early_return()) { - JvmtiThreadState* state = _current->jvmti_thread_state(); - if (_current->has_pending_popframe() || (state != nullptr && state->is_earlyret_pending())) { - _failed = true; - } - } - - // Don't preempt in case there is an async exception installed since - // we would incorrectly throw it during the unmount logic in the carrier. - if (_current->has_async_exception_condition()) { + // Don't preempt if there is a pending popframe or earlyret operation. This can + // be installed in in process_at_transition_start() so we need to check it here. + if (JvmtiExport::can_pop_frame() || JvmtiExport::can_force_early_return()) { + JvmtiThreadState* state = _current->jvmti_thread_state(); + if (_current->has_pending_popframe() || (state != nullptr && state->is_earlyret_pending())) { _failed = true; } - } else { - _current->set_is_in_VTMS_transition(true); - java_lang_Thread::set_is_in_VTMS_transition(_vthread(), true); + } + + // Don't preempt in case there is an async exception installed since + // we would incorrectly throw it during the unmount logic in the carrier. + if (_current->has_async_exception_condition()) { + _failed = true; } } - ~JvmtiUnmountBeginMark() { + ~UnmountBeginMark() { assert(!_current->is_suspended(), "must be"); - - assert(_current->is_in_VTMS_transition(), "must be"); - assert(java_lang_Thread::is_in_VTMS_transition(_vthread()), "must be"); - - // Read it again since for late binding agents the flag could have - // been set while blocked in the allocation path during freeze. - bool jvmti_present = JvmtiVTMSTransitionDisabler::VTMS_notify_jvmti_events(); + assert(_current->is_in_vthread_transition(), "must be"); if (_result != freeze_ok) { // Undo transition - if (jvmti_present) { - JvmtiVTMSTransitionDisabler::VTMS_vthread_mount((jthread)_vthread.raw_value(), false); - } else { - _current->set_is_in_VTMS_transition(false); - java_lang_Thread::set_is_in_VTMS_transition(_vthread(), false); - } + MountUnmountDisabler::end_transition(_current, _vthread(), true /*is_mount*/, false /*is_thread_start*/); } } void set_result(freeze_result res) { _result = res; } bool failed() { return _failed; } }; +#if INCLUDE_JVMTI static bool is_vthread_safe_to_preempt_for_jvmti(JavaThread* current) { - if (current->is_in_VTMS_transition()) { + if (current->is_in_vthread_transition()) { // We are at the end of a mount transition. return false; } @@ -150,11 +135,11 @@ freeze_result Continuation::try_preempt(JavaThread* current, oop continuation) { return freeze_pinned_native; } - JVMTI_ONLY(JvmtiUnmountBeginMark jubm(current);) - JVMTI_ONLY(if (jubm.failed()) return freeze_pinned_native;) + UnmountBeginMark ubm(current); + if (ubm.failed()) return freeze_pinned_native; freeze_result res = CAST_TO_FN_PTR(FreezeContFnT, freeze_preempt_entry())(current, current->last_Java_sp()); log_trace(continuations, preempt)("try_preempt: %d", res); - JVMTI_ONLY(jubm.set_result(res);) + ubm.set_result(res); if (current->has_pending_exception()) { assert(res == freeze_exception, "expecting an exception result from freeze"); diff --git a/src/hotspot/share/runtime/continuationFreezeThaw.cpp b/src/hotspot/share/runtime/continuationFreezeThaw.cpp index 2a31e5fb5b2..9df89f1f12a 100644 --- a/src/hotspot/share/runtime/continuationFreezeThaw.cpp +++ b/src/hotspot/share/runtime/continuationFreezeThaw.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -58,6 +58,7 @@ #include "runtime/javaThread.inline.hpp" #include "runtime/jniHandles.inline.hpp" #include "runtime/keepStackGCProcessed.hpp" +#include "runtime/mountUnmountDisabler.hpp" #include "runtime/objectMonitor.inline.hpp" #include "runtime/orderAccess.hpp" #include "runtime/prefetch.inline.hpp" @@ -1690,7 +1691,7 @@ static void jvmti_mount_end(JavaThread* current, ContinuationWrapper& cont, fram AnchorMark am(current, top); // Set anchor so that the stack is walkable. JRT_BLOCK - JvmtiVTMSTransitionDisabler::VTMS_vthread_mount((jthread)vth.raw_value(), false); + MountUnmountDisabler::end_transition(current, vth(), true /*is_mount*/, false /*is_thread_start*/); if (current->pending_contended_entered_event()) { // No monitor JVMTI events for ObjectLocker case. @@ -1735,8 +1736,8 @@ static void verify_frame_kind(frame& top, Continuation::preempt_kind preempt_kin m = top.interpreter_frame_method(); assert(!m->is_native() || m->is_synchronized(), "invalid method %s", m->external_name()); address bcp = top.interpreter_frame_bcp(); - assert(bcp != 0 || m->is_native(), ""); - at_sync_method = m->is_synchronized() && (bcp == 0 || bcp == m->code_base()); + assert(bcp != nullptr || m->is_native(), ""); + at_sync_method = m->is_synchronized() && (bcp == nullptr || bcp == m->code_base()); // bcp is advanced on monitorenter before making the VM call, adjust for that. bool at_sync_bytecode = bcp > m->code_base() && Bytecode(m, bcp - 1).code() == Bytecodes::Code::_monitorenter; assert(at_sync_method || at_sync_bytecode, ""); @@ -2629,19 +2630,21 @@ intptr_t* ThawBase::handle_preempted_continuation(intptr_t* sp, Continuation::pr DEBUG_ONLY(verify_frame_kind(top, preempt_kind);) NOT_PRODUCT(int64_t tid = _thread->monitor_owner_id();) -#if INCLUDE_JVMTI // Finish the VTMS transition. - assert(_thread->is_in_VTMS_transition(), "must be"); + assert(_thread->is_in_vthread_transition(), "must be"); bool is_vthread = Continuation::continuation_scope(_cont.continuation()) == java_lang_VirtualThread::vthread_scope(); if (is_vthread) { - if (JvmtiVTMSTransitionDisabler::VTMS_notify_jvmti_events()) { +#if INCLUDE_JVMTI + if (MountUnmountDisabler::notify_jvmti_events()) { jvmti_mount_end(_thread, _cont, top, preempt_kind); - } else { - _thread->set_is_in_VTMS_transition(false); - java_lang_Thread::set_is_in_VTMS_transition(_thread->vthread(), false); + } else +#endif + { // Faster version of MountUnmountDisabler::end_transition() to avoid + // unnecessary extra instructions from jvmti_mount_end(). + java_lang_Thread::set_is_in_vthread_transition(_thread->vthread(), false); + _thread->set_is_in_vthread_transition(false); } } -#endif if (fast_case) { // If we thawed in the slow path the runtime stub/native wrapper frame already diff --git a/src/hotspot/share/runtime/continuationHelper.inline.hpp b/src/hotspot/share/runtime/continuationHelper.inline.hpp index 9aadfd8a388..7bbdbd4935d 100644 --- a/src/hotspot/share/runtime/continuationHelper.inline.hpp +++ b/src/hotspot/share/runtime/continuationHelper.inline.hpp @@ -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 diff --git a/src/hotspot/share/runtime/continuationJavaClasses.hpp b/src/hotspot/share/runtime/continuationJavaClasses.hpp index 91224b94e1e..8397c7c1493 100644 --- a/src/hotspot/share/runtime/continuationJavaClasses.hpp +++ b/src/hotspot/share/runtime/continuationJavaClasses.hpp @@ -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 diff --git a/src/hotspot/share/runtime/continuationWrapper.inline.hpp b/src/hotspot/share/runtime/continuationWrapper.inline.hpp index ddf5e0be02d..1f9226f8bd3 100644 --- a/src/hotspot/share/runtime/continuationWrapper.inline.hpp +++ b/src/hotspot/share/runtime/continuationWrapper.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/hotspot/share/runtime/cpuTimeCounters.cpp b/src/hotspot/share/runtime/cpuTimeCounters.cpp index e5364550b6c..e174407089c 100644 --- a/src/hotspot/share/runtime/cpuTimeCounters.cpp +++ b/src/hotspot/share/runtime/cpuTimeCounters.cpp @@ -118,8 +118,5 @@ ThreadTotalCPUTimeClosure::~ThreadTotalCPUTimeClosure() { } void ThreadTotalCPUTimeClosure::do_thread(Thread* thread) { - // The default code path (fast_thread_cpu_time()) asserts that - // pthread_getcpuclockid() and clock_gettime() must return 0. Thus caller - // must ensure the thread exists and has not terminated. _total += os::thread_cpu_time(thread); } diff --git a/src/hotspot/share/runtime/cpuTimeCounters.hpp b/src/hotspot/share/runtime/cpuTimeCounters.hpp index 9ad00492731..c2e636bdb1d 100644 --- a/src/hotspot/share/runtime/cpuTimeCounters.hpp +++ b/src/hotspot/share/runtime/cpuTimeCounters.hpp @@ -28,6 +28,7 @@ #define SHARE_RUNTIME_CPUTIMECOUNTERS_HPP +#include "gc/shared/gc_globals.hpp" #include "memory/iterator.hpp" #include "runtime/os.hpp" #include "runtime/perfData.hpp" @@ -83,7 +84,9 @@ public: assert(_instance == nullptr, "we can only allocate one CPUTimeCounters object"); if (UsePerfData && os::is_thread_cpu_time_supported()) { _instance = new CPUTimeCounters(); - create_counter(SUN_THREADS, CPUTimeGroups::CPUTimeType::gc_total); + if (UseG1GC || UseParallelGC) { + create_counter(SUN_THREADS, CPUTimeGroups::CPUTimeType::gc_total); + } } } diff --git a/src/hotspot/share/runtime/deoptimization.cpp b/src/hotspot/share/runtime/deoptimization.cpp index 0aa7b392b17..e2029a26d37 100644 --- a/src/hotspot/share/runtime/deoptimization.cpp +++ b/src/hotspot/share/runtime/deoptimization.cpp @@ -498,6 +498,9 @@ Deoptimization::UnrollBlock* Deoptimization::fetch_unroll_info_helper(JavaThread RegisterMap::WalkContinuation::skip); // Now get the deoptee with a valid map frame deoptee = stub_frame.sender(&map); + if (exec_mode == Unpack_deopt) { + assert(deoptee.is_deoptimized_frame(), "frame is not marked for deoptimization"); + } // Set the deoptee nmethod assert(current->deopt_compiled_method() == nullptr, "Pending deopt!"); nmethod* nm = deoptee.cb()->as_nmethod_or_null(); diff --git a/src/hotspot/share/runtime/flags/flagSetting.hpp b/src/hotspot/share/runtime/flags/flagSetting.hpp index 0f75636621a..f4e20a951d9 100644 --- a/src/hotspot/share/runtime/flags/flagSetting.hpp +++ b/src/hotspot/share/runtime/flags/flagSetting.hpp @@ -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 diff --git a/src/hotspot/share/runtime/flags/jvmFlag.cpp b/src/hotspot/share/runtime/flags/jvmFlag.cpp index 487528c49bd..405b47e1813 100644 --- a/src/hotspot/share/runtime/flags/jvmFlag.cpp +++ b/src/hotspot/share/runtime/flags/jvmFlag.cpp @@ -162,7 +162,7 @@ void JVMFlag::print_on(outputStream* st, bool withComments, bool printRanges) co // uintx ThresholdTolerance = 10 {product} {default} // size_t TLABSize = 0 {product} {default} // uintx SurvivorRatio = 8 {product} {default} - // double InitialRAMPercentage = 1.562500 {product} {default} + // double InitialRAMPercentage = 0.000000 {product} {default} // ccstr CompileCommandFile = MyFile.cmd {product} {command line} // ccstrlist CompileOnly = Method1 // CompileOnly += Method2 {product} {command line} @@ -711,7 +711,7 @@ void JVMFlag::printFlags(outputStream* out, bool withComments, bool printRanges, for (size_t i = 0; i < length; i++) { const bool skip = (skipDefaults && flagTable[i].is_default()); const bool visited = iteratorMarkers.at(i); - if (!visited && flagTable[i].is_unlocked() && !skip) { + if (!visited && !skip) { if ((bestFlag == nullptr) || (strcmp(bestFlag->name(), flagTable[i].name()) > 0)) { bestFlag = &flagTable[i]; bestFlagIndex = i; diff --git a/src/hotspot/share/runtime/flags/jvmFlagConstraintsCompiler.cpp b/src/hotspot/share/runtime/flags/jvmFlagConstraintsCompiler.cpp index d0141c2e6cc..63d3424b3ed 100644 --- a/src/hotspot/share/runtime/flags/jvmFlagConstraintsCompiler.cpp +++ b/src/hotspot/share/runtime/flags/jvmFlagConstraintsCompiler.cpp @@ -306,7 +306,7 @@ JVMFlag::Error TypeProfileLevelConstraintFunc(uint value, bool verbose) { } JVMFlag::Error VerifyIterativeGVNConstraintFunc(uint value, bool verbose) { - const int max_modes = 4; + const int max_modes = 5; uint original_value = value; for (int i = 0; i < max_modes; i++) { if (value % 10 > 1) { diff --git a/src/hotspot/share/runtime/flags/jvmFlagLookup.hpp b/src/hotspot/share/runtime/flags/jvmFlagLookup.hpp index 256f674f19c..6459aed60e6 100644 --- a/src/hotspot/share/runtime/flags/jvmFlagLookup.hpp +++ b/src/hotspot/share/runtime/flags/jvmFlagLookup.hpp @@ -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 diff --git a/src/hotspot/share/runtime/frame.cpp b/src/hotspot/share/runtime/frame.cpp index b5cd4acc75d..8f969600ba8 100644 --- a/src/hotspot/share/runtime/frame.cpp +++ b/src/hotspot/share/runtime/frame.cpp @@ -206,7 +206,7 @@ address frame::raw_pc() const { if (is_deoptimized_frame()) { nmethod* nm = cb()->as_nmethod_or_null(); assert(nm != nullptr, "only nmethod is expected here"); - return nm->deopt_handler_begin() - pc_return_offset; + return nm->deopt_handler_entry() - pc_return_offset; } else { return (pc() - pc_return_offset); } @@ -355,7 +355,7 @@ void frame::deoptimize(JavaThread* thread) { // If the call site is a MethodHandle call site use the MH deopt handler. nmethod* nm = _cb->as_nmethod(); - address deopt = nm->deopt_handler_begin(); + address deopt = nm->deopt_handler_entry(); NativePostCallNop* inst = nativePostCallNop_at(pc()); diff --git a/src/hotspot/share/runtime/globals.hpp b/src/hotspot/share/runtime/globals.hpp index d002edd48cd..6a2bbc9c648 100644 --- a/src/hotspot/share/runtime/globals.hpp +++ b/src/hotspot/share/runtime/globals.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -809,6 +809,7 @@ const int ObjectAlignmentInBytes = 8; \ develop(uintx, PreallocatedOutOfMemoryErrorCount, 4, \ "Number of OutOfMemoryErrors preallocated with backtrace") \ + range(0, 1024) \ \ product(bool, UseXMMForArrayCopy, false, \ "Use SSE2 MOVQ instruction for Arraycopy") \ @@ -1369,6 +1370,7 @@ const int ObjectAlignmentInBytes = 8; \ product(int, SpecTrapLimitExtraEntries, 3, EXPERIMENTAL, \ "Extra method data trap entries for speculation") \ + range(0, 100) \ \ product(double, InlineFrequencyRatio, 0.25, DIAGNOSTIC, \ "Ratio of call site execution to caller method invocation") \ @@ -1671,8 +1673,9 @@ const int ObjectAlignmentInBytes = 8; "putback") \ \ /* new oopmap storage allocation */ \ - develop(intx, MinOopMapAllocation, 8, \ + develop(int, MinOopMapAllocation, 8, \ "Minimum number of OopMap entries in an OopMapSet") \ + range(0, max_jint) \ \ /* recompilation */ \ product_pd(intx, CompileThreshold, \ diff --git a/src/hotspot/share/runtime/handles.hpp b/src/hotspot/share/runtime/handles.hpp index 7b51b4aff1c..68c948a493b 100644 --- a/src/hotspot/share/runtime/handles.hpp +++ b/src/hotspot/share/runtime/handles.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 diff --git a/src/hotspot/share/runtime/handles.inline.hpp b/src/hotspot/share/runtime/handles.inline.hpp index 15b06315823..4e1e2ed8686 100644 --- a/src/hotspot/share/runtime/handles.inline.hpp +++ b/src/hotspot/share/runtime/handles.inline.hpp @@ -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 diff --git a/src/hotspot/share/runtime/handshake.cpp b/src/hotspot/share/runtime/handshake.cpp index f468f27e2c0..89b02717a7a 100644 --- a/src/hotspot/share/runtime/handshake.cpp +++ b/src/hotspot/share/runtime/handshake.cpp @@ -34,6 +34,7 @@ #include "runtime/handshake.hpp" #include "runtime/interfaceSupport.inline.hpp" #include "runtime/javaThread.inline.hpp" +#include "runtime/mountUnmountDisabler.hpp" #include "runtime/os.hpp" #include "runtime/osThread.hpp" #include "runtime/stackWatermarkSet.hpp" @@ -361,6 +362,27 @@ void Handshake::execute(HandshakeClosure* hs_cl) { VMThread::execute(&handshake); } +void Handshake::execute(HandshakeClosure* hs_cl, oop vthread) { + assert(java_lang_VirtualThread::is_instance(vthread), ""); + Handle vth(JavaThread::current(), vthread); + + MountUnmountDisabler md(vthread); + oop carrier_thread = java_lang_VirtualThread::carrier_thread(vth()); + if (carrier_thread != nullptr) { + JavaThread* target = java_lang_Thread::thread(carrier_thread); + assert(target != nullptr, ""); + // Technically there is no need for a ThreadsListHandle since the target + // will block if it tries to unmount the vthread, so it can never exit. + ThreadsListHandle tlh(JavaThread::current()); + assert(tlh.includes(target), ""); + execute(hs_cl, &tlh, target); + assert(target->threadObj() == java_lang_VirtualThread::carrier_thread(vth()), ""); + } else { + // unmounted vthread, execute closure with the current thread + hs_cl->do_thread(nullptr); + } +} + void Handshake::execute(HandshakeClosure* hs_cl, JavaThread* target) { // tlh == nullptr means we rely on a ThreadsListHandle somewhere // in the caller's context (and we sanity check for that). diff --git a/src/hotspot/share/runtime/handshake.hpp b/src/hotspot/share/runtime/handshake.hpp index 1304dca12b7..c764bbcfcd2 100644 --- a/src/hotspot/share/runtime/handshake.hpp +++ b/src/hotspot/share/runtime/handshake.hpp @@ -69,6 +69,7 @@ class Handshake : public AllStatic { // This version of execute() relies on a ThreadListHandle somewhere in // the caller's context to protect target (and we sanity check for that). static void execute(HandshakeClosure* hs_cl, JavaThread* target); + static void execute(HandshakeClosure* hs_cl, oop vthread); // This version of execute() is used when you have a ThreadListHandle in // hand and are using it to protect target. If tlh == nullptr, then we // sanity check for a ThreadListHandle somewhere in the caller's context diff --git a/src/hotspot/share/runtime/icache.hpp b/src/hotspot/share/runtime/icache.hpp index e442c4d88e0..bc153862323 100644 --- a/src/hotspot/share/runtime/icache.hpp +++ b/src/hotspot/share/runtime/icache.hpp @@ -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 diff --git a/src/hotspot/share/runtime/interfaceSupport.inline.hpp b/src/hotspot/share/runtime/interfaceSupport.inline.hpp index c52c2664faa..ecd7397d81a 100644 --- a/src/hotspot/share/runtime/interfaceSupport.inline.hpp +++ b/src/hotspot/share/runtime/interfaceSupport.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2021, Azul Systems, Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/src/hotspot/share/runtime/java.cpp b/src/hotspot/share/runtime/java.cpp index fb4abdac2ef..c49a9f5d4b8 100644 --- a/src/hotspot/share/runtime/java.cpp +++ b/src/hotspot/share/runtime/java.cpp @@ -71,6 +71,7 @@ #include "runtime/interfaceSupport.inline.hpp" #include "runtime/java.hpp" #include "runtime/javaThread.hpp" +#include "runtime/os.hpp" #include "runtime/sharedRuntime.hpp" #include "runtime/stubRoutines.hpp" #include "runtime/task.hpp" @@ -765,3 +766,23 @@ void JDK_Version::to_string(char* buffer, size_t buflen) const { } } } + +void JDK_Version::set_java_version(const char* version) { + _java_version = os::strdup(version); +} + +void JDK_Version::set_runtime_name(const char* name) { + _runtime_name = os::strdup(name); +} + +void JDK_Version::set_runtime_version(const char* version) { + _runtime_version = os::strdup(version); +} + +void JDK_Version::set_runtime_vendor_version(const char* vendor_version) { + _runtime_vendor_version = os::strdup(vendor_version); +} + +void JDK_Version::set_runtime_vendor_vm_bug_url(const char* vendor_vm_bug_url) { + _runtime_vendor_vm_bug_url = os::strdup(vendor_vm_bug_url); +} diff --git a/src/hotspot/share/runtime/java.hpp b/src/hotspot/share/runtime/java.hpp index 67cf3fb686a..2eb8d6b8ead 100644 --- a/src/hotspot/share/runtime/java.hpp +++ b/src/hotspot/share/runtime/java.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 @@ -25,7 +25,6 @@ #ifndef SHARE_RUNTIME_JAVA_HPP #define SHARE_RUNTIME_JAVA_HPP -#include "runtime/os.hpp" #include "utilities/globalDefinitions.hpp" class Handle; @@ -133,38 +132,27 @@ class JDK_Version { static const char* java_version() { return _java_version; } - static void set_java_version(const char* version) { - _java_version = os::strdup(version); - } + static void set_java_version(const char* version); static const char* runtime_name() { return _runtime_name; } - static void set_runtime_name(const char* name) { - _runtime_name = os::strdup(name); - } + static void set_runtime_name(const char* name); static const char* runtime_version() { return _runtime_version; } - static void set_runtime_version(const char* version) { - _runtime_version = os::strdup(version); - } + static void set_runtime_version(const char* version); static const char* runtime_vendor_version() { return _runtime_vendor_version; } - static void set_runtime_vendor_version(const char* vendor_version) { - _runtime_vendor_version = os::strdup(vendor_version); - } + static void set_runtime_vendor_version(const char* vendor_version); static const char* runtime_vendor_vm_bug_url() { return _runtime_vendor_vm_bug_url; } - static void set_runtime_vendor_vm_bug_url(const char* vendor_vm_bug_url) { - _runtime_vendor_vm_bug_url = os::strdup(vendor_vm_bug_url); - } - + static void set_runtime_vendor_vm_bug_url(const char* vendor_vm_bug_url); }; #endif // SHARE_RUNTIME_JAVA_HPP diff --git a/src/hotspot/share/runtime/javaThread.cpp b/src/hotspot/share/runtime/javaThread.cpp index 28bc47c4c74..4ee9a9dfd79 100644 --- a/src/hotspot/share/runtime/javaThread.cpp +++ b/src/hotspot/share/runtime/javaThread.cpp @@ -448,15 +448,11 @@ JavaThread::JavaThread(MemTag mem_tag) : _do_not_unlock_if_synchronized(false), #if INCLUDE_JVMTI _carrier_thread_suspended(false), - _is_in_VTMS_transition(false), _is_disable_suspend(false), _is_in_java_upcall(false), - _VTMS_transition_mark(false), + _jvmti_events_disabled(0), _on_monitor_waited_event(false), _contended_entered_monitor(nullptr), -#ifdef ASSERT - _is_VTMS_transition_disabler(false), -#endif #endif _jni_attach_state(_not_attaching_via_jni), _is_in_internal_oome_mark(false), @@ -502,6 +498,10 @@ JavaThread::JavaThread(MemTag mem_tag) : _handshake(this), _suspend_resume_manager(this, &_handshake._lock), + _is_in_vthread_transition(false), + DEBUG_ONLY(_is_vthread_transition_disabler(false) COMMA) + DEBUG_ONLY(_is_disabler_at_start(false) COMMA) + _popframe_preserved_args(nullptr), _popframe_preserved_args_size(0), @@ -1052,7 +1052,11 @@ void JavaThread::set_exception_oop(oop o) { } void JavaThread::handle_special_runtime_exit_condition() { - if (is_obj_deopt_suspend()) { + // We mustn't block for object deopt if the thread is + // currently executing in a JNI critical region, as that + // can cause deadlock because allocation may be locked out + // and the object deopt suspender may try to allocate. + if (is_obj_deopt_suspend() && !in_critical()) { frame_anchor()->make_walkable(); wait_for_object_deoptimization(); } @@ -1149,17 +1153,26 @@ void JavaThread::send_async_exception(JavaThread* target, oop java_throwable) { Handshake::execute(&iaeh, target); } -#if INCLUDE_JVMTI -void JavaThread::set_is_in_VTMS_transition(bool val) { - assert(is_in_VTMS_transition() != val, "already %s transition", val ? "inside" : "outside"); - _is_in_VTMS_transition = val; +bool JavaThread::is_in_vthread_transition() const { + DEBUG_ONLY(Thread* current = Thread::current();) + assert(is_handshake_safe_for(current) || SafepointSynchronize::is_at_safepoint() + || JavaThread::cast(current)->is_disabler_at_start(), "not safe"); + return AtomicAccess::load(&_is_in_vthread_transition); +} + +void JavaThread::set_is_in_vthread_transition(bool val) { + assert(is_in_vthread_transition() != val, "already %s transition", val ? "inside" : "outside"); + AtomicAccess::store(&_is_in_vthread_transition, val); } #ifdef ASSERT -void JavaThread::set_is_VTMS_transition_disabler(bool val) { - _is_VTMS_transition_disabler = val; +void JavaThread::set_is_vthread_transition_disabler(bool val) { + _is_vthread_transition_disabler = val; +} + +void JavaThread::set_is_disabler_at_start(bool val) { + _is_disabler_at_start = val; } -#endif #endif // External suspension mechanism. @@ -1169,11 +1182,8 @@ void JavaThread::set_is_VTMS_transition_disabler(bool val) { // - Target thread will not enter any new monitors. // bool JavaThread::java_suspend(bool register_vthread_SR) { -#if INCLUDE_JVMTI - // Suspending a JavaThread in VTMS transition or disabling VTMS transitions can cause deadlocks. - assert(!is_in_VTMS_transition(), "no suspend allowed in VTMS transition"); - assert(!is_VTMS_transition_disabler(), "no suspend allowed for VTMS transition disablers"); -#endif + // Suspending a vthread transition disabler can cause deadlocks. + assert(!is_vthread_transition_disabler(), "no suspend allowed for vthread transition disablers"); guarantee(Thread::is_JavaThread_protected(/* target */ this), "target JavaThread is not protected in calling context."); diff --git a/src/hotspot/share/runtime/javaThread.hpp b/src/hotspot/share/runtime/javaThread.hpp index b0cd6fb3e4f..d4c12887e10 100644 --- a/src/hotspot/share/runtime/javaThread.hpp +++ b/src/hotspot/share/runtime/javaThread.hpp @@ -322,15 +322,11 @@ class JavaThread: public Thread { // never locked) when throwing an exception. Used by interpreter only. #if INCLUDE_JVMTI volatile bool _carrier_thread_suspended; // Carrier thread is externally suspended - bool _is_in_VTMS_transition; // thread is in virtual thread mount state transition bool _is_disable_suspend; // JVMTI suspend is temporarily disabled; used on current thread only bool _is_in_java_upcall; // JVMTI is doing a Java upcall, so JVMTI events must be hidden - bool _VTMS_transition_mark; // used for sync between VTMS transitions and disablers + int _jvmti_events_disabled; // JVMTI events disabled manually bool _on_monitor_waited_event; // Avoid callee arg processing for enterSpecial when posting waited event ObjectMonitor* _contended_entered_monitor; // Monitor for pending monitor_contended_entered callback -#ifdef ASSERT - bool _is_VTMS_transition_disabler; // thread currently disabled VTMS transitions -#endif #endif // JNI attach states: @@ -736,6 +732,20 @@ public: // current thread, i.e. reverts optimizations based on escape analysis. void wait_for_object_deoptimization(); +private: + bool _is_in_vthread_transition; // thread is in virtual thread mount state transition + DEBUG_ONLY(bool _is_vthread_transition_disabler;) // thread currently disabled vthread transitions + DEBUG_ONLY(bool _is_disabler_at_start;) // thread at process of disabling vthread transitions +public: + bool is_in_vthread_transition() const; + void set_is_in_vthread_transition(bool val); +#ifdef ASSERT + bool is_vthread_transition_disabler() const { return _is_vthread_transition_disabler; } + void set_is_vthread_transition_disabler(bool val); + bool is_disabler_at_start() const { return _is_disabler_at_start; } + void set_is_disabler_at_start(bool val); +#endif + #if INCLUDE_JVMTI inline bool set_carrier_thread_suspended(); inline bool clear_carrier_thread_suspended(); @@ -744,33 +754,28 @@ public: return AtomicAccess::load(&_carrier_thread_suspended); } - bool is_in_VTMS_transition() const { return _is_in_VTMS_transition; } - void set_is_in_VTMS_transition(bool val); - bool is_disable_suspend() const { return _is_disable_suspend; } - void toggle_is_disable_suspend() { _is_disable_suspend = !_is_disable_suspend; }; + void toggle_is_disable_suspend() { _is_disable_suspend = !_is_disable_suspend; } bool is_in_java_upcall() const { return _is_in_java_upcall; } - void toggle_is_in_java_upcall() { _is_in_java_upcall = !_is_in_java_upcall; }; + void toggle_is_in_java_upcall() { _is_in_java_upcall = !_is_in_java_upcall; } - bool VTMS_transition_mark() const { return AtomicAccess::load(&_VTMS_transition_mark); } - void set_VTMS_transition_mark(bool val) { AtomicAccess::store(&_VTMS_transition_mark, val); } + void disable_jvmti_events() { _jvmti_events_disabled++; } + void enable_jvmti_events() { _jvmti_events_disabled--; } // Temporarily skip posting JVMTI events for safety reasons when executions is in a critical section: - // - is in a VTMS transition (_is_in_VTMS_transition) + // - is in a vthread transition (_is_in_vthread_transition) // - is in an interruptLock or similar critical section (_is_disable_suspend) // - JVMTI is making a Java upcall (_is_in_java_upcall) - bool should_hide_jvmti_events() const { return _is_in_VTMS_transition || _is_disable_suspend || _is_in_java_upcall; } + bool should_hide_jvmti_events() const { + return _is_in_vthread_transition || _is_disable_suspend || _is_in_java_upcall || _jvmti_events_disabled != 0; + } bool on_monitor_waited_event() { return _on_monitor_waited_event; } void set_on_monitor_waited_event(bool val) { _on_monitor_waited_event = val; } bool pending_contended_entered_event() { return _contended_entered_monitor != nullptr; } ObjectMonitor* contended_entered_monitor() { return _contended_entered_monitor; } -#ifdef ASSERT - bool is_VTMS_transition_disabler() const { return _is_VTMS_transition_disabler; } - void set_is_VTMS_transition_disabler(bool val); -#endif #endif void set_contended_entered_monitor(ObjectMonitor* val) NOT_JVMTI_RETURN JVMTI_ONLY({ _contended_entered_monitor = val; }) @@ -925,9 +930,9 @@ public: static ByteSize preempt_alternate_return_offset() { return byte_offset_of(JavaThread, _preempt_alternate_return); } DEBUG_ONLY(static ByteSize interp_at_preemptable_vmcall_cnt_offset() { return byte_offset_of(JavaThread, _interp_at_preemptable_vmcall_cnt); }) static ByteSize unlocked_inflated_monitor_offset() { return byte_offset_of(JavaThread, _unlocked_inflated_monitor); } + static ByteSize is_in_vthread_transition_offset() { return byte_offset_of(JavaThread, _is_in_vthread_transition); } #if INCLUDE_JVMTI - static ByteSize is_in_VTMS_transition_offset() { return byte_offset_of(JavaThread, _is_in_VTMS_transition); } static ByteSize is_disable_suspend_offset() { return byte_offset_of(JavaThread, _is_disable_suspend); } #endif diff --git a/src/hotspot/share/runtime/jfieldIDWorkaround.hpp b/src/hotspot/share/runtime/jfieldIDWorkaround.hpp index 68db2e36d45..fcca5bc244c 100644 --- a/src/hotspot/share/runtime/jfieldIDWorkaround.hpp +++ b/src/hotspot/share/runtime/jfieldIDWorkaround.hpp @@ -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 diff --git a/src/hotspot/share/runtime/monitorDeflationThread.hpp b/src/hotspot/share/runtime/monitorDeflationThread.hpp index 6b681617a4c..29fb5394c6f 100644 --- a/src/hotspot/share/runtime/monitorDeflationThread.hpp +++ b/src/hotspot/share/runtime/monitorDeflationThread.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/hotspot/share/runtime/mountUnmountDisabler.cpp b/src/hotspot/share/runtime/mountUnmountDisabler.cpp new file mode 100644 index 00000000000..8635eeb2dcc --- /dev/null +++ b/src/hotspot/share/runtime/mountUnmountDisabler.cpp @@ -0,0 +1,458 @@ +/* + * 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 "classfile/javaClasses.inline.hpp" +#include "prims/jvmtiEventController.hpp" +#include "prims/jvmtiExport.hpp" +#include "prims/jvmtiThreadState.inline.hpp" +#include "runtime/handles.inline.hpp" +#include "runtime/javaThread.hpp" +#include "runtime/jniHandles.hpp" +#include "runtime/mountUnmountDisabler.hpp" +#include "runtime/threadSMR.hpp" + +volatile int MountUnmountDisabler::_global_vthread_transition_disable_count = 0; +volatile int MountUnmountDisabler::_active_disablers = 0; +bool MountUnmountDisabler::_exclusive_operation_ongoing = false; +bool MountUnmountDisabler::_notify_jvmti_events = false; + +#if INCLUDE_JVMTI +class JVMTIStartTransition : public StackObj { + JavaThread* _current; + Handle _vthread; + bool _is_mount; + bool _is_thread_end; + public: + JVMTIStartTransition(JavaThread* current, oop vthread, bool is_mount, bool is_thread_end) : + _current(current), _vthread(current, vthread), _is_mount(is_mount), _is_thread_end(is_thread_end) { + assert(DoJVMTIVirtualThreadTransitions || !JvmtiExport::can_support_virtual_threads(), "sanity check"); + if (DoJVMTIVirtualThreadTransitions && MountUnmountDisabler::notify_jvmti_events()) { + // post VirtualThreadUnmount event before VirtualThreadEnd + if (!_is_mount && JvmtiExport::should_post_vthread_unmount()) { + JvmtiExport::post_vthread_unmount((jthread)_vthread.raw_value()); + } + if (_is_thread_end && JvmtiExport::should_post_vthread_end()) { + JvmtiExport::post_vthread_end((jthread)_vthread.raw_value()); + } + } + } + ~JVMTIStartTransition() { + if (DoJVMTIVirtualThreadTransitions && MountUnmountDisabler::notify_jvmti_events()) { + if (_is_thread_end && _current->jvmti_thread_state() != nullptr) { + JvmtiExport::cleanup_thread(_current); + assert(_current->jvmti_thread_state() == nullptr, "should be null"); + assert(java_lang_Thread::jvmti_thread_state(_vthread()) == nullptr, "should be null"); + } + if (!_is_mount) { + _current->rebind_to_jvmti_thread_state_of(_current->threadObj()); + } + } + } +}; + +class JVMTIEndTransition : public StackObj { + JavaThread* _current; + Handle _vthread; + bool _is_mount; + bool _is_thread_start; + public: + JVMTIEndTransition(JavaThread* current, oop vthread, bool is_mount, bool is_thread_start) : + _current(current), _vthread(current, vthread), _is_mount(is_mount), _is_thread_start(is_thread_start) { + assert(DoJVMTIVirtualThreadTransitions || !JvmtiExport::can_support_virtual_threads(), "sanity check"); + if (DoJVMTIVirtualThreadTransitions && MountUnmountDisabler::notify_jvmti_events()) { + if (_is_mount) { + _current->rebind_to_jvmti_thread_state_of(_vthread()); + } + DEBUG_ONLY(bool is_virtual = java_lang_VirtualThread::is_instance(_current->jvmti_vthread())); + assert(_is_mount == is_virtual, "wrong identity"); + } + } + ~JVMTIEndTransition() { + if (DoJVMTIVirtualThreadTransitions && MountUnmountDisabler::notify_jvmti_events()) { + if (!_is_mount && _current->is_carrier_thread_suspended()) { + MonitorLocker ml(VThreadTransition_lock); + while (_current->is_carrier_thread_suspended()) { + ml.wait(200); + } + } + + if (_is_thread_start) { + // If interp_only_mode has been enabled then we must eagerly create JvmtiThreadState + // objects for globally enabled virtual thread filtered events. Otherwise, + // it is an important optimization to create JvmtiThreadState objects lazily. + // This optimization is disabled when watchpoint capabilities are present. It is to + // work around a bug with virtual thread frames which can be not deoptimized in time. + if (JvmtiThreadState::seen_interp_only_mode() || + JvmtiExport::should_post_field_access() || + JvmtiExport::should_post_field_modification()){ + JvmtiEventController::thread_started(_current); + } + if (JvmtiExport::should_post_vthread_start()) { + JvmtiExport::post_vthread_start((jthread)_vthread.raw_value()); + } + } + if (_is_mount && JvmtiExport::should_post_vthread_mount()) { + JvmtiExport::post_vthread_mount((jthread)_vthread.raw_value()); + } + } + } +}; +#endif // INCLUDE_JVMTI + +bool MountUnmountDisabler::is_start_transition_disabled(JavaThread* thread, oop vthread) { + // We need to read the per-vthread and global counters to check if transitions are disabled. + // In case of JVMTI present, the global counter will always be at least 1. This is to force + // the slow path and check for possible event posting. Here we need to check if transitions + // are actually disabled, so we compare the global counter against 1 or 0 accordingly. + // In case of JVMTI we also need to check for suspension. + int base_disable_count = notify_jvmti_events() ? 1 : 0; + return java_lang_Thread::vthread_transition_disable_count(vthread) > 0 + || global_vthread_transition_disable_count() > base_disable_count + JVMTI_ONLY(|| (JvmtiVTSuspender::is_vthread_suspended(java_lang_Thread::thread_id(vthread)) || thread->is_suspended())); +} + +void MountUnmountDisabler::start_transition(JavaThread* current, oop vthread, bool is_mount, bool is_thread_end) { + assert(!java_lang_Thread::is_in_vthread_transition(vthread), ""); + assert(!current->is_in_vthread_transition(), ""); + Handle vth = Handle(current, vthread); + JVMTI_ONLY(JVMTIStartTransition jst(current, vthread, is_mount, is_thread_end);) + + java_lang_Thread::set_is_in_vthread_transition(vth(), true); + current->set_is_in_vthread_transition(true); + + // Prevent loads of disable conditions from floating up. + OrderAccess::storeload(); + + while (is_start_transition_disabled(current, vth())) { + java_lang_Thread::set_is_in_vthread_transition(vth(), false); + current->set_is_in_vthread_transition(false); + { + // Block while transitions are disabled + MonitorLocker ml(VThreadTransition_lock); + while (is_start_transition_disabled(current, vth())) { + ml.wait(200); + } + } + + // Try to start transition again... + java_lang_Thread::set_is_in_vthread_transition(vth(), true); + current->set_is_in_vthread_transition(true); + OrderAccess::storeload(); + } + + // Start of the critical section. If this is a mount, we need an acquire barrier to + // synchronize with a possible disabler that executed an operation while this thread + // was unmounted. We make VirtualThread.mount guarantee such ordering and avoid barriers + // here. If this is an unmount, the handshake that the disabler executed against this + // thread already provided the needed synchronization. + // This pairs with the release barrier in xx_enable_for_one()/xx_enable_for_all(). +} + +void MountUnmountDisabler::end_transition(JavaThread* current, oop vthread, bool is_mount, bool is_thread_start) { + assert(java_lang_Thread::is_in_vthread_transition(vthread), ""); + assert(current->is_in_vthread_transition(), ""); + Handle vth = Handle(current, vthread); + JVMTI_ONLY(JVMTIEndTransition jst(current, vthread, is_mount, is_thread_start);) + + // End of the critical section. If this is an unmount, we need a release barrier before + // clearing the in_transition flags to make sure any memory operations executed in the + // transition are visible to a possible disabler that executes while this thread is unmounted. + // We make VirtualThread.unmount guarantee such ordering and avoid barriers here. If this is + // a mount, the only thing that needs to be published is the setting of carrierThread, since + // the handshake that the disabler will execute against it already provides the needed + // synchronization. This order is already guaranteed by the barriers in VirtualThread.mount. + // This pairs with the acquire barrier in xx_disable_for_one()/xx_disable_for_all(). + + java_lang_Thread::set_is_in_vthread_transition(vth(), false); + current->set_is_in_vthread_transition(false); + + // Unblock waiting transition disablers. + if (active_disablers() > 0) { + MonitorLocker ml(VThreadTransition_lock); + ml.notify_all(); + } +} + +// disable transitions for one virtual thread +// disable transitions for all threads if thread is nullptr or a platform thread +MountUnmountDisabler::MountUnmountDisabler(jthread thread) + : MountUnmountDisabler(JNIHandles::resolve_external_guard(thread)) +{ +} + +// disable transitions for one virtual thread +// disable transitions for all threads if thread is nullptr or a platform thread +MountUnmountDisabler::MountUnmountDisabler(oop thread_oop) + : _is_exclusive(false), + _is_self(false) +{ + if (!Continuations::enabled()) { + return; // MountUnmountDisabler is no-op without virtual threads + } + if (Thread::current_or_null() == nullptr) { + return; // Detached thread, can be a call from Agent_OnLoad. + } + JavaThread* current = JavaThread::current(); + assert(!current->is_in_vthread_transition(), ""); + + bool is_virtual = java_lang_VirtualThread::is_instance(thread_oop); + if (thread_oop == nullptr || + (!is_virtual && thread_oop == current->threadObj()) || + (is_virtual && thread_oop == current->vthread())) { + _is_self = true; + return; // no need for current thread to disable and enable transitions for itself + } + + // Target can be virtual or platform thread. + // If target is a platform thread then we have to disable transitions for all threads. + // It is by several reasons: + // - carrier threads can mount virtual threads which may cause incorrect behavior + // - there is no mechanism to disable transitions for a specific carrier thread yet + if (is_virtual) { + _vthread = Handle(current, thread_oop); + disable_transition_for_one(); // disable transitions for one virtual thread + } else { + disable_transition_for_all(); // disable transitions for all virtual threads + } +} + +// disable transitions for all virtual threads +MountUnmountDisabler::MountUnmountDisabler(bool exclusive) + : _is_exclusive(exclusive), + _is_self(false) +{ + if (!Continuations::enabled()) { + return; // MountUnmountDisabler is no-op without virtual threads + } + if (Thread::current_or_null() == nullptr) { + return; // Detached thread, can be a call from Agent_OnLoad. + } + assert(!JavaThread::current()->is_in_vthread_transition(), ""); + disable_transition_for_all(); +} + +MountUnmountDisabler::~MountUnmountDisabler() { + if (!Continuations::enabled()) { + return; // MountUnmountDisabler is a no-op without virtual threads + } + if (Thread::current_or_null() == nullptr) { + return; // Detached thread, can be a call from Agent_OnLoad. + } + if (_is_self) { + return; // no need for current thread to disable and enable transitions for itself + } + if (_vthread() != nullptr) { + enable_transition_for_one(); // enable transitions for one virtual thread + } else { + enable_transition_for_all(); // enable transitions for all virtual threads + } +} + +// disable transitions for one virtual thread +void +MountUnmountDisabler::disable_transition_for_one() { + MonitorLocker ml(VThreadTransition_lock); + while (exclusive_operation_ongoing()) { + ml.wait(10); + } + + inc_active_disablers(); + java_lang_Thread::inc_vthread_transition_disable_count(_vthread()); + + // Prevent load of transition flag from floating up. + OrderAccess::storeload(); + + while (java_lang_Thread::is_in_vthread_transition(_vthread())) { + ml.wait(10); // wait while the virtual thread is in transition + } + + // Start of the critical section. If the target is unmounted, we need an acquire + // barrier to make sure memory operations executed in the last transition are visible. + // If the target is mounted, although the handshake that will be executed against it + // already provides the needed synchronization, we still need to prevent the load of + // carrierThread to float up. + // This pairs with the release barrier in end_transition(). + OrderAccess::acquire(); + DEBUG_ONLY(JavaThread::current()->set_is_vthread_transition_disabler(true);) +} + +// disable transitions for all virtual threads +void +MountUnmountDisabler::disable_transition_for_all() { + DEBUG_ONLY(JavaThread* thread = JavaThread::current();) + DEBUG_ONLY(thread->set_is_disabler_at_start(true);) + + MonitorLocker ml(VThreadTransition_lock); + while (exclusive_operation_ongoing()) { + ml.wait(10); + } + if (_is_exclusive) { + set_exclusive_operation_ongoing(true); + while (active_disablers() > 0) { + ml.wait(10); + } + } + inc_active_disablers(); + inc_global_vthread_transition_disable_count(); + + // Prevent loads of transition flag from floating up. Technically not + // required since JavaThreadIteratorWithHandle includes full fence. + OrderAccess::storeload(); + + // Block while some mount/unmount transitions are in progress. + // Debug version fails and prints diagnostic information. + for (JavaThreadIteratorWithHandle jtiwh; JavaThread *jt = jtiwh.next(); ) { + while (jt->is_in_vthread_transition()) { + ml.wait(10); + } + } + + // Start of the critical section. If some target is unmounted, we need an acquire + // barrier to make sure memory operations executed in the last transition are visible. + // If a target is mounted, although the handshake that will be executed against it + // already provides the needed synchronization, we still need to prevent the load of + // carrierThread to float up. + // This pairs with the release barrier in end_transition(). + OrderAccess::acquire(); + DEBUG_ONLY(thread->set_is_vthread_transition_disabler(true);) + DEBUG_ONLY(thread->set_is_disabler_at_start(false);) +} + +// enable transitions for one virtual thread +void +MountUnmountDisabler::enable_transition_for_one() { + assert(java_lang_VirtualThread::is_instance(_vthread()), ""); + + // End of the critical section. If the target was unmounted, we need a + // release barrier before decrementing _vthread_transition_disable_count to + // make sure any memory operations executed by the disabler are visible to + // the target once it mounts again. If the target was mounted, the handshake + // executed against it already provided the needed synchronization. + // This pairs with the equivalent acquire barrier in start_transition(). + OrderAccess::release(); + + MonitorLocker ml(VThreadTransition_lock); + dec_active_disablers(); + java_lang_Thread::dec_vthread_transition_disable_count(_vthread()); + if (java_lang_Thread::vthread_transition_disable_count(_vthread()) == 0) { + ml.notify_all(); + } + DEBUG_ONLY(JavaThread::current()->set_is_vthread_transition_disabler(false);) +} + +// enable transitions for all virtual threads +void +MountUnmountDisabler::enable_transition_for_all() { + JavaThread* thread = JavaThread::current(); + + // End of the critical section. If some target was unmounted, we need a + // release barrier before decrementing _global_vthread_transition_disable_count + // to make sure any memory operations executed by the disabler are visible to + // the target once it mounts again. If a target was mounted, the handshake + // executed against it already provided the needed synchronization. + // This pairs with the equivalent acquire barrier in start_transition(). + OrderAccess::release(); + + MonitorLocker ml(VThreadTransition_lock); + if (_is_exclusive) { + set_exclusive_operation_ongoing(false); + } + dec_active_disablers(); + dec_global_vthread_transition_disable_count(); + int base_disable_count = notify_jvmti_events() ? 1 : 0; + if (global_vthread_transition_disable_count() == base_disable_count || _is_exclusive) { + ml.notify_all(); + } + DEBUG_ONLY(thread->set_is_vthread_transition_disabler(false);) +} + +int MountUnmountDisabler::global_vthread_transition_disable_count() { + assert(_global_vthread_transition_disable_count >= 0, ""); + return AtomicAccess::load(&_global_vthread_transition_disable_count); +} + +void MountUnmountDisabler::inc_global_vthread_transition_disable_count() { + assert(VThreadTransition_lock->owned_by_self() || SafepointSynchronize::is_at_safepoint(), "Must be locked"); + assert(_global_vthread_transition_disable_count >= 0, ""); + AtomicAccess::store(&_global_vthread_transition_disable_count, _global_vthread_transition_disable_count + 1); +} + +void MountUnmountDisabler::dec_global_vthread_transition_disable_count() { + assert(VThreadTransition_lock->owned_by_self() || SafepointSynchronize::is_at_safepoint(), "Must be locked"); + assert(_global_vthread_transition_disable_count > 0, ""); + AtomicAccess::store(&_global_vthread_transition_disable_count, _global_vthread_transition_disable_count - 1); +} + +bool MountUnmountDisabler::exclusive_operation_ongoing() { + assert(VThreadTransition_lock->owned_by_self(), "Must be locked"); + return _exclusive_operation_ongoing; +} + +void MountUnmountDisabler::set_exclusive_operation_ongoing(bool val) { + assert(VThreadTransition_lock->owned_by_self(), "Must be locked"); + assert(_exclusive_operation_ongoing != val, ""); + _exclusive_operation_ongoing = val; +} + +int MountUnmountDisabler::active_disablers() { + assert(_active_disablers >= 0, ""); + return AtomicAccess::load(&_active_disablers); +} + +void MountUnmountDisabler::inc_active_disablers() { + assert(VThreadTransition_lock->owned_by_self(), "Must be locked"); + assert(_active_disablers >= 0, ""); + _active_disablers++; +} + +void MountUnmountDisabler::dec_active_disablers() { + assert(VThreadTransition_lock->owned_by_self(), "Must be locked"); + assert(_active_disablers > 0, ""); + _active_disablers--; +} + +bool MountUnmountDisabler::notify_jvmti_events() { + return _notify_jvmti_events; +} + +void MountUnmountDisabler::set_notify_jvmti_events(bool val, bool is_onload) { + if (val == _notify_jvmti_events || !DoJVMTIVirtualThreadTransitions) return; + + // Force slow path on start/end vthread transitions for JVMTI bookkeeping. + // 'val' is always true except with WhiteBox methods for testing purposes. + if (is_onload) { + // Skip existing increment methods since asserts will fail. + assert(val && _global_vthread_transition_disable_count == 0, ""); + AtomicAccess::inc(&_global_vthread_transition_disable_count); + } else { + assert(SafepointSynchronize::is_at_safepoint(), ""); + if (val) { + inc_global_vthread_transition_disable_count(); + } else { + dec_global_vthread_transition_disable_count(); + } + } + log_trace(continuations,tracking)("%s _notify_jvmti_events, _global_vthread_transition_disable_count=%d", val ? "enabling" : "disabling", _global_vthread_transition_disable_count); + _notify_jvmti_events = val; +} diff --git a/src/hotspot/share/runtime/mountUnmountDisabler.hpp b/src/hotspot/share/runtime/mountUnmountDisabler.hpp new file mode 100644 index 00000000000..4c9b6124b46 --- /dev/null +++ b/src/hotspot/share/runtime/mountUnmountDisabler.hpp @@ -0,0 +1,93 @@ +/* + * 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. + * + */ + +#ifndef SHARE_RUNTIME_MOUNTUNMOUNTDISABLER_HPP +#define SHARE_RUNTIME_MOUNTUNMOUNTDISABLER_HPP + +#include "memory/allocation.hpp" +#include "runtime/handles.hpp" + +class JavaThread; + +// This class adds support to disable virtual thread transitions (mount/unmount). +// This is needed to safely execute operations that access virtual thread state. +// Users should use the Handshake class when possible instead of using this directly. +class MountUnmountDisabler : public AnyObj { + // The global counter is used for operations that require disabling + // transitions for all virtual threads. Currently this is only used + // by some JVMTI operations. We also increment this counter when the + // first JVMTI agent attaches to always force the slowpath when starting + // a transition. This is needed because if JVMTI is present we need to + // check for possible event posting. + static volatile int _global_vthread_transition_disable_count; + static volatile int _active_disablers; + static bool _exclusive_operation_ongoing; + + bool _is_exclusive; // currently only for suspender or resumer + bool _is_virtual; // target thread is virtual + bool _is_self; // MountUnmountDisabler is a no-op for current platform, carrier or virtual thread + Handle _vthread; // virtual thread to disable transitions for, no-op if it is a platform thread + + //DEBUG_ONLY(static void print_info();) + void disable_transition_for_one(); + void disable_transition_for_all(); + void enable_transition_for_one(); + void enable_transition_for_all(); + + public: + MountUnmountDisabler(bool exlusive = false); + MountUnmountDisabler(oop thread_oop); + MountUnmountDisabler(jthread thread); + ~MountUnmountDisabler(); + + static int global_vthread_transition_disable_count(); + static void inc_global_vthread_transition_disable_count(); + static void dec_global_vthread_transition_disable_count(); + + static volatile int* global_vthread_transition_disable_count_address() { + return &_global_vthread_transition_disable_count; + } + + static bool exclusive_operation_ongoing(); + static void set_exclusive_operation_ongoing(bool val); + + static int active_disablers(); + static void inc_active_disablers(); + static void dec_active_disablers(); + + static void start_transition(JavaThread* thread, oop vthread, bool is_mount, bool is_thread_end); + static void end_transition(JavaThread* thread, oop vthread, bool is_mount, bool is_thread_start); + + static bool is_start_transition_disabled(JavaThread* thread, oop vthread); + + // enable notifications from VirtualThread about Mount/Unmount events + static bool _notify_jvmti_events; + static bool notify_jvmti_events(); + static void set_notify_jvmti_events(bool val, bool is_onload = false); + static bool* notify_jvmti_events_address() { + return &_notify_jvmti_events; + } +}; + +#endif // SHARE_RUNTIME_MOUNTUNMOUNTDISABLER_HPP diff --git a/src/hotspot/share/runtime/mutexLocker.cpp b/src/hotspot/share/runtime/mutexLocker.cpp index e4707a342a7..b102e6424f1 100644 --- a/src/hotspot/share/runtime/mutexLocker.cpp +++ b/src/hotspot/share/runtime/mutexLocker.cpp @@ -49,7 +49,7 @@ Mutex* JfieldIdCreation_lock = nullptr; Monitor* JNICritical_lock = nullptr; Mutex* JvmtiThreadState_lock = nullptr; Monitor* EscapeBarrier_lock = nullptr; -Monitor* JvmtiVTMSTransition_lock = nullptr; +Monitor* VThreadTransition_lock = nullptr; Mutex* JvmtiVThreadSuspend_lock = nullptr; Monitor* Heap_lock = nullptr; #if INCLUDE_PARALLELGC @@ -244,7 +244,7 @@ void mutex_init() { MUTEX_DEFN(SymbolArena_lock , PaddedMutex , nosafepoint); MUTEX_DEFN(ExceptionCache_lock , PaddedMutex , safepoint); #ifndef PRODUCT - MUTEX_DEFN(FullGCALot_lock , PaddedMutex , safepoint); // a lock to make FullGCALot MT safe + MUTEX_DEFN(FullGCALot_lock , PaddedMutex , nosafepoint); // a lock to make FullGCALot MT safe #endif MUTEX_DEFN(BeforeExit_lock , PaddedMonitor, safepoint); @@ -265,7 +265,7 @@ void mutex_init() { MUTEX_DEFN(CompileStatistics_lock , PaddedMutex , safepoint); MUTEX_DEFN(DirectivesStack_lock , PaddedMutex , nosafepoint); - MUTEX_DEFN(JvmtiVTMSTransition_lock , PaddedMonitor, safepoint); // used for Virtual Thread Mount State transition management + MUTEX_DEFN(VThreadTransition_lock , PaddedMonitor, safepoint); MUTEX_DEFN(JvmtiVThreadSuspend_lock , PaddedMutex, nosafepoint-1); MUTEX_DEFN(EscapeBarrier_lock , PaddedMonitor, nosafepoint); // Used to synchronize object reallocation/relocking triggered by JVMTI MUTEX_DEFN(Management_lock , PaddedMutex , safepoint); // used for JVM management @@ -361,8 +361,8 @@ void mutex_init() { // JVMCIRuntime_lock must be acquired before JVMCI_lock to avoid deadlock MUTEX_DEFL(JVMCI_lock , PaddedMonitor, JVMCIRuntime_lock); #endif - MUTEX_DEFL(JvmtiThreadState_lock , PaddedMutex , JvmtiVTMSTransition_lock); // Used by JvmtiThreadState/JvmtiEventController - MUTEX_DEFL(SharedDecoder_lock , PaddedMutex , NmtVirtualMemory_lock); // Must be lower than NmtVirtualMemory_lock due to MemTracker::print_containing_region + MUTEX_DEFL(JvmtiThreadState_lock , PaddedMutex , VThreadTransition_lock); // Used by JvmtiThreadState/JvmtiEventController + MUTEX_DEFL(SharedDecoder_lock , PaddedMutex , NmtVirtualMemory_lock); // Must be lower than NmtVirtualMemory_lock due to MemTracker::print_containing_region // Allocate RecursiveMutex MultiArray_lock = new RecursiveMutex(); diff --git a/src/hotspot/share/runtime/mutexLocker.hpp b/src/hotspot/share/runtime/mutexLocker.hpp index 74fe4650b7c..f6c0a967718 100644 --- a/src/hotspot/share/runtime/mutexLocker.hpp +++ b/src/hotspot/share/runtime/mutexLocker.hpp @@ -47,7 +47,7 @@ extern Mutex* JfieldIdCreation_lock; // a lock on creating JNI stati extern Monitor* JNICritical_lock; // a lock used while synchronizing with threads entering/leaving JNI critical regions extern Mutex* JvmtiThreadState_lock; // a lock on modification of JVMTI thread data extern Monitor* EscapeBarrier_lock; // a lock to sync reallocating and relocking objects because of JVMTI access -extern Monitor* JvmtiVTMSTransition_lock; // a lock for Virtual Thread Mount State transition (VTMS transition) management +extern Monitor* VThreadTransition_lock; // a lock used when disabling virtual thread transitions extern Mutex* JvmtiVThreadSuspend_lock; // a lock for virtual threads suspension extern Monitor* Heap_lock; // a lock on the heap #if INCLUDE_PARALLELGC diff --git a/src/hotspot/share/runtime/notificationThread.hpp b/src/hotspot/share/runtime/notificationThread.hpp index e8e72052e4a..b516a147828 100644 --- a/src/hotspot/share/runtime/notificationThread.hpp +++ b/src/hotspot/share/runtime/notificationThread.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/hotspot/share/runtime/objectMonitor.cpp b/src/hotspot/share/runtime/objectMonitor.cpp index ee7629ec6f5..785ee2af592 100644 --- a/src/hotspot/share/runtime/objectMonitor.cpp +++ b/src/hotspot/share/runtime/objectMonitor.cpp @@ -59,6 +59,7 @@ #include "utilities/globalDefinitions.hpp" #include "utilities/macros.hpp" #include "utilities/preserveException.hpp" +#include "utilities/spinCriticalSection.hpp" #if INCLUDE_JFR #include "jfr/support/jfrFlush.hpp" #endif @@ -1863,9 +1864,10 @@ void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) { // returns because of a timeout of interrupt. Contention is exceptionally rare // so we use a simple spin-lock instead of a heavier-weight blocking lock. - Thread::SpinAcquire(&_wait_set_lock); - add_waiter(&node); - Thread::SpinRelease(&_wait_set_lock); + { + SpinCriticalSection scs(&_wait_set_lock); + add_waiter(&node); + } intx save = _recursions; // record the old recursion count _waiters++; // increment the number of waiters @@ -1922,12 +1924,11 @@ void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) { // That is, we fail toward safety. if (node.TState == ObjectWaiter::TS_WAIT) { - Thread::SpinAcquire(&_wait_set_lock); + SpinCriticalSection scs(&_wait_set_lock); if (node.TState == ObjectWaiter::TS_WAIT) { dequeue_specific_waiter(&node); // unlink from wait_set node.TState = ObjectWaiter::TS_RUN; } - Thread::SpinRelease(&_wait_set_lock); } // The thread is now either on off-list (TS_RUN), @@ -2036,7 +2037,7 @@ void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) { bool ObjectMonitor::notify_internal(JavaThread* current) { bool did_notify = false; - Thread::SpinAcquire(&_wait_set_lock); + SpinCriticalSection scs(&_wait_set_lock); ObjectWaiter* iterator = dequeue_waiter(); if (iterator != nullptr) { guarantee(iterator->TState == ObjectWaiter::TS_WAIT, "invariant"); @@ -2095,7 +2096,6 @@ bool ObjectMonitor::notify_internal(JavaThread* current) { } } } - Thread::SpinRelease(&_wait_set_lock); return did_notify; } @@ -2198,9 +2198,10 @@ void ObjectMonitor::vthread_wait(JavaThread* current, jlong millis, bool interru // returns because of a timeout or interrupt. Contention is exceptionally rare // so we use a simple spin-lock instead of a heavier-weight blocking lock. - Thread::SpinAcquire(&_wait_set_lock); - add_waiter(node); - Thread::SpinRelease(&_wait_set_lock); + { + SpinCriticalSection scs(&_wait_set_lock); + add_waiter(node); + } node->_recursions = _recursions; // record the old recursion count _recursions = 0; // set the recursion level to be 0 @@ -2221,12 +2222,11 @@ bool ObjectMonitor::vthread_wait_reenter(JavaThread* current, ObjectWaiter* node // need to check if we were interrupted or the wait timed-out, and // in that case remove ourselves from the _wait_set queue. if (node->TState == ObjectWaiter::TS_WAIT) { - Thread::SpinAcquire(&_wait_set_lock); + SpinCriticalSection scs(&_wait_set_lock); if (node->TState == ObjectWaiter::TS_WAIT) { dequeue_specific_waiter(node); // unlink from wait_set node->TState = ObjectWaiter::TS_RUN; } - Thread::SpinRelease(&_wait_set_lock); } // If this was an interrupted case, set the _interrupted boolean so that diff --git a/src/hotspot/share/runtime/objectMonitorTable.cpp b/src/hotspot/share/runtime/objectMonitorTable.cpp new file mode 100644 index 00000000000..9b522720a28 --- /dev/null +++ b/src/hotspot/share/runtime/objectMonitorTable.cpp @@ -0,0 +1,308 @@ +/* + * 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 "logging/log.hpp" +#include "runtime/interfaceSupport.inline.hpp" +#include "runtime/javaThread.hpp" +#include "runtime/mutexLocker.hpp" +#include "runtime/objectMonitorTable.hpp" +#include "runtime/safepoint.hpp" +#include "runtime/thread.hpp" +#include "runtime/timerTrace.hpp" +#include "runtime/trimNativeHeap.hpp" +#include "utilities/concurrentHashTableTasks.inline.hpp" +#include "utilities/globalDefinitions.hpp" + +// ----------------------------------------------------------------------------- +// ConcurrentHashTable storing links from objects to ObjectMonitors + +using ConcurrentTable = ConcurrentHashTable; + +static ConcurrentTable* _table = nullptr; +static volatile size_t _items_count = 0; +static size_t _table_size = 0; +static volatile bool _resize = false; + +class ObjectMonitorTableConfig : public AllStatic { + public: + using Value = ObjectMonitor*; + static uintx get_hash(Value const& value, bool* is_dead) { + return (uintx)value->hash(); + } + static void* allocate_node(void* context, size_t size, Value const& value) { + ObjectMonitorTable::inc_items_count(); + return AllocateHeap(size, mtObjectMonitor); + }; + static void free_node(void* context, void* memory, Value const& value) { + ObjectMonitorTable::dec_items_count(); + FreeHeap(memory); + } +}; + +class Lookup : public StackObj { + oop _obj; + + public: + explicit Lookup(oop obj) : _obj(obj) {} + + uintx get_hash() const { + uintx hash = _obj->mark().hash(); + assert(hash != 0, "should have a hash"); + return hash; + } + + bool equals(ObjectMonitor** value) { + assert(*value != nullptr, "must be"); + return (*value)->object_refers_to(_obj); + } + + bool is_dead(ObjectMonitor** value) { + assert(*value != nullptr, "must be"); + return false; + } +}; + +class LookupMonitor : public StackObj { + ObjectMonitor* _monitor; + + public: + explicit LookupMonitor(ObjectMonitor* monitor) : _monitor(monitor) {} + + uintx get_hash() const { + return _monitor->hash(); + } + + bool equals(ObjectMonitor** value) { + return (*value) == _monitor; + } + + bool is_dead(ObjectMonitor** value) { + assert(*value != nullptr, "must be"); + return (*value)->object_is_dead(); + } +}; + +void ObjectMonitorTable::inc_items_count() { + AtomicAccess::inc(&_items_count, memory_order_relaxed); +} + +void ObjectMonitorTable::dec_items_count() { + AtomicAccess::dec(&_items_count, memory_order_relaxed); +} + +double ObjectMonitorTable::get_load_factor() { + size_t count = AtomicAccess::load(&_items_count); + return (double)count / (double)_table_size; +} + +size_t ObjectMonitorTable::table_size(Thread* current) { + return ((size_t)1) << _table->get_size_log2(current); +} + +size_t ObjectMonitorTable::max_log_size() { + // TODO[OMTable]: Evaluate the max size. + // TODO[OMTable]: Need to fix init order to use Universe::heap()->max_capacity(); + // Using MaxHeapSize directly this early may be wrong, and there + // are definitely rounding errors (alignment). + const size_t max_capacity = MaxHeapSize; + const size_t min_object_size = CollectedHeap::min_dummy_object_size() * HeapWordSize; + const size_t max_objects = max_capacity / MAX2(MinObjAlignmentInBytes, checked_cast(min_object_size)); + const size_t log_max_objects = log2i_graceful(max_objects); + + return MAX2(MIN2(SIZE_BIG_LOG2, log_max_objects), min_log_size()); +} + +// ~= log(AvgMonitorsPerThreadEstimate default) +size_t ObjectMonitorTable::min_log_size() { + return 10; +} + +template +size_t ObjectMonitorTable::clamp_log_size(V log_size) { + return MAX2(MIN2(log_size, checked_cast(max_log_size())), checked_cast(min_log_size())); +} + +size_t ObjectMonitorTable::initial_log_size() { + const size_t estimate = log2i(MAX2(os::processor_count(), 1)) + log2i(MAX2(AvgMonitorsPerThreadEstimate, size_t(1))); + return clamp_log_size(estimate); +} + +size_t ObjectMonitorTable::grow_hint() { + return ConcurrentTable::DEFAULT_GROW_HINT; +} + +void ObjectMonitorTable::create() { + _table = new ConcurrentTable(initial_log_size(), max_log_size(), grow_hint()); + _items_count = 0; + _table_size = table_size(Thread::current()); + _resize = false; +} + +void ObjectMonitorTable::verify_monitor_get_result(oop obj, ObjectMonitor* monitor) { +#ifdef ASSERT + if (SafepointSynchronize::is_at_safepoint()) { + bool has_monitor = obj->mark().has_monitor(); + assert(has_monitor == (monitor != nullptr), + "Inconsistency between markWord and ObjectMonitorTable has_monitor: %s monitor: " PTR_FORMAT, + BOOL_TO_STR(has_monitor), p2i(monitor)); + } +#endif +} + +ObjectMonitor* ObjectMonitorTable::monitor_get(Thread* current, oop obj) { + ObjectMonitor* result = nullptr; + Lookup lookup_f(obj); + auto found_f = [&](ObjectMonitor** found) { + assert((*found)->object_peek() == obj, "must be"); + result = *found; + }; + _table->get(current, lookup_f, found_f); + verify_monitor_get_result(obj, result); + return result; +} + +void ObjectMonitorTable::try_notify_grow() { + if (!_table->is_max_size_reached() && !AtomicAccess::load(&_resize)) { + AtomicAccess::store(&_resize, true); + if (Service_lock->try_lock()) { + Service_lock->notify(); + Service_lock->unlock(); + } + } +} + +bool ObjectMonitorTable::should_grow() { + return get_load_factor() > GROW_LOAD_FACTOR && !_table->is_max_size_reached(); +} + +bool ObjectMonitorTable::should_resize() { + return should_grow() || should_shrink() || AtomicAccess::load(&_resize); +} + +template +bool ObjectMonitorTable::run_task(JavaThread* current, Task& task, const char* task_name, Args&... args) { + if (task.prepare(current)) { + log_trace(monitortable)("Started to %s", task_name); + TraceTime timer(task_name, TRACETIME_LOG(Debug, monitortable, perf)); + while (task.do_task(current, args...)) { + task.pause(current); + { + ThreadBlockInVM tbivm(current); + } + task.cont(current); + } + task.done(current); + return true; + } + return false; +} + +bool ObjectMonitorTable::grow(JavaThread* current) { + ConcurrentTable::GrowTask grow_task(_table); + if (run_task(current, grow_task, "Grow")) { + _table_size = table_size(current); + log_info(monitortable)("Grown to size: %zu", _table_size); + return true; + } + return false; +} + +bool ObjectMonitorTable::clean(JavaThread* current) { + ConcurrentTable::BulkDeleteTask clean_task(_table); + auto is_dead = [&](ObjectMonitor** monitor) { + return (*monitor)->object_is_dead(); + }; + auto do_nothing = [&](ObjectMonitor** monitor) {}; + NativeHeapTrimmer::SuspendMark sm("ObjectMonitorTable"); + return run_task(current, clean_task, "Clean", is_dead, do_nothing); +} + +bool ObjectMonitorTable::resize(JavaThread* current) { + LogTarget(Info, monitortable) lt; + bool success = false; + + if (should_grow()) { + lt.print("Start growing with load factor %f", get_load_factor()); + success = grow(current); + } else { + if (!_table->is_max_size_reached() && AtomicAccess::load(&_resize)) { + lt.print("WARNING: Getting resize hints with load factor %f", get_load_factor()); + } + lt.print("Start cleaning with load factor %f", get_load_factor()); + success = clean(current); + } + + AtomicAccess::store(&_resize, false); + + return success; +} + +ObjectMonitor* ObjectMonitorTable::monitor_put_get(Thread* current, ObjectMonitor* monitor, oop obj) { + // Enter the monitor into the concurrent hashtable. + ObjectMonitor* result = monitor; + Lookup lookup_f(obj); + auto found_f = [&](ObjectMonitor** found) { + assert((*found)->object_peek() == obj, "must be"); + result = *found; + }; + bool grow; + _table->insert_get(current, lookup_f, monitor, found_f, &grow); + verify_monitor_get_result(obj, result); + if (grow) { + try_notify_grow(); + } + return result; +} + +bool ObjectMonitorTable::remove_monitor_entry(Thread* current, ObjectMonitor* monitor) { + LookupMonitor lookup_f(monitor); + return _table->remove(current, lookup_f); +} + +bool ObjectMonitorTable::contains_monitor(Thread* current, ObjectMonitor* monitor) { + LookupMonitor lookup_f(monitor); + bool result = false; + auto found_f = [&](ObjectMonitor** found) { + result = true; + }; + _table->get(current, lookup_f, found_f); + return result; +} + +void ObjectMonitorTable::print_on(outputStream* st) { + auto printer = [&] (ObjectMonitor** entry) { + ObjectMonitor* om = *entry; + oop obj = om->object_peek(); + st->print("monitor=" PTR_FORMAT ", ", p2i(om)); + st->print("object=" PTR_FORMAT, p2i(obj)); + assert(obj->mark().hash() == om->hash(), "hash must match"); + st->cr(); + return true; + }; + if (SafepointSynchronize::is_at_safepoint()) { + _table->do_safepoint_scan(printer); + } else { + _table->do_scan(Thread::current(), printer); + } +} diff --git a/src/hotspot/share/runtime/objectMonitorTable.hpp b/src/hotspot/share/runtime/objectMonitorTable.hpp new file mode 100644 index 00000000000..146468d46b2 --- /dev/null +++ b/src/hotspot/share/runtime/objectMonitorTable.hpp @@ -0,0 +1,77 @@ +/* + * 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_RUNTIME_OBJECTMONITORTABLE_HPP +#define SHARE_RUNTIME_OBJECTMONITORTABLE_HPP + +#include "memory/allStatic.hpp" +#include "oops/oopsHierarchy.hpp" +#include "utilities/globalDefinitions.hpp" + +class JavaThread; +class ObjectMonitor; +class ObjectMonitorTableConfig; +class outputStream; +class Thread; + +class ObjectMonitorTable : AllStatic { + friend class ObjectMonitorTableConfig; + + private: + static void inc_items_count(); + static void dec_items_count(); + static double get_load_factor(); + static size_t table_size(Thread* current); + static size_t max_log_size(); + static size_t min_log_size(); + + template + static size_t clamp_log_size(V log_size); + static size_t initial_log_size(); + static size_t grow_hint(); + + public: + static void create(); + static void verify_monitor_get_result(oop obj, ObjectMonitor* monitor); + static ObjectMonitor* monitor_get(Thread* current, oop obj); + static void try_notify_grow(); + static bool should_shrink() { return false; } // Not implemented + + static constexpr double GROW_LOAD_FACTOR = 0.75; + + static bool should_grow(); + static bool should_resize(); + + template + static bool run_task(JavaThread* current, Task& task, const char* task_name, Args&... args); + static bool grow(JavaThread* current); + static bool clean(JavaThread* current); + static bool resize(JavaThread* current); + static ObjectMonitor* monitor_put_get(Thread* current, ObjectMonitor* monitor, oop obj); + static bool remove_monitor_entry(Thread* current, ObjectMonitor* monitor); + static bool contains_monitor(Thread* current, ObjectMonitor* monitor); + static void print_on(outputStream* st); +}; + +#endif // SHARE_RUNTIME_OBJECTMONITORTABLE_HPP diff --git a/src/hotspot/share/runtime/os.cpp b/src/hotspot/share/runtime/os.cpp index ceff9b54c33..cf18388c625 100644 --- a/src/hotspot/share/runtime/os.cpp +++ b/src/hotspot/share/runtime/os.cpp @@ -2577,6 +2577,10 @@ jint os::set_minimum_stack_sizes() { return JNI_OK; } +jlong os::get_minimum_java_stack_size() { + return static_cast(_java_thread_min_stack_allowed); +} + // Builds a platform dependent Agent_OnLoad_ function name // which is used to find statically linked in agents. // Parameters: diff --git a/src/hotspot/share/runtime/os.hpp b/src/hotspot/share/runtime/os.hpp index e008f29eecc..d585d3e5fc0 100644 --- a/src/hotspot/share/runtime/os.hpp +++ b/src/hotspot/share/runtime/os.hpp @@ -390,6 +390,8 @@ class os: AllStatic { static jint set_minimum_stack_sizes(); public: + // get allowed minimum java stack size + static jlong get_minimum_java_stack_size(); // Find committed memory region within specified range (start, start + size), // return true if found any static bool committed_in_range(address start, size_t size, address& committed_start, size_t& committed_size); @@ -534,6 +536,7 @@ class os: AllStatic { static void realign_memory(char *addr, size_t bytes, size_t alignment_hint); // NUMA-specific interface + static void numa_set_thread_affinity(Thread* thread, int node); static void numa_make_local(char *addr, size_t bytes, int lgrp_hint); static void numa_make_global(char *addr, size_t bytes); static size_t numa_get_groups_num(); @@ -979,10 +982,7 @@ class os: AllStatic { // The thread_cpu_time() and current_thread_cpu_time() are only // supported if is_thread_cpu_time_supported() returns true. - // Thread CPU Time - return the fast estimate on a platform - // On Linux - fast clock_gettime where available - user+sys - // - otherwise: very slow /proc fs - user+sys - // On Windows - GetThreadTimes - user+sys + // Thread CPU Time - return the fast estimate on a platform - user+sys static jlong current_thread_cpu_time(); static jlong thread_cpu_time(Thread* t); diff --git a/src/hotspot/share/runtime/park.cpp b/src/hotspot/share/runtime/park.cpp index 37dfe6fcc3d..338a01bbfb9 100644 --- a/src/hotspot/share/runtime/park.cpp +++ b/src/hotspot/share/runtime/park.cpp @@ -25,6 +25,7 @@ #include "memory/allocation.inline.hpp" #include "nmt/memTracker.hpp" #include "runtime/javaThread.hpp" +#include "utilities/spinCriticalSection.hpp" // Lifecycle management for TSM ParkEvents. // ParkEvents are type-stable (TSM). @@ -60,14 +61,13 @@ ParkEvent * ParkEvent::Allocate (Thread * t) { // Using a spin lock since we are part of the mutex impl. // 8028280: using concurrent free list without memory management can leak // pretty badly it turns out. - Thread::SpinAcquire(&ListLock); { + SpinCriticalSection scs(&ListLock); ev = FreeList; if (ev != nullptr) { FreeList = ev->FreeNext; } } - Thread::SpinRelease(&ListLock); if (ev != nullptr) { guarantee (ev->AssociatedWith == nullptr, "invariant") ; @@ -88,12 +88,11 @@ void ParkEvent::Release (ParkEvent * ev) { ev->AssociatedWith = nullptr ; // Note that if we didn't have the TSM/immortal constraint, then // when reattaching we could trim the list. - Thread::SpinAcquire(&ListLock); { + SpinCriticalSection scs(&ListLock); ev->FreeNext = FreeList; FreeList = ev; } - Thread::SpinRelease(&ListLock); } // Override operator new and delete so we can ensure that the diff --git a/src/hotspot/share/runtime/park.hpp b/src/hotspot/share/runtime/park.hpp index f353ce34b74..4406a66da0d 100644 --- a/src/hotspot/share/runtime/park.hpp +++ b/src/hotspot/share/runtime/park.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/hotspot/share/runtime/perfDataTypes.hpp b/src/hotspot/share/runtime/perfDataTypes.hpp index 9928975b8e2..c6ea228bdf1 100644 --- a/src/hotspot/share/runtime/perfDataTypes.hpp +++ b/src/hotspot/share/runtime/perfDataTypes.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/hotspot/share/runtime/prefetch.hpp b/src/hotspot/share/runtime/prefetch.hpp deleted file mode 100644 index 601337c14af..00000000000 --- a/src/hotspot/share/runtime/prefetch.hpp +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved. - * 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_RUNTIME_PREFETCH_HPP -#define SHARE_RUNTIME_PREFETCH_HPP - -#include "memory/allStatic.hpp" - -// If calls to prefetch methods are in a loop, the loop should be cloned -// such that if Prefetch{Scan,Copy}Interval and/or PrefetchFieldInterval -// say not to do prefetching, these methods aren't called. At the very -// least, they take up a memory issue slot. They should be implemented -// as inline assembly code: doing an actual call isn't worth the cost. - -class Prefetch : AllStatic { - public: - // Prefetch anticipating read; must not fault, semantically a no-op - static void read(const void* loc, intx interval); - - // Prefetch anticipating write; must not fault, semantically a no-op - static void write(void* loc, intx interval); -}; - -#endif // SHARE_RUNTIME_PREFETCH_HPP diff --git a/src/hotspot/share/runtime/prefetch.inline.hpp b/src/hotspot/share/runtime/prefetch.inline.hpp index 4cc1d93f613..18630f71a62 100644 --- a/src/hotspot/share/runtime/prefetch.inline.hpp +++ b/src/hotspot/share/runtime/prefetch.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,9 +25,24 @@ #ifndef SHARE_RUNTIME_PREFETCH_INLINE_HPP #define SHARE_RUNTIME_PREFETCH_INLINE_HPP -#include "runtime/prefetch.hpp" - +#include "memory/allStatic.hpp" #include "utilities/macros.hpp" + +// If calls to prefetch methods are in a loop, the loop should be cloned +// such that if Prefetch{Scan,Copy}Interval and/or PrefetchFieldInterval +// say not to do prefetching, these methods aren't called. At the very +// least, they take up a memory issue slot. They should be implemented +// as inline assembly code: doing an actual call isn't worth the cost. + +class Prefetch : AllStatic { + public: + // Prefetch anticipating read; must not fault, semantically a no-op + static void read(const void* loc, intx interval); + + // Prefetch anticipating write; must not fault, semantically a no-op + static void write(void* loc, intx interval); +}; + #include OS_CPU_HEADER_INLINE(prefetch) #endif // SHARE_RUNTIME_PREFETCH_INLINE_HPP diff --git a/src/hotspot/share/runtime/reflection.cpp b/src/hotspot/share/runtime/reflection.cpp index d6c10c5c70c..1797471d84b 100644 --- a/src/hotspot/share/runtime/reflection.cpp +++ b/src/hotspot/share/runtime/reflection.cpp @@ -270,17 +270,16 @@ void Reflection::array_set(jvalue* value, arrayOop a, int index, BasicType value if (!a->is_within_bounds(index)) { THROW(vmSymbols::java_lang_ArrayIndexOutOfBoundsException()); } - if (a->is_objArray()) { - if (value_type == T_OBJECT) { - oop obj = cast_to_oop(value->l); - if (obj != nullptr) { - Klass* element_klass = ObjArrayKlass::cast(a->klass())->element_klass(); - if (!obj->is_a(element_klass)) { - THROW_MSG(vmSymbols::java_lang_IllegalArgumentException(), "array element type mismatch"); - } + if (value_type == T_OBJECT) { + assert(a->is_objArray(), "just checking"); + oop obj = cast_to_oop(value->l); + if (obj != nullptr) { + Klass* element_klass = ObjArrayKlass::cast(a->klass())->element_klass(); + if (!obj->is_a(element_klass)) { + THROW_MSG(vmSymbols::java_lang_IllegalArgumentException(), "array element type mismatch"); } - objArrayOop(a)->obj_at_put(index, obj); } + objArrayOop(a)->obj_at_put(index, obj); } else { assert(a->is_typeArray(), "just checking"); BasicType array_type = TypeArrayKlass::cast(a->klass())->element_type(); diff --git a/src/hotspot/share/runtime/safefetch.hpp b/src/hotspot/share/runtime/safefetch.hpp index a1781962ec0..43872c781b4 100644 --- a/src/hotspot/share/runtime/safefetch.hpp +++ b/src/hotspot/share/runtime/safefetch.hpp @@ -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. * Copyright (c) 2022 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/src/hotspot/share/runtime/safepoint.hpp b/src/hotspot/share/runtime/safepoint.hpp index 190095ec30b..c6f3a5cad2c 100644 --- a/src/hotspot/share/runtime/safepoint.hpp +++ b/src/hotspot/share/runtime/safepoint.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 diff --git a/src/hotspot/share/runtime/safepointVerifiers.cpp b/src/hotspot/share/runtime/safepointVerifiers.cpp index 6eb61efe0ca..0c6f2e9add3 100644 --- a/src/hotspot/share/runtime/safepointVerifiers.cpp +++ b/src/hotspot/share/runtime/safepointVerifiers.cpp @@ -30,7 +30,9 @@ #ifdef ASSERT -NoSafepointVerifier::NoSafepointVerifier(bool active) : _thread(Thread::current()), _active(active) { +NoSafepointVerifier::NoSafepointVerifier(bool active) + : _thread(active ? Thread::current() : nullptr), + _active(active) { if (!_active) { return; } diff --git a/src/hotspot/share/runtime/safepointVerifiers.hpp b/src/hotspot/share/runtime/safepointVerifiers.hpp index 8d309a973d8..f652b6e4803 100644 --- a/src/hotspot/share/runtime/safepointVerifiers.hpp +++ b/src/hotspot/share/runtime/safepointVerifiers.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2019, 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 diff --git a/src/hotspot/share/runtime/sharedRuntime.cpp b/src/hotspot/share/runtime/sharedRuntime.cpp index 79c7c0b32b4..d426fd31a43 100644 --- a/src/hotspot/share/runtime/sharedRuntime.cpp +++ b/src/hotspot/share/runtime/sharedRuntime.cpp @@ -87,6 +87,9 @@ #ifdef COMPILER1 #include "c1/c1_Runtime1.hpp" #endif +#ifdef COMPILER2 +#include "opto/runtime.hpp" +#endif #if INCLUDE_JFR #include "jfr/jfr.inline.hpp" #endif @@ -601,6 +604,11 @@ address SharedRuntime::raw_exception_handler_for_return_address(JavaThread* curr // The deferred StackWatermarkSet::after_unwind check will be performed in // * OptoRuntime::handle_exception_C_helper for C2 code // * exception_handler_for_pc_helper via Runtime1::handle_exception_from_callee_id for C1 code +#ifdef COMPILER2 + if (nm->compiler_type() == compiler_c2) { + return OptoRuntime::exception_blob()->entry_point(); + } +#endif // COMPILER2 return nm->exception_begin(); } } @@ -729,34 +737,6 @@ void SharedRuntime::throw_and_post_jvmti_exception(JavaThread* current, Symbol* throw_and_post_jvmti_exception(current, h_exception); } -#if INCLUDE_JVMTI -JRT_ENTRY(void, SharedRuntime::notify_jvmti_vthread_start(oopDesc* vt, jboolean hide, JavaThread* current)) - assert(hide == JNI_FALSE, "must be VTMS transition finish"); - jobject vthread = JNIHandles::make_local(const_cast(vt)); - JvmtiVTMSTransitionDisabler::VTMS_vthread_start(vthread); - JNIHandles::destroy_local(vthread); -JRT_END - -JRT_ENTRY(void, SharedRuntime::notify_jvmti_vthread_end(oopDesc* vt, jboolean hide, JavaThread* current)) - assert(hide == JNI_TRUE, "must be VTMS transition start"); - jobject vthread = JNIHandles::make_local(const_cast(vt)); - JvmtiVTMSTransitionDisabler::VTMS_vthread_end(vthread); - JNIHandles::destroy_local(vthread); -JRT_END - -JRT_ENTRY(void, SharedRuntime::notify_jvmti_vthread_mount(oopDesc* vt, jboolean hide, JavaThread* current)) - jobject vthread = JNIHandles::make_local(const_cast(vt)); - JvmtiVTMSTransitionDisabler::VTMS_vthread_mount(vthread, hide); - JNIHandles::destroy_local(vthread); -JRT_END - -JRT_ENTRY(void, SharedRuntime::notify_jvmti_vthread_unmount(oopDesc* vt, jboolean hide, JavaThread* current)) - jobject vthread = JNIHandles::make_local(const_cast(vt)); - JvmtiVTMSTransitionDisabler::VTMS_vthread_unmount(vthread, hide); - JNIHandles::destroy_local(vthread); -JRT_END -#endif // INCLUDE_JVMTI - // The interpreter code to call this tracing function is only // called/generated when UL is on for redefine, class and has the right level // and tags. Since obsolete methods are never compiled, we don't have @@ -801,6 +781,8 @@ address SharedRuntime::compute_compiled_exc_handler(nmethod* nm, address ret_pc, // determine handler bci, if any EXCEPTION_MARK; + Handle orig_exception(THREAD, exception()); + int handler_bci = -1; int scope_depth = 0; if (!force_unwind) { @@ -822,7 +804,7 @@ address SharedRuntime::compute_compiled_exc_handler(nmethod* nm, address ret_pc, // thrown (bugs 4307310 and 4546590). Set "exception" reference // argument to ensure that the correct exception is thrown (4870175). recursive_exception_occurred = true; - exception = Handle(THREAD, PENDING_EXCEPTION); + exception.replace(PENDING_EXCEPTION); CLEAR_PENDING_EXCEPTION; if (handler_bci >= 0) { bci = handler_bci; @@ -851,8 +833,10 @@ address SharedRuntime::compute_compiled_exc_handler(nmethod* nm, address ret_pc, // If the compiler did not anticipate a recursive exception, resulting in an exception // thrown from the catch bci, then the compiled exception handler might be missing. - // This is rare. Just deoptimize and let the interpreter handle it. + // This is rare. Just deoptimize and let the interpreter rethrow the original + // exception at the original bci. if (t == nullptr && recursive_exception_occurred) { + exception.replace(orig_exception()); // restore original exception bool make_not_entrant = false; return Deoptimization::deoptimize_for_missing_exception_handler(nm, make_not_entrant); } @@ -3378,7 +3362,7 @@ JRT_LEAF(intptr_t*, SharedRuntime::OSR_migration_begin( JavaThread *current) ) RegisterMap::WalkContinuation::skip); frame sender = fr.sender(&map); if (sender.is_interpreted_frame()) { - current->push_cont_fastpath(sender.sp()); + current->push_cont_fastpath(sender.unextended_sp()); } return buf; diff --git a/src/hotspot/share/runtime/sharedRuntime.hpp b/src/hotspot/share/runtime/sharedRuntime.hpp index a696ce5a71b..140207e5d63 100644 --- a/src/hotspot/share/runtime/sharedRuntime.hpp +++ b/src/hotspot/share/runtime/sharedRuntime.hpp @@ -317,14 +317,6 @@ class SharedRuntime: AllStatic { static void throw_and_post_jvmti_exception(JavaThread* current, Handle h_exception); static void throw_and_post_jvmti_exception(JavaThread* current, Symbol* name, const char *message = nullptr); -#if INCLUDE_JVMTI - // Functions for JVMTI notifications - static void notify_jvmti_vthread_start(oopDesc* vt, jboolean hide, JavaThread* current); - static void notify_jvmti_vthread_end(oopDesc* vt, jboolean hide, JavaThread* current); - static void notify_jvmti_vthread_mount(oopDesc* vt, jboolean hide, JavaThread* current); - static void notify_jvmti_vthread_unmount(oopDesc* vt, jboolean hide, JavaThread* current); -#endif - // RedefineClasses() tracing support for obsolete method entry static int rc_trace_method_entry(JavaThread* thread, Method* m); diff --git a/src/hotspot/share/runtime/signature.cpp b/src/hotspot/share/runtime/signature.cpp index 9ae5b3df415..044319d1752 100644 --- a/src/hotspot/share/runtime/signature.cpp +++ b/src/hotspot/share/runtime/signature.cpp @@ -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 diff --git a/src/hotspot/share/runtime/smallRegisterMap.inline.hpp b/src/hotspot/share/runtime/smallRegisterMap.inline.hpp index 09febdaf450..f3923c01c2f 100644 --- a/src/hotspot/share/runtime/smallRegisterMap.inline.hpp +++ b/src/hotspot/share/runtime/smallRegisterMap.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/hotspot/share/runtime/stackChunkFrameStream.hpp b/src/hotspot/share/runtime/stackChunkFrameStream.hpp index cb46254d125..921e44f3a3a 100644 --- a/src/hotspot/share/runtime/stackChunkFrameStream.hpp +++ b/src/hotspot/share/runtime/stackChunkFrameStream.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. * 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/runtime/stackChunkFrameStream.inline.hpp b/src/hotspot/share/runtime/stackChunkFrameStream.inline.hpp index 97166950752..e3696d03b4c 100644 --- a/src/hotspot/share/runtime/stackChunkFrameStream.inline.hpp +++ b/src/hotspot/share/runtime/stackChunkFrameStream.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/hotspot/share/runtime/stackWatermark.hpp b/src/hotspot/share/runtime/stackWatermark.hpp index 4f1c65c903c..4cb30a26db5 100644 --- a/src/hotspot/share/runtime/stackWatermark.hpp +++ b/src/hotspot/share/runtime/stackWatermark.hpp @@ -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 diff --git a/src/hotspot/share/runtime/stubDeclarations.hpp b/src/hotspot/share/runtime/stubDeclarations.hpp index b051c6d0e18..c478eda3e7c 100644 --- a/src/hotspot/share/runtime/stubDeclarations.hpp +++ b/src/hotspot/share/runtime/stubDeclarations.hpp @@ -191,31 +191,17 @@ // // C2 stub blob/field names // -// C2 stubs are provided with names in the format "C2 Runtime -// _blob". +// C2 stubs are provided with names in the format "_blob (C2 runtime)". // // A stub creation method OptoRuntime::generate(ciEnv* env) is // generated which invokes the C2 compiler to generate each stub in // declaration order. #ifdef COMPILER2 -// do_jvmti_stub(name) -#if INCLUDE_JVMTI -#define C2_JVMTI_STUBS_DO(do_jvmti_stub) \ - do_jvmti_stub(notify_jvmti_vthread_start) \ - do_jvmti_stub(notify_jvmti_vthread_end) \ - do_jvmti_stub(notify_jvmti_vthread_mount) \ - do_jvmti_stub(notify_jvmti_vthread_unmount) \ - -#else -#define C2_JVMTI_STUBS_DO(do_jvmti_stub) -#endif // INCLUDE_JVMTI - // client macro to operate on c2 stubs // // do_blob(name, type) // do_stub(name, fancy_jump, pass_tls, return_pc) -// do_jvmti_stub(name) // // do_blob is used for stubs that are generated via direct invocation // of the assembler to write into a blob of the appropriate type @@ -225,10 +211,8 @@ // in the IR graph employ a special type of jump (0, 1 or 2) or // provide access to TLS and the return pc. // -// do_jvmti_stub generates a JVMTI stub as an IR intrinsic which -// employs jump 0, and requires no special access -#define C2_STUBS_DO(do_blob, do_stub, do_jvmti_stub) \ +#define C2_STUBS_DO(do_blob, do_stub) \ do_blob(uncommon_trap, UncommonTrapBlob) \ do_blob(exception, ExceptionBlob) \ do_stub(new_instance, 0, true, false) \ @@ -239,16 +223,19 @@ do_stub(multianewarray4, 0, true, false) \ do_stub(multianewarray5, 0, true, false) \ do_stub(multianewarrayN, 0, true, false) \ - C2_JVMTI_STUBS_DO(do_jvmti_stub) \ do_stub(complete_monitor_locking, 0, false, false) \ do_stub(monitor_notify, 0, false, false) \ do_stub(monitor_notifyAll, 0, false, false) \ do_stub(rethrow, 2, true, true) \ do_stub(slow_arraycopy, 0, false, false) \ do_stub(register_finalizer, 0, false, false) \ + do_stub(vthread_end_first_transition, 0, false, false) \ + do_stub(vthread_start_final_transition, 0, false, false) \ + do_stub(vthread_start_transition, 0, false, false) \ + do_stub(vthread_end_transition, 0, false, false) \ #else -#define C2_STUBS_DO(do_blob, do_stub, do_jvmti_stub) +#define C2_STUBS_DO(do_blob, do_stub) #endif // Stubgen stub declarations @@ -1190,9 +1177,6 @@ // ignore do_stub(name, fancy_jump, pass_tls, return_pc) declarations #define DO_STUB_EMPTY4(name, fancy_jump, pass_tls, return_pc) -// ignore do_jvmti_stub(name) declarations -#define DO_JVMTI_STUB_EMPTY1(stub_name) - // ignore do_stub(blob_name, stub_name) declarations #define DO_STUB_EMPTY2(blob_name, stub_name) diff --git a/src/hotspot/share/runtime/stubInfo.cpp b/src/hotspot/share/runtime/stubInfo.cpp index bca6ff344ea..ee90631145a 100644 --- a/src/hotspot/share/runtime/stubInfo.cpp +++ b/src/hotspot/share/runtime/stubInfo.cpp @@ -479,7 +479,7 @@ void StubInfo::process_stubgen_entry(StubGroup& group_cursor, #define PROCESS_SHARED_BLOB(name, type) \ process_shared_blob(_group_cursor, _blob_cursor, \ _stub_cursor, _entry_cursor, \ - "Shared Runtime " # name "_blob", \ + #name "_blob (shared runtime)", \ BlobId:: JOIN3(shared, name, id), \ StubId:: JOIN3(shared, name, id), \ EntryId:: JOIN3(shared, name, id), \ @@ -488,7 +488,7 @@ void StubInfo::process_stubgen_entry(StubGroup& group_cursor, #define PROCESS_C1_BLOB(name) \ process_c1_blob(_group_cursor, _blob_cursor, \ _stub_cursor, _entry_cursor, \ - "C1 Runtime " # name "_blob", \ + #name "_blob (C1 runtime)", \ BlobId:: JOIN3(c1, name, id), \ StubId:: JOIN3(c1, name, id), \ EntryId:: JOIN3(c1, name, id)); \ @@ -496,7 +496,7 @@ void StubInfo::process_stubgen_entry(StubGroup& group_cursor, #define PROCESS_C2_BLOB(name, type) \ process_c2_blob(_group_cursor, _blob_cursor, \ _stub_cursor, _entry_cursor, \ - "C2 Runtime " # name "_blob", \ + #name "_blob (C2 runtime)", \ BlobId:: JOIN3(c2, name, id), \ StubId:: JOIN3(c2, name, id), \ EntryId:: JOIN3(c2, name, id)); \ @@ -504,15 +504,7 @@ void StubInfo::process_stubgen_entry(StubGroup& group_cursor, #define PROCESS_C2_STUB(name, fancy_jump, pass_tls, return_pc) \ process_c2_blob(_group_cursor, _blob_cursor, \ _stub_cursor, _entry_cursor, \ - "C2 Runtime " # name "_blob", \ - BlobId:: JOIN3(c2, name, id), \ - StubId:: JOIN3(c2, name, id), \ - EntryId:: JOIN3(c2, name, id)); \ - -#define PROCESS_C2_JVMTI_STUB(name) \ - process_c2_blob(_group_cursor, _blob_cursor, \ - _stub_cursor, _entry_cursor, \ - "C2 Runtime " # name "_blob", \ + #name "_blob (C2 runtime)", \ BlobId:: JOIN3(c2, name, id), \ StubId:: JOIN3(c2, name, id), \ EntryId:: JOIN3(c2, name, id)); \ @@ -520,20 +512,20 @@ void StubInfo::process_stubgen_entry(StubGroup& group_cursor, #define PROCESS_STUBGEN_BLOB(blob) \ process_stubgen_blob(_group_cursor, _blob_cursor, \ _stub_cursor, _entry_cursor, \ - "Stub Generator " # blob "_blob", \ + #blob "_blob (stub gen)", \ BlobId:: JOIN3(stubgen, blob, id)); \ #define PROCESS_STUBGEN_STUB(blob, stub) \ process_stubgen_stub(_group_cursor, _blob_cursor, \ _stub_cursor, _entry_cursor, \ - "Stub Generator " # stub "_stub", \ + #stub "_stub (stub gen)", \ BlobId:: JOIN3(stubgen, blob, id), \ StubId:: JOIN3(stubgen, stub, id)); \ #define PROCESS_STUBGEN_ENTRY(blob, stub, field_name, getter_name) \ process_stubgen_entry(_group_cursor, _blob_cursor, \ _stub_cursor, _entry_cursor, \ - "Stub Generator " # field_name "_entry", \ + #field_name "_entry (stub gen)", \ BlobId:: JOIN3(stubgen, blob, id), \ StubId:: JOIN3(stubgen, stub, id), \ EntryId:: JOIN3(stubgen, field_name, id), \ @@ -543,7 +535,7 @@ void StubInfo::process_stubgen_entry(StubGroup& group_cursor, init_funcion) \ process_stubgen_entry(_group_cursor, _blob_cursor, \ _stub_cursor, _entry_cursor, \ - "Stub Generator " # field_name "_entry", \ + #field_name "_entry (stub gen)", \ BlobId:: JOIN3(stubgen, blob, id), \ StubId:: JOIN3(stubgen, stub, id), \ EntryId:: JOIN3(stubgen, field_name, id), \ @@ -553,7 +545,7 @@ void StubInfo::process_stubgen_entry(StubGroup& group_cursor, count) \ process_stubgen_entry(_group_cursor, _blob_cursor, \ _stub_cursor, _entry_cursor, \ - "Stub Generator " # field_name "_entry", \ + #field_name "_entry (stub gen)", \ BlobId:: JOIN3(stubgen, blob, id), \ StubId:: JOIN3(stubgen, stub, id), \ EntryId:: JOIN3(stubgen, field_name, id), \ @@ -575,7 +567,7 @@ void StubInfo::process_stubgen_entry(StubGroup& group_cursor, init_function) \ process_stubgen_entry(_group_cursor, _blob_cursor, \ _stub_cursor, _entry_cursor, \ - "Stub Generator " # arch_name "_" # field_name "_entry", \ + #arch_name "_" # field_name "_entry (stub gen)",\ BlobId:: JOIN3(stubgen, blob, id), \ StubId:: JOIN3(stubgen, stub, id), \ EntryId:: JOIN4(stubgen, arch_name, \ @@ -610,7 +602,7 @@ void StubInfo::populate_stub_tables() { group_details(_group_cursor)._max = BlobId::NO_BLOBID; group_details(_group_cursor)._entry_base = EntryId::NO_ENTRYID; group_details(_group_cursor)._entry_max = EntryId::NO_ENTRYID; - C2_STUBS_DO(PROCESS_C2_BLOB, PROCESS_C2_STUB, PROCESS_C2_JVMTI_STUB); + C2_STUBS_DO(PROCESS_C2_BLOB, PROCESS_C2_STUB); _group_cursor = StubGroup::STUBGEN; group_details(_group_cursor)._name = "StubGen Stubs"; @@ -637,7 +629,6 @@ void StubInfo::populate_stub_tables() { #undef PROCESS_C1_BLOB #undef PROCESS_C2_BLOB #undef PROCESS_C2_STUB -#undef PROCESS_C2_JVMTI_STUB #undef PROCESS_STUBGEN_BLOB #undef PROCESS_STUBGEN_STUB #undef PROCESS_STUBGEN_ENTRY diff --git a/src/hotspot/share/runtime/stubInfo.hpp b/src/hotspot/share/runtime/stubInfo.hpp index eaea07f7e6c..9ed6e0cb9f9 100644 --- a/src/hotspot/share/runtime/stubInfo.hpp +++ b/src/hotspot/share/runtime/stubInfo.hpp @@ -164,7 +164,6 @@ enum class StubGroup : int { #define SHARED_DECLARE_TAG(name, type) JOIN3(shared, name, id) , #define C1_DECLARE_TAG(name) JOIN3(c1, name, id) , -#define C2_DECLARE_TAG1(name) JOIN3(c2, name, id) , #define C2_DECLARE_TAG2(name, _1) JOIN3(c2, name, id) , #define C2_DECLARE_TAG4(name, _1, _2, _3) JOIN3(c2, name, id) , #define STUBGEN_DECLARE_TAG(name) JOIN3(stubgen, name, id) , @@ -177,8 +176,7 @@ enum class BlobId : int { C1_STUBS_DO(C1_DECLARE_TAG) // declare an enum tag for each opto runtime blob or stub C2_STUBS_DO(C2_DECLARE_TAG2, - C2_DECLARE_TAG4, - C2_DECLARE_TAG1) + C2_DECLARE_TAG4) // declare an enum tag for each stubgen blob STUBGEN_BLOBS_DO(STUBGEN_DECLARE_TAG) NUM_BLOBIDS @@ -214,7 +212,6 @@ enum class BlobId : int { #define SHARED_DECLARE_TAG(name, type) JOIN3(shared, name, id) , #define C1_DECLARE_TAG(name) JOIN3(c1, name, id) , -#define C2_DECLARE_TAG1(name) JOIN3(c2, name, id) , #define C2_DECLARE_TAG2(name, _1) JOIN3(c2, name, id) , #define C2_DECLARE_TAG4(name, _1, _2, _3) JOIN3(c2, name, id) , #define STUBGEN_DECLARE_TAG(blob, name) JOIN3(stubgen, name, id) , @@ -227,8 +224,7 @@ enum class StubId : int { C1_STUBS_DO(C1_DECLARE_TAG) // declare an enum tag for each opto runtime blob or stub C2_STUBS_DO(C2_DECLARE_TAG2, - C2_DECLARE_TAG4, - C2_DECLARE_TAG1) + C2_DECLARE_TAG4) // declare an enum tag for each stubgen runtime stub STUBGEN_STUBS_DO(STUBGEN_DECLARE_TAG) NUM_STUBIDS @@ -307,7 +303,7 @@ enum class StubId : int { type ::ENTRY_COUNT - 1, \ // macros to declare a tag for a C1 generated blob or a C2 generated -// blob, stub or JVMTI stub all of which have a single unique entry +// blob, stub all of which have a single unique entry #define C1_DECLARE_TAG(name) \ JOIN3(c1, name, id), \ @@ -318,9 +314,6 @@ enum class StubId : int { #define C2_DECLARE_STUB_TAG(name, fancy_jump, pass_tls, return_pc) \ JOIN3(c2, name, id), \ -#define C2_DECLARE_JVMTI_STUB_TAG(name) \ - JOIN3(c2, name, id), \ - // macros to declare a tag for a StubGen normal entry or initialized // entry @@ -366,8 +359,7 @@ enum class EntryId : int { C1_STUBS_DO(C1_DECLARE_TAG) // declare an enum tag for each opto runtime blob or stub C2_STUBS_DO(C2_DECLARE_BLOB_TAG, - C2_DECLARE_STUB_TAG, - C2_DECLARE_JVMTI_STUB_TAG) + C2_DECLARE_STUB_TAG) // declare an enum tag for each stubgen entry or, in the case of an // array of entries for the first and last entries. STUBGEN_ALL_ENTRIES_DO(STUBGEN_DECLARE_TAG, @@ -382,7 +374,6 @@ enum class EntryId : int { #undef C1_DECLARE_TAG #undef C2_DECLARE_BLOB_TAG #undef C2_DECLARE_STUB_TAG -#undef C2_DECLARE_JVMTI_STUB_TAG #undef STUBGEN_DECLARE_TAG #undef STUBGEN_DECLARE_INIT_TAG #undef STUBGEN_DECLARE_ARRAY_TAG @@ -402,7 +393,7 @@ enum class EntryId : int { 0 C1_STUBS_DO(COUNT1) #define C2_STUB_COUNT_INITIALIZER \ - 0 C2_STUBS_DO(COUNT2, COUNT4, COUNT1) + 0 C2_STUBS_DO(COUNT2, COUNT4) #define STUBGEN_BLOB_COUNT_INITIALIZER \ 0 STUBGEN_BLOBS_DO(COUNT1) diff --git a/src/hotspot/share/runtime/suspendResumeManager.cpp b/src/hotspot/share/runtime/suspendResumeManager.cpp index 1b9606cf633..067579b6386 100644 --- a/src/hotspot/share/runtime/suspendResumeManager.cpp +++ b/src/hotspot/share/runtime/suspendResumeManager.cpp @@ -93,11 +93,12 @@ void SuspendResumeManager::set_suspended_current_thread(int64_t vthread_id, bool } bool SuspendResumeManager::suspend(bool register_vthread_SR) { - JVMTI_ONLY(assert(!_target->is_in_VTMS_transition(), "no suspend allowed in VTMS transition");) JavaThread* self = JavaThread::current(); if (_target == self) { // If target is the current thread we can bypass the handshake machinery // and just suspend directly. + // Self-suspending while in transition can cause deadlocks. + assert(!self->is_in_vthread_transition(), "no self-suspend allowed in transition"); // The vthread() oop must only be accessed before state is set to _thread_blocked. int64_t id = java_lang_Thread::thread_id(_target->vthread()); ThreadBlockInVM tbivm(self); diff --git a/src/hotspot/share/runtime/synchronizer.cpp b/src/hotspot/share/runtime/synchronizer.cpp index fe95320c574..fde1aa50400 100644 --- a/src/hotspot/share/runtime/synchronizer.cpp +++ b/src/hotspot/share/runtime/synchronizer.cpp @@ -44,6 +44,7 @@ #include "runtime/lockStack.inline.hpp" #include "runtime/mutexLocker.hpp" #include "runtime/objectMonitor.inline.hpp" +#include "runtime/objectMonitorTable.hpp" #include "runtime/os.inline.hpp" #include "runtime/osThread.hpp" #include "runtime/safepointMechanism.inline.hpp" @@ -1470,291 +1471,6 @@ void ObjectSynchronizer::log_in_use_monitor_details(outputStream* out, bool log_ out->flush(); } -// ----------------------------------------------------------------------------- -// ConcurrentHashTable storing links from objects to ObjectMonitors -class ObjectMonitorTable : AllStatic { - struct Config { - using Value = ObjectMonitor*; - static uintx get_hash(Value const& value, bool* is_dead) { - return (uintx)value->hash(); - } - static void* allocate_node(void* context, size_t size, Value const& value) { - ObjectMonitorTable::inc_items_count(); - return AllocateHeap(size, mtObjectMonitor); - }; - static void free_node(void* context, void* memory, Value const& value) { - ObjectMonitorTable::dec_items_count(); - FreeHeap(memory); - } - }; - using ConcurrentTable = ConcurrentHashTable; - - static ConcurrentTable* _table; - static volatile size_t _items_count; - static size_t _table_size; - static volatile bool _resize; - - class Lookup : public StackObj { - oop _obj; - - public: - explicit Lookup(oop obj) : _obj(obj) {} - - uintx get_hash() const { - uintx hash = _obj->mark().hash(); - assert(hash != 0, "should have a hash"); - return hash; - } - - bool equals(ObjectMonitor** value) { - assert(*value != nullptr, "must be"); - return (*value)->object_refers_to(_obj); - } - - bool is_dead(ObjectMonitor** value) { - assert(*value != nullptr, "must be"); - return false; - } - }; - - class LookupMonitor : public StackObj { - ObjectMonitor* _monitor; - - public: - explicit LookupMonitor(ObjectMonitor* monitor) : _monitor(monitor) {} - - uintx get_hash() const { - return _monitor->hash(); - } - - bool equals(ObjectMonitor** value) { - return (*value) == _monitor; - } - - bool is_dead(ObjectMonitor** value) { - assert(*value != nullptr, "must be"); - return (*value)->object_is_dead(); - } - }; - - static void inc_items_count() { - AtomicAccess::inc(&_items_count, memory_order_relaxed); - } - - static void dec_items_count() { - AtomicAccess::dec(&_items_count, memory_order_relaxed); - } - - static double get_load_factor() { - size_t count = AtomicAccess::load(&_items_count); - return (double)count / (double)_table_size; - } - - static size_t table_size(Thread* current = Thread::current()) { - return ((size_t)1) << _table->get_size_log2(current); - } - - static size_t max_log_size() { - // TODO[OMTable]: Evaluate the max size. - // TODO[OMTable]: Need to fix init order to use Universe::heap()->max_capacity(); - // Using MaxHeapSize directly this early may be wrong, and there - // are definitely rounding errors (alignment). - const size_t max_capacity = MaxHeapSize; - const size_t min_object_size = CollectedHeap::min_dummy_object_size() * HeapWordSize; - const size_t max_objects = max_capacity / MAX2(MinObjAlignmentInBytes, checked_cast(min_object_size)); - const size_t log_max_objects = log2i_graceful(max_objects); - - return MAX2(MIN2(SIZE_BIG_LOG2, log_max_objects), min_log_size()); - } - - static size_t min_log_size() { - // ~= log(AvgMonitorsPerThreadEstimate default) - return 10; - } - - template - static size_t clamp_log_size(V log_size) { - return MAX2(MIN2(log_size, checked_cast(max_log_size())), checked_cast(min_log_size())); - } - - static size_t initial_log_size() { - const size_t estimate = log2i(MAX2(os::processor_count(), 1)) + log2i(MAX2(AvgMonitorsPerThreadEstimate, size_t(1))); - return clamp_log_size(estimate); - } - - static size_t grow_hint () { - return ConcurrentTable::DEFAULT_GROW_HINT; - } - - public: - static void create() { - _table = new ConcurrentTable(initial_log_size(), max_log_size(), grow_hint()); - _items_count = 0; - _table_size = table_size(); - _resize = false; - } - - static void verify_monitor_get_result(oop obj, ObjectMonitor* monitor) { -#ifdef ASSERT - if (SafepointSynchronize::is_at_safepoint()) { - bool has_monitor = obj->mark().has_monitor(); - assert(has_monitor == (monitor != nullptr), - "Inconsistency between markWord and ObjectMonitorTable has_monitor: %s monitor: " PTR_FORMAT, - BOOL_TO_STR(has_monitor), p2i(monitor)); - } -#endif - } - - static ObjectMonitor* monitor_get(Thread* current, oop obj) { - ObjectMonitor* result = nullptr; - Lookup lookup_f(obj); - auto found_f = [&](ObjectMonitor** found) { - assert((*found)->object_peek() == obj, "must be"); - result = *found; - }; - _table->get(current, lookup_f, found_f); - verify_monitor_get_result(obj, result); - return result; - } - - static void try_notify_grow() { - if (!_table->is_max_size_reached() && !AtomicAccess::load(&_resize)) { - AtomicAccess::store(&_resize, true); - if (Service_lock->try_lock()) { - Service_lock->notify(); - Service_lock->unlock(); - } - } - } - - static bool should_shrink() { - // Not implemented; - return false; - } - - static constexpr double GROW_LOAD_FACTOR = 0.75; - - static bool should_grow() { - return get_load_factor() > GROW_LOAD_FACTOR && !_table->is_max_size_reached(); - } - - static bool should_resize() { - return should_grow() || should_shrink() || AtomicAccess::load(&_resize); - } - - template - static bool run_task(JavaThread* current, Task& task, const char* task_name, Args&... args) { - if (task.prepare(current)) { - log_trace(monitortable)("Started to %s", task_name); - TraceTime timer(task_name, TRACETIME_LOG(Debug, monitortable, perf)); - while (task.do_task(current, args...)) { - task.pause(current); - { - ThreadBlockInVM tbivm(current); - } - task.cont(current); - } - task.done(current); - return true; - } - return false; - } - - static bool grow(JavaThread* current) { - ConcurrentTable::GrowTask grow_task(_table); - if (run_task(current, grow_task, "Grow")) { - _table_size = table_size(current); - log_info(monitortable)("Grown to size: %zu", _table_size); - return true; - } - return false; - } - - static bool clean(JavaThread* current) { - ConcurrentTable::BulkDeleteTask clean_task(_table); - auto is_dead = [&](ObjectMonitor** monitor) { - return (*monitor)->object_is_dead(); - }; - auto do_nothing = [&](ObjectMonitor** monitor) {}; - NativeHeapTrimmer::SuspendMark sm("ObjectMonitorTable"); - return run_task(current, clean_task, "Clean", is_dead, do_nothing); - } - - static bool resize(JavaThread* current) { - LogTarget(Info, monitortable) lt; - bool success = false; - - if (should_grow()) { - lt.print("Start growing with load factor %f", get_load_factor()); - success = grow(current); - } else { - if (!_table->is_max_size_reached() && AtomicAccess::load(&_resize)) { - lt.print("WARNING: Getting resize hints with load factor %f", get_load_factor()); - } - lt.print("Start cleaning with load factor %f", get_load_factor()); - success = clean(current); - } - - AtomicAccess::store(&_resize, false); - - return success; - } - - static ObjectMonitor* monitor_put_get(Thread* current, ObjectMonitor* monitor, oop obj) { - // Enter the monitor into the concurrent hashtable. - ObjectMonitor* result = monitor; - Lookup lookup_f(obj); - auto found_f = [&](ObjectMonitor** found) { - assert((*found)->object_peek() == obj, "must be"); - result = *found; - }; - bool grow; - _table->insert_get(current, lookup_f, monitor, found_f, &grow); - verify_monitor_get_result(obj, result); - if (grow) { - try_notify_grow(); - } - return result; - } - - static bool remove_monitor_entry(Thread* current, ObjectMonitor* monitor) { - LookupMonitor lookup_f(monitor); - return _table->remove(current, lookup_f); - } - - static bool contains_monitor(Thread* current, ObjectMonitor* monitor) { - LookupMonitor lookup_f(monitor); - bool result = false; - auto found_f = [&](ObjectMonitor** found) { - result = true; - }; - _table->get(current, lookup_f, found_f); - return result; - } - - static void print_on(outputStream* st) { - auto printer = [&] (ObjectMonitor** entry) { - ObjectMonitor* om = *entry; - oop obj = om->object_peek(); - st->print("monitor=" PTR_FORMAT ", ", p2i(om)); - st->print("object=" PTR_FORMAT, p2i(obj)); - assert(obj->mark().hash() == om->hash(), "hash must match"); - st->cr(); - return true; - }; - if (SafepointSynchronize::is_at_safepoint()) { - _table->do_safepoint_scan(printer); - } else { - _table->do_scan(Thread::current(), printer); - } - } -}; - -ObjectMonitorTable::ConcurrentTable* ObjectMonitorTable::_table = nullptr; -volatile size_t ObjectMonitorTable::_items_count = 0; -size_t ObjectMonitorTable::_table_size = 0; -volatile bool ObjectMonitorTable::_resize = false; - ObjectMonitor* ObjectSynchronizer::get_or_insert_monitor_from_table(oop object, JavaThread* current, bool* inserted) { ObjectMonitor* monitor = get_monitor_from_table(current, object); if (monitor != nullptr) { diff --git a/src/hotspot/share/runtime/thread.cpp b/src/hotspot/share/runtime/thread.cpp index d018c8a1a3a..f3c08ae62f7 100644 --- a/src/hotspot/share/runtime/thread.cpp +++ b/src/hotspot/share/runtime/thread.cpp @@ -566,49 +566,3 @@ bool Thread::set_as_starting_thread(JavaThread* jt) { DEBUG_ONLY(_starting_thread = jt;) return os::create_main_thread(jt); } - -// Ad-hoc mutual exclusion primitive: spin lock -// -// We employ a spin lock _only for low-contention, fixed-length -// short-duration critical sections where we're concerned -// about native mutex_t or HotSpot Mutex:: latency. - -void Thread::SpinAcquire(volatile int * adr) { - if (AtomicAccess::cmpxchg(adr, 0, 1) == 0) { - return; // normal fast-path return - } - - // Slow-path : We've encountered contention -- Spin/Yield/Block strategy. - int ctr = 0; - int Yields = 0; - for (;;) { - while (*adr != 0) { - ++ctr; - if ((ctr & 0xFFF) == 0 || !os::is_MP()) { - if (Yields > 5) { - os::naked_short_sleep(1); - } else { - os::naked_yield(); - ++Yields; - } - } else { - SpinPause(); - } - } - if (AtomicAccess::cmpxchg(adr, 0, 1) == 0) return; - } -} - -void Thread::SpinRelease(volatile int * adr) { - assert(*adr != 0, "invariant"); - // Roach-motel semantics. - // It's safe if subsequent LDs and STs float "up" into the critical section, - // but prior LDs and STs within the critical section can't be allowed - // to reorder or float past the ST that releases the lock. - // Loads and stores in the critical section - which appear in program - // order before the store that releases the lock - must also appear - // before the store that releases the lock in memory visibility order. - // So we need a #loadstore|#storestore "release" memory barrier before - // the ST of 0 into the lock-word which releases the lock. - AtomicAccess::release_store(adr, 0); -} diff --git a/src/hotspot/share/runtime/thread.hpp b/src/hotspot/share/runtime/thread.hpp index 240821e90bd..181dfc46f87 100644 --- a/src/hotspot/share/runtime/thread.hpp +++ b/src/hotspot/share/runtime/thread.hpp @@ -602,11 +602,6 @@ protected: jint _hashStateY; jint _hashStateZ; - // Low-level leaf-lock primitives used to implement synchronization. - // Not for general synchronization use. - static void SpinAcquire(volatile int * Lock); - static void SpinRelease(volatile int * Lock); - #if defined(__APPLE__) && defined(AARCH64) private: DEBUG_ONLY(bool _wx_init); diff --git a/src/hotspot/share/runtime/threadIdentifier.hpp b/src/hotspot/share/runtime/threadIdentifier.hpp index 70e11713841..69956538969 100644 --- a/src/hotspot/share/runtime/threadIdentifier.hpp +++ b/src/hotspot/share/runtime/threadIdentifier.hpp @@ -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 diff --git a/src/hotspot/share/runtime/vframe.hpp b/src/hotspot/share/runtime/vframe.hpp index c8c166f3236..882b84e2b6f 100644 --- a/src/hotspot/share/runtime/vframe.hpp +++ b/src/hotspot/share/runtime/vframe.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 diff --git a/src/hotspot/share/runtime/vmOperations.hpp b/src/hotspot/share/runtime/vmOperations.hpp index 2ed9626652c..86f77f44161 100644 --- a/src/hotspot/share/runtime/vmOperations.hpp +++ b/src/hotspot/share/runtime/vmOperations.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 diff --git a/src/hotspot/share/runtime/vmStructs.cpp b/src/hotspot/share/runtime/vmStructs.cpp index a75e67e9b56..4ecc8f9ca01 100644 --- a/src/hotspot/share/runtime/vmStructs.cpp +++ b/src/hotspot/share/runtime/vmStructs.cpp @@ -54,6 +54,7 @@ #include "oops/array.hpp" #include "oops/arrayKlass.hpp" #include "oops/arrayOop.hpp" +#include "oops/bsmAttribute.hpp" #include "oops/constantPool.hpp" #include "oops/constMethod.hpp" #include "oops/cpCache.hpp" @@ -166,10 +167,12 @@ nonstatic_field(ArrayKlass, _dimension, int) \ volatile_nonstatic_field(ArrayKlass, _higher_dimension, ObjArrayKlass*) \ volatile_nonstatic_field(ArrayKlass, _lower_dimension, ArrayKlass*) \ + nonstatic_field(BSMAttributeEntries, _offsets, Array*) \ + nonstatic_field(BSMAttributeEntries, _bootstrap_methods, Array*) \ + nonstatic_field(ConstantPool, _bsm_entries, BSMAttributeEntries) \ nonstatic_field(ConstantPool, _tags, Array*) \ nonstatic_field(ConstantPool, _cache, ConstantPoolCache*) \ nonstatic_field(ConstantPool, _pool_holder, InstanceKlass*) \ - nonstatic_field(ConstantPool, _operands, Array*) \ nonstatic_field(ConstantPool, _resolved_klasses, Array*) \ nonstatic_field(ConstantPool, _length, int) \ nonstatic_field(ConstantPool, _minor_version, u2) \ @@ -213,6 +216,7 @@ nonstatic_field(InstanceKlass, _annotations, Annotations*) \ nonstatic_field(InstanceKlass, _method_ordering, Array*) \ nonstatic_field(InstanceKlass, _default_vtable_indices, Array*) \ + nonstatic_field(InstanceKlass, _access_flags, AccessFlags) \ nonstatic_field(Klass, _super_check_offset, juint) \ nonstatic_field(Klass, _secondary_super_cache, Klass*) \ nonstatic_field(Klass, _secondary_supers, Array*) \ @@ -222,7 +226,6 @@ volatile_nonstatic_field(Klass, _subklass, Klass*) \ nonstatic_field(Klass, _layout_helper, jint) \ nonstatic_field(Klass, _name, Symbol*) \ - nonstatic_field(Klass, _access_flags, AccessFlags) \ volatile_nonstatic_field(Klass, _next_sibling, Klass*) \ nonstatic_field(Klass, _next_link, Klass*) \ nonstatic_field(Klass, _vtable_len, int) \ @@ -350,6 +353,7 @@ nonstatic_field(ThreadLocalAllocBuffer, _pf_top, HeapWord*) \ nonstatic_field(ThreadLocalAllocBuffer, _desired_size, size_t) \ nonstatic_field(ThreadLocalAllocBuffer, _refill_waste_limit, size_t) \ + static_field(ThreadLocalAllocBuffer, _reserve_for_allocation_prefetch, int) \ static_field(ThreadLocalAllocBuffer, _target_refills, unsigned) \ nonstatic_field(ThreadLocalAllocBuffer, _number_of_refills, unsigned) \ nonstatic_field(ThreadLocalAllocBuffer, _refill_waste, unsigned) \ @@ -534,7 +538,7 @@ nonstatic_field(nmethod, _osr_link, nmethod*) \ nonstatic_field(nmethod, _state, volatile signed char) \ nonstatic_field(nmethod, _exception_offset, int) \ - nonstatic_field(nmethod, _deopt_handler_offset, int) \ + nonstatic_field(nmethod, _deopt_handler_entry_offset, int) \ nonstatic_field(nmethod, _orig_pc_offset, int) \ nonstatic_field(nmethod, _stub_offset, int) \ nonstatic_field(nmethod, _immutable_data_ref_count_offset, int) \ @@ -733,6 +737,7 @@ unchecked_nonstatic_field(Array, _data, sizeof(int)) \ unchecked_nonstatic_field(Array, _data, sizeof(u1)) \ unchecked_nonstatic_field(Array, _data, sizeof(u2)) \ + unchecked_nonstatic_field(Array, _data, sizeof(u4)) \ unchecked_nonstatic_field(Array, _data, sizeof(Method*)) \ unchecked_nonstatic_field(Array, _data, sizeof(Klass*)) \ unchecked_nonstatic_field(Array, _data, sizeof(ResolvedFieldEntry)) \ @@ -964,6 +969,7 @@ declare_toplevel_type(volatile Metadata*) \ \ declare_toplevel_type(DataLayout) \ + declare_toplevel_type(BSMAttributeEntries) \ \ /********/ \ /* Oops */ \ diff --git a/src/hotspot/share/services/cpuTimeUsage.cpp b/src/hotspot/share/services/cpuTimeUsage.cpp index 0c7ecfdb655..27b5e90fbaf 100644 --- a/src/hotspot/share/services/cpuTimeUsage.cpp +++ b/src/hotspot/share/services/cpuTimeUsage.cpp @@ -36,7 +36,6 @@ volatile bool CPUTimeUsage::Error::_has_error = false; static inline jlong thread_cpu_time_or_zero(Thread* thread) { - assert(!Universe::is_shutting_down(), "Should not query during shutdown"); jlong cpu_time = os::thread_cpu_time(thread); if (cpu_time == -1) { CPUTimeUsage::Error::mark_error(); diff --git a/src/hotspot/share/services/diagnosticCommand.cpp b/src/hotspot/share/services/diagnosticCommand.cpp index 5bef650891d..0846f339227 100644 --- a/src/hotspot/share/services/diagnosticCommand.cpp +++ b/src/hotspot/share/services/diagnosticCommand.cpp @@ -22,6 +22,7 @@ * */ +#include "cds/aotMetaspace.hpp" #include "cds/cds_globals.hpp" #include "cds/cdsConfig.hpp" #include "classfile/classLoaderDataGraph.hpp" @@ -90,84 +91,82 @@ static void loadAgentModule(TRAPS) { THREAD); } -void DCmd::register_dcmds(){ - // Registration of the diagnostic commands - // First argument specifies which interfaces will export the command - // Second argument specifies if the command is enabled - // Third argument specifies if the command is hidden +void DCmd::register_dcmds() { + // Argument specifies on which interfaces a command is made available: uint32_t full_export = DCmd_Source_Internal | DCmd_Source_AttachAPI | DCmd_Source_MBean; - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); #if INCLUDE_SERVICES - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(DCmd_Source_Internal | DCmd_Source_AttachAPI, true, false)); - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(DCmd_Source_Internal | DCmd_Source_AttachAPI)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); #if INCLUDE_JVMTI // Both JVMTI and SERVICES have to be enabled to have this dcmd - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); #endif // INCLUDE_JVMTI #endif // INCLUDE_SERVICES #if INCLUDE_JVMTI - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); #endif // INCLUDE_JVMTI - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); #if INCLUDE_JVMTI - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); #endif // INCLUDE_JVMTI - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); #ifdef LINUX - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); #endif // LINUX #if defined(LINUX) || defined(_WIN64) || defined(__APPLE__) - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true,false)); - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true,false)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); #endif // LINUX or WINDOWS or MacOS - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); // Enhanced JMX Agent Support // These commands not currently exported via the DiagnosticCommandMBean uint32_t jmx_agent_export_flags = DCmd_Source_Internal | DCmd_Source_AttachAPI; - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(jmx_agent_export_flags, true,false)); - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(jmx_agent_export_flags, true,false)); - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(jmx_agent_export_flags, true,false)); - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(jmx_agent_export_flags, true,false)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(jmx_agent_export_flags)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(jmx_agent_export_flags)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(jmx_agent_export_flags)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(jmx_agent_export_flags)); #if INCLUDE_CDS - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); #endif // INCLUDE_CDS - DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); } HelpDCmd::HelpDCmd(outputStream* output, bool heap) : DCmdWithParser(output, heap), @@ -190,8 +189,7 @@ void HelpDCmd::execute(DCmdSource source, TRAPS) { for (int i = 0; i < cmd_list->length(); i++) { DCmdFactory* factory = DCmdFactory::factory(source, cmd_list->at(i), strlen(cmd_list->at(i))); - output()->print_cr("%s%s", factory->name(), - factory->is_enabled() ? "" : " [disabled]"); + output()->print_cr("%s", factory->name()); output()->print_cr("\t%s", factory->description()); output()->cr(); factory = factory->next(); @@ -201,8 +199,7 @@ void HelpDCmd::execute(DCmdSource source, TRAPS) { DCmdFactory* factory = DCmdFactory::factory(source, _cmd.value(), strlen(_cmd.value())); if (factory != nullptr) { - output()->print_cr("%s%s", factory->name(), - factory->is_enabled() ? "" : " [disabled]"); + output()->print_cr("%s", factory->name()); output()->print_cr("%s", factory->description()); output()->print_cr("\nImpact: %s", factory->impact()); output()->cr(); @@ -221,8 +218,7 @@ void HelpDCmd::execute(DCmdSource source, TRAPS) { for (int i = 0; i < cmd_list->length(); i++) { DCmdFactory* factory = DCmdFactory::factory(source, cmd_list->at(i), strlen(cmd_list->at(i))); - output()->print_cr("%s%s", factory->name(), - factory->is_enabled() ? "" : " [disabled]"); + output()->print_cr("%s", factory->name()); factory = factory->_next; } output()->print_cr("\nFor more information about a specific command use 'help '."); @@ -986,6 +982,28 @@ void ClassesDCmd::execute(DCmdSource source, TRAPS) { VMThread::execute(&vmop); } +#if INCLUDE_CDS +void AOTEndRecordingDCmd::execute(DCmdSource source, TRAPS) { + if (!CDSConfig::is_dumping_preimage_static_archive()) { + output()->print_cr("AOT.end_recording is unsupported when VM flags -XX:AOTMode=record or -XX:AOTCacheOutput= are missing."); + return; + } + + if (AOTMetaspace::preimage_static_archive_dumped()) { + output()->print_cr("Recording has already ended."); + return; + } + + AOTMetaspace::dump_static_archive(THREAD); + if (!AOTMetaspace::preimage_static_archive_dumped()) { + output()->print_cr("Error: Failed to end recording."); + return; + } + + output()->print_cr("Recording ended successfully."); +} +#endif // INCLUDE_CDS + #if INCLUDE_CDS #define DEFAULT_CDS_ARCHIVE_FILENAME "java_pid%p_.jsa" diff --git a/src/hotspot/share/services/diagnosticCommand.hpp b/src/hotspot/share/services/diagnosticCommand.hpp index 2364b0ce4cd..c41e7bf2e2e 100644 --- a/src/hotspot/share/services/diagnosticCommand.hpp +++ b/src/hotspot/share/services/diagnosticCommand.hpp @@ -325,6 +325,21 @@ public: virtual void execute(DCmdSource source, TRAPS); }; +#if INCLUDE_CDS +class AOTEndRecordingDCmd : public DCmd { +public: + AOTEndRecordingDCmd(outputStream* output, bool heap) : DCmd(output, heap) { } + static const char* name() { return "AOT.end_recording"; } + static const char* description() { + return "End AOT recording."; + } + static const char* impact() { + return "Medium: Pause time depends on number of loaded classes"; + } + virtual void execute(DCmdSource source, TRAPS); +}; +#endif // INCLUDE_CDS + #if INCLUDE_CDS class DumpSharedArchiveDCmd: public DCmdWithParser { protected: diff --git a/src/hotspot/share/services/diagnosticFramework.cpp b/src/hotspot/share/services/diagnosticFramework.cpp index c37fe7b4e1e..71cc461f161 100644 --- a/src/hotspot/share/services/diagnosticFramework.cpp +++ b/src/hotspot/share/services/diagnosticFramework.cpp @@ -563,10 +563,6 @@ DCmd* DCmdFactory::create_local_DCmd(DCmdSource source, CmdLine &line, outputStream* out, TRAPS) { DCmdFactory* f = factory(source, line.cmd_addr(), line.cmd_len()); if (f != nullptr) { - if (!f->is_enabled()) { - THROW_MSG_NULL(vmSymbols::java_lang_IllegalArgumentException(), - f->disabled_message()); - } return f->create_resource_instance(out); } THROW_MSG_NULL(vmSymbols::java_lang_IllegalArgumentException(), @@ -594,8 +590,7 @@ GrowableArray* DCmdFactory::DCmdInfo_list(DCmdSource source ) { if (!factory->is_hidden() && (factory->export_flags() & source)) { array->append(new DCmdInfo(factory->name(), factory->description(), factory->impact(), - factory->num_arguments(), - factory->is_enabled())); + factory->num_arguments())); } factory = factory->next(); } diff --git a/src/hotspot/share/services/diagnosticFramework.hpp b/src/hotspot/share/services/diagnosticFramework.hpp index 206b5ed27b4..5f9253b492e 100644 --- a/src/hotspot/share/services/diagnosticFramework.hpp +++ b/src/hotspot/share/services/diagnosticFramework.hpp @@ -117,21 +117,18 @@ protected: const char* const _description; /* Short description */ const char* const _impact; /* Impact on the JVM */ const int _num_arguments; /* Number of supported options or arguments */ - const bool _is_enabled; /* True if the diagnostic command can be invoked, false otherwise */ public: DCmdInfo(const char* name, const char* description, const char* impact, - int num_arguments, - bool enabled) + int num_arguments) : _name(name), _description(description), _impact(impact), - _num_arguments(num_arguments), _is_enabled(enabled) {} + _num_arguments(num_arguments) {} const char* name() const { return _name; } bool name_equals(const char* cmd_name) const; const char* description() const { return _description; } const char* impact() const { return _impact; } int num_arguments() const { return _num_arguments; } - bool is_enabled() const { return _is_enabled; } }; // A DCmdArgumentInfo instance provides a description of a diagnostic command @@ -233,8 +230,6 @@ public: // static const char* name() { return "";} // static const char* description() { return "";} - static const char* disabled_message() { return "Diagnostic command currently disabled"; } - // The impact() method returns a description of the intrusiveness of the diagnostic // command on the Java Virtual Machine behavior. The rational for this method is that some // diagnostic commands can seriously disrupt the behavior of the Java Virtual Machine @@ -337,8 +332,7 @@ public: }; // Diagnostic commands are not directly instantiated but created with a factory. -// Each diagnostic command class has its own factory. The DCmdFactory class also -// manages the status of the diagnostic command (hidden, enabled). A DCmdFactory +// Each diagnostic command class has its own factory. A DCmdFactory // has to be registered to make the diagnostic command available (see // management.cpp) class DCmdFactory: public CHeapObj { @@ -350,10 +344,6 @@ private: // Pointer to the next factory in the singly-linked list of registered // diagnostic commands DCmdFactory* _next; - // When disabled, a diagnostic command cannot be executed. Any attempt to - // execute it will result in the printing of the disabled message without - // instantiating the command. - const bool _enabled; // When hidden, a diagnostic command doesn't appear in the list of commands // provided by the 'help' command. const bool _hidden; @@ -361,10 +351,9 @@ private: const int _num_arguments; public: - DCmdFactory(int num_arguments, uint32_t flags, bool enabled, bool hidden) - : _next(nullptr), _enabled(enabled), _hidden(hidden), + DCmdFactory(int num_arguments, uint32_t flags, bool hidden) + : _next(nullptr), _hidden(hidden), _export_flags(flags), _num_arguments(num_arguments) {} - bool is_enabled() const { return _enabled; } bool is_hidden() const { return _hidden; } uint32_t export_flags() const { return _export_flags; } int num_arguments() const { return _num_arguments; } @@ -373,11 +362,8 @@ public: virtual const char* name() const = 0; virtual const char* description() const = 0; virtual const char* impact() const = 0; - virtual const char* disabled_message() const = 0; // Register a DCmdFactory to make a diagnostic command available. // Once registered, a diagnostic command must not be unregistered. - // To prevent a diagnostic command from being executed, just set the - // enabled flag to false. static int register_DCmdFactory(DCmdFactory* factory); static DCmdFactory* factory(DCmdSource source, const char* cmd, size_t len); // Returns a resourceArea allocated diagnostic command for the given command line @@ -401,8 +387,8 @@ private: // where this template is used to create and register factories. template class DCmdFactoryImpl : public DCmdFactory { public: - DCmdFactoryImpl(uint32_t flags, bool enabled, bool hidden) : - DCmdFactory(get_num_arguments(), flags, enabled, hidden) { } + DCmdFactoryImpl(uint32_t flags, bool hidden = false) : + DCmdFactory(get_num_arguments(), flags, hidden) { } // Returns a resourceArea allocated instance DCmd* create_resource_instance(outputStream* output) const { return new DCmdClass(output, false); @@ -416,9 +402,6 @@ public: const char* impact() const { return DCmdClass::impact(); } - const char* disabled_message() const { - return DCmdClass::disabled_message(); - } private: #ifdef ASSERT diff --git a/src/hotspot/share/services/gcNotifier.hpp b/src/hotspot/share/services/gcNotifier.hpp index f74d81b5b6e..1597e2698c4 100644 --- a/src/hotspot/share/services/gcNotifier.hpp +++ b/src/hotspot/share/services/gcNotifier.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/hotspot/share/services/management.cpp b/src/hotspot/share/services/management.cpp index cc26e2e1352..290e4839c96 100644 --- a/src/hotspot/share/services/management.cpp +++ b/src/hotspot/share/services/management.cpp @@ -2032,7 +2032,11 @@ JVM_ENTRY(void, jmm_GetDiagnosticCommandInfo(JNIEnv *env, jobjectArray cmds, infoArray[i].description = info->description(); infoArray[i].impact = info->impact(); infoArray[i].num_arguments = info->num_arguments(); - infoArray[i].enabled = info->is_enabled(); + + // All registered DCmds are always enabled. We set the dcmdInfo::enabled + // field to true to be compatible with the Java API + // com.sun.management.internal.DiagnosticCommandInfo. + infoArray[i].enabled = true; } JVM_END diff --git a/src/hotspot/share/services/threadIdTable.hpp b/src/hotspot/share/services/threadIdTable.hpp index b295cec2514..15dfb89d670 100644 --- a/src/hotspot/share/services/threadIdTable.hpp +++ b/src/hotspot/share/services/threadIdTable.hpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. +* Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/hotspot/share/services/threadService.cpp b/src/hotspot/share/services/threadService.cpp index 48f4eb16cf1..8772195df0b 100644 --- a/src/hotspot/share/services/threadService.cpp +++ b/src/hotspot/share/services/threadService.cpp @@ -1179,12 +1179,13 @@ public: GrowableArray* _bcis; JavaThreadStatus _thread_status; OopHandle _thread_name; + OopHandle _carrier_thread; GrowableArray* _locks; Blocker _blocker; - GetThreadSnapshotHandshakeClosure(Handle thread_h, JavaThread* java_thread): + GetThreadSnapshotHandshakeClosure(Handle thread_h): HandshakeClosure("GetThreadSnapshotHandshakeClosure"), - _thread_h(thread_h), _java_thread(java_thread), + _thread_h(thread_h), _java_thread(nullptr), _frame_count(0), _methods(nullptr), _bcis(nullptr), _thread_status(), _thread_name(nullptr), _locks(nullptr), _blocker() { @@ -1275,14 +1276,15 @@ private: public: void do_thread(Thread* th) override { Thread* current = Thread::current(); + _java_thread = th != nullptr ? JavaThread::cast(th) : nullptr; bool is_virtual = java_lang_VirtualThread::is_instance(_thread_h()); if (_java_thread != nullptr) { if (is_virtual) { // mounted vthread, use carrier thread state - oop carrier_thread = java_lang_VirtualThread::carrier_thread(_thread_h()); - assert(carrier_thread != nullptr, "should only get here for a mounted vthread"); - _thread_status = java_lang_Thread::get_thread_status(carrier_thread); + _carrier_thread = OopHandle(oop_storage(), java_lang_VirtualThread::carrier_thread(_thread_h())); + assert(_carrier_thread.resolve() == _java_thread->threadObj(), ""); + _thread_status = java_lang_Thread::get_thread_status(_carrier_thread.resolve()); } else { _thread_status = java_lang_Thread::get_thread_status(_thread_h()); } @@ -1459,67 +1461,21 @@ oop ThreadSnapshotFactory::get_thread_snapshot(jobject jthread, TRAPS) { oop thread_oop; bool has_javathread = tlh.cv_internal_thread_to_JavaThread(jthread, &java_thread, &thread_oop); assert((has_javathread && thread_oop != nullptr) || !has_javathread, "Missing Thread oop"); - Handle thread_h(THREAD, thread_oop); - bool is_virtual = java_lang_VirtualThread::is_instance(thread_h()); // Deals with null + bool is_virtual = java_lang_VirtualThread::is_instance(thread_oop); // Deals with null if (!has_javathread && !is_virtual) { return nullptr; // thread terminated so not of interest } - // wrapper to auto delete JvmtiVTMSTransitionDisabler - class TransitionDisabler { - JvmtiVTMSTransitionDisabler* _transition_disabler; - public: - TransitionDisabler(): _transition_disabler(nullptr) {} - ~TransitionDisabler() { - reset(); - } - void init(jobject jthread) { - _transition_disabler = new (mtInternal) JvmtiVTMSTransitionDisabler(jthread); - } - void reset() { - if (_transition_disabler != nullptr) { - delete _transition_disabler; - _transition_disabler = nullptr; - } - } - } transition_disabler; - - Handle carrier_thread; - if (is_virtual) { - // 1st need to disable mount/unmount transitions - transition_disabler.init(jthread); - - carrier_thread = Handle(THREAD, java_lang_VirtualThread::carrier_thread(thread_h())); - if (carrier_thread != nullptr) { - // Note: The java_thread associated with this carrier_thread may not be - // protected by the ThreadsListHandle above. There could have been an - // unmount and remount after the ThreadsListHandle above was created - // and before the JvmtiVTMSTransitionDisabler was created. However, as - // we have disabled transitions, if we are mounted on it, then it cannot - // terminate and so is safe to handshake with. - java_thread = java_lang_Thread::thread(carrier_thread()); - } else { - // We may have previously found a carrier but the virtual thread has unmounted - // after that, so clear that previous reference. - java_thread = nullptr; - } - } else { - java_thread = java_lang_Thread::thread(thread_h()); - } - // Handshake with target - GetThreadSnapshotHandshakeClosure cl(thread_h, java_thread); - if (java_thread == nullptr) { - // unmounted vthread, execute on the current thread - cl.do_thread(nullptr); + Handle thread_h(THREAD, thread_oop); + GetThreadSnapshotHandshakeClosure cl(thread_h); + if (java_lang_VirtualThread::is_instance(thread_oop)) { + Handshake::execute(&cl, thread_oop); } else { Handshake::execute(&cl, &tlh, java_thread); } - // all info is collected, can enable transitions. - transition_disabler.reset(); - // StackTrace InstanceKlass* ste_klass = vmClasses::StackTraceElement_klass(); assert(ste_klass != nullptr, "must be loaded"); @@ -1569,7 +1525,7 @@ oop ThreadSnapshotFactory::get_thread_snapshot(jobject jthread, TRAPS) { Handle snapshot = jdk_internal_vm_ThreadSnapshot::allocate(InstanceKlass::cast(snapshot_klass), CHECK_NULL); jdk_internal_vm_ThreadSnapshot::set_name(snapshot(), cl._thread_name.resolve()); jdk_internal_vm_ThreadSnapshot::set_thread_status(snapshot(), (int)cl._thread_status); - jdk_internal_vm_ThreadSnapshot::set_carrier_thread(snapshot(), carrier_thread()); + jdk_internal_vm_ThreadSnapshot::set_carrier_thread(snapshot(), cl._carrier_thread.resolve()); jdk_internal_vm_ThreadSnapshot::set_stack_trace(snapshot(), trace()); jdk_internal_vm_ThreadSnapshot::set_locks(snapshot(), locks()); if (!cl._blocker.is_empty()) { diff --git a/src/hotspot/share/utilities/accessFlags.hpp b/src/hotspot/share/utilities/accessFlags.hpp index a752c09cb42..54bbaeb2c13 100644 --- a/src/hotspot/share/utilities/accessFlags.hpp +++ b/src/hotspot/share/utilities/accessFlags.hpp @@ -67,7 +67,7 @@ class AccessFlags { void set_flags(u2 flags) { _flags = flags; } private: - friend class Klass; + friend class InstanceKlass; friend class ClassFileParser; // the functions below should only be called on the _access_flags inst var directly, // otherwise they are just changing a copy of the flags diff --git a/src/hotspot/share/utilities/byteswap.hpp b/src/hotspot/share/utilities/byteswap.hpp index 371095bce49..9fc367c784e 100644 --- a/src/hotspot/share/utilities/byteswap.hpp +++ b/src/hotspot/share/utilities/byteswap.hpp @@ -1,4 +1,5 @@ /* + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2023, 2024, Google and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -141,7 +142,7 @@ struct ByteswapImpl : public ByteswapFallbackImpl {}; *****************************************************************************/ #elif defined(TARGET_COMPILER_visCPP) -#include +#include "cppstdlib/cstdlib.hpp" #pragma intrinsic(_byteswap_ushort) #pragma intrinsic(_byteswap_ulong) diff --git a/src/hotspot/share/utilities/concurrentHashTable.hpp b/src/hotspot/share/utilities/concurrentHashTable.hpp index 48166b22126..d2317847307 100644 --- a/src/hotspot/share/utilities/concurrentHashTable.hpp +++ b/src/hotspot/share/utilities/concurrentHashTable.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,6 +26,7 @@ #define SHARE_UTILITIES_CONCURRENTHASHTABLE_HPP #include "memory/allocation.hpp" +#include "runtime/atomic.hpp" #include "runtime/mutex.hpp" #include "utilities/globalCounter.hpp" #include "utilities/globalDefinitions.hpp" @@ -69,7 +70,7 @@ class ConcurrentHashTable : public CHeapObj { class Node { private: DEBUG_ONLY(size_t _saved_hash); - Node * volatile _next; + Atomic _next; VALUE _value; public: Node(const VALUE& value, Node* next = nullptr) @@ -79,8 +80,8 @@ class ConcurrentHashTable : public CHeapObj { } Node* next() const; - void set_next(Node* node) { _next = node; } - Node* const volatile * next_ptr() { return &_next; } + void set_next(Node* node) { _next.store_relaxed(node); } + const Atomic* next_ptr() const { return &_next; } #ifdef ASSERT size_t saved_hash() const { return _saved_hash; } void set_saved_hash(size_t hash) { _saved_hash = hash; } @@ -122,7 +123,7 @@ class ConcurrentHashTable : public CHeapObj { // unlocked -> locked -> redirect // Locked state only applies to an updater. // Reader only check for redirect. - Node * volatile _first; + Atomic _first; static const uintptr_t STATE_LOCK_BIT = 0x1; static const uintptr_t STATE_REDIRECT_BIT = 0x2; @@ -156,20 +157,25 @@ class ConcurrentHashTable : public CHeapObj { // A bucket is only one pointer with the embedded state. Bucket() : _first(nullptr) {}; + NONCOPYABLE(Bucket); + + // Copy bucket's first entry to this. + void assign(const Bucket& bucket); + // Get the first pointer unmasked. Node* first() const; // Get a pointer to the const first pointer. Do not deference this // pointer, the pointer pointed to _may_ contain an embedded state. Such // pointer should only be used as input to release_assign_node_ptr. - Node* const volatile * first_ptr() { return &_first; } + const Atomic* first_ptr() const { return &_first; } // This is the only place where a pointer to a Node pointer that potentially // is _first should be changed. Otherwise we destroy the embedded state. We // only give out pointer to const Node pointer to avoid accidental // assignment, thus here we must cast const part away. Method is not static // due to an assert. - void release_assign_node_ptr(Node* const volatile * dst, Node* node) const; + void release_assign_node_ptr(const Atomic* dst, Node* node) const; // This method assigns this buckets last Node next ptr to input Node. void release_assign_last_node_next(Node* node); @@ -246,7 +252,7 @@ class ConcurrentHashTable : public CHeapObj { const size_t _log2_start_size; // Start size. const size_t _grow_hint; // Number of linked items - volatile bool _size_limit_reached; + Atomic _size_limit_reached; // We serialize resizers and other bulk operations which do not support // concurrent resize with this lock. @@ -255,7 +261,7 @@ class ConcurrentHashTable : public CHeapObj { // taking the mutex after a safepoint this bool is the actual state. After // acquiring the mutex you must check if this is already locked. If so you // must drop the mutex until the real lock holder grabs the mutex. - volatile Thread* _resize_lock_owner; + Atomic _resize_lock_owner; // Return true if lock mutex/state succeeded. bool try_resize_lock(Thread* locker); @@ -273,7 +279,7 @@ class ConcurrentHashTable : public CHeapObj { // this field keep tracks if a version of the hash-table was ever been seen. // We the working thread pointer as tag for debugging. The _invisible_epoch // can only be used by the owner of _resize_lock. - volatile Thread* _invisible_epoch; + Atomic _invisible_epoch; // Scoped critical section, which also handles the invisible epochs. // An invisible epoch/version do not need a write_synchronize(). @@ -435,11 +441,11 @@ class ConcurrentHashTable : public CHeapObj { size_t get_size_log2(Thread* thread); static size_t get_node_size() { return sizeof(Node); } static size_t get_dynamic_node_size(size_t value_size); - bool is_max_size_reached() { return _size_limit_reached; } + bool is_max_size_reached() { return _size_limit_reached.load_relaxed(); } // This means no paused bucket resize operation is going to resume // on this table. - bool is_safepoint_safe() { return _resize_lock_owner == nullptr; } + bool is_safepoint_safe() { return _resize_lock_owner.load_relaxed() == nullptr; } // Re-size operations. bool shrink(Thread* thread, size_t size_limit_log2 = 0); @@ -451,8 +457,7 @@ class ConcurrentHashTable : public CHeapObj { // All callbacks for get are under critical sections. Other callbacks may be // under critical section or may have locked parts of table. Calling any - // methods on the table during a callback is not supported.Only MultiGetHandle - // supports multiple gets. + // methods on the table during a callback is not supported. // Get methods return true on found item with LOOKUP_FUNC and FOUND_FUNC is // called. @@ -538,18 +543,6 @@ class ConcurrentHashTable : public CHeapObj { // Must be done at a safepoint. void rehash_nodes_to(Thread* thread, ConcurrentHashTable* to_cht); - // Scoped multi getter. - class MultiGetHandle : private ScopedCS { - public: - MultiGetHandle(Thread* thread, ConcurrentHashTable* cht) - : ScopedCS(thread, cht) {} - // In the MultiGetHandle scope you can lookup items matching LOOKUP_FUNC. - // The VALUEs are safe as long as you never save the VALUEs outside the - // scope, e.g. after ~MultiGetHandle(). - template - VALUE* get(LOOKUP_FUNC& lookup_f, bool* grow_hint = nullptr); - }; - private: class BucketsOperation; diff --git a/src/hotspot/share/utilities/concurrentHashTable.inline.hpp b/src/hotspot/share/utilities/concurrentHashTable.inline.hpp index 908405d3617..62d2dd29dab 100644 --- a/src/hotspot/share/utilities/concurrentHashTable.inline.hpp +++ b/src/hotspot/share/utilities/concurrentHashTable.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,6 +29,7 @@ #include "cppstdlib/type_traits.hpp" #include "memory/allocation.inline.hpp" +#include "runtime/atomic.hpp" #include "runtime/atomicAccess.hpp" #include "runtime/orderAccess.hpp" #include "runtime/prefetch.inline.hpp" @@ -62,7 +63,7 @@ inline typename ConcurrentHashTable::Node* ConcurrentHashTable:: Node::next() const { - return AtomicAccess::load_acquire(&_next); + return _next.load_acquire(); } // Bucket @@ -71,19 +72,26 @@ inline typename ConcurrentHashTable::Node* ConcurrentHashTable:: Bucket::first_raw() const { - return AtomicAccess::load_acquire(&_first); + return _first.load_acquire(); } template inline void ConcurrentHashTable:: Bucket::release_assign_node_ptr( - typename ConcurrentHashTable::Node* const volatile * dst, + const Atomic::Node*>* dst, typename ConcurrentHashTable::Node* node) const { // Due to this assert this methods is not static. assert(is_locked(), "Must be locked."); - Node** tmp = (Node**)dst; - AtomicAccess::release_store(tmp, clear_set_state(node, *dst)); + Atomic* tmp = const_cast*>(dst); + tmp->release_store(clear_set_state(node, dst->load_relaxed())); +} + +template +inline void ConcurrentHashTable:: + Bucket::assign(const Bucket& bucket) +{ + _first.store_relaxed(bucket._first.load_relaxed()); } template @@ -92,7 +100,7 @@ ConcurrentHashTable:: Bucket::first() const { // We strip the states bit before returning the ptr. - return clear_state(AtomicAccess::load_acquire(&_first)); + return clear_state(_first.load_acquire()); } template @@ -133,9 +141,9 @@ inline void ConcurrentHashTable:: typename ConcurrentHashTable::Node* node) { assert(is_locked(), "Must be locked."); - Node* const volatile * ret = first_ptr(); - while (clear_state(*ret) != nullptr) { - ret = clear_state(*ret)->next_ptr(); + const Atomic* ret = first_ptr(); + while (clear_state(ret->load_relaxed()) != nullptr) { + ret = clear_state(ret->load_relaxed())->next_ptr(); } release_assign_node_ptr(ret, node); } @@ -149,7 +157,7 @@ inline bool ConcurrentHashTable:: if (is_locked()) { return false; } - if (AtomicAccess::cmpxchg(&_first, expect, node) == expect) { + if (_first.compare_set(expect, node)) { return true; } return false; @@ -164,7 +172,7 @@ inline bool ConcurrentHashTable:: } // We will expect a clean first pointer. Node* tmp = first(); - if (AtomicAccess::cmpxchg(&_first, tmp, set_state(tmp, STATE_LOCK_BIT)) == tmp) { + if (_first.compare_set(tmp, set_state(tmp, STATE_LOCK_BIT))) { return true; } return false; @@ -177,7 +185,7 @@ inline void ConcurrentHashTable:: assert(is_locked(), "Must be locked."); assert(!have_redirect(), "Unlocking a bucket after it has reached terminal state."); - AtomicAccess::release_store(&_first, clear_state(first())); + _first.release_store(clear_state(first())); } template @@ -185,7 +193,7 @@ inline void ConcurrentHashTable:: Bucket::redirect() { assert(is_locked(), "Must be locked."); - AtomicAccess::release_store(&_first, set_state(_first, STATE_REDIRECT_BIT)); + _first.release_store(set_state(_first.load_relaxed(), STATE_REDIRECT_BIT)); } // InternalTable @@ -221,8 +229,8 @@ inline ConcurrentHashTable:: _cs_context(GlobalCounter::critical_section_begin(_thread)) { // This version is published now. - if (AtomicAccess::load_acquire(&_cht->_invisible_epoch) != nullptr) { - AtomicAccess::release_store_fence(&_cht->_invisible_epoch, (Thread*)nullptr); + if (_cht->_invisible_epoch.load_acquire() != nullptr) { + _cht->_invisible_epoch.release_store_fence(nullptr); } } @@ -233,14 +241,6 @@ inline ConcurrentHashTable:: GlobalCounter::critical_section_end(_thread, _cs_context); } -template -template -inline typename CONFIG::Value* ConcurrentHashTable:: - MultiGetHandle::get(LOOKUP_FUNC& lookup_f, bool* grow_hint) -{ - return ScopedCS::_cht->internal_get(ScopedCS::_thread, lookup_f, grow_hint); -} - // HaveDeletables template template @@ -290,16 +290,16 @@ template inline void ConcurrentHashTable:: write_synchonize_on_visible_epoch(Thread* thread) { - assert(_resize_lock_owner == thread, "Re-size lock not held"); + assert(_resize_lock_owner.load_relaxed() == thread, "Re-size lock not held"); OrderAccess::fence(); // Prevent below load from floating up. // If no reader saw this version we can skip write_synchronize. - if (AtomicAccess::load_acquire(&_invisible_epoch) == thread) { + if (_invisible_epoch.load_acquire() == thread) { return; } - assert(_invisible_epoch == nullptr, "Two thread doing bulk operations"); + assert(_invisible_epoch.load_relaxed() == nullptr, "Two thread doing bulk operations"); // We set this/next version that we are synchronizing for to not published. // A reader will zero this flag if it reads this/next version. - AtomicAccess::release_store(&_invisible_epoch, thread); + _invisible_epoch.release_store(thread); GlobalCounter::write_synchronize(); } @@ -308,8 +308,8 @@ inline bool ConcurrentHashTable:: try_resize_lock(Thread* locker) { if (_resize_lock->try_lock()) { - if (_resize_lock_owner != nullptr) { - assert(locker != _resize_lock_owner, "Already own lock"); + if (_resize_lock_owner.load_relaxed() != nullptr) { + assert(locker != _resize_lock_owner.load_relaxed(), "Already own lock"); // We got mutex but internal state is locked. _resize_lock->unlock(); return false; @@ -317,8 +317,8 @@ inline bool ConcurrentHashTable:: } else { return false; } - _invisible_epoch = nullptr; - _resize_lock_owner = locker; + _invisible_epoch.store_relaxed(nullptr); + _resize_lock_owner.store_relaxed(locker); return true; } @@ -334,8 +334,8 @@ inline void ConcurrentHashTable:: _resize_lock->lock_without_safepoint_check(); // If holder of lock dropped mutex for safepoint mutex might be unlocked, // and _resize_lock_owner will contain the owner. - if (_resize_lock_owner != nullptr) { - assert(locker != _resize_lock_owner, "Already own lock"); + if (_resize_lock_owner.load_relaxed() != nullptr) { + assert(locker != _resize_lock_owner.load_relaxed(), "Already own lock"); // We got mutex but internal state is locked. _resize_lock->unlock(); yield.wait(); @@ -343,17 +343,17 @@ inline void ConcurrentHashTable:: break; } } while(true); - _resize_lock_owner = locker; - _invisible_epoch = nullptr; + _resize_lock_owner.store_relaxed(locker); + _invisible_epoch.store_relaxed(nullptr); } template inline void ConcurrentHashTable:: unlock_resize_lock(Thread* locker) { - _invisible_epoch = nullptr; - assert(locker == _resize_lock_owner, "Not unlocked by locker."); - _resize_lock_owner = nullptr; + _invisible_epoch.store_relaxed(nullptr); + assert(locker == _resize_lock_owner.load_relaxed(), "Not unlocked by locker."); + _resize_lock_owner.store_relaxed(nullptr); _resize_lock->unlock(); } @@ -420,8 +420,8 @@ inline void ConcurrentHashTable:: bucket->lock(); size_t odd_index = even_index + _table->_size; - _new_table->get_buckets()[even_index] = *bucket; - _new_table->get_buckets()[odd_index] = *bucket; + _new_table->get_buckets()[even_index].assign(*bucket); + _new_table->get_buckets()[odd_index].assign(*bucket); // Moves lockers go to new table, where they will wait until unlock() below. bucket->redirect(); /* Must release stores above */ @@ -452,7 +452,7 @@ inline bool ConcurrentHashTable:: { Bucket* bucket = get_bucket_locked(thread, lookup_f.get_hash()); assert(bucket->is_locked(), "Must be locked."); - Node* const volatile * rem_n_prev = bucket->first_ptr(); + const Atomic* rem_n_prev = bucket->first_ptr(); Node* rem_n = bucket->first(); while (rem_n != nullptr) { if (lookup_f.equals(rem_n->value())) { @@ -485,8 +485,8 @@ inline void ConcurrentHashTable:: { // Here we have resize lock so table is SMR safe, and there is no new // table. Can do this in parallel if we want. - assert((is_mt && _resize_lock_owner != nullptr) || - (!is_mt && _resize_lock_owner == thread), "Re-size lock not held"); + assert((is_mt && _resize_lock_owner.load_relaxed() != nullptr) || + (!is_mt && _resize_lock_owner.load_relaxed() == thread), "Re-size lock not held"); Node* ndel_stack[StackBufferSize]; InternalTable* table = get_table(); assert(start_idx < stop_idx, "Must be"); @@ -541,7 +541,7 @@ inline void ConcurrentHashTable:: size_t dels = 0; Node* ndel[StackBufferSize]; - Node* const volatile * rem_n_prev = bucket->first_ptr(); + const Atomic* rem_n_prev = bucket->first_ptr(); Node* rem_n = bucket->first(); while (rem_n != nullptr) { if (lookup_f.is_dead(rem_n->value())) { @@ -650,8 +650,8 @@ inline bool ConcurrentHashTable:: return false; } Node* delete_me = nullptr; - Node* const volatile * even = new_table->get_bucket(even_index)->first_ptr(); - Node* const volatile * odd = new_table->get_bucket(odd_index)->first_ptr(); + const Atomic* even = new_table->get_bucket(even_index)->first_ptr(); + const Atomic* odd = new_table->get_bucket(odd_index)->first_ptr(); while (aux != nullptr) { bool dead_hash = false; size_t aux_hash = CONFIG::get_hash(*aux->value(), &dead_hash); @@ -704,7 +704,7 @@ inline bool ConcurrentHashTable:: if (!try_resize_lock(thread)) { return false; } - assert(_resize_lock_owner == thread, "Re-size lock not held"); + assert(_resize_lock_owner.load_relaxed() == thread, "Re-size lock not held"); if (_table->_log2_size == _log2_start_size || _table->_log2_size <= log2_size) { unlock_resize_lock(thread); @@ -718,10 +718,10 @@ template inline void ConcurrentHashTable:: internal_shrink_epilog(Thread* thread) { - assert(_resize_lock_owner == thread, "Re-size lock not held"); + assert(_resize_lock_owner.load_relaxed() == thread, "Re-size lock not held"); InternalTable* old_table = set_table_from_new(); - _size_limit_reached = false; + _size_limit_reached.store_relaxed(false); unlock_resize_lock(thread); #ifdef ASSERT for (size_t i = 0; i < old_table->_size; i++) { @@ -749,11 +749,11 @@ inline void ConcurrentHashTable:: b_old_even->lock(); b_old_odd->lock(); - _new_table->get_buckets()[bucket_it] = *b_old_even; + _new_table->get_buckets()[bucket_it].assign(*b_old_even); // Put chains together. _new_table->get_bucket(bucket_it)-> - release_assign_last_node_next(*(b_old_odd->first_ptr())); + release_assign_last_node_next(b_old_odd->first_ptr()->load_relaxed()); b_old_even->redirect(); b_old_odd->redirect(); @@ -775,13 +775,13 @@ inline bool ConcurrentHashTable:: internal_shrink(Thread* thread, size_t log2_size) { if (!internal_shrink_prolog(thread, log2_size)) { - assert(_resize_lock_owner != thread, "Re-size lock held"); + assert(_resize_lock_owner.load_relaxed() != thread, "Re-size lock held"); return false; } - assert(_resize_lock_owner == thread, "Should be locked by me"); + assert(_resize_lock_owner.load_relaxed() == thread, "Should be locked by me"); internal_shrink_range(thread, 0, _new_table->_size); internal_shrink_epilog(thread); - assert(_resize_lock_owner != thread, "Re-size lock held"); + assert(_resize_lock_owner.load_relaxed() != thread, "Re-size lock held"); return true; } @@ -795,7 +795,7 @@ inline void ConcurrentHashTable:: delete _table; // Create and publish a new table InternalTable* table = new InternalTable(log2_size); - _size_limit_reached = (log2_size == _log2_size_limit); + _size_limit_reached.store_relaxed(log2_size == _log2_size_limit); AtomicAccess::release_store(&_table, table); } @@ -820,7 +820,7 @@ inline bool ConcurrentHashTable:: } _new_table = new InternalTable(_table->_log2_size + 1); - _size_limit_reached = _new_table->_log2_size == _log2_size_limit; + _size_limit_reached.store_relaxed(_new_table->_log2_size == _log2_size_limit); return true; } @@ -829,7 +829,7 @@ template inline void ConcurrentHashTable:: internal_grow_epilog(Thread* thread) { - assert(_resize_lock_owner == thread, "Should be locked"); + assert(_resize_lock_owner.load_relaxed() == thread, "Should be locked"); InternalTable* old_table = set_table_from_new(); unlock_resize_lock(thread); @@ -848,13 +848,13 @@ inline bool ConcurrentHashTable:: internal_grow(Thread* thread, size_t log2_size) { if (!internal_grow_prolog(thread, log2_size)) { - assert(_resize_lock_owner != thread, "Re-size lock held"); + assert(_resize_lock_owner.load_relaxed() != thread, "Re-size lock held"); return false; } - assert(_resize_lock_owner == thread, "Should be locked by me"); + assert(_resize_lock_owner.load_relaxed() == thread, "Should be locked by me"); internal_grow_range(thread, 0, _table->_size); internal_grow_epilog(thread); - assert(_resize_lock_owner != thread, "Re-size lock held"); + assert(_resize_lock_owner.load_relaxed() != thread, "Re-size lock held"); return true; } @@ -969,7 +969,7 @@ template inline void ConcurrentHashTable:: do_scan_locked(Thread* thread, FUNC& scan_f) { - assert(_resize_lock_owner == thread, "Re-size lock not held"); + assert(_resize_lock_owner.load_relaxed() == thread, "Re-size lock not held"); // We can do a critical section over the entire loop but that would block // updates for a long time. Instead we choose to block resizes. InternalTable* table = get_table(); @@ -988,7 +988,7 @@ inline size_t ConcurrentHashTable:: size_t num_del, Node** ndel, GrowableArrayCHeap& extra) { size_t dels = 0; - Node* const volatile * rem_n_prev = bucket->first_ptr(); + const Atomic* rem_n_prev = bucket->first_ptr(); Node* rem_n = bucket->first(); while (rem_n != nullptr) { if (eval_f(rem_n->value())) { @@ -1028,7 +1028,7 @@ ConcurrentHashTable(size_t log2size, size_t log2size_limit, size_t grow_hint, bo _resize_lock = new Mutex(rank, "ConcurrentHashTableResize_lock"); _table = new InternalTable(log2size); assert(log2size_limit >= log2size, "bad ergo"); - _size_limit_reached = _table->_log2_size == _log2_size_limit; + _size_limit_reached.store_relaxed(_table->_log2_size == _log2_size_limit); } template @@ -1147,11 +1147,11 @@ inline void ConcurrentHashTable:: { assert(!SafepointSynchronize::is_at_safepoint(), "must be outside a safepoint"); - assert(_resize_lock_owner != thread, "Re-size lock held"); + assert(_resize_lock_owner.load_relaxed() != thread, "Re-size lock held"); lock_resize_lock(thread); do_scan_locked(thread, scan_f); unlock_resize_lock(thread); - assert(_resize_lock_owner != thread, "Re-size lock held"); + assert(_resize_lock_owner.load_relaxed() != thread, "Re-size lock held"); } template @@ -1213,7 +1213,7 @@ inline bool ConcurrentHashTable:: } do_bulk_delete_locked(thread, eval_f, del_f); unlock_resize_lock(thread); - assert(_resize_lock_owner != thread, "Re-size lock held"); + assert(_resize_lock_owner.load_relaxed() != thread, "Re-size lock held"); return true; } diff --git a/src/hotspot/share/utilities/concurrentHashTableTasks.inline.hpp b/src/hotspot/share/utilities/concurrentHashTableTasks.inline.hpp index 086a548ede5..054008953a8 100644 --- a/src/hotspot/share/utilities/concurrentHashTableTasks.inline.hpp +++ b/src/hotspot/share/utilities/concurrentHashTableTasks.inline.hpp @@ -27,6 +27,7 @@ // No concurrentHashTableTasks.hpp +#include "runtime/atomic.hpp" #include "runtime/atomicAccess.hpp" #include "utilities/concurrentHashTable.inline.hpp" #include "utilities/globalDefinitions.hpp" @@ -41,7 +42,7 @@ class ConcurrentHashTable::BucketsOperation { ConcurrentHashTable* _cht; class InternalTableClaimer { - volatile size_t _next; + Atomic _next; size_t _limit; size_t _size; @@ -56,14 +57,14 @@ public: void set(size_t claim_size, InternalTable* table) { assert(table != nullptr, "precondition"); - _next = 0; + _next.store_relaxed(0); _limit = table->_size; _size = MIN2(claim_size, _limit); } bool claim(size_t* start, size_t* stop) { - if (AtomicAccess::load(&_next) < _limit) { - size_t claimed = AtomicAccess::fetch_then_add(&_next, _size); + if (_next.load_relaxed() < _limit) { + size_t claimed = _next.fetch_then_add(_size); if (claimed < _limit) { *start = claimed; *stop = MIN2(claimed + _size, _limit); @@ -78,7 +79,7 @@ public: } bool have_more_work() { - return AtomicAccess::load_acquire(&_next) >= _limit; + return _next.load_acquire() >= _limit; } }; @@ -108,13 +109,13 @@ public: } void thread_owns_resize_lock(Thread* thread) { - assert(BucketsOperation::_cht->_resize_lock_owner == thread, + assert(BucketsOperation::_cht->_resize_lock_owner.load_relaxed() == thread, "Should be locked by me"); assert(BucketsOperation::_cht->_resize_lock->owned_by_self(), "Operations lock not held"); } void thread_owns_only_state_lock(Thread* thread) { - assert(BucketsOperation::_cht->_resize_lock_owner == thread, + assert(BucketsOperation::_cht->_resize_lock_owner.load_relaxed() == thread, "Should be locked by me"); assert(!BucketsOperation::_cht->_resize_lock->owned_by_self(), "Operations lock held"); @@ -122,7 +123,7 @@ public: void thread_do_not_own_resize_lock(Thread* thread) { assert(!BucketsOperation::_cht->_resize_lock->owned_by_self(), "Operations lock held"); - assert(BucketsOperation::_cht->_resize_lock_owner != thread, + assert(BucketsOperation::_cht->_resize_lock_owner.load_relaxed() != thread, "Should not be locked by me"); } @@ -169,7 +170,7 @@ class ConcurrentHashTable::BulkDeleteTask : template bool do_task(Thread* thread, EVALUATE_FUNC& eval_f, DELETE_FUNC& del_f) { size_t start, stop; - assert(BucketsOperation::_cht->_resize_lock_owner != nullptr, + assert(BucketsOperation::_cht->_resize_lock_owner.load_relaxed() != nullptr, "Should be locked"); if (!this->claim(&start, &stop)) { return false; @@ -177,7 +178,7 @@ class ConcurrentHashTable::BulkDeleteTask : BucketsOperation::_cht->do_bulk_delete_locked_for(thread, start, stop, eval_f, del_f, BucketsOperation::_is_mt); - assert(BucketsOperation::_cht->_resize_lock_owner != nullptr, + assert(BucketsOperation::_cht->_resize_lock_owner.load_relaxed() != nullptr, "Should be locked"); return true; } @@ -210,13 +211,13 @@ class ConcurrentHashTable::GrowTask : // Re-sizes a portion of the table. Returns true if there is more work. bool do_task(Thread* thread) { size_t start, stop; - assert(BucketsOperation::_cht->_resize_lock_owner != nullptr, + assert(BucketsOperation::_cht->_resize_lock_owner.load_relaxed() != nullptr, "Should be locked"); if (!this->claim(&start, &stop)) { return false; } BucketsOperation::_cht->internal_grow_range(thread, start, stop); - assert(BucketsOperation::_cht->_resize_lock_owner != nullptr, + assert(BucketsOperation::_cht->_resize_lock_owner.load_relaxed() != nullptr, "Should be locked"); return true; } @@ -253,13 +254,13 @@ class ConcurrentHashTable::StatisticsTask : template bool do_task(Thread* thread, VALUE_SIZE_FUNC& sz) { size_t start, stop; - assert(BucketsOperation::_cht->_resize_lock_owner != nullptr, + assert(BucketsOperation::_cht->_resize_lock_owner.load_relaxed() != nullptr, "Should be locked"); if (!this->claim(&start, &stop)) { return false; } BucketsOperation::_cht->internal_statistics_range(thread, start, stop, sz, _summary, _literal_bytes); - assert(BucketsOperation::_cht->_resize_lock_owner != nullptr, + assert(BucketsOperation::_cht->_resize_lock_owner.load_relaxed() != nullptr, "Should be locked"); return true; } diff --git a/src/hotspot/share/utilities/deferredStatic.hpp b/src/hotspot/share/utilities/deferredStatic.hpp index 3a32f920fe8..a37b169803a 100644 --- a/src/hotspot/share/utilities/deferredStatic.hpp +++ b/src/hotspot/share/utilities/deferredStatic.hpp @@ -35,7 +35,7 @@ // object must be explicitly initialized before use. This avoids problems // resulting from the unspecified initialization time and ordering between // different objects that comes from using undeferred objects (the so-called -// "Static Initialization Order Fiasco). +// "Static Initialization Order Fiasco"). // // Once initialized, the object is never destroyed. This avoids similar issues // with the timing and ordering of destruction on normal program exit. @@ -53,7 +53,7 @@ class DeferredStatic { public: NONCOPYABLE(DeferredStatic); - DeferredStatic() + constexpr DeferredStatic() DEBUG_ONLY(: _initialized(false)) { // Do not construct value, on purpose. } diff --git a/src/hotspot/share/utilities/elfFile.hpp b/src/hotspot/share/utilities/elfFile.hpp index 1298892533e..8abd846364c 100644 --- a/src/hotspot/share/utilities/elfFile.hpp +++ b/src/hotspot/share/utilities/elfFile.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/hotspot/share/utilities/forbiddenFunctions.hpp b/src/hotspot/share/utilities/forbiddenFunctions.hpp index 47becd7b4c7..9d1b88e6233 100644 --- a/src/hotspot/share/utilities/forbiddenFunctions.hpp +++ b/src/hotspot/share/utilities/forbiddenFunctions.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -32,12 +32,6 @@ #include #include -// Workaround for noreturn functions: exit, _exit, _Exit - see the clang -// definition of FORBIDDEN_FUNCTION_NORETURN_ATTRIBUTE. -#ifdef __clang__ -#include -#endif - #ifdef _WINDOWS #include "forbiddenFunctions_windows.hpp" #else @@ -49,12 +43,6 @@ // or have security concerns, either with preferred alternatives, or to be // avoided entirely. -FORBID_IMPORTED_NORETURN_C_FUNCTION(void exit(int), noexcept, "use os::exit") -FORBID_IMPORTED_NORETURN_C_FUNCTION(void _Exit(int), noexcept, "use os::exit") - -// Windows puts _exit in , POSIX in . -FORBID_IMPORTED_NORETURN_C_FUNCTION(void _exit(int), /* not noexcept */, "use os::exit") - FORBID_IMPORTED_C_FUNCTION(char* strerror(int), noexcept, "use os::strerror"); FORBID_IMPORTED_C_FUNCTION(char* strtok(char*, const char*), noexcept, "use strtok_r"); @@ -70,13 +58,8 @@ FORBID_C_FUNCTION(int vsprintf(char*, const char*, va_list), noexcept, "use os:: FORBID_C_FUNCTION(int vsnprintf(char*, size_t, const char*, va_list), noexcept, "use os::vsnprintf"); PRAGMA_DIAG_POP -// All of the following functions return raw C-heap pointers (sometimes as an -// option, e.g. realpath or getwd) or, in case of free(), take raw C-heap -// pointers. We generally want allocation to be done through NMT. -FORBID_IMPORTED_C_FUNCTION(void* malloc(size_t size), noexcept, "use os::malloc"); -FORBID_IMPORTED_C_FUNCTION(void free(void *ptr), noexcept, "use os::free"); -FORBID_IMPORTED_C_FUNCTION(void* calloc(size_t nmemb, size_t size), noexcept, "use os::malloc and zero out manually"); -FORBID_IMPORTED_C_FUNCTION(void* realloc(void *ptr, size_t size), noexcept, "use os::realloc"); +// All of the following functions return raw C-heap pointers. We generally +// want allocation to be done through NMT. FORBID_IMPORTED_C_FUNCTION(char* strdup(const char *s), noexcept, "use os::strdup"); FORBID_IMPORTED_C_FUNCTION(wchar_t* wcsdup(const wchar_t *s), noexcept, "don't use"); diff --git a/src/hotspot/share/utilities/globalDefinitions.hpp b/src/hotspot/share/utilities/globalDefinitions.hpp index 3284fd3bd15..54602297759 100644 --- a/src/hotspot/share/utilities/globalDefinitions.hpp +++ b/src/hotspot/share/utilities/globalDefinitions.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -1374,14 +1374,6 @@ template int primitive_compare(const K& k0, const K& k1) { template std::add_rvalue_reference_t declval() noexcept; -// This provides a workaround for static_assert(false) in discarded or -// otherwise uninstantiated places. Instead use -// static_assert(DependentAlwaysFalse, "...") -// See http://wg21.link/p2593r1. Some, but not all, compiler versions we're -// using have implemented that change as a DR: -// https://cplusplus.github.io/CWG/issues/2518.html -template inline constexpr bool DependentAlwaysFalse = false; - // Quickly test to make sure IEEE-754 subnormal numbers are correctly // handled. bool IEEE_subnormal_handling_OK(); diff --git a/src/hotspot/share/utilities/globalDefinitions_gcc.hpp b/src/hotspot/share/utilities/globalDefinitions_gcc.hpp index f82f9b1386a..302a62de6c1 100644 --- a/src/hotspot/share/utilities/globalDefinitions_gcc.hpp +++ b/src/hotspot/share/utilities/globalDefinitions_gcc.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,6 +31,8 @@ // globally used constants & types, class (forward) // declarations and a few frequently used utility functions. +#include "cppstdlib/cstdlib.hpp" + #include #include #include @@ -44,15 +46,6 @@ #include #include #include -#include -// In stdlib.h on AIX malloc is defined as a macro causing -// compiler errors when resolving them in different depths as it -// happens in the log tags. This avoids the macro. -#if (defined(__VEC__) || defined(__AIXVEC)) && defined(AIX) \ - && defined(__open_xl_version__) && __open_xl_version__ >= 17 - #undef malloc - extern void *malloc(size_t) asm("vec_malloc"); -#endif #include #include #include diff --git a/src/hotspot/share/utilities/globalDefinitions_visCPP.hpp b/src/hotspot/share/utilities/globalDefinitions_visCPP.hpp index b9d25096cd5..dfd6f2f1880 100644 --- a/src/hotspot/share/utilities/globalDefinitions_visCPP.hpp +++ b/src/hotspot/share/utilities/globalDefinitions_visCPP.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -34,6 +34,8 @@ // Need this on windows to get the math constants (e.g., M_PI). #define _USE_MATH_DEFINES +#include "cppstdlib/cstdlib.hpp" + # include # include # include // for _isnan @@ -45,7 +47,6 @@ # include // for offsetof # include # include -# include # include # include # include diff --git a/src/hotspot/share/utilities/intn_t.hpp b/src/hotspot/share/utilities/intn_t.hpp index 6f43f5c2556..594e62a1694 100644 --- a/src/hotspot/share/utilities/intn_t.hpp +++ b/src/hotspot/share/utilities/intn_t.hpp @@ -79,6 +79,7 @@ public: static_assert(min < max, ""); constexpr bool operator==(intn_t o) const { return (_v & _mask) == (o._v & _mask); } + constexpr bool operator!=(intn_t o) const { return !(*this == o); } constexpr bool operator<(intn_t o) const { return int(*this) < int(o); } constexpr bool operator>(intn_t o) const { return int(*this) > int(o); } constexpr bool operator<=(intn_t o) const { return int(*this) <= int(o); } diff --git a/src/hotspot/share/utilities/lockFreeStack.hpp b/src/hotspot/share/utilities/lockFreeStack.hpp index 3f63482a268..871e697f404 100644 --- a/src/hotspot/share/utilities/lockFreeStack.hpp +++ b/src/hotspot/share/utilities/lockFreeStack.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,6 +25,7 @@ #ifndef SHARE_UTILITIES_LOCKFREESTACK_HPP #define SHARE_UTILITIES_LOCKFREESTACK_HPP +#include "metaprogramming/dependentAlwaysFalse.hpp" #include "runtime/atomic.hpp" #include "runtime/atomicAccess.hpp" #include "utilities/debug.hpp" diff --git a/src/hotspot/share/utilities/macros.hpp b/src/hotspot/share/utilities/macros.hpp index 9bdf4c2dd1f..8404fc757f0 100644 --- a/src/hotspot/share/utilities/macros.hpp +++ b/src/hotspot/share/utilities/macros.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 diff --git a/src/hotspot/share/utilities/nativeCallStack.hpp b/src/hotspot/share/utilities/nativeCallStack.hpp index 32635a72d51..cb7823c0c0a 100644 --- a/src/hotspot/share/utilities/nativeCallStack.hpp +++ b/src/hotspot/share/utilities/nativeCallStack.hpp @@ -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 diff --git a/src/hotspot/share/utilities/numberSeq.hpp b/src/hotspot/share/utilities/numberSeq.hpp index fff4b416745..b70b081586b 100644 --- a/src/hotspot/share/utilities/numberSeq.hpp +++ b/src/hotspot/share/utilities/numberSeq.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/hotspot/share/utilities/objectBitSet.hpp b/src/hotspot/share/utilities/objectBitSet.hpp index 73f1c89dd40..8f3e334b1a5 100644 --- a/src/hotspot/share/utilities/objectBitSet.hpp +++ b/src/hotspot/share/utilities/objectBitSet.hpp @@ -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 diff --git a/src/hotspot/share/utilities/objectBitSet.inline.hpp b/src/hotspot/share/utilities/objectBitSet.inline.hpp index e1b4d728b10..e204841e6e2 100644 --- a/src/hotspot/share/utilities/objectBitSet.inline.hpp +++ b/src/hotspot/share/utilities/objectBitSet.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/hotspot/share/utilities/parseInteger.hpp b/src/hotspot/share/utilities/parseInteger.hpp index 3e3eafada87..8840275a8cb 100644 --- a/src/hotspot/share/utilities/parseInteger.hpp +++ b/src/hotspot/share/utilities/parseInteger.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2022 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,6 +26,7 @@ #ifndef SHARE_UTILITIES_PARSE_INTEGER_HPP #define SHARE_UTILITIES_PARSE_INTEGER_HPP +#include "cppstdlib/cstdlib.hpp" #include "cppstdlib/limits.hpp" #include "metaprogramming/enableIf.hpp" #include "utilities/debug.hpp" @@ -33,7 +34,6 @@ #include "utilities/macros.hpp" #include -#include // ************************************************************************* // ** Attention compatibility! ** diff --git a/src/hotspot/share/utilities/permitForbiddenFunctions.hpp b/src/hotspot/share/utilities/permitForbiddenFunctions.hpp index 1ba42f8e386..71719ac8a76 100644 --- a/src/hotspot/share/utilities/permitForbiddenFunctions.hpp +++ b/src/hotspot/share/utilities/permitForbiddenFunctions.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,6 +25,7 @@ #ifndef SHARE_UTILITIES_PERMITFORBIDDENFUNCTIONS_HPP #define SHARE_UTILITIES_PERMITFORBIDDENFUNCTIONS_HPP +#include "cppstdlib/cstdlib.hpp" #include "utilities/compilerWarnings.hpp" #include "utilities/globalDefinitions.hpp" @@ -34,6 +35,9 @@ #include "permitForbiddenFunctions_posix.hpp" #endif +#include +#include + // Provide wrappers for some functions otherwise forbidden from use in HotSpot. // // There may be special circumstances where an otherwise forbidden function @@ -53,7 +57,6 @@ namespace permit_forbidden_function { BEGIN_ALLOW_FORBIDDEN_FUNCTIONS [[noreturn]] inline void exit(int status) { ::exit(status); } -[[noreturn]] inline void _exit(int status) { ::_exit(status); } ATTRIBUTE_PRINTF(3, 0) inline int vsnprintf(char* str, size_t size, const char* format, va_list ap) { diff --git a/src/hotspot/share/utilities/resizableHashTable.hpp b/src/hotspot/share/utilities/resizableHashTable.hpp index b445ed8a55e..a5b53698b11 100644 --- a/src/hotspot/share/utilities/resizableHashTable.hpp +++ b/src/hotspot/share/utilities/resizableHashTable.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/hotspot/share/utilities/spinCriticalSection.cpp b/src/hotspot/share/utilities/spinCriticalSection.cpp new file mode 100644 index 00000000000..4064c234e97 --- /dev/null +++ b/src/hotspot/share/utilities/spinCriticalSection.cpp @@ -0,0 +1,58 @@ +/* + * 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 "runtime/atomicAccess.hpp" +#include "utilities/spinCriticalSection.hpp" +#include "utilities/spinYield.hpp" + +void SpinCriticalSection::spin_acquire(volatile int* adr) { + if (AtomicAccess::cmpxchg(adr, 0, 1) == 0) { + return; // normal fast-path return + } + + SpinYield sy(4096, 5, millis_to_nanos(1)); + + // Slow-path : We've encountered contention -- Spin/Yield/Block strategy. + for (;;) { + while (*adr != 0) { + sy.wait(); + } + if (AtomicAccess::cmpxchg(adr, 0, 1) == 0) return; + } +} + +void SpinCriticalSection::spin_release(volatile int* adr) { + assert(*adr != 0, "invariant"); + // Roach-motel semantics. + // It's safe if subsequent LDs and STs float "up" into the critical section, + // but prior LDs and STs within the critical section can't be allowed + // to reorder or float past the ST that releases the lock. + // Loads and stores in the critical section - which appear in program + // order before the store that releases the lock - must also appear + // before the store that releases the lock in memory visibility order. + // So we need a #loadstore|#storestore "release" memory barrier before + // the ST of 0 into the lock-word which releases the lock. + AtomicAccess::release_store(adr, 0); +} + diff --git a/src/hotspot/share/utilities/spinCriticalSection.hpp b/src/hotspot/share/utilities/spinCriticalSection.hpp new file mode 100644 index 00000000000..0ebdc5de63e --- /dev/null +++ b/src/hotspot/share/utilities/spinCriticalSection.hpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_UTILITIES_SPINCRITICALSECTION_HPP +#define SHARE_UTILITIES_SPINCRITICALSECTION_HPP + +#include "runtime/javaThread.hpp" +#include "runtime/safepointVerifiers.hpp" +#include "runtime/thread.hpp" +#include "utilities/macros.hpp" + +// Ad-hoc mutual exclusion primitive: spin critical section, +// which employs a spin lock. +// +// We use this critical section only for low-contention code, and +// when it is know that the duration is short. To be used where +// we're concerned about native mutex_t or HotSpot Mutex:: latency. +// This class uses low-level leaf-lock primitives to implement +// synchronization and is not for general synchronization use. +// Should not be used in signal-handling contexts. +class SpinCriticalSection { +private: + // We use int type as 32-bit atomic operation is the most performant + // compared to smaller/larger types. + volatile int* const _lock; + DEBUG_ONLY(NoSafepointVerifier _nsv;) + + static void spin_acquire(volatile int* Lock); + static void spin_release(volatile int* Lock); +public: + NONCOPYABLE(SpinCriticalSection); + SpinCriticalSection(volatile int* lock) + : _lock(lock) + DEBUG_ONLY(COMMA _nsv(Thread::current_or_null() != nullptr)) { + spin_acquire(_lock); + } + ~SpinCriticalSection() { + spin_release(_lock); + } +}; + +#endif // SHARE_UTILITIES_SPINCRITICALSECTION_HPP diff --git a/src/hotspot/share/utilities/ticks.hpp b/src/hotspot/share/utilities/ticks.hpp index 88dc9a787f9..7822e565f32 100644 --- a/src/hotspot/share/utilities/ticks.hpp +++ b/src/hotspot/share/utilities/ticks.hpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013, 2022, Oracle and/or its affiliates. All rights reserved. +* Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/hotspot/share/utilities/vmError.cpp b/src/hotspot/share/utilities/vmError.cpp index e0cbb60c744..a290602e0be 100644 --- a/src/hotspot/share/utilities/vmError.cpp +++ b/src/hotspot/share/utilities/vmError.cpp @@ -664,6 +664,7 @@ void VMError::report(outputStream* st, bool _verbose) { BEGIN if (MemTracker::enabled() && NmtVirtualMemory_lock != nullptr && + _thread != nullptr && NmtVirtualMemory_lock->owned_by_self()) { // Manually unlock to avoid reentrancy due to mallocs in detailed mode. NmtVirtualMemory_lock->unlock(); @@ -1305,7 +1306,7 @@ void VMError::report(outputStream* st, bool _verbose) { os::print_signal_handlers(st, buf, sizeof(buf)); st->cr(); - STEP_IF("Native Memory Tracking", _verbose) + STEP_IF("Native Memory Tracking", _verbose && _thread != nullptr) MemTracker::error_report(st); st->cr(); diff --git a/src/hotspot/share/utilities/waitBarrier_generic.cpp b/src/hotspot/share/utilities/waitBarrier_generic.cpp index a6436d93ffc..0892feab699 100644 --- a/src/hotspot/share/utilities/waitBarrier_generic.cpp +++ b/src/hotspot/share/utilities/waitBarrier_generic.cpp @@ -23,7 +23,6 @@ * */ -#include "runtime/atomicAccess.hpp" #include "runtime/orderAccess.hpp" #include "runtime/os.hpp" #include "utilities/spinYield.hpp" @@ -79,10 +78,10 @@ void GenericWaitBarrier::arm(int barrier_tag) { assert(barrier_tag != 0, "Pre arm: Should be arming with armed value"); - assert(AtomicAccess::load(&_barrier_tag) == 0, + assert(_barrier_tag.load_relaxed() == 0, "Pre arm: Should not be already armed. Tag: %d", - AtomicAccess::load(&_barrier_tag)); - AtomicAccess::release_store(&_barrier_tag, barrier_tag); + _barrier_tag.load_relaxed()); + _barrier_tag.release_store(barrier_tag); Cell &cell = tag_to_cell(barrier_tag); cell.arm(barrier_tag); @@ -92,9 +91,9 @@ void GenericWaitBarrier::arm(int barrier_tag) { } void GenericWaitBarrier::disarm() { - int barrier_tag = AtomicAccess::load_acquire(&_barrier_tag); + int barrier_tag = _barrier_tag.load_acquire(); assert(barrier_tag != 0, "Pre disarm: Should be armed. Tag: %d", barrier_tag); - AtomicAccess::release_store(&_barrier_tag, 0); + _barrier_tag.release_store(0); Cell &cell = tag_to_cell(barrier_tag); cell.disarm(barrier_tag); @@ -121,7 +120,7 @@ void GenericWaitBarrier::Cell::arm(int32_t requested_tag) { SpinYield sp; while (true) { - state = AtomicAccess::load_acquire(&_state); + state = _state.load_acquire(); assert(decode_tag(state) == 0, "Pre arm: Should not be armed. " "Tag: " INT32_FORMAT "; Waiters: " INT32_FORMAT, @@ -134,7 +133,7 @@ void GenericWaitBarrier::Cell::arm(int32_t requested_tag) { // Try to swing cell to armed. This should always succeed after the check above. int64_t new_state = encode(requested_tag, 0); - int64_t prev_state = AtomicAccess::cmpxchg(&_state, state, new_state); + int64_t prev_state = _state.compare_exchange(state, new_state); if (prev_state != state) { fatal("Cannot arm the wait barrier. " "Tag: " INT32_FORMAT "; Waiters: " INT32_FORMAT, @@ -145,14 +144,14 @@ void GenericWaitBarrier::Cell::arm(int32_t requested_tag) { int GenericWaitBarrier::Cell::signal_if_needed(int max) { int signals = 0; while (true) { - int cur = AtomicAccess::load_acquire(&_outstanding_wakeups); + int cur = _outstanding_wakeups.load_acquire(); if (cur == 0) { // All done, no more waiters. return 0; } assert(cur > 0, "Sanity"); - int prev = AtomicAccess::cmpxchg(&_outstanding_wakeups, cur, cur - 1); + int prev = _outstanding_wakeups.compare_exchange(cur, cur - 1); if (prev != cur) { // Contention, return to caller for early return or backoff. return prev; @@ -172,7 +171,7 @@ void GenericWaitBarrier::Cell::disarm(int32_t expected_tag) { int32_t waiters; while (true) { - int64_t state = AtomicAccess::load_acquire(&_state); + int64_t state = _state.load_acquire(); int32_t tag = decode_tag(state); waiters = decode_waiters(state); @@ -182,7 +181,7 @@ void GenericWaitBarrier::Cell::disarm(int32_t expected_tag) { tag, waiters); int64_t new_state = encode(0, waiters); - if (AtomicAccess::cmpxchg(&_state, state, new_state) == state) { + if (_state.compare_set(state, new_state)) { // Successfully disarmed. break; } @@ -191,19 +190,19 @@ void GenericWaitBarrier::Cell::disarm(int32_t expected_tag) { // Wake up waiters, if we have at least one. // Allow other threads to assist with wakeups, if possible. if (waiters > 0) { - AtomicAccess::release_store(&_outstanding_wakeups, waiters); + _outstanding_wakeups.release_store(waiters); SpinYield sp; while (signal_if_needed(INT_MAX) > 0) { sp.wait(); } } - assert(AtomicAccess::load(&_outstanding_wakeups) == 0, "Post disarm: Should not have outstanding wakeups"); + assert(_outstanding_wakeups.load_relaxed() == 0, "Post disarm: Should not have outstanding wakeups"); } void GenericWaitBarrier::Cell::wait(int32_t expected_tag) { // Try to register ourselves as pending waiter. while (true) { - int64_t state = AtomicAccess::load_acquire(&_state); + int64_t state = _state.load_acquire(); int32_t tag = decode_tag(state); if (tag != expected_tag) { // Cell tag had changed while waiting here. This means either the cell had @@ -219,7 +218,7 @@ void GenericWaitBarrier::Cell::wait(int32_t expected_tag) { tag, waiters); int64_t new_state = encode(tag, waiters + 1); - if (AtomicAccess::cmpxchg(&_state, state, new_state) == state) { + if (_state.compare_set(state, new_state)) { // Success! Proceed to wait. break; } @@ -238,7 +237,7 @@ void GenericWaitBarrier::Cell::wait(int32_t expected_tag) { // Register ourselves as completed waiter before leaving. while (true) { - int64_t state = AtomicAccess::load_acquire(&_state); + int64_t state = _state.load_acquire(); int32_t tag = decode_tag(state); int32_t waiters = decode_waiters(state); @@ -248,7 +247,7 @@ void GenericWaitBarrier::Cell::wait(int32_t expected_tag) { tag, waiters); int64_t new_state = encode(tag, waiters - 1); - if (AtomicAccess::cmpxchg(&_state, state, new_state) == state) { + if (_state.compare_set(state, new_state)) { // Success! break; } diff --git a/src/hotspot/share/utilities/waitBarrier_generic.hpp b/src/hotspot/share/utilities/waitBarrier_generic.hpp index 8ed9ef3ac6e..0cbba1041db 100644 --- a/src/hotspot/share/utilities/waitBarrier_generic.hpp +++ b/src/hotspot/share/utilities/waitBarrier_generic.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,6 +27,7 @@ #include "memory/allocation.hpp" #include "memory/padded.hpp" +#include "runtime/atomic.hpp" #include "runtime/semaphore.hpp" #include "utilities/globalDefinitions.hpp" @@ -43,10 +44,10 @@ private: Semaphore _sem; // Cell state, tracks the arming + waiters status - volatile int64_t _state; + Atomic _state; // Wakeups to deliver for current waiters - volatile int _outstanding_wakeups; + Atomic _outstanding_wakeups; int signal_if_needed(int max); @@ -83,7 +84,7 @@ private: // Trailing padding to protect the last cell. DEFINE_PAD_MINUS_SIZE(0, DEFAULT_PADDING_SIZE, 0); - volatile int _barrier_tag; + Atomic _barrier_tag; // Trailing padding to insulate the rest of the barrier from adjacent // data structures. The leading padding is not needed, as cell padding diff --git a/src/java.base/aix/classes/sun/nio/ch/AixPollPort.java b/src/java.base/aix/classes/sun/nio/ch/AixPollPort.java index b8610655b6c..1d2121cb50f 100644 --- a/src/java.base/aix/classes/sun/nio/ch/AixPollPort.java +++ b/src/java.base/aix/classes/sun/nio/ch/AixPollPort.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. * Copyright (c) 2012, 2024 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/src/java.base/linux/classes/jdk/internal/platform/cgroupv2/CgroupV2Subsystem.java b/src/java.base/linux/classes/jdk/internal/platform/cgroupv2/CgroupV2Subsystem.java index 3e3f637cd4c..be270bb79c3 100644 --- a/src/java.base/linux/classes/jdk/internal/platform/cgroupv2/CgroupV2Subsystem.java +++ b/src/java.base/linux/classes/jdk/internal/platform/cgroupv2/CgroupV2Subsystem.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2020, 2022, Red Hat Inc. - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/java.base/macosx/native/libjava/java_props_macosx.c b/src/java.base/macosx/native/libjava/java_props_macosx.c index 6656bf04efc..94749ee3efe 100644 --- a/src/java.base/macosx/native/libjava/java_props_macosx.c +++ b/src/java.base/macosx/native/libjava/java_props_macosx.c @@ -23,7 +23,6 @@ * questions. */ -#include #include #include #include @@ -230,50 +229,33 @@ void setOSNameAndVersion(java_props_t *sprops) { NSString *nsVerStr = NULL; char* osVersionCStr = NULL; NSOperatingSystemVersion osVer = [[NSProcessInfo processInfo] operatingSystemVersion]; - // Some macOS versions require special handling. For example, - // when the NSOperatingSystemVersion reports 10.16 as the version - // then it should be treated as 11. Similarly, when it reports 16.0 - // as the version then it should be treated as 26. - // If the SYSTEM_VERSION_COMPAT environment variable (a macOS construct) - // is set to 1, then we don't do any special handling for any versions - // and just literally use the value that NSOperatingSystemVersion reports. - const char* envVal = getenv("SYSTEM_VERSION_COMPAT"); - const bool versionCompatEnabled = envVal != NULL - && strncmp(envVal, "1", 1) == 0; - const bool requiresSpecialHandling = - ((long) osVer.majorVersion == 10 && (long) osVer.minorVersion >= 16) - || ((long) osVer.majorVersion == 16 && (long) osVer.minorVersion >= 0); - if (!requiresSpecialHandling || versionCompatEnabled) { - // no special handling - just use the version reported - // by NSOperatingSystemVersion - if (osVer.patchVersion == 0) { - // Omit trailing ".0" + // Copy out the char* if running on version other than 10.16 Mac OS (10.16 == 11.x) + // or explicitly requesting version compatibility + if (!((long)osVer.majorVersion == 10 && (long)osVer.minorVersion >= 16) || + (getenv("SYSTEM_VERSION_COMPAT") != NULL)) { + if (osVer.patchVersion == 0) { // Omit trailing ".0" nsVerStr = [NSString stringWithFormat:@"%ld.%ld", (long)osVer.majorVersion, (long)osVer.minorVersion]; } else { nsVerStr = [NSString stringWithFormat:@"%ld.%ld.%ld", - (long)osVer.majorVersion, (long)osVer.minorVersion, - (long)osVer.patchVersion]; + (long)osVer.majorVersion, (long)osVer.minorVersion, (long)osVer.patchVersion]; } } else { - // Requires special handling. We ignore the version reported - // by the NSOperatingSystemVersion API and instead read the - // *real* ProductVersion from - // /System/Library/CoreServices/.SystemVersionPlatform.plist. - // If not found there, then as a last resort we fallback to - // /System/Library/CoreServices/SystemVersion.plist - NSDictionary *version = [NSDictionary dictionaryWithContentsOfFile: - @"/System/Library/CoreServices/.SystemVersionPlatform.plist"]; + // Version 10.16, without explicit env setting of SYSTEM_VERSION_COMPAT + // AKA 11+ Read the *real* ProductVersion from the hidden link to avoid SYSTEM_VERSION_COMPAT + // If not found, fallback below to the SystemVersion.plist + NSDictionary *version = [NSDictionary dictionaryWithContentsOfFile : + @"/System/Library/CoreServices/.SystemVersionPlatform.plist"]; if (version != NULL) { - nsVerStr = [version objectForKey: @"ProductVersion"]; + nsVerStr = [version objectForKey : @"ProductVersion"]; } } - // Last resort - fallback to reading the SystemVersion.plist + // Fallback to reading the SystemVersion.plist if (nsVerStr == NULL) { - NSDictionary *version = [NSDictionary dictionaryWithContentsOfFile: - @"/System/Library/CoreServices/SystemVersion.plist"]; + NSDictionary *version = [NSDictionary dictionaryWithContentsOfFile : + @"/System/Library/CoreServices/SystemVersion.plist"]; if (version != NULL) { - nsVerStr = [version objectForKey: @"ProductVersion"]; + nsVerStr = [version objectForKey : @"ProductVersion"]; } } diff --git a/src/java.base/share/classes/com/sun/crypto/provider/AES_Crypt.java b/src/java.base/share/classes/com/sun/crypto/provider/AES_Crypt.java index 19dceae01af..27429f6a2fd 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/AES_Crypt.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/AES_Crypt.java @@ -54,11 +54,11 @@ final class AES_Crypt extends SymmetricCipher { private int rounds; private byte[] prevKey = null; - // Following two attributes are specific to Intrinsics where sessionK is - // used for PPC64, S390, and RISCV64 architectures, whereas K is used for - // everything else. - private int[][] sessionK = null; - private int[] K = null; + // Following attributes are specific to Intrinsics, where sessionKe is the + // unprocessed key that is also used for decryption on PPC64, S390 and + // RISCV64 architectures. Other ones use sessionKd for decryption. + private int[] sessionKe = null; // key for encryption + private int[] sessionKd = null; // preprocessed key for decryption // Round constant private static final int[] RCON = { @@ -904,7 +904,6 @@ final class AES_Crypt extends SymmetricCipher { */ void init(boolean decrypting, String algorithm, byte[] key) throws InvalidKeyException { - int decrypt = decrypting ? 1 : 0; if (!algorithm.equalsIgnoreCase("AES") && !algorithm.equalsIgnoreCase("Rijndael")) { @@ -920,21 +919,25 @@ final class AES_Crypt extends SymmetricCipher { throw new InvalidKeyException("Invalid key length (" + key.length + ")."); } + if (!MessageDigest.isEqual(prevKey, key)) { - if (sessionK == null) { - sessionK = new int[2][]; - } else { - Arrays.fill(sessionK[0], 0); - Arrays.fill(sessionK[1], 0); + if (sessionKe != null) { + Arrays.fill(sessionKe, 0); + } + sessionKe = genRoundKeys(key, rounds); + if (sessionKd != null) { + Arrays.fill(sessionKd, 0); + sessionKd = null; } - sessionK[0] = genRoundKeys(key, rounds); - sessionK[1] = genInvRoundKeys(sessionK[0], rounds); if (prevKey != null) { Arrays.fill(prevKey, (byte) 0); } prevKey = key.clone(); } - K = sessionK[decrypt]; + + if (decrypting && (sessionKd == null)) { + sessionKd = genInvRoundKeys(sessionKe, rounds); + } } /** @@ -1035,6 +1038,7 @@ final class AES_Crypt extends SymmetricCipher { */ @IntrinsicCandidate private void implEncryptBlock(byte[] p, int po, byte[] c, int co) { + int[] K = sessionKe; int ti0, ti1, ti2, ti3; int a0, a1, a2, a3; int w = K.length - WB; @@ -1213,6 +1217,7 @@ final class AES_Crypt extends SymmetricCipher { */ @IntrinsicCandidate private void implDecryptBlock(byte[] c, int co, byte[] p, int po) { + int[] K = sessionKd; int ti0, ti1, ti2, ti3; int a0, a1, a2, a3; diff --git a/src/java.base/share/classes/com/sun/crypto/provider/DHKEM.java b/src/java.base/share/classes/com/sun/crypto/provider/DHKEM.java index b27320ed24b..c7372a4c2c8 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/DHKEM.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/DHKEM.java @@ -26,26 +26,51 @@ package com.sun.crypto.provider; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.Serial; import java.math.BigInteger; -import java.security.*; -import java.security.interfaces.ECKey; +import java.security.AsymmetricKey; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.ProviderException; +import java.security.PublicKey; +import java.security.SecureRandom; import java.security.interfaces.ECPublicKey; -import java.security.interfaces.XECKey; import java.security.interfaces.XECPublicKey; -import java.security.spec.*; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPrivateKeySpec; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.security.spec.NamedParameterSpec; +import java.security.spec.XECPrivateKeySpec; +import java.security.spec.XECPublicKeySpec; import java.util.Arrays; import java.util.Objects; -import javax.crypto.*; -import javax.crypto.spec.SecretKeySpec; +import javax.crypto.DecapsulateException; +import javax.crypto.KDF; +import javax.crypto.KEM; +import javax.crypto.KEMSpi; +import javax.crypto.KeyAgreement; +import javax.crypto.SecretKey; import javax.crypto.spec.HKDFParameterSpec; +import javax.crypto.spec.SecretKeySpec; import sun.security.jca.JCAUtil; -import sun.security.util.*; - -import jdk.internal.access.SharedSecrets; +import sun.security.util.ArrayUtil; +import sun.security.util.CurveDB; +import sun.security.util.ECUtil; +import sun.security.util.InternalPrivateKey; +import sun.security.util.NamedCurve; +import sun.security.util.SliceableSecretKey; // Implementing DHKEM defined inside https://www.rfc-editor.org/rfc/rfc9180.html, -// without the AuthEncap and AuthDecap functions public class DHKEM implements KEMSpi { private static final byte[] KEM = new byte[] @@ -65,80 +90,86 @@ public class DHKEM implements KEMSpi { private static final byte[] EMPTY = new byte[0]; private record Handler(Params params, SecureRandom secureRandom, - PrivateKey skR, PublicKey pkR) + PrivateKey skS, PublicKey pkS, // sender keys + PrivateKey skR, PublicKey pkR) // receiver keys implements EncapsulatorSpi, DecapsulatorSpi { @Override public KEM.Encapsulated engineEncapsulate(int from, int to, String algorithm) { - Objects.checkFromToIndex(from, to, params.Nsecret); + Objects.checkFromToIndex(from, to, params.nsecret); Objects.requireNonNull(algorithm, "null algorithm"); KeyPair kpE = params.generateKeyPair(secureRandom); PrivateKey skE = kpE.getPrivate(); PublicKey pkE = kpE.getPublic(); - byte[] pkEm = params.SerializePublicKey(pkE); - byte[] pkRm = params.SerializePublicKey(pkR); - byte[] kem_context = concat(pkEm, pkRm); - byte[] key = null; + byte[] pkEm = params.serializePublicKey(pkE); + byte[] pkRm = params.serializePublicKey(pkR); try { - byte[] dh = params.DH(skE, pkR); - key = params.ExtractAndExpand(dh, kem_context); - return new KEM.Encapsulated( - new SecretKeySpec(key, from, to - from, algorithm), - pkEm, null); + SecretKey key; + if (skS == null) { + byte[] kem_context = concat(pkEm, pkRm); + key = params.deriveKey(algorithm, from, to, kem_context, + params.dh(skE, pkR)); + } else { + byte[] pkSm = params.serializePublicKey(pkS); + byte[] kem_context = concat(pkEm, pkRm, pkSm); + key = params.deriveKey(algorithm, from, to, kem_context, + params.dh(skE, pkR), params.dh(skS, pkR)); + } + return new KEM.Encapsulated(key, pkEm, null); + } catch (UnsupportedOperationException e) { + throw e; } catch (Exception e) { throw new ProviderException("internal error", e); - } finally { - // `key` has been cloned into the `SecretKeySpec` within the - // returned `KEM.Encapsulated`, so it can now be cleared. - if (key != null) { - Arrays.fill(key, (byte)0); - } } } @Override public SecretKey engineDecapsulate(byte[] encapsulation, int from, int to, String algorithm) throws DecapsulateException { - Objects.checkFromToIndex(from, to, params.Nsecret); + Objects.checkFromToIndex(from, to, params.nsecret); Objects.requireNonNull(algorithm, "null algorithm"); Objects.requireNonNull(encapsulation, "null encapsulation"); - if (encapsulation.length != params.Npk) { + if (encapsulation.length != params.npk) { throw new DecapsulateException("incorrect encapsulation size"); } - byte[] key = null; try { - PublicKey pkE = params.DeserializePublicKey(encapsulation); - byte[] dh = params.DH(skR, pkE); - byte[] pkRm = params.SerializePublicKey(pkR); - byte[] kem_context = concat(encapsulation, pkRm); - key = params.ExtractAndExpand(dh, kem_context); - return new SecretKeySpec(key, from, to - from, algorithm); + PublicKey pkE = params.deserializePublicKey(encapsulation); + byte[] pkRm = params.serializePublicKey(pkR); + if (pkS == null) { + byte[] kem_context = concat(encapsulation, pkRm); + return params.deriveKey(algorithm, from, to, kem_context, + params.dh(skR, pkE)); + } else { + byte[] pkSm = params.serializePublicKey(pkS); + byte[] kem_context = concat(encapsulation, pkRm, pkSm); + return params.deriveKey(algorithm, from, to, kem_context, + params.dh(skR, pkE), params.dh(skR, pkS)); + } + } catch (UnsupportedOperationException e) { + throw e; } catch (IOException | InvalidKeyException e) { throw new DecapsulateException("Cannot decapsulate", e); } catch (Exception e) { throw new ProviderException("internal error", e); - } finally { - if (key != null) { - Arrays.fill(key, (byte)0); - } } } @Override public int engineSecretSize() { - return params.Nsecret; + return params.nsecret; } @Override public int engineEncapsulationSize() { - return params.Npk; + return params.npk; } } // Not really a random. For KAT test only. It generates key pair from ikm. public static class RFC9180DeriveKeyPairSR extends SecureRandom { - static final long serialVersionUID = 0L; + @Serial + private static final long serialVersionUID = 0L; private final byte[] ikm; @@ -147,7 +178,7 @@ public class DHKEM implements KEMSpi { this.ikm = ikm; } - public KeyPair derive(Params params) { + private KeyPair derive(Params params) { try { return params.deriveKeyPair(ikm); } catch (Exception e) { @@ -183,9 +214,9 @@ public class DHKEM implements KEMSpi { ; private final int kem_id; - private final int Nsecret; - private final int Nsk; - private final int Npk; + private final int nsecret; + private final int nsk; + private final int npk; private final String kaAlgorithm; private final String keyAlgorithm; private final AlgorithmParameterSpec spec; @@ -193,18 +224,18 @@ public class DHKEM implements KEMSpi { private final byte[] suiteId; - Params(int kem_id, int Nsecret, int Nsk, int Npk, + Params(int kem_id, int nsecret, int nsk, int npk, String kaAlgorithm, String keyAlgorithm, AlgorithmParameterSpec spec, String hkdfAlgorithm) { this.kem_id = kem_id; this.spec = spec; - this.Nsecret = Nsecret; - this.Nsk = Nsk; - this.Npk = Npk; + this.nsecret = nsecret; + this.nsk = nsk; + this.npk = npk; this.kaAlgorithm = kaAlgorithm; this.keyAlgorithm = keyAlgorithm; this.hkdfAlgorithm = hkdfAlgorithm; - suiteId = concat(KEM, I2OSP(kem_id, 2)); + suiteId = concat(KEM, i2OSP(kem_id, 2)); } private boolean isEC() { @@ -224,18 +255,18 @@ public class DHKEM implements KEMSpi { } } - private byte[] SerializePublicKey(PublicKey k) { + private byte[] serializePublicKey(PublicKey k) { if (isEC()) { ECPoint w = ((ECPublicKey) k).getW(); return ECUtil.encodePoint(w, ((NamedCurve) spec).getCurve()); } else { byte[] uArray = ((XECPublicKey) k).getU().toByteArray(); ArrayUtil.reverse(uArray); - return Arrays.copyOf(uArray, Npk); + return Arrays.copyOf(uArray, npk); } } - private PublicKey DeserializePublicKey(byte[] data) + private PublicKey deserializePublicKey(byte[] data) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { KeySpec keySpec; if (isEC()) { @@ -251,29 +282,59 @@ public class DHKEM implements KEMSpi { return KeyFactory.getInstance(keyAlgorithm).generatePublic(keySpec); } - private byte[] DH(PrivateKey skE, PublicKey pkR) + private SecretKey dh(PrivateKey skE, PublicKey pkR) throws NoSuchAlgorithmException, InvalidKeyException { KeyAgreement ka = KeyAgreement.getInstance(kaAlgorithm); ka.init(skE); ka.doPhase(pkR, true); - return ka.generateSecret(); + return ka.generateSecret("Generic"); } - private byte[] ExtractAndExpand(byte[] dh, byte[] kem_context) - throws NoSuchAlgorithmException, InvalidKeyException { - KDF hkdf = KDF.getInstance(hkdfAlgorithm); - SecretKey eae_prk = LabeledExtract(hkdf, suiteId, EAE_PRK, dh); - try { - return LabeledExpand(hkdf, suiteId, eae_prk, SHARED_SECRET, - kem_context, Nsecret); - } finally { - if (eae_prk instanceof SecretKeySpec s) { - SharedSecrets.getJavaxCryptoSpecAccess() - .clearSecretKeySpec(s); + // The final shared secret derivation of either the encapsulator + // or the decapsulator. The key slicing is implemented inside. + // Throws UOE if a slice of the key cannot be found. + private SecretKey deriveKey(String alg, int from, int to, + byte[] kem_context, SecretKey... dhs) + throws NoSuchAlgorithmException { + if (from == 0 && to == nsecret) { + return extractAndExpand(kem_context, alg, dhs); + } else { + // First get shared secrets in "Generic" and then get a slice + // of it in the requested algorithm. + var fullKey = extractAndExpand(kem_context, "Generic", dhs); + if ("RAW".equalsIgnoreCase(fullKey.getFormat())) { + byte[] km = fullKey.getEncoded(); + if (km == null) { + // Should not happen if format is "RAW" + throw new UnsupportedOperationException("Key extract failed"); + } else { + try { + return new SecretKeySpec(km, from, to - from, alg); + } finally { + Arrays.fill(km, (byte)0); + } + } + } else if (fullKey instanceof SliceableSecretKey ssk) { + return ssk.slice(alg, from, to); + } else { + throw new UnsupportedOperationException("Cannot extract key"); } } } + private SecretKey extractAndExpand(byte[] kem_context, String alg, SecretKey... dhs) + throws NoSuchAlgorithmException { + var kdf = KDF.getInstance(hkdfAlgorithm); + var builder = labeledExtract(suiteId, EAE_PRK); + for (var dh : dhs) builder.addIKM(dh); + try { + return kdf.deriveKey(alg, + labeledExpand(builder, suiteId, SHARED_SECRET, kem_context, nsecret)); + } catch (InvalidAlgorithmParameterException e) { + throw new ProviderException(e); + } + } + private PublicKey getPublicKey(PrivateKey sk) throws InvalidKeyException { if (!(sk instanceof InternalPrivateKey)) { @@ -298,45 +359,37 @@ public class DHKEM implements KEMSpi { // For KAT tests only. See RFC9180DeriveKeyPairSR. public KeyPair deriveKeyPair(byte[] ikm) throws Exception { - KDF hkdf = KDF.getInstance(hkdfAlgorithm); - SecretKey dkp_prk = LabeledExtract(hkdf, suiteId, DKP_PRK, ikm); - try { - if (isEC()) { - NamedCurve curve = (NamedCurve) spec; - BigInteger sk = BigInteger.ZERO; - int counter = 0; - while (sk.signum() == 0 || - sk.compareTo(curve.getOrder()) >= 0) { - if (counter > 255) { - throw new RuntimeException(); - } - byte[] bytes = LabeledExpand(hkdf, suiteId, dkp_prk, - CANDIDATE, I2OSP(counter, 1), Nsk); - // bitmask is defined to be 0xFF for P-256 and P-384, - // and 0x01 for P-521 - if (this == Params.P521) { - bytes[0] = (byte) (bytes[0] & 0x01); - } - sk = new BigInteger(1, (bytes)); - counter = counter + 1; + var kdf = KDF.getInstance(hkdfAlgorithm); + var builder = labeledExtract(suiteId, DKP_PRK).addIKM(ikm); + if (isEC()) { + NamedCurve curve = (NamedCurve) spec; + BigInteger sk = BigInteger.ZERO; + int counter = 0; + while (sk.signum() == 0 || sk.compareTo(curve.getOrder()) >= 0) { + if (counter > 255) { + // So unlucky and should not happen + throw new ProviderException("DeriveKeyPairError"); } - PrivateKey k = DeserializePrivateKey(sk.toByteArray()); - return new KeyPair(getPublicKey(k), k); - } else { - byte[] sk = LabeledExpand(hkdf, suiteId, dkp_prk, SK, EMPTY, - Nsk); - PrivateKey k = DeserializePrivateKey(sk); - return new KeyPair(getPublicKey(k), k); - } - } finally { - if (dkp_prk instanceof SecretKeySpec s) { - SharedSecrets.getJavaxCryptoSpecAccess() - .clearSecretKeySpec(s); + byte[] bytes = kdf.deriveData(labeledExpand(builder, + suiteId, CANDIDATE, i2OSP(counter, 1), nsk)); + // bitmask is defined to be 0xFF for P-256 and P-384, and 0x01 for P-521 + if (this == Params.P521) { + bytes[0] = (byte) (bytes[0] & 0x01); + } + sk = new BigInteger(1, (bytes)); + counter = counter + 1; } + PrivateKey k = deserializePrivateKey(sk.toByteArray()); + return new KeyPair(getPublicKey(k), k); + } else { + byte[] sk = kdf.deriveData(labeledExpand(builder, + suiteId, SK, EMPTY, nsk)); + PrivateKey k = deserializePrivateKey(sk); + return new KeyPair(getPublicKey(k), k); } } - private PrivateKey DeserializePrivateKey(byte[] data) throws Exception { + private PrivateKey deserializePrivateKey(byte[] data) throws Exception { KeySpec keySpec = isEC() ? new ECPrivateKeySpec(new BigInteger(1, (data)), (NamedCurve) spec) : new XECPrivateKeySpec(spec, data); @@ -359,7 +412,22 @@ public class DHKEM implements KEMSpi { throw new InvalidAlgorithmParameterException("no spec needed"); } Params params = paramsFromKey(pk); - return new Handler(params, getSecureRandom(secureRandom), null, pk); + return new Handler(params, getSecureRandom(secureRandom), null, null, null, pk); + } + + // AuthEncap is not public KEM API + public EncapsulatorSpi engineNewAuthEncapsulator(PublicKey pkR, PrivateKey skS, + AlgorithmParameterSpec spec, SecureRandom secureRandom) + throws InvalidAlgorithmParameterException, InvalidKeyException { + if (pkR == null || skS == null) { + throw new InvalidKeyException("input key is null"); + } + if (spec != null) { + throw new InvalidAlgorithmParameterException("no spec needed"); + } + Params params = paramsFromKey(pkR); + return new Handler(params, getSecureRandom(secureRandom), + skS, params.getPublicKey(skS), null, pkR); } @Override @@ -372,20 +440,34 @@ public class DHKEM implements KEMSpi { throw new InvalidAlgorithmParameterException("no spec needed"); } Params params = paramsFromKey(sk); - return new Handler(params, null, sk, params.getPublicKey(sk)); + return new Handler(params, null, null, null, sk, params.getPublicKey(sk)); } - private Params paramsFromKey(Key k) throws InvalidKeyException { - if (k instanceof ECKey eckey) { - if (ECUtil.equals(eckey.getParams(), CurveDB.P_256)) { + // AuthDecap is not public KEM API + public DecapsulatorSpi engineNewAuthDecapsulator( + PrivateKey skR, PublicKey pkS, AlgorithmParameterSpec spec) + throws InvalidAlgorithmParameterException, InvalidKeyException { + if (skR == null || pkS == null) { + throw new InvalidKeyException("input key is null"); + } + if (spec != null) { + throw new InvalidAlgorithmParameterException("no spec needed"); + } + Params params = paramsFromKey(skR); + return new Handler(params, null, null, pkS, skR, params.getPublicKey(skR)); + } + + private Params paramsFromKey(AsymmetricKey k) throws InvalidKeyException { + var p = k.getParams(); + if (p instanceof ECParameterSpec ecp) { + if (ECUtil.equals(ecp, CurveDB.P_256)) { return Params.P256; - } else if (ECUtil.equals(eckey.getParams(), CurveDB.P_384)) { + } else if (ECUtil.equals(ecp, CurveDB.P_384)) { return Params.P384; - } else if (ECUtil.equals(eckey.getParams(), CurveDB.P_521)) { + } else if (ECUtil.equals(ecp, CurveDB.P_521)) { return Params.P521; } - } else if (k instanceof XECKey xkey - && xkey.getParams() instanceof NamedParameterSpec ns) { + } else if (p instanceof NamedParameterSpec ns) { if (ns.getName().equalsIgnoreCase("X25519")) { return Params.X25519; } else if (ns.getName().equalsIgnoreCase("X448")) { @@ -401,8 +483,11 @@ public class DHKEM implements KEMSpi { return o.toByteArray(); } - private static byte[] I2OSP(int n, int w) { - assert n < 256; + // I2OSP(n, w) as defined in RFC 9180 Section 3. + // In DHKEM and HPKE, number is always <65536 + // and converted to at most 2 bytes. + public static byte[] i2OSP(int n, int w) { + assert n < 65536; assert w == 1 || w == 2; if (w == 1) { return new byte[] { (byte) n }; @@ -411,32 +496,32 @@ public class DHKEM implements KEMSpi { } } - private static SecretKey LabeledExtract(KDF hkdf, byte[] suite_id, - byte[] label, byte[] ikm) throws InvalidKeyException { - SecretKeySpec s = new SecretKeySpec(concat(HPKE_V1, suite_id, label, - ikm), "IKM"); - try { - HKDFParameterSpec spec = - HKDFParameterSpec.ofExtract().addIKM(s).extractOnly(); - return hkdf.deriveKey("Generic", spec); - } catch (InvalidAlgorithmParameterException | - NoSuchAlgorithmException e) { - throw new InvalidKeyException(e.getMessage(), e); - } finally { - SharedSecrets.getJavaxCryptoSpecAccess().clearSecretKeySpec(s); - } + // Create a LabeledExtract builder with labels. + // You can add more IKM and salt into the result. + public static HKDFParameterSpec.Builder labeledExtract( + byte[] suiteId, byte[] label) { + return HKDFParameterSpec.ofExtract() + .addIKM(HPKE_V1).addIKM(suiteId).addIKM(label); } - private static byte[] LabeledExpand(KDF hkdf, byte[] suite_id, - SecretKey prk, byte[] label, byte[] info, int L) - throws InvalidKeyException { - byte[] labeled_info = concat(I2OSP(L, 2), HPKE_V1, suite_id, label, - info); - try { - return hkdf.deriveData(HKDFParameterSpec.expandOnly( - prk, labeled_info, L)); - } catch (InvalidAlgorithmParameterException iape) { - throw new InvalidKeyException(iape.getMessage(), iape); - } + // Create a labeled info from info and labels + private static byte[] labeledInfo( + byte[] suiteId, byte[] label, byte[] info, int length) { + return concat(i2OSP(length, 2), HPKE_V1, suiteId, label, info); + } + + // LabeledExpand from a builder + public static HKDFParameterSpec labeledExpand( + HKDFParameterSpec.Builder builder, + byte[] suiteId, byte[] label, byte[] info, int length) { + return builder.thenExpand( + labeledInfo(suiteId, label, info, length), length); + } + + // LabeledExpand from a prk + public static HKDFParameterSpec labeledExpand( + SecretKey prk, byte[] suiteId, byte[] label, byte[] info, int length) { + return HKDFParameterSpec.expandOnly( + prk, labeledInfo(suiteId, label, info, length), length); } } diff --git a/src/java.base/share/classes/com/sun/crypto/provider/HPKE.java b/src/java.base/share/classes/com/sun/crypto/provider/HPKE.java new file mode 100644 index 00000000000..eee5f59cc75 --- /dev/null +++ b/src/java.base/share/classes/com/sun/crypto/provider/HPKE.java @@ -0,0 +1,588 @@ +/* + * 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 com.sun.crypto.provider; + +import sun.security.util.CurveDB; +import sun.security.util.ECUtil; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.CipherSpi; +import javax.crypto.DecapsulateException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.KDF; +import javax.crypto.KEM; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.ShortBufferException; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.HPKEParameterSpec; +import javax.crypto.spec.IvParameterSpec; +import java.io.ByteArrayOutputStream; +import java.nio.ByteBuffer; +import java.security.AlgorithmParameters; +import java.security.AsymmetricKey; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.ProviderException; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.NamedParameterSpec; +import java.util.Arrays; + +public class HPKE extends CipherSpi { + + private static final byte[] HPKE = new byte[] + {'H', 'P', 'K', 'E'}; + private static final byte[] SEC = new byte[] + {'s', 'e', 'c'}; + private static final byte[] PSK_ID_HASH = new byte[] + {'p', 's', 'k', '_', 'i', 'd', '_', 'h', 'a', 's', 'h'}; + private static final byte[] INFO_HASH = new byte[] + {'i', 'n', 'f', 'o', '_', 'h', 'a', 's', 'h'}; + private static final byte[] SECRET = new byte[] + {'s', 'e', 'c', 'r', 'e', 't'}; + private static final byte[] EXP = new byte[] + {'e', 'x', 'p'}; + private static final byte[] KEY = new byte[] + {'k', 'e', 'y'}; + private static final byte[] BASE_NONCE = new byte[] + {'b', 'a', 's', 'e', '_', 'n', 'o', 'n', 'c', 'e'}; + + private static final int BEGIN = 1; + private static final int EXPORT_ONLY = 2; // init done with aead_id == 65535 + private static final int ENCRYPT_AND_EXPORT = 3; // int done with AEAD + private static final int AFTER_FINAL = 4; // after doFinal, need reinit internal cipher + + private int state = BEGIN; + private Impl impl; + + @Override + protected void engineSetMode(String mode) throws NoSuchAlgorithmException { + throw new NoSuchAlgorithmException(mode); + } + + @Override + protected void engineSetPadding(String padding) throws NoSuchPaddingException { + throw new NoSuchPaddingException(padding); + } + + @Override + protected int engineGetBlockSize() { + if (state == ENCRYPT_AND_EXPORT || state == AFTER_FINAL) { + return impl.aead.cipher.getBlockSize(); + } else { + return 0; + } + } + + @Override + protected int engineGetOutputSize(int inputLen) { + if (state == ENCRYPT_AND_EXPORT || state == AFTER_FINAL) { + return impl.aead.cipher.getOutputSize(inputLen); + } else { + return 0; + } + } + + @Override + protected byte[] engineGetIV() { + return (state == BEGIN || impl.kemEncaps == null) + ? null : impl.kemEncaps.clone(); + } + + @Override + protected AlgorithmParameters engineGetParameters() { + return null; + } + + @Override + protected void engineInit(int opmode, Key key, SecureRandom random) + throws InvalidKeyException { + throw new InvalidKeyException("HPKEParameterSpec must be provided"); + } + + @Override + protected void engineInit(int opmode, Key key, + AlgorithmParameterSpec params, SecureRandom random) + throws InvalidKeyException, InvalidAlgorithmParameterException { + impl = new Impl(opmode); + if (!(key instanceof AsymmetricKey ak)) { + throw new InvalidKeyException("Not an asymmetric key"); + } + if (params == null) { + throw new InvalidAlgorithmParameterException( + "HPKEParameterSpec must be provided"); + } else if (params instanceof HPKEParameterSpec hps) { + impl.init(ak, hps, random); + } else { + throw new InvalidAlgorithmParameterException( + "Unsupported params type: " + params.getClass()); + } + if (impl.hasEncrypt()) { + impl.aead.start(impl.opmode, impl.context.k, impl.context.computeNonce()); + state = ENCRYPT_AND_EXPORT; + } else { + state = EXPORT_ONLY; + } + } + + @Override + protected void engineInit(int opmode, Key key, + AlgorithmParameters params, SecureRandom random) + throws InvalidKeyException, InvalidAlgorithmParameterException { + throw new InvalidKeyException("HPKEParameterSpec must be provided"); + } + + // state is ENCRYPT_AND_EXPORT after this call succeeds + private void maybeReinitInternalCipher() { + if (state == BEGIN) { + throw new IllegalStateException("Illegal state: " + state); + } + if (state == EXPORT_ONLY) { + throw new UnsupportedOperationException(); + } + if (state == AFTER_FINAL) { + impl.aead.start(impl.opmode, impl.context.k, impl.context.computeNonce()); + state = ENCRYPT_AND_EXPORT; + } + } + + @Override + protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) { + maybeReinitInternalCipher(); + return impl.aead.cipher.update(input, inputOffset, inputLen); + } + + @Override + protected int engineUpdate(byte[] input, int inputOffset, int inputLen, + byte[] output, int outputOffset) throws ShortBufferException { + maybeReinitInternalCipher(); + return impl.aead.cipher.update( + input, inputOffset, inputLen, output, outputOffset); + } + + @Override + protected void engineUpdateAAD(byte[] src, int offset, int len) { + maybeReinitInternalCipher(); + impl.aead.cipher.updateAAD(src, offset, len); + } + + @Override + protected void engineUpdateAAD(ByteBuffer src) { + maybeReinitInternalCipher(); + impl.aead.cipher.updateAAD(src); + } + + @Override + protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen) + throws IllegalBlockSizeException, BadPaddingException { + maybeReinitInternalCipher(); + impl.context.IncrementSeq(); + state = AFTER_FINAL; + if (input == null) { // a bug in doFinal(null, ?, ?) + return impl.aead.cipher.doFinal(); + } else { + return impl.aead.cipher.doFinal(input, inputOffset, inputLen); + } + } + + @Override + protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, + byte[] output, int outputOffset) throws ShortBufferException, + IllegalBlockSizeException, BadPaddingException { + maybeReinitInternalCipher(); + impl.context.IncrementSeq(); + state = AFTER_FINAL; + return impl.aead.cipher.doFinal( + input, inputOffset, inputLen, output, outputOffset); + } + + //@Override + protected SecretKey engineExportKey(String algorithm, byte[] context, int length) { + if (state == BEGIN) { + throw new IllegalStateException("State: " + state); + } else { + return impl.context.exportKey(algorithm, context, length); + } + } + + //@Override + protected byte[] engineExportData(byte[] context, int length) { + if (state == BEGIN) { + throw new IllegalStateException("State: " + state); + } else { + return impl.context.exportData(context, length); + } + } + + private static class AEAD { + final Cipher cipher; + final int nk, nn, nt; + final int id; + public AEAD(int id) throws InvalidAlgorithmParameterException { + this.id = id; + try { + switch (id) { + case HPKEParameterSpec.AEAD_AES_128_GCM -> { + cipher = Cipher.getInstance("AES/GCM/NoPadding"); + nk = 16; + } + case HPKEParameterSpec.AEAD_AES_256_GCM -> { + cipher = Cipher.getInstance("AES/GCM/NoPadding"); + nk = 32; + } + case HPKEParameterSpec.AEAD_CHACHA20_POLY1305 -> { + cipher = Cipher.getInstance("ChaCha20-Poly1305"); + nk = 32; + } + case HPKEParameterSpec.EXPORT_ONLY -> { + cipher = null; + nk = -1; + } + default -> throw new InvalidAlgorithmParameterException( + "Unknown aead_id: " + id); + } + } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { + throw new ProviderException("Internal error", e); + } + nn = 12; nt = 16; + } + + void start(int opmode, SecretKey key, byte[] nonce) { + try { + if (id == HPKEParameterSpec.AEAD_CHACHA20_POLY1305) { + cipher.init(opmode, key, new IvParameterSpec(nonce)); + } else { + cipher.init(opmode, key, new GCMParameterSpec(nt * 8, nonce)); + } + } catch (InvalidAlgorithmParameterException | InvalidKeyException e) { + throw new ProviderException("Internal error", e); + } + } + } + + private static class Impl { + + final int opmode; + + HPKEParameterSpec params; + Context context; + AEAD aead; + + byte[] suite_id; + String kdfAlg; + int kdfNh; + + // only used on sender side + byte[] kemEncaps; + + class Context { + final SecretKey k; // null if only export + final byte[] base_nonce; + final SecretKey exporter_secret; + + byte[] seq = new byte[aead.nn]; + + public Context(SecretKey sk, byte[] base_nonce, + SecretKey exporter_secret) { + this.k = sk; + this.base_nonce = base_nonce; + this.exporter_secret = exporter_secret; + } + + SecretKey exportKey(String algorithm, byte[] exporter_context, int length) { + if (exporter_context == null) { + throw new IllegalArgumentException("Null exporter_context"); + } + try { + var kdf = KDF.getInstance(kdfAlg); + return kdf.deriveKey(algorithm, DHKEM.labeledExpand( + exporter_secret, suite_id, SEC, exporter_context, length)); + } catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException e) { + // algorithm not accepted by HKDF, length too big or too small + throw new IllegalArgumentException("Invalid input", e); + } + } + + byte[] exportData(byte[] exporter_context, int length) { + if (exporter_context == null) { + throw new IllegalArgumentException("Null exporter_context"); + } + try { + var kdf = KDF.getInstance(kdfAlg); + return kdf.deriveData(DHKEM.labeledExpand( + exporter_secret, suite_id, SEC, exporter_context, length)); + } catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException e) { + // algorithm not accepted by HKDF, length too big or too small + throw new IllegalArgumentException("Invalid input", e); + } + } + + private byte[] computeNonce() { + var result = new byte[aead.nn]; + for (var i = 0; i < result.length; i++) { + result[i] = (byte)(seq[i] ^ base_nonce[i]); + } + return result; + } + + private void IncrementSeq() { + for (var i = seq.length - 1; i >= 0; i--) { + if ((seq[i] & 0xff) == 0xff) { + seq[i] = 0; + } else { + seq[i]++; + return; + } + } + // seq >= (1 << (8*aead.Nn)) - 1 when this method is called + throw new ProviderException("MessageLimitReachedError"); + } + } + + public Impl(int opmode) { + this.opmode = opmode; + } + + public boolean hasEncrypt() { + return params.aead_id() != 65535; + } + + // Section 7.2.1 of RFC 9180 has restrictions on size of psk, psk_id, + // info, and exporter_context (~2^61 for HMAC-SHA256 and ~2^125 for + // HMAC-SHA384 and HMAC-SHA512). This method does not pose any + // restrictions. + public void init(AsymmetricKey key, HPKEParameterSpec p, SecureRandom rand) + throws InvalidKeyException, InvalidAlgorithmParameterException { + if (opmode != Cipher.ENCRYPT_MODE && opmode != Cipher.DECRYPT_MODE) { + throw new UnsupportedOperationException( + "Can only be used for encryption and decryption"); + } + setParams(p); + SecretKey shared_secret; + if (opmode == Cipher.ENCRYPT_MODE) { + if (!(key instanceof PublicKey pk)) { + throw new InvalidKeyException( + "Cannot encrypt with private key"); + } + if (p.encapsulation() != null) { + throw new InvalidAlgorithmParameterException( + "Must not provide key encapsulation message on sender side"); + } + checkMatch(false, pk, params.kem_id()); + KEM.Encapsulated enc; + switch (p.authKey()) { + case null -> { + var e = kem().newEncapsulator(pk, rand); + enc = e.encapsulate(); + } + case PrivateKey skS -> { + checkMatch(true, skS, params.kem_id()); + // AuthEncap not public KEM API but it's internally supported + var e = new DHKEM().engineNewAuthEncapsulator(pk, skS, null, rand); + enc = e.engineEncapsulate(0, e.engineSecretSize(), "Generic"); + } + default -> throw new InvalidAlgorithmParameterException( + "Cannot auth with public key"); + } + kemEncaps = enc.encapsulation(); + shared_secret = enc.key(); + } else { + if (!(key instanceof PrivateKey sk)) { + throw new InvalidKeyException("Cannot decrypt with public key"); + } + checkMatch(false, sk, params.kem_id()); + try { + var encap = p.encapsulation(); + if (encap == null) { + throw new InvalidAlgorithmParameterException( + "Must provide key encapsulation message on recipient side"); + } + switch (p.authKey()) { + case null -> { + var d = kem().newDecapsulator(sk); + shared_secret = d.decapsulate(encap); + } + case PublicKey pkS -> { + checkMatch(true, pkS, params.kem_id()); + // AuthDecap not public KEM API but it's internally supported + var d = new DHKEM().engineNewAuthDecapsulator(sk, pkS, null); + shared_secret = d.engineDecapsulate( + encap, 0, d.engineSecretSize(), "Generic"); + } + default -> throw new InvalidAlgorithmParameterException( + "Cannot auth with private key"); + } + } catch (DecapsulateException e) { + throw new InvalidAlgorithmParameterException(e); + } + } + + var usePSK = usePSK(params.psk()); + int mode = params.authKey() == null ? (usePSK ? 1 : 0) : (usePSK ? 3 : 2); + context = keySchedule(mode, shared_secret, + params.info(), + params.psk(), + params.psk_id()); + } + + private static void checkMatch(boolean inSpec, AsymmetricKey k, int kem_id) + throws InvalidKeyException, InvalidAlgorithmParameterException { + var p = k.getParams(); + switch (p) { + case ECParameterSpec ecp -> { + if ((!ECUtil.equals(ecp, CurveDB.P_256) + || kem_id != HPKEParameterSpec.KEM_DHKEM_P_256_HKDF_SHA256) + && (!ECUtil.equals(ecp, CurveDB.P_384) + || kem_id != HPKEParameterSpec.KEM_DHKEM_P_384_HKDF_SHA384) + && (!ECUtil.equals(ecp, CurveDB.P_521) + || kem_id != HPKEParameterSpec.KEM_DHKEM_P_521_HKDF_SHA512)) { + var name = ECUtil.getCurveName(ecp); + throw new InvalidAlgorithmParameterException( + name + " does not match " + kem_id); + } + } + case NamedParameterSpec ns -> { + var name = ns.getName(); + if ((!name.equalsIgnoreCase("x25519") + || kem_id != HPKEParameterSpec.KEM_DHKEM_X25519_HKDF_SHA256) + && (!name.equalsIgnoreCase("x448") + || kem_id != HPKEParameterSpec.KEM_DHKEM_X448_HKDF_SHA512)) { + throw new InvalidAlgorithmParameterException( + name + " does not match " + kem_id); + } + } + case null, default -> { + var msg = k.getClass() + " does not match " + kem_id; + if (inSpec) { + throw new InvalidAlgorithmParameterException(msg); + } else { + throw new InvalidKeyException(msg); + } + } + } + } + + private KEM kem() { + try { + return KEM.getInstance("DHKEM"); + } catch (NoSuchAlgorithmException e) { + throw new ProviderException("Internal error", e); + } + } + + private void setParams(HPKEParameterSpec p) + throws InvalidAlgorithmParameterException { + params = p; + suite_id = concat( + HPKE, + DHKEM.i2OSP(params.kem_id(), 2), + DHKEM.i2OSP(params.kdf_id(), 2), + DHKEM.i2OSP(params.aead_id(), 2)); + switch (params.kdf_id()) { + case HPKEParameterSpec.KDF_HKDF_SHA256 -> { + kdfAlg = "HKDF-SHA256"; + kdfNh = 32; + } + case HPKEParameterSpec.KDF_HKDF_SHA384 -> { + kdfAlg = "HKDF-SHA384"; + kdfNh = 48; + } + case HPKEParameterSpec.KDF_HKDF_SHA512 -> { + kdfAlg = "HKDF-SHA512"; + kdfNh = 64; + } + default -> throw new InvalidAlgorithmParameterException( + "Unsupported kdf_id: " + params.kdf_id()); + } + aead = new AEAD(params.aead_id()); + } + + private Context keySchedule(int mode, + SecretKey shared_secret, + byte[] info, + SecretKey psk, + byte[] psk_id) { + try { + var psk_id_hash_x = DHKEM.labeledExtract(suite_id, PSK_ID_HASH) + .addIKM(psk_id).extractOnly(); + var info_hash_x = DHKEM.labeledExtract(suite_id, INFO_HASH) + .addIKM(info).extractOnly(); + + // deriveData must and can be called because all info to + // thw builder are just byte arrays. Any KDF impl can handle this. + var kdf = KDF.getInstance(kdfAlg); + var key_schedule_context = concat(new byte[]{(byte) mode}, + kdf.deriveData(psk_id_hash_x), + kdf.deriveData(info_hash_x)); + + var secret_x_builder = DHKEM.labeledExtract(suite_id, SECRET); + if (psk != null) { + secret_x_builder.addIKM(psk); + } + secret_x_builder.addSalt(shared_secret); + var secret_x = kdf.deriveKey("Generic", secret_x_builder.extractOnly()); + + // A new KDF object must be created because secret_x_builder + // might contain provider-specific keys which the previous + // KDF (provider already chosen) cannot handle. + kdf = KDF.getInstance(kdfAlg); + var exporter_secret = kdf.deriveKey("Generic", DHKEM.labeledExpand( + secret_x, suite_id, EXP, key_schedule_context, kdfNh)); + + if (hasEncrypt()) { + // ChaCha20-Poly1305 does not care about algorithm name + var key = kdf.deriveKey("AES", DHKEM.labeledExpand(secret_x, + suite_id, KEY, key_schedule_context, aead.nk)); + // deriveData must be called because we need to increment nonce + var base_nonce = kdf.deriveData(DHKEM.labeledExpand(secret_x, + suite_id, BASE_NONCE, key_schedule_context, aead.nn)); + return new Context(key, base_nonce, exporter_secret); + } else { + return new Context(null, null, exporter_secret); + } + } catch (InvalidAlgorithmParameterException + | NoSuchAlgorithmException | UnsupportedOperationException e) { + throw new ProviderException("Internal error", e); + } + } + } + + private static boolean usePSK(SecretKey psk) { + return psk != null; + } + + private static byte[] concat(byte[]... inputs) { + var o = new ByteArrayOutputStream(); + Arrays.stream(inputs).forEach(o::writeBytes); + return o.toByteArray(); + } +} diff --git a/src/java.base/share/classes/com/sun/crypto/provider/JceKeyStore.java b/src/java.base/share/classes/com/sun/crypto/provider/JceKeyStore.java index ec8e0f3757d..ad98653b9c2 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/JceKeyStore.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/JceKeyStore.java @@ -661,6 +661,10 @@ public final class JceKeyStore extends KeyStoreSpi { dos.close(); } } + + if (debug != null) { + emitWeakKeyStoreWarning(); + } } } @@ -862,6 +866,10 @@ public final class JceKeyStore extends KeyStoreSpi { secretKeyCount); } + if (debug != null) { + emitWeakKeyStoreWarning(); + } + /* * If a password has been provided, we check the keyed digest * at the end. If this check fails, the store has been tampered @@ -978,4 +986,12 @@ public final class JceKeyStore extends KeyStoreSpi { return Status.UNDECIDED; } } + + private void emitWeakKeyStoreWarning() { + debug.println("WARNING: JCEKS uses outdated cryptographic " + + "algorithms and will be removed in a future " + + "release. Migrate to PKCS12 using:"); + debug.println("keytool -importkeystore -srckeystore " + + "-destkeystore -deststoretype pkcs12"); + } } diff --git a/src/java.base/share/classes/com/sun/crypto/provider/RSACipher.java b/src/java.base/share/classes/com/sun/crypto/provider/RSACipher.java index 9f19e1415bd..458edff7e76 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/RSACipher.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/RSACipher.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java b/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java index 22d5f17c6e0..4b38bd55809 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java @@ -371,6 +371,8 @@ public final class SunJCE extends Provider { ps("Cipher", "PBEWithHmacSHA512/256AndAES_256", "com.sun.crypto.provider.PBES2Core$HmacSHA512_256AndAES_256"); + ps("Cipher", "HPKE", "com.sun.crypto.provider.HPKE"); + /* * Key(pair) Generator engines */ diff --git a/src/java.base/share/classes/java/lang/Byte.java b/src/java.base/share/classes/java/lang/Byte.java index d9913e354a4..0f3f7f40d05 100644 --- a/src/java.base/share/classes/java/lang/Byte.java +++ b/src/java.base/share/classes/java/lang/Byte.java @@ -26,6 +26,7 @@ package java.lang; import jdk.internal.misc.CDS; +import jdk.internal.vm.annotation.AOTSafeClassInitializer; import jdk.internal.vm.annotation.IntrinsicCandidate; import jdk.internal.vm.annotation.Stable; @@ -103,6 +104,7 @@ public final class Byte extends Number implements Comparable, Constable { return Optional.of(DynamicConstantDesc.ofNamed(BSM_EXPLICIT_CAST, DEFAULT_NAME, CD_byte, intValue())); } + @AOTSafeClassInitializer private static final class ByteCache { private ByteCache() {} diff --git a/src/java.base/share/classes/java/lang/Character.java b/src/java.base/share/classes/java/lang/Character.java index d866202909c..ffda729a45a 100644 --- a/src/java.base/share/classes/java/lang/Character.java +++ b/src/java.base/share/classes/java/lang/Character.java @@ -26,6 +26,7 @@ package java.lang; import jdk.internal.misc.CDS; +import jdk.internal.vm.annotation.AOTSafeClassInitializer; import jdk.internal.vm.annotation.IntrinsicCandidate; import jdk.internal.vm.annotation.Stable; @@ -743,7 +744,8 @@ class Character implements java.io.Serializable, Comparable, Constabl */ public static final class UnicodeBlock extends Subset { /** - * NUM_ENTITIES should match the total number of UnicodeBlocks. + * NUM_ENTITIES should match the total number of UnicodeBlock identifier + * names plus their aliases. * It should be adjusted whenever the Unicode Character Database * is upgraded. */ @@ -9378,6 +9380,7 @@ class Character implements java.io.Serializable, Comparable, Constabl this.value = value; } + @AOTSafeClassInitializer private static final class CharacterCache { private CharacterCache(){} diff --git a/src/java.base/share/classes/java/lang/CharacterData00.java.template b/src/java.base/share/classes/java/lang/CharacterData00.java.template index 247a3ec7050..cb1a9c9cc0b 100644 --- a/src/java.base/share/classes/java/lang/CharacterData00.java.template +++ b/src/java.base/share/classes/java/lang/CharacterData00.java.template @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/java.base/share/classes/java/lang/CharacterDataLatin1.java.template b/src/java.base/share/classes/java/lang/CharacterDataLatin1.java.template index 8316164c7c5..8513f52fd20 100644 --- a/src/java.base/share/classes/java/lang/CharacterDataLatin1.java.template +++ b/src/java.base/share/classes/java/lang/CharacterDataLatin1.java.template @@ -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 diff --git a/src/java.base/share/classes/java/lang/CharacterDataPrivateUse.java b/src/java.base/share/classes/java/lang/CharacterDataPrivateUse.java index 22eae86f4a3..cf6106d47b0 100644 --- a/src/java.base/share/classes/java/lang/CharacterDataPrivateUse.java +++ b/src/java.base/share/classes/java/lang/CharacterDataPrivateUse.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 diff --git a/src/java.base/share/classes/java/lang/CharacterDataUndefined.java b/src/java.base/share/classes/java/lang/CharacterDataUndefined.java index 4db9ff4ed3c..d3281f5a2f3 100644 --- a/src/java.base/share/classes/java/lang/CharacterDataUndefined.java +++ b/src/java.base/share/classes/java/lang/CharacterDataUndefined.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 diff --git a/src/java.base/share/classes/java/lang/Class.java b/src/java.base/share/classes/java/lang/Class.java index cfd2fc82235..eab1993a2b4 100644 --- a/src/java.base/share/classes/java/lang/Class.java +++ b/src/java.base/share/classes/java/lang/Class.java @@ -43,6 +43,7 @@ import java.lang.reflect.Executable; import java.lang.reflect.Field; import java.lang.reflect.GenericArrayType; import java.lang.reflect.GenericDeclaration; +import java.lang.reflect.GenericSignatureFormatError; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Member; import java.lang.reflect.Method; @@ -159,6 +160,10 @@ import sun.reflect.annotation.*; * other members are the classes and interfaces whose declarations are * enclosed within the top-level class declaration. * + *

Unless otherwise specified, methods in this class throw a + * {@link NullPointerException} when they are called with {@code null} + * or an array that contains {@code null} as an argument. + * *

Hidden Classes

* A class or interface created by the invocation of * {@link java.lang.invoke.MethodHandles.Lookup#defineHiddenClass(byte[], boolean, MethodHandles.Lookup.ClassOption...) @@ -529,7 +534,8 @@ public final class Class implements java.io.Serializable, * (which implies linking). See Section {@jls * 12.4} of The Java Language * Specification. - * @param loader class loader from which the class must be loaded + * @param loader class loader from which the class must be loaded, + * may be {@code null} * @return class object representing the desired class * * @throws LinkageError if the linkage fails @@ -588,8 +594,6 @@ public final class Class implements java.io.Serializable, * @return {@code Class} object of the given name defined in the given module; * {@code null} if not found. * - * @throws NullPointerException if the given module or name is {@code null} - * * @throws LinkageError if the linkage fails * * @jls 12.2 Loading of Classes and Interfaces @@ -619,8 +623,6 @@ public final class Class implements java.io.Serializable, * * @param primitiveName the name of the primitive type to find * - * @throws NullPointerException if the argument is {@code null} - * * @jls 4.2 Primitive Types and Values * @jls 15.8.2 Class Literals * @since 22 @@ -756,7 +758,7 @@ public final class Class implements java.io.Serializable, * this {@code Class} object represents a primitive type, this method * returns {@code false}. * - * @param obj the object to check + * @param obj the object to check, may be {@code null} * @return true if {@code obj} is an instance of this class * * @since 1.1 @@ -786,8 +788,6 @@ public final class Class implements java.io.Serializable, * @param cls the {@code Class} object to be checked * @return the {@code boolean} value indicating whether objects of the * type {@code cls} can be assigned to objects of this class - * @throws NullPointerException if the specified Class parameter is - * null. * @since 1.1 */ @IntrinsicCandidate @@ -1445,7 +1445,6 @@ public final class Class implements java.io.Serializable, if (!enclosingInfo.isMethod()) return null; - // Descriptor already validated by VM List> types = BytecodeDescriptor.parseMethod(enclosingInfo.getDescriptor(), getClassLoader()); Class returnType = types.removeLast(); Class[] parameterClasses = types.toArray(EMPTY_CLASS_ARRAY); @@ -1533,8 +1532,15 @@ public final class Class implements java.io.Serializable, String getName() { return name; } - String getDescriptor() { return descriptor; } - + String getDescriptor() { + // hotspot validates this descriptor to be either a field or method + // descriptor as the "type" in a NameAndType in verification. + // So this can still be a field descriptor + if (descriptor.isEmpty() || descriptor.charAt(0) != '(') { + throw new GenericSignatureFormatError("Bad method signature: " + descriptor); + } + return descriptor; + } } private static Class toClass(Type o) { @@ -1567,7 +1573,6 @@ public final class Class implements java.io.Serializable, if (!enclosingInfo.isConstructor()) return null; - // Descriptor already validated by VM List> types = BytecodeDescriptor.parseMethod(enclosingInfo.getDescriptor(), getClassLoader()); types.removeLast(); Class[] parameterClasses = types.toArray(EMPTY_CLASS_ARRAY); @@ -2051,7 +2056,6 @@ public final class Class implements java.io.Serializable, * {@code name} * @throws NoSuchFieldException if a field with the specified name is * not found. - * @throws NullPointerException if {@code name} is {@code null} * * @since 1.1 * @jls 8.2 Class Members @@ -2142,13 +2146,13 @@ public final class Class implements java.io.Serializable, * overriding method as it would have a more specific return type. * * @param name the name of the method - * @param parameterTypes the list of parameters + * @param parameterTypes the list of parameters, may be {@code null} * @return the {@code Method} object that matches the specified * {@code name} and {@code parameterTypes} - * @throws NoSuchMethodException if a matching method is not found + * @throws NoSuchMethodException if a matching method is not found, + * if {@code parameterTypes} contains {@code null}, * or if the name is {@value ConstantDescs#INIT_NAME} or - * {@value ConstantDescs#CLASS_INIT_NAME}. - * @throws NullPointerException if {@code name} is {@code null} + * {@value ConstantDescs#CLASS_INIT_NAME} * * @jls 8.2 Class Members * @jls 8.4 Method Declarations @@ -2179,12 +2183,13 @@ public final class Class implements java.io.Serializable, * represented by this {@code Class} object whose formal parameter * types match those specified by {@code parameterTypes}. * - * @param parameterTypes the parameter array + * @param parameterTypes the parameter array, may be {@code null} * @return the {@code Constructor} object of the public constructor that * matches the specified {@code parameterTypes} * @throws NoSuchMethodException if a matching constructor is not found, - * including when this {@code Class} object represents - * an interface, a primitive type, an array class, or void. + * if this {@code Class} object represents an interface, a primitive + * type, an array class, or void, or if {@code parameterTypes} + * contains {@code null} * * @see #getDeclaredConstructor(Class[]) * @since 1.1 @@ -2365,7 +2370,6 @@ public final class Class implements java.io.Serializable, * class * @throws NoSuchFieldException if a field with the specified name is * not found. - * @throws NullPointerException if {@code name} is {@code null} * * @since 1.1 * @jls 8.2 Class Members @@ -2400,11 +2404,13 @@ public final class Class implements java.io.Serializable, * method does not find the {@code clone()} method. * * @param name the name of the method - * @param parameterTypes the parameter array - * @return the {@code Method} object for the method of this class - * matching the specified name and parameters - * @throws NoSuchMethodException if a matching method is not found. - * @throws NullPointerException if {@code name} is {@code null} + * @param parameterTypes the parameter array, may be {@code null} + * @return the {@code Method} object for the method of this class + * matching the specified name and parameters + * @throws NoSuchMethodException if a matching method is not found, + * if {@code parameterTypes} contains {@code null}, + * or if the name is {@value ConstantDescs#INIT_NAME} or + * {@value ConstantDescs#CLASS_INIT_NAME} * * @jls 8.2 Class Members * @jls 8.4 Method Declarations @@ -2471,12 +2477,13 @@ public final class Class implements java.io.Serializable, * declared in a non-static context, the formal parameter types * include the explicit enclosing instance as the first parameter. * - * @param parameterTypes the parameter array + * @param parameterTypes the parameter array, may be {@code null} * @return The {@code Constructor} object for the constructor with the * specified parameter list * @throws NoSuchMethodException if a matching constructor is not found, - * including when this {@code Class} object represents - * an interface, a primitive type, an array class, or void. + * if this {@code Class} object represents an interface, a + * primitive type, an array class, or void, or if + * {@code parameterTypes} contains {@code null} * * @see #getConstructor(Class[]) * @since 1.1 @@ -2535,7 +2542,6 @@ public final class Class implements java.io.Serializable, * resource with this name is found, or the resource is in a package * that is not {@linkplain Module#isOpen(String, Module) open} to at * least the caller module. - * @throws NullPointerException If {@code name} is {@code null} * * @see Module#getResourceAsStream(String) * @since 1.1 @@ -2631,7 +2637,6 @@ public final class Class implements java.io.Serializable, * resource is in a package that is not * {@linkplain Module#isOpen(String, Module) open} to at least the caller * module. - * @throws NullPointerException If {@code name} is {@code null} * @since 1.1 */ @CallerSensitive @@ -3473,7 +3478,7 @@ public final class Class implements java.io.Serializable, * Casts an object to the class or interface represented * by this {@code Class} object. * - * @param obj the object to be cast + * @param obj the object to be cast, may be {@code null} * @return the object after casting, or null if obj is null * * @throws ClassCastException if the object is not @@ -3528,7 +3533,6 @@ public final class Class implements java.io.Serializable, *

Note that any annotation returned by this method is a * declaration annotation. * - * @throws NullPointerException {@inheritDoc} * @since 1.5 */ @Override @@ -3541,7 +3545,6 @@ public final class Class implements java.io.Serializable, /** * {@inheritDoc} - * @throws NullPointerException {@inheritDoc} * @since 1.5 */ @Override @@ -3554,7 +3557,6 @@ public final class Class implements java.io.Serializable, *

Note that any annotations returned by this method are * declaration annotations. * - * @throws NullPointerException {@inheritDoc} * @since 1.8 */ @Override @@ -3584,7 +3586,6 @@ public final class Class implements java.io.Serializable, *

Note that any annotation returned by this method is a * declaration annotation. * - * @throws NullPointerException {@inheritDoc} * @since 1.8 */ @Override @@ -3600,7 +3601,6 @@ public final class Class implements java.io.Serializable, *

Note that any annotations returned by this method are * declaration annotations. * - * @throws NullPointerException {@inheritDoc} * @since 1.8 */ @Override @@ -3831,6 +3831,7 @@ public final class Class implements java.io.Serializable, * @since 11 */ public boolean isNestmateOf(Class c) { + Objects.requireNonNull(c); if (this == c) { return true; } diff --git a/src/java.base/share/classes/java/lang/Integer.java b/src/java.base/share/classes/java/lang/Integer.java index 2742ec40abf..a9da1c32490 100644 --- a/src/java.base/share/classes/java/lang/Integer.java +++ b/src/java.base/share/classes/java/lang/Integer.java @@ -28,6 +28,8 @@ package java.lang; import jdk.internal.misc.CDS; import jdk.internal.misc.VM; import jdk.internal.util.DecimalDigits; +import jdk.internal.vm.annotation.AOTRuntimeSetup; +import jdk.internal.vm.annotation.AOTSafeClassInitializer; import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.IntrinsicCandidate; import jdk.internal.vm.annotation.Stable; @@ -891,15 +893,20 @@ public final class Integer extends Number * with new Integer object(s) after initialization. */ + @AOTSafeClassInitializer private static final class IntegerCache { static final int low = -128; - static final int high; + @Stable static int high; - @Stable - static final Integer[] cache; + @Stable static Integer[] cache; static Integer[] archivedCache; static { + runtimeSetup(); + } + + @AOTRuntimeSetup + private static void runtimeSetup() { // high value may be configured by property int h = 127; String integerCacheHighPropValue = @@ -915,34 +922,50 @@ public final class Integer extends Number } high = h; - // Load IntegerCache.archivedCache from archive, if possible - CDS.initializeFromArchive(IntegerCache.class); - int size = (high - low) + 1; - - // Use the archived cache if it exists and is large enough - if (archivedCache == null || size > archivedCache.length) { - Integer[] c = new Integer[size]; - int j = low; - // If archive has Integer cache, we must use all instances from it. - // Otherwise, the identity checks between archived Integers and - // runtime-cached Integers would fail. - int archivedSize = (archivedCache == null) ? 0 : archivedCache.length; - for (int i = 0; i < archivedSize; i++) { - c[i] = archivedCache[i]; - assert j == archivedCache[i]; - j++; - } - // Fill the rest of the cache. - for (int i = archivedSize; i < size; i++) { - c[i] = new Integer(j++); - } - archivedCache = c; + Integer[] precomputed = null; + if (cache != null) { + // IntegerCache has been AOT-initialized. + precomputed = cache; + } else { + // Legacy CDS archive support (to be deprecated): + // Load IntegerCache.archivedCache from archive, if possible + CDS.initializeFromArchive(IntegerCache.class); + precomputed = archivedCache; } - cache = archivedCache; + + cache = loadOrInitializeCache(precomputed); + archivedCache = cache; // Legacy CDS archive support (to be deprecated) // range [-128, 127] must be interned (JLS7 5.1.7) assert IntegerCache.high >= 127; } + private static Integer[] loadOrInitializeCache(Integer[] precomputed) { + int size = (high - low) + 1; + + // Use the precomputed cache if it exists and is large enough + if (precomputed != null && size <= precomputed.length) { + return precomputed; + } + + Integer[] c = new Integer[size]; + int j = low; + // If we loading a precomputed cache (from AOT cache or CDS archive), + // we must use all instances from it. + // Otherwise, the Integers from the AOT cache (or CDS archive) will not + // have the same object identity as items in IntegerCache.cache[]. + int precomputedSize = (precomputed == null) ? 0 : precomputed.length; + for (int i = 0; i < precomputedSize; i++) { + c[i] = precomputed[i]; + assert j == precomputed[i]; + j++; + } + // Fill the rest of the cache. + for (int i = precomputedSize; i < size; i++) { + c[i] = new Integer(j++); + } + return c; + } + private IntegerCache() {} } diff --git a/src/java.base/share/classes/java/lang/LazyConstant.java b/src/java.base/share/classes/java/lang/LazyConstant.java index 34f3d754a10..703d67b8abf 100644 --- a/src/java.base/share/classes/java/lang/LazyConstant.java +++ b/src/java.base/share/classes/java/lang/LazyConstant.java @@ -39,12 +39,16 @@ import java.util.function.Supplier; *

* A lazy constant is created using the factory method * {@linkplain LazyConstant#of(Supplier) LazyConstant.of({@code })}. + *

* When created, the lazy constant is not initialized, meaning it has no contents. + *

* The lazy constant (of type {@code T}) can then be initialized * (and its contents retrieved) by calling {@linkplain #get() get()}. The first time * {@linkplain #get() get()} is called, the underlying computing function * (provided at construction) will be invoked and the result will be used to initialize - * the constant. Once a lazy constant is initialized, its contents can never change + * the constant. + *

+ * Once a lazy constant is initialized, its contents can never change * and will be retrieved over and over again upon subsequent {@linkplain #get() get()} * invocations. *

@@ -64,7 +68,7 @@ import java.util.function.Supplier; * // ... * } * } - *} + * } *

* Initially, the lazy constant is not initialized. When {@code logger.get()} * is first invoked, it evaluates the computing function and initializes the constant to @@ -121,7 +125,7 @@ import java.util.function.Supplier; * } * * } - *} + * } * Calling {@code BAR.get()} will create the {@code Bar} singleton if it is not already * created. Upon such a creation, a dependent {@code Foo} will first be created if * the {@code Foo} does not already exist. @@ -238,10 +242,11 @@ public sealed interface LazyConstant // Object methods /** - * {@return if this lazy constant is the same as the provided {@code obj}} + * {@return {@code true} if this lazy constant is the same instance as + * the provided {@code obj}, otherwise {@code false}} *

* In other words, equals compares the identity of this lazy constant and {@code obj} - * to determine equality. Hence, two lazy constants with the same contents are + * to determine equality. Hence, two distinct lazy constants with the same contents are * not equal. *

* This method never triggers initialization of this lazy constant. diff --git a/src/java.base/share/classes/java/lang/Long.java b/src/java.base/share/classes/java/lang/Long.java index 3077e7c0a38..c5cd9650f2d 100644 --- a/src/java.base/share/classes/java/lang/Long.java +++ b/src/java.base/share/classes/java/lang/Long.java @@ -35,6 +35,7 @@ import java.util.Optional; import jdk.internal.misc.CDS; import jdk.internal.util.DecimalDigits; +import jdk.internal.vm.annotation.AOTSafeClassInitializer; import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.IntrinsicCandidate; import jdk.internal.vm.annotation.Stable; @@ -911,6 +912,7 @@ public final class Long extends Number return Long.valueOf(parseLong(s, 10)); } + @AOTSafeClassInitializer private static final class LongCache { private LongCache() {} diff --git a/src/java.base/share/classes/java/lang/Math.java b/src/java.base/share/classes/java/lang/Math.java index 0f39ecf0a8a..55659bed57b 100644 --- a/src/java.base/share/classes/java/lang/Math.java +++ b/src/java.base/share/classes/java/lang/Math.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1994, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1994, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -2378,6 +2378,20 @@ public final class Math { */ @IntrinsicCandidate public static double fma(double a, double b, double c) { + // Implementation note: this method is intentionally coded in + // a straightforward manner relying on BigDecimal for the + // heavy-lifting of the numerical computation. It would be + // possible for the computation to be done solely using binary + // floating-point and integer operations, at the cost of more + // complicated logic. Since most processors have hardware + // support for fma and this method is an intrinsic candidate, + // the software implementation below would only be used on + // processors without native fma support (and also possibly on + // processors with native fma support while running in the + // interpreter). Therefore, the direct performance of the code + // is less of a concern than the code's simplicity, + // maintainability, and testability. + /* * Infinity and NaN arithmetic is not quite the same with two * roundings as opposed to just one so the simple expression @@ -2492,6 +2506,8 @@ public final class Math { */ @IntrinsicCandidate public static float fma(float a, float b, float c) { + // See implementation note in fma(double, double, double). + if (Float.isFinite(a) && Float.isFinite(b) && Float.isFinite(c)) { if (a == 0.0 || b == 0.0) { return a * b + c; // Handled signed zero cases diff --git a/src/java.base/share/classes/java/lang/Module.java b/src/java.base/share/classes/java/lang/Module.java index cd2b8095ee4..bd04345554b 100644 --- a/src/java.base/share/classes/java/lang/Module.java +++ b/src/java.base/share/classes/java/lang/Module.java @@ -69,6 +69,7 @@ import jdk.internal.module.ServicesCatalog; import jdk.internal.module.Resources; import jdk.internal.reflect.CallerSensitive; import jdk.internal.reflect.Reflection; +import jdk.internal.vm.annotation.AOTSafeClassInitializer; import jdk.internal.vm.annotation.Stable; /** @@ -391,6 +392,7 @@ public final class Module implements AnnotatedElement { private static final Module EVERYONE_MODULE; private static final Set EVERYONE_SET; + @AOTSafeClassInitializer private static class ArchivedData { private static ArchivedData archivedData; private final Module allUnnamedModule; diff --git a/src/java.base/share/classes/java/lang/ModuleLayer.java b/src/java.base/share/classes/java/lang/ModuleLayer.java index 9d922f787a6..a073de6b14a 100644 --- a/src/java.base/share/classes/java/lang/ModuleLayer.java +++ b/src/java.base/share/classes/java/lang/ModuleLayer.java @@ -53,6 +53,7 @@ import jdk.internal.module.ServicesCatalog; import jdk.internal.misc.CDS; import jdk.internal.reflect.CallerSensitive; import jdk.internal.reflect.Reflection; +import jdk.internal.vm.annotation.AOTSafeClassInitializer; import jdk.internal.vm.annotation.Stable; /** @@ -145,6 +146,7 @@ import jdk.internal.vm.annotation.Stable; * @see Module#getLayer() */ +@AOTSafeClassInitializer public final class ModuleLayer { // the empty layer (may be initialized from the CDS archive) diff --git a/src/java.base/share/classes/java/lang/Short.java b/src/java.base/share/classes/java/lang/Short.java index 4c64427b6df..920500a7fa3 100644 --- a/src/java.base/share/classes/java/lang/Short.java +++ b/src/java.base/share/classes/java/lang/Short.java @@ -26,6 +26,7 @@ package java.lang; import jdk.internal.misc.CDS; +import jdk.internal.vm.annotation.AOTSafeClassInitializer; import jdk.internal.vm.annotation.IntrinsicCandidate; import jdk.internal.vm.annotation.Stable; @@ -230,6 +231,7 @@ public final class Short extends Number implements Comparable, Constable return Optional.of(DynamicConstantDesc.ofNamed(BSM_EXPLICIT_CAST, DEFAULT_NAME, CD_short, intValue())); } + @AOTSafeClassInitializer private static final class ShortCache { private ShortCache() {} diff --git a/src/java.base/share/classes/java/lang/String.java b/src/java.base/share/classes/java/lang/String.java index 52f908c9e98..d3eda052740 100644 --- a/src/java.base/share/classes/java/lang/String.java +++ b/src/java.base/share/classes/java/lang/String.java @@ -117,9 +117,38 @@ import sun.nio.cs.UTF_8; * Unicode code points (i.e., characters), in addition to those for * dealing with Unicode code units (i.e., {@code char} values). * - *

Unless otherwise noted, methods for comparing Strings do not take locale - * into account. The {@link java.text.Collator} class provides methods for - * finer-grain, locale-sensitive String comparison. + *

String comparison and case-insensitive matching + * + *

There are several related ways to compare {@code String} values; choose + * the one whose semantics fit your purpose: + * + *

    + *
  • Exact content equality — {@link #equals(Object)} checks that two + * strings contain the identical char sequence of UTF-16 code units. This is + * a strict, case-sensitive comparison suitable for exact matching, hashing + * and any situation that requires bit-for-bit stability.
  • + * + *
  • Simple case-insensitive equality — {@link #equalsIgnoreCase(String)} + * (and the corresponding {@link #compareToIgnoreCase(String)} and {@link #CASE_INSENSITIVE_ORDER}) + * performs a per-code-point, locale-independent comparison using + * {@link Character#toUpperCase(int)} and {@link Character#toLowerCase(int)}. + * It is convenient for many common case-insensitive checks.
  • + * + *
  • Unicode case-folded equivalence — {@link #equalsFoldCase(String)} + * (and the corresponding {@link #compareToFoldCase(String)} and {@link #UNICODE_CASEFOLD_ORDER}) + * implement the Unicode {@index "full case folding"} rules defined in + * Unicode CaseFolding.txt. + * Case folding is locale-independent and language-neutral and may map a single code + * point to multiple code points (1:M mappings). For example, the German sharp + * s ({@code U+00DF}) is folded to the sequence {@code "ss"}. + * Use these methods when you need Unicode-compliant + * + * caseless matching, searching, or ordering.
  • + *
+ * + *

Unless otherwise noted, methods for comparing Strings do not take locale into + * account. The {@link java.text.Collator} class provides methods for finer-grain, + * locale-sensitive String comparison. * * @implNote The implementation of the string concatenation operator is left to * the discretion of a Java compiler, as long as the compiler ultimately conforms @@ -1082,7 +1111,7 @@ public final class String int sp = 0; int sl = len; while (sp < sl) { - int ret = StringCoding.encodeISOArray(val, sp, dst, dp, len); + int ret = StringCoding.implEncodeISOArray(val, sp, dst, dp, len); sp = sp + ret; dp = dp + ret; if (ret != len) { @@ -2179,6 +2208,7 @@ public final class String * false} otherwise * * @see #equals(Object) + * @see #equalsFoldCase(String) * @see #codePoints() */ public boolean equalsIgnoreCase(String anotherString) { @@ -2188,6 +2218,57 @@ public final class String && regionMatches(true, 0, anotherString, 0, length()); } + /** + * Compares this {@code String} to another {@code String} for equality, + * using {@index "Unicode case folding"}. Two strings are considered equal + * by this method if their case-folded forms are identical. + *

+ * Case folding is defined by the Unicode Standard in + * CaseFolding.txt, + * including 1:M mappings. For example, {@code "Fuß".equalsFoldCase("FUSS")} + * returns {@code true}, since the character {@code U+00DF} (sharp s) folds + * to {@code "ss"}. + *

+ * Case folding is locale-independent and language-neutral, unlike + * locale-sensitive transformations such as {@link #toLowerCase()} or + * {@link #toUpperCase()}. It is intended for caseless matching, + * searching, and indexing. + * + * @apiNote + * This method is the Unicode-compliant alternative to + * {@link #equalsIgnoreCase(String)}. It implements full case folding as + * defined by the Unicode Standard, which may differ from the simpler + * per-character mapping performed by {@code equalsIgnoreCase}. + * For example: + * {@snippet lang=java : + * String a = "Fuß"; + * String b = "FUSS"; + * boolean equalsFoldCase = a.equalsFoldCase(b); // returns true + * boolean equalsIgnoreCase = a.equalsIgnoreCase(b); // returns false + * } + * + * @param anotherString + * The {@code String} to compare this {@code String} against + * + * @return {@code true} if the given object is not {@code null} and represents + * the same sequence of characters as this string under Unicode case + * folding; {@code false} otherwise. + * + * @spec https://www.unicode.org/versions/latest/core-spec/chapter-5/#G21790 Unicode Caseless Matching + * @see #compareToFoldCase(String) + * @see #equalsIgnoreCase(String) + * @since 26 + */ + public boolean equalsFoldCase(String anotherString) { + if (this == anotherString) { + return true; + } + if (anotherString == null) { + return false; + } + return UNICODE_CASEFOLD_ORDER.compare(this, anotherString) == 0; + } + /** * Compares two strings lexicographically. * The comparison is based on the Unicode value of each character in @@ -2303,12 +2384,86 @@ public final class String * than this String, ignoring case considerations. * @see java.text.Collator * @see #codePoints() + * @see #compareToFoldCase(String) * @since 1.2 */ public int compareToIgnoreCase(String str) { return CASE_INSENSITIVE_ORDER.compare(this, str); } + /** + * A Comparator that orders {@code String} objects as by + * {@link #compareToFoldCase(String) compareToFoldCase()}. + * + * @see #compareToFoldCase(String) + * @since 26 + */ + public static final Comparator UNICODE_CASEFOLD_ORDER + = new FoldCaseComparator(); + + private static class FoldCaseComparator implements Comparator { + + @Override + public int compare(String s1, String s2) { + byte[] v1 = s1.value; + byte[] v2 = s2.value; + if (s1.coder == s2.coder()) { + return s1.coder == LATIN1 ? StringLatin1.compareToFC(v1, v2) + : StringUTF16.compareToFC(v1, v2); + } + return s1.coder == LATIN1 ? StringLatin1.compareToFC_UTF16(v1, v2) + : StringUTF16.compareToFC_Latin1(v1, v2); + } + } + + /** + * Compares two strings lexicographically using {@index "Unicode case folding"}. + * This method returns an integer whose sign is that of calling {@code compareTo} + * on the Unicode case folded version of the strings. Unicode Case folding + * eliminates differences in case according to the Unicode Standard, using the + * mappings defined in + * CaseFolding.txt, + * including 1:M mappings, such as {@code"ß"} → {@code }"ss"}. + *

+ * Case folding is a locale-independent, language-neutral form of case mapping, + * primarily intended for caseless matching. Unlike {@link #compareToIgnoreCase(String)}, + * which applies a simpler locale-insensitive uppercase mapping. This method + * follows the Unicode {@index "full"} case folding, providing stable and + * consistent results across all environments. + *

+ * Note that this method does not take locale into account, and may + * produce results that differ from locale-sensitive ordering. Use + * {@link java.text.Collator} for locale-sensitive comparison. + * + * @apiNote + * This method is the Unicode-compliant alternative to + * {@link #compareToIgnoreCase(String)}. It implements the + * {@index "full case folding"} as defined by the Unicode Standard, which + * may differ from the simpler per-character mapping performed by + * {@code compareToIgnoreCase}. + * For example: + * {@snippet lang=java : + * String a = "Fuß"; + * String b = "FUSS"; + * int cmpFoldCase = a.compareToFoldCase(b); // returns 0 + * int cmpIgnoreCase = a.compareToIgnoreCase(b); // returns > 0 + * } + * + * @param str the {@code String} to be compared. + * @return a negative integer, zero, or a positive integer as the specified + * String is greater than, equal to, or less than this String, + * ignoring case considerations by case folding. + * + * @spec https://www.unicode.org/versions/latest/core-spec/chapter-5/#G21790 Unicode Caseless Matching + * @see java.text.Collator + * @see #compareToIgnoreCase(String) + * @see #equalsFoldCase(String) + * @since 26 + */ + public int compareToFoldCase(String str) { + return UNICODE_CASEFOLD_ORDER.compare(this, str); + } + /** * Tests if two string regions are equal. *

diff --git a/src/java.base/share/classes/java/lang/StringCoding.java b/src/java.base/share/classes/java/lang/StringCoding.java index 545f216b755..c02af28c37d 100644 --- a/src/java.base/share/classes/java/lang/StringCoding.java +++ b/src/java.base/share/classes/java/lang/StringCoding.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2024, Alibaba Group Holding Limited. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,11 +26,8 @@ package java.lang; -import jdk.internal.util.Preconditions; import jdk.internal.vm.annotation.IntrinsicCandidate; -import java.util.function.BiFunction; - /** * Utility class for string encoding and decoding. */ @@ -41,7 +38,7 @@ class StringCoding { /** * Count the number of leading non-zero ascii chars in the range. */ - static int countNonZeroAscii(String s) { + public static int countNonZeroAscii(String s) { byte[] value = s.value(); if (s.isLatin1()) { return countNonZeroAsciiLatin1(value, 0, value.length); @@ -53,7 +50,7 @@ class StringCoding { /** * Count the number of non-zero ascii chars in the range. */ - private static int countNonZeroAsciiLatin1(byte[] ba, int off, int len) { + public static int countNonZeroAsciiLatin1(byte[] ba, int off, int len) { int limit = off + len; for (int i = off; i < limit; i++) { if (ba[i] <= 0) { @@ -66,7 +63,7 @@ class StringCoding { /** * Count the number of leading non-zero ascii chars in the range. */ - private static int countNonZeroAsciiUTF16(byte[] ba, int off, int strlen) { + public static int countNonZeroAsciiUTF16(byte[] ba, int off, int strlen) { int limit = off + strlen; for (int i = off; i < limit; i++) { char c = StringUTF16.charAt(ba, i); @@ -77,7 +74,7 @@ class StringCoding { return strlen; } - static boolean hasNegatives(byte[] ba, int off, int len) { + public static boolean hasNegatives(byte[] ba, int off, int len) { return countPositives(ba, off, len) != len; } @@ -88,24 +85,9 @@ class StringCoding { * bytes in the range. If there are negative bytes, the implementation must return * a value that is less than or equal to the index of the first negative byte * in the range. - * - * @param ba a byte array - * @param off the index of the first byte to start reading from - * @param len the total number of bytes to read - * @throws NullPointerException if {@code ba} is null - * @throws ArrayIndexOutOfBoundsException if the provided sub-range is - * {@linkplain Preconditions#checkFromIndexSize(int, int, int, BiFunction) out of bounds} */ - static int countPositives(byte[] ba, int off, int len) { - Preconditions.checkFromIndexSize( - off, len, - ba.length, // Implicit null check on `ba` - Preconditions.AIOOBE_FORMATTER); - return countPositives0(ba, off, len); - } - @IntrinsicCandidate - private static int countPositives0(byte[] ba, int off, int len) { + public static int countPositives(byte[] ba, int off, int len) { int limit = off + len; for (int i = off; i < limit; i++) { if (ba[i] < 0) { @@ -115,37 +97,9 @@ class StringCoding { return len; } - /** - * Encodes as many ISO-8859-1 codepoints as possible from the source byte - * array containing characters encoded in UTF-16, into the destination byte - * array, assuming that the encoding is ISO-8859-1 compatible. - * - * @param sa the source byte array containing characters encoded in UTF-16 - * @param sp the index of the character (not byte!) from the source array to start reading from - * @param da the target byte array - * @param dp the index of the target array to start writing to - * @param len the maximum number of characters (not bytes!) to be encoded - * @return the total number of characters (not bytes!) successfully encoded - * @throws NullPointerException if any of the provided arrays is null - */ - static int encodeISOArray(byte[] sa, int sp, - byte[] da, int dp, int len) { - // This method should tolerate invalid arguments, matching the lenient behavior of the VM intrinsic. - // Hence, using operator expressions instead of `Preconditions`, which throw on failure. - int sl; - if ((sp | dp | len) < 0 || - // Halving the length of `sa` to obtain the number of characters: - sp >= (sl = sa.length >>> 1) || // Implicit null check on `sa` - dp >= da.length) { // Implicit null check on `da` - return 0; - } - int minLen = Math.min(len, Math.min(sl - sp, da.length - dp)); - return encodeISOArray0(sa, sp, da, dp, minLen); - } - @IntrinsicCandidate - private static int encodeISOArray0(byte[] sa, int sp, - byte[] da, int dp, int len) { + public static int implEncodeISOArray(byte[] sa, int sp, + byte[] da, int dp, int len) { int i = 0; for (; i < len; i++) { char c = StringUTF16.getChar(sa, sp++); @@ -156,35 +110,10 @@ class StringCoding { return i; } - /** - * Encodes as many ASCII codepoints as possible from the source - * character array into the destination byte array, assuming that - * the encoding is ASCII compatible. - * - * @param sa the source character array - * @param sp the index of the source array to start reading from - * @param da the target byte array - * @param dp the index of the target array to start writing to - * @param len the maximum number of characters to be encoded - * @return the total number of characters successfully encoded - * @throws NullPointerException if any of the provided arrays is null - */ - static int encodeAsciiArray(char[] sa, int sp, - byte[] da, int dp, int len) { - // This method should tolerate invalid arguments, matching the lenient behavior of the VM intrinsic. - // Hence, using operator expressions instead of `Preconditions`, which throw on failure. - if ((sp | dp | len) < 0 || - sp >= sa.length || // Implicit null check on `sa` - dp >= da.length) { // Implicit null check on `da` - return 0; - } - int minLen = Math.min(len, Math.min(sa.length - sp, da.length - dp)); - return encodeAsciiArray0(sa, sp, da, dp, minLen); - } - @IntrinsicCandidate - static int encodeAsciiArray0(char[] sa, int sp, - byte[] da, int dp, int len) { + public static int implEncodeAsciiArray(char[] sa, int sp, + byte[] da, int dp, int len) + { int i = 0; for (; i < len; i++) { char c = sa[sp++]; diff --git a/src/java.base/share/classes/java/lang/StringLatin1.java b/src/java.base/share/classes/java/lang/StringLatin1.java index 61c62d049bc..21a8b2dd61a 100644 --- a/src/java.base/share/classes/java/lang/StringLatin1.java +++ b/src/java.base/share/classes/java/lang/StringLatin1.java @@ -32,6 +32,8 @@ import java.util.function.Consumer; import java.util.function.IntConsumer; import java.util.stream.Stream; import java.util.stream.StreamSupport; + +import jdk.internal.lang.CaseFolding; import jdk.internal.util.ArraysSupport; import jdk.internal.vm.annotation.IntrinsicCandidate; @@ -179,6 +181,128 @@ final class StringLatin1 { return len1 - len2; } + private static int compareToFC0(byte[] value, int off, int last, byte[] other, int ooff, int olast) { + int k1 = off, k2 = ooff; + boolean lo1 = false, lo2 = false; // true if we have a leftover 's' from u+00df -> ss + while ((k1 < last || lo1) && (k2 < olast || lo2)) { + int c1, c2; + if (lo1) { + c1 = 0x73; // leftover 's' + lo1 = false; + } else { + c1 = getChar(value, k1++); + if (c1 == 0xdf) { + c1 = 0x73; + lo1 = true; + } + } + if (lo2) { + c2 = 0x73; // 's' + lo2 = false; + } else { + c2 = getChar(other, k2++); + if (c2 == 0xdf) { + c2 = 0x73; + lo2 = true; + } + } + if (!CharacterDataLatin1.equalsIgnoreCase((byte)c1, (byte)c2)) { + return Character.toLowerCase(c1) - Character.toLowerCase(c2); + } + } + if (k1 < last || lo1) { + return 1; + } + if (k2 < olast || lo2) { + return -1; + } + return 0; + } + + static int compareToFC(byte[] value, byte[] other) { + int len = value.length; + int olen = other.length; + int lim = Math.min(len, olen); + for (int k = 0; k < lim; k++) { + byte b1 = value[k]; + byte b2 = other[k]; + if (!CharacterDataLatin1.equalsIgnoreCase(b1, b2)) { + int c1 = b1 & 0xff; + int c2 = b2 & 0xff; + if (c1 == 0xdf || c2 == 0xdf) { // 0xdf is the only 1:M in latin1 range + return compareToFC0(value, k, len, other, k, olen); + } + return Character.toLowerCase(c1) - Character.toLowerCase(c2); + } + } + return len - olen; + } + + private static int compareToFC0_UTF16(byte[] value, int off, int last, byte[] other, int ooff, int olast) { + int f1 = 0, f2 = 0; + int k1 = off, k2 = ooff; + while ((k1 < last || f1 != 0) && (k2 < olast || f2 != 0)) { + int c1, c2; + if (f1 != 0) { + c1 = (f1 & 0xffff); f1 >>>= 16; + } else { + c1 = getChar(value, k1++); + var f = CaseFolding.fold(c1); + if (CaseFolding.isSingleCodePoint(f)) { + c1 = (int)(f & 0xfffff); + } else { + c1 = (int)f & 0xffff; + f1 = (int)(f >>> 16); + } + } + if (f2 != 0) { + c2 = f2 & 0xffff; f2 >>>= 16; + } else { + c2 = StringUTF16.codePointAt(other, k2, olast, true); + k2 += Character.charCount(c2); + var f = CaseFolding.fold(c2); + if (CaseFolding.isSingleCodePoint(f)) { + c2 = (int)(f & 0xfffff); + } else { + c2 = (int)(f & 0xffff); + f2 = (int)(f >>> 16); + } + } + if (c1 != c2) { + return c1 - c2; + } + } + if (k1 < last || f1 != 0) { + return 1; + } + if (k2 < olast || f2 != 0) { + return -1; + } + return 0; + } + + // latin1 vs utf16 + static int compareToFC_UTF16(byte[] value, byte[] other) { + int last = length(value); + int olast = StringUTF16.length(other); + int lim = Math.min(last, olast); + for (int k = 0; k < lim; k++) { + int cp1 = getChar(value, k); + int cp2 = StringUTF16.codePointAt(other, k, olast, true); + if (cp1 != cp2) { + long cf1 = CaseFolding.fold(cp1); + long cf2 = CaseFolding.fold(cp2); + if (cf1 != cf2) { + if (!CaseFolding.isSingleCodePoint(cf1) || !CaseFolding.isSingleCodePoint(cf2)) { + return compareToFC0_UTF16(value, k, last, other, k, olast); + } + return (int)(cf1 - cf2); + } + } + } + return last - olast; + } + static int hashCode(byte[] value) { return ArraysSupport.hashCodeOfUnsigned(value, 0, value.length, 0); } diff --git a/src/java.base/share/classes/java/lang/StringUTF16.java b/src/java.base/share/classes/java/lang/StringUTF16.java index 4e31c9728e9..75c9e8239ba 100644 --- a/src/java.base/share/classes/java/lang/StringUTF16.java +++ b/src/java.base/share/classes/java/lang/StringUTF16.java @@ -34,6 +34,7 @@ import java.util.function.IntConsumer; import java.util.stream.Stream; import java.util.stream.StreamSupport; +import jdk.internal.lang.CaseFolding; import jdk.internal.misc.Unsafe; import jdk.internal.util.ArraysSupport; import jdk.internal.vm.annotation.ForceInline; @@ -93,7 +94,7 @@ final class StringUTF16 { return value.length >> 1; } - private static int codePointAt(byte[] value, int index, int end, boolean checked) { + static int codePointAt(byte[] value, int index, int end, boolean checked) { assert index < end; if (checked) { checkIndex(index, value); @@ -592,6 +593,77 @@ final class StringUTF16 { return -StringLatin1.compareToCI_UTF16(other, value); } + public static int compareToFC_Latin1(byte[] value, byte[] other) { + return -StringLatin1.compareToFC_UTF16(other, value); + } + + private static int compareToFC0(byte[] value, int off, int last, byte[] other, int ooff, int olast) { + int f1 = 0, f2 = 0; + int k1 = off, k2 = ooff; + while ((k1 < last || f1 != 0) && (k2 < olast || f2 != 0)) { + int c1, c2; + if (f1 != 0) { + c1 = f1 & 0xffff; f1 >>>= 16; + } else { + c1 = StringUTF16.codePointAt(value, k1, last, true); + k1 += Character.charCount(c1); + var f = CaseFolding.fold(c1); + if (CaseFolding.isSingleCodePoint(f)) { + c1 = (int)(f & 0xfffff); + } else { + c1 = (int)(f & 0xffff); + f1 = (int)(f >> 16); + } + } + if (f2 != 0) { + c2 = f2 & 0xffff; f2 >>>= 16; + } else { + c2 = StringUTF16.codePointAt(other, k2, olast, true); + k2 += Character.charCount(c2); + var f = CaseFolding.fold(c2); + if (CaseFolding.isSingleCodePoint(f)) { + c2 = (int)(f & 0xfffff); + } else { + c2 = (int)(f & 0xffff); + f2 = (int)(f >>> 16); + } + } + if (c1 != c2) { + return c1 - c2; + } + } + if (k1 < last || f1 != 0) { + return 1; + } + if (k2 < olast || f2 != 0) { + return -1; + } + return 0; + } + + public static int compareToFC(byte[] value, byte[] other) { + int tlast = length(value); + int olast = length(other); + int lim = Math.min(tlast, olast); + int k = 0; + while (k < lim) { + int cp1 = codePointAt(value, k, tlast, true); + int cp2 = codePointAt(other, k, olast, true); + if (cp1 != cp2) { + long cf1 = CaseFolding.fold(cp1); + long cf2 = CaseFolding.fold(cp2); + if (cf1 != cf2) { + if (!CaseFolding.isSingleCodePoint(cf1) || !CaseFolding.isSingleCodePoint(cf2)) { + return compareToFC0(value, k, tlast, other, k, olast); + } + return (int) cf1 - (int) cf2; + } + } + k += Character.charCount(cp1); + } + return tlast - olast; + } + static int hashCode(byte[] value) { return ArraysSupport.hashCodeOfUTF16(value, 0, value.length >> 1, 0); } diff --git a/src/java.base/share/classes/java/lang/System.java b/src/java.base/share/classes/java/lang/System.java index 5c2b47afe3d..f3a57c34165 100644 --- a/src/java.base/share/classes/java/lang/System.java +++ b/src/java.base/share/classes/java/lang/System.java @@ -55,6 +55,7 @@ import java.util.Properties; import java.util.ResourceBundle; import java.util.Set; import java.util.concurrent.Executor; +import java.util.concurrent.ScheduledExecutorService; import java.util.function.Supplier; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Stream; @@ -2176,8 +2177,8 @@ public final class System { return String.decodeASCII(src, srcOff, dst, dstOff, len); } - public int encodeASCII(char[] sa, int sp, byte[] da, int dp, int len) { - return StringCoding.encodeAsciiArray(sa, sp, da, dp, len); + public int uncheckedEncodeASCII(char[] src, int srcOff, byte[] dst, int dstOff, int len) { + return StringCoding.implEncodeAsciiArray(src, srcOff, dst, dstOff, len); } public InputStream initialSystemIn() { diff --git a/src/java.base/share/classes/java/lang/ThreadBuilders.java b/src/java.base/share/classes/java/lang/ThreadBuilders.java index 62c29651d11..150ce263b01 100644 --- a/src/java.base/share/classes/java/lang/ThreadBuilders.java +++ b/src/java.base/share/classes/java/lang/ThreadBuilders.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -309,7 +309,7 @@ class ThreadBuilders { String nextThreadName() { if (hasCounter) { - return name + (long) COUNT.getAndAdd(this, 1); + return name + (long) COUNT.getAndAdd(this, 1L); } else { return name; } diff --git a/src/java.base/share/classes/java/lang/VirtualThread.java b/src/java.base/share/classes/java/lang/VirtualThread.java index 6064b46d50a..93862db9105 100644 --- a/src/java.base/share/classes/java/lang/VirtualThread.java +++ b/src/java.base/share/classes/java/lang/VirtualThread.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -89,15 +89,19 @@ final class VirtualThread extends BaseVirtualThread { * * RUNNING -> PARKING // Thread parking with LockSupport.park * PARKING -> PARKED // cont.yield successful, parked indefinitely - * PARKING -> PINNED // cont.yield failed, parked indefinitely on carrier * PARKED -> UNPARKED // unparked, may be scheduled to continue - * PINNED -> RUNNING // unparked, continue execution on same carrier * UNPARKED -> RUNNING // continue execution after park * + * PARKING -> RUNNING // cont.yield failed, need to park on carrier + * RUNNING -> PINNED // park on carrier + * PINNED -> RUNNING // unparked, continue execution on same carrier + * * RUNNING -> TIMED_PARKING // Thread parking with LockSupport.parkNanos * TIMED_PARKING -> TIMED_PARKED // cont.yield successful, timed-parked - * TIMED_PARKING -> TIMED_PINNED // cont.yield failed, timed-parked on carrier * TIMED_PARKED -> UNPARKED // unparked, may be scheduled to continue + * + * TIMED_PARKING -> RUNNING // cont.yield failed, need to park on carrier + * RUNNING -> TIMED_PINNED // park on carrier * TIMED_PINNED -> RUNNING // unparked, continue execution on same carrier * * RUNNING -> BLOCKING // blocking on monitor enter @@ -108,7 +112,7 @@ final class VirtualThread extends BaseVirtualThread { * RUNNING -> WAITING // transitional state during wait on monitor * WAITING -> WAIT // waiting on monitor * WAIT -> BLOCKED // notified, waiting to be unblocked by monitor owner - * WAIT -> UNBLOCKED // timed-out/interrupted + * WAIT -> UNBLOCKED // interrupted * * RUNNING -> TIMED_WAITING // transition state during timed-waiting on monitor * TIMED_WAITING -> TIMED_WAIT // timed-waiting on monitor @@ -246,11 +250,11 @@ final class VirtualThread extends BaseVirtualThread { @Hidden @JvmtiHideEvents public void run() { - vthread.notifyJvmtiStart(); // notify JVMTI + vthread.endFirstTransition(); try { vthread.run(task); } finally { - vthread.notifyJvmtiEnd(); // notify JVMTI + vthread.startFinalTransition(); } } }; @@ -315,6 +319,18 @@ final class VirtualThread extends BaseVirtualThread { } } + /** + * Submits the given task to the given executor. If the scheduler is a + * ForkJoinPool then the task is first adapted to a ForkJoinTask. + */ + private void submit(Executor executor, Runnable task) { + if (executor instanceof ForkJoinPool pool) { + pool.submit(ForkJoinTask.adapt(task)); + } else { + executor.execute(task); + } + } + /** * Submits the runContinuation task to the scheduler. For the default scheduler, * and calling it on a worker thread, the task will be pushed to the local queue, @@ -335,12 +351,12 @@ final class VirtualThread extends BaseVirtualThread { if (currentThread().isVirtual()) { Continuation.pin(); try { - scheduler.execute(runContinuation); + submit(scheduler, runContinuation); } finally { Continuation.unpin(); } } else { - scheduler.execute(runContinuation); + submit(scheduler, runContinuation); } done = true; } catch (RejectedExecutionException ree) { @@ -479,8 +495,9 @@ final class VirtualThread extends BaseVirtualThread { @ChangesCurrentThread @ReservedStackAccess private void mount() { - // notify JVMTI before mount - notifyJvmtiMount(/*hide*/true); + startTransition(/*is_mount*/true); + // We assume following volatile accesses provide equivalent + // of acquire ordering, otherwise we need U.loadFence() here. // sets the carrier thread Thread carrier = Thread.currentCarrierThread(); @@ -521,8 +538,9 @@ final class VirtualThread extends BaseVirtualThread { } carrier.clearInterrupt(); - // notify JVMTI after unmount - notifyJvmtiUnmount(/*hide*/false); + // We assume previous volatile accesses provide equivalent + // of release ordering, otherwise we need U.storeFence() here. + endTransition(/*is_mount*/false); } /** @@ -531,11 +549,11 @@ final class VirtualThread extends BaseVirtualThread { */ @Hidden private boolean yieldContinuation() { - notifyJvmtiUnmount(/*hide*/true); + startTransition(/*is_mount*/false); try { return Continuation.yield(VTHREAD_SCOPE); } finally { - notifyJvmtiMount(/*hide*/false); + endTransition(/*is_mount*/true); } } @@ -842,16 +860,20 @@ final class VirtualThread extends BaseVirtualThread { * Re-enables this virtual thread for scheduling. If this virtual thread is parked * then its task is scheduled to continue, otherwise its next call to {@code park} or * {@linkplain #parkNanos(long) parkNanos} is guaranteed not to block. + * @param lazySubmit to use lazySubmit if possible * @throws RejectedExecutionException if the scheduler cannot accept a task */ - @Override - void unpark() { + private void unpark(boolean lazySubmit) { if (!getAndSetParkPermit(true) && currentThread() != this) { int s = state(); // unparked while parked if ((s == PARKED || s == TIMED_PARKED) && compareAndSetState(s, UNPARKED)) { - submitRunContinuation(); + if (lazySubmit) { + lazySubmitRunContinuation(); + } else { + submitRunContinuation(); + } return; } @@ -874,6 +896,11 @@ final class VirtualThread extends BaseVirtualThread { } } + @Override + void unpark() { + unpark(false); + } + /** * Invoked by unblocker thread to unblock this virtual thread. */ @@ -890,11 +917,7 @@ final class VirtualThread extends BaseVirtualThread { */ private void parkTimeoutExpired() { assert !VirtualThread.currentThread().isVirtual(); - if (!getAndSetParkPermit(true) - && (state() == TIMED_PARKED) - && compareAndSetState(TIMED_PARKED, UNPARKED)) { - lazySubmitRunContinuation(); - } + unpark(true); } /** @@ -1389,23 +1412,34 @@ final class VirtualThread extends BaseVirtualThread { this.carrierThread = carrier; } - // -- JVM TI support -- + // The following four methods notify the VM when a "transition" starts and ends. + // A "mount transition" embodies the steps to transfer control from a platform + // thread to a virtual thread, changing the thread identity, and starting or + // resuming the virtual thread's continuation on the carrier. + // An "unmount transition" embodies the steps to transfer control from a virtual + // thread to its carrier, suspending the virtual thread's continuation, and + // restoring the thread identity to the platform thread. + // The notifications to the VM are necessary in order to coordinate with functions + // (JVMTI mostly) that disable transitions for one or all virtual threads. Starting + // a transition may block if transitions are disabled. Ending a transition may + // notify a thread that is waiting to disable transitions. The notifications are + // also used to post JVMTI events for virtual thread start and end. @IntrinsicCandidate @JvmtiMountTransition - private native void notifyJvmtiStart(); + private native void endFirstTransition(); @IntrinsicCandidate @JvmtiMountTransition - private native void notifyJvmtiEnd(); + private native void startFinalTransition(); @IntrinsicCandidate @JvmtiMountTransition - private native void notifyJvmtiMount(boolean hide); + private native void startTransition(boolean is_mount); @IntrinsicCandidate @JvmtiMountTransition - private native void notifyJvmtiUnmount(boolean hide); + private native void endTransition(boolean is_mount); @IntrinsicCandidate private static native void notifyJvmtiDisableSuspend(boolean enter); @@ -1536,4 +1570,4 @@ final class VirtualThread extends BaseVirtualThread { unblocker.setDaemon(true); unblocker.start(); } -} \ No newline at end of file +} diff --git a/src/java.base/share/classes/java/lang/classfile/ClassFile.java b/src/java.base/share/classes/java/lang/classfile/ClassFile.java index 216facbdddf..5a2b7e6297e 100644 --- a/src/java.base/share/classes/java/lang/classfile/ClassFile.java +++ b/src/java.base/share/classes/java/lang/classfile/ClassFile.java @@ -1038,6 +1038,14 @@ public sealed interface ClassFile */ int JAVA_26_VERSION = 70; + /** + * The class major version introduced by Java SE 27, {@value}. + * + * @see ClassFileFormatVersion#RELEASE_27 + * @since 27 + */ + int JAVA_27_VERSION = 71; + /** * A minor version number {@value} indicating a class uses preview features * of a Java SE release since 12, for major versions {@value @@ -1049,7 +1057,7 @@ public sealed interface ClassFile * {@return the latest class major version supported by the current runtime} */ static int latestMajorVersion() { - return JAVA_26_VERSION; + return JAVA_27_VERSION; } /** diff --git a/src/java.base/share/classes/java/lang/classfile/attribute/package-info.java b/src/java.base/share/classes/java/lang/classfile/attribute/package-info.java index 1ddd0511d85..43bf1984a00 100644 --- a/src/java.base/share/classes/java/lang/classfile/attribute/package-info.java +++ b/src/java.base/share/classes/java/lang/classfile/attribute/package-info.java @@ -32,9 +32,8 @@ * including {@link Attribute}, {@link AttributedElement}, {@link AttributeMapper}, and {@link CustomAttribute}, which * do not reside in this package. *

- * Unless otherwise specified, passing {@code null} or an array or collection containing a {@code null} element as an - * argument to a constructor or method of any Class-File API class or interface will cause a {@link NullPointerException} - * to be thrown. + * APIs in this package perform {@linkplain java.lang.classfile##checks null and unrepresentable argument checks}, + * unless otherwise noted. * *

Reading Attributes

* The general way to obtain attributes is through {@link AttributedElement}. In addition to that, many attributes diff --git a/src/java.base/share/classes/java/lang/classfile/constantpool/package-info.java b/src/java.base/share/classes/java/lang/classfile/constantpool/package-info.java index 66e72496d3a..0828e04ae22 100644 --- a/src/java.base/share/classes/java/lang/classfile/constantpool/package-info.java +++ b/src/java.base/share/classes/java/lang/classfile/constantpool/package-info.java @@ -30,9 +30,8 @@ * {@code class} file format. Constant pool entries are low-level models to faithfully represent the exact structure * of a {@code class} file. *

- * Unless otherwise specified, passing {@code null} or an array or collection containing a {@code null} element as an - * argument to a constructor or method of any Class-File API class or interface will cause a {@link NullPointerException} - * to be thrown. + * APIs in this package perform {@linkplain java.lang.classfile##checks null and unrepresentable argument checks}, + * unless otherwise noted. * *

Reading the constant pool entries

* When read from {@code class} files, the pool entries are lazily inflated; the contents of these entries, besides the diff --git a/src/java.base/share/classes/java/lang/classfile/instruction/package-info.java b/src/java.base/share/classes/java/lang/classfile/instruction/package-info.java index e732aadf1ec..990731721a8 100644 --- a/src/java.base/share/classes/java/lang/classfile/instruction/package-info.java +++ b/src/java.base/share/classes/java/lang/classfile/instruction/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,9 +29,8 @@ * The {@code java.lang.classfile.instruction} package contains interfaces describing code instructions. * Implementations of these interfaces are immutable. *

- * Unless otherwise specified, passing {@code null} or an array or collection containing a {@code null} element as an - * argument to a constructor or method of any Class-File API class or interface will cause a {@link NullPointerException} - * to be thrown. + * APIs in this package perform {@linkplain java.lang.classfile##checks null and unrepresentable argument checks}, + * unless otherwise noted. * *

Reading of instructions

* Instructions and pseudo-instructions are usually accessed from a {@link CodeModel}, such as {@link CodeModel#forEach diff --git a/src/java.base/share/classes/java/lang/classfile/package-info.java b/src/java.base/share/classes/java/lang/classfile/package-info.java index da9ad7fbf0d..460f6699e7b 100644 --- a/src/java.base/share/classes/java/lang/classfile/package-info.java +++ b/src/java.base/share/classes/java/lang/classfile/package-info.java @@ -250,12 +250,6 @@ * the convenience method {@code CodeBuilder.invoke}, which in turn behaves * as if it calls method {@code CodeBuilder.with}. This composing of method calls on the * builder enables the composing of transforms (as described later). - *

- * Unless otherwise noted, passing a {@code null} argument to a constructor - * or method of any Class-File API class or interface will cause a {@link - * NullPointerException} to be thrown. Additionally, - * invoking a method with an array or collection containing a {@code null} element - * will cause a {@code NullPointerException}, unless otherwise specified. * *

Symbolic information

* To describe symbolic information for classes and types, the API uses the @@ -272,14 +266,22 @@ * symbolic information, one accepting nominal descriptors, and the other * accepting constant pool entries. * - *

Consistency checks, syntax checks and verification

- * The Class-File API performs checks to ensure arguments are representable in - * the {@code class} file format. A value that is lost when it is built to a - * {@code class} file and re-parsed to a model is rejected with an {@link - * IllegalArgumentException}. For example, a negative value or a value over - * {@code 65535} is lost when built to a {@link ##u2 u2} item, with - * the range {@code [0, 65535]}. In particular, any variable-sized table - * exceeding its maximum representable size is rejected. + *

Consistency checks, syntax checks and verification

+ * The Class-File API performs checks to ensure arguments to construct {@code + * class} file structures are representable in the {@code class} file format. + * An argument value that cannot be representable by its data type is rejected + * with an {@link IllegalArgumentException}. For example, an {@code int} value + * cannot be out of the range of its {@linkplain java.lang.classfile##data-types + * data type}; a {@code List} cannot exceed the maximum representable size of + * its table data type, or contain an unrepresentable element. Restrictions + * based on underlying data type, such as the {@code int} and {@code List} ones + * before, are specified on the corresponding APIs. Unless otherwise noted, in + * all structures, a {@code String} cannot exceed {@code 65535} bytes when + * represented in modified UTF-8 format. + *

+ * Unless otherwise noted, passing null or an array or collection that contains + * null as an element to a constructor or method of any Class-File API class or + * interface will cause a {@link NullPointerException} to be thrown. *

* No consistency checks are performed while building or transforming classfiles * (except for null and representable arguments checks). All builders and diff --git a/src/java.base/share/classes/java/lang/invoke/CallSite.java b/src/java.base/share/classes/java/lang/invoke/CallSite.java index e9e3477f96f..1f1a03bccca 100644 --- a/src/java.base/share/classes/java/lang/invoke/CallSite.java +++ b/src/java.base/share/classes/java/lang/invoke/CallSite.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2023, 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 diff --git a/src/java.base/share/classes/java/lang/invoke/MethodType.java b/src/java.base/share/classes/java/lang/invoke/MethodType.java index 3d15ce68710..afbe63709fd 100644 --- a/src/java.base/share/classes/java/lang/invoke/MethodType.java +++ b/src/java.base/share/classes/java/lang/invoke/MethodType.java @@ -1194,10 +1194,6 @@ class MethodType static MethodType fromDescriptor(String descriptor, ClassLoader loader) throws IllegalArgumentException, TypeNotPresentException { - if (!descriptor.startsWith("(") || // also generates NPE if needed - descriptor.indexOf(')') < 0 || - descriptor.indexOf('.') >= 0) - throw newIllegalArgumentException("not a method descriptor: "+descriptor); List> types = BytecodeDescriptor.parseMethod(descriptor, loader); Class rtype = types.remove(types.size() - 1); Class[] ptypes = listToArray(types); diff --git a/src/java.base/share/classes/java/lang/invoke/VarHandle.java b/src/java.base/share/classes/java/lang/invoke/VarHandle.java index 9246fdc0395..0bcb9a660da 100644 --- a/src/java.base/share/classes/java/lang/invoke/VarHandle.java +++ b/src/java.base/share/classes/java/lang/invoke/VarHandle.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 diff --git a/src/java.base/share/classes/java/lang/module/Configuration.java b/src/java.base/share/classes/java/lang/module/Configuration.java index a76a32cfb28..40eeddc3f0b 100644 --- a/src/java.base/share/classes/java/lang/module/Configuration.java +++ b/src/java.base/share/classes/java/lang/module/Configuration.java @@ -44,6 +44,7 @@ import java.util.stream.Stream; import jdk.internal.misc.CDS; import jdk.internal.module.ModuleReferenceImpl; import jdk.internal.module.ModuleTarget; +import jdk.internal.vm.annotation.AOTSafeClassInitializer; import jdk.internal.vm.annotation.Stable; /** @@ -155,6 +156,7 @@ import jdk.internal.vm.annotation.Stable; * @since 9 * @see java.lang.ModuleLayer */ +@AOTSafeClassInitializer public final class Configuration { // @see Configuration#empty() diff --git a/src/java.base/share/classes/java/lang/ref/Cleaner.java b/src/java.base/share/classes/java/lang/ref/Cleaner.java index 20a964be21d..ebe230a2f00 100644 --- a/src/java.base/share/classes/java/lang/ref/Cleaner.java +++ b/src/java.base/share/classes/java/lang/ref/Cleaner.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 diff --git a/src/java.base/share/classes/java/lang/ref/PhantomReference.java b/src/java.base/share/classes/java/lang/ref/PhantomReference.java index a6e144dc7b6..5f6f47a14b3 100644 --- a/src/java.base/share/classes/java/lang/ref/PhantomReference.java +++ b/src/java.base/share/classes/java/lang/ref/PhantomReference.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 diff --git a/src/java.base/share/classes/java/lang/ref/SoftReference.java b/src/java.base/share/classes/java/lang/ref/SoftReference.java index 0bc372eb105..1409cd89d0f 100644 --- a/src/java.base/share/classes/java/lang/ref/SoftReference.java +++ b/src/java.base/share/classes/java/lang/ref/SoftReference.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 diff --git a/src/java.base/share/classes/java/lang/ref/WeakReference.java b/src/java.base/share/classes/java/lang/ref/WeakReference.java index 4b4f834485e..4bdce2ace98 100644 --- a/src/java.base/share/classes/java/lang/ref/WeakReference.java +++ b/src/java.base/share/classes/java/lang/ref/WeakReference.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 diff --git a/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java b/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java index 1637d26b571..54000b916e2 100644 --- a/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java +++ b/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java @@ -97,6 +97,8 @@ public class AccessibleObject implements AnnotatedElement { * objects in the array * @throws SecurityException if an element in the array is a constructor for {@code * java.lang.Class} + * @throws NullPointerException if {@code array} or any of its elements is + * {@code null} */ @CallerSensitive public static void setAccessible(AccessibleObject[] array, boolean flag) { diff --git a/src/java.base/share/classes/java/lang/reflect/Array.java b/src/java.base/share/classes/java/lang/reflect/Array.java index 2fbad52c374..4e676c050d7 100644 --- a/src/java.base/share/classes/java/lang/reflect/Array.java +++ b/src/java.base/share/classes/java/lang/reflect/Array.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -98,8 +98,8 @@ class Array { * @param dimensions an array of {@code int} representing the dimensions of * the new array * @return the new array - * @throws NullPointerException if the specified - * {@code componentType} argument is null + * @throws NullPointerException if any of the specified + * {@code componentType} or {@code dimensions} arguments is null * @throws IllegalArgumentException if the specified {@code dimensions} * argument is a zero-dimensional array, if componentType is {@link * Void#TYPE}, or if the number of dimensions of the requested array @@ -117,8 +117,8 @@ class Array { * * @param array the array * @return the length of the array - * @throws IllegalArgumentException if the object argument is not - * an array + * @throws NullPointerException if {@code array} is {@code null} + * @throws IllegalArgumentException if {@code array} is not an array */ @IntrinsicCandidate public static native int getLength(Object array) diff --git a/src/java.base/share/classes/java/lang/reflect/ClassFileFormatVersion.java b/src/java.base/share/classes/java/lang/reflect/ClassFileFormatVersion.java index 4a63dd157f8..1990a467b60 100644 --- a/src/java.base/share/classes/java/lang/reflect/ClassFileFormatVersion.java +++ b/src/java.base/share/classes/java/lang/reflect/ClassFileFormatVersion.java @@ -379,10 +379,22 @@ public enum ClassFileFormatVersion { * @since 26 * * @see + * href="https://docs.oracle.com/en/java/javase/26/docs/specs/jvms/index.html"> * The Java Virtual Machine Specification, Java SE 26 Edition */ RELEASE_26(70), + + /** + * The version introduced by the Java Platform, Standard Edition + * 27. + * + * @since 27 + * + * @see + * The Java Virtual Machine Specification, Java SE 27 Edition + */ + RELEASE_27(71), ; // Reduce code churn when appending new constants // Note to maintainers: when adding constants for newer releases, @@ -398,7 +410,7 @@ public enum ClassFileFormatVersion { * {@return the latest class file format version} */ public static ClassFileFormatVersion latest() { - return RELEASE_26; + return RELEASE_27; } /** @@ -433,6 +445,7 @@ public enum ClassFileFormatVersion { * ClassFileFormatVersion.valueOf(Runtime.Version.parse("17"))} * * @param rv runtime version to map to a class file format version + * @throws NullPointerException if {@code rv} is {@code null} * @throws IllegalArgumentException if the feature of version * argument is greater than the feature of the platform version. */ diff --git a/src/java.base/share/classes/java/lang/reflect/Field.java b/src/java.base/share/classes/java/lang/reflect/Field.java index 663e3453343..a4f0afa0199 100644 --- a/src/java.base/share/classes/java/lang/reflect/Field.java +++ b/src/java.base/share/classes/java/lang/reflect/Field.java @@ -367,7 +367,7 @@ class Field extends AccessibleObject implements Member { * @jls 8.3.1 Field Modifiers */ public String toString() { - int mod = getModifiers(); + int mod = getModifiers() & Modifier.fieldModifiers(); return (((mod == 0) ? "" : (Modifier.toString(mod) + " ")) + getType().getTypeName() + " " + getDeclaringClass().getTypeName() + "." @@ -400,7 +400,7 @@ class Field extends AccessibleObject implements Member { * @jls 8.3.1 Field Modifiers */ public String toGenericString() { - int mod = getModifiers(); + int mod = getModifiers() & Modifier.fieldModifiers(); Type fieldType = getGenericType(); return (((mod == 0) ? "" : (Modifier.toString(mod) + " ")) + fieldType.getTypeName() + " " diff --git a/src/java.base/share/classes/java/lang/reflect/InaccessibleObjectException.java b/src/java.base/share/classes/java/lang/reflect/InaccessibleObjectException.java index 7bc9ea33b2b..29d6eb13602 100644 --- a/src/java.base/share/classes/java/lang/reflect/InaccessibleObjectException.java +++ b/src/java.base/share/classes/java/lang/reflect/InaccessibleObjectException.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 @@ -47,7 +47,7 @@ public class InaccessibleObjectException extends RuntimeException { * message. * * @param msg - * The detail message + * The detail message, may be {@code null} */ public InaccessibleObjectException(String msg) { super(msg); diff --git a/src/java.base/share/classes/java/lang/reflect/InvocationTargetException.java b/src/java.base/share/classes/java/lang/reflect/InvocationTargetException.java index a6ef4fa289c..efdca9bcbd1 100644 --- a/src/java.base/share/classes/java/lang/reflect/InvocationTargetException.java +++ b/src/java.base/share/classes/java/lang/reflect/InvocationTargetException.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -62,7 +62,7 @@ public class InvocationTargetException extends ReflectiveOperationException { /** * Constructs a InvocationTargetException with a target exception. * - * @param target the target exception + * @param target the target exception, may be {@code null} */ public InvocationTargetException(Throwable target) { super((Throwable)null); // Disallow initCause @@ -73,8 +73,8 @@ public class InvocationTargetException extends ReflectiveOperationException { * Constructs a InvocationTargetException with a target exception * and a detail message. * - * @param target the target exception - * @param s the detail message + * @param target the target exception, may be {@code null} + * @param s the detail message, may be {@code null} */ public InvocationTargetException(Throwable target, String s) { super(s, null); // Disallow initCause diff --git a/src/java.base/share/classes/java/lang/reflect/MalformedParametersException.java b/src/java.base/share/classes/java/lang/reflect/MalformedParametersException.java index ae2da8c9279..fa754be5474 100644 --- a/src/java.base/share/classes/java/lang/reflect/MalformedParametersException.java +++ b/src/java.base/share/classes/java/lang/reflect/MalformedParametersException.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -65,7 +65,7 @@ public class MalformedParametersException extends RuntimeException { /** * Create a {@code MalformedParametersException}. * - * @param reason The reason for the exception. + * @param reason The reason for the exception, may be {@code null} */ public MalformedParametersException(String reason) { super(reason); diff --git a/src/java.base/share/classes/java/lang/reflect/Parameter.java b/src/java.base/share/classes/java/lang/reflect/Parameter.java index d4a53e193a9..8ecf8060615 100644 --- a/src/java.base/share/classes/java/lang/reflect/Parameter.java +++ b/src/java.base/share/classes/java/lang/reflect/Parameter.java @@ -126,7 +126,7 @@ public final class Parameter implements AnnotatedElement { final Type type = getParameterizedType(); final String typename = type.getTypeName(); - sb.append(Modifier.toString(getModifiers())); + sb.append(Modifier.toString(getModifiers() & Modifier.parameterModifiers() )); if(0 != modifiers) sb.append(' '); diff --git a/src/java.base/share/classes/java/lang/reflect/Proxy.java b/src/java.base/share/classes/java/lang/reflect/Proxy.java index dc512e86590..b811deb863d 100644 --- a/src/java.base/share/classes/java/lang/reflect/Proxy.java +++ b/src/java.base/share/classes/java/lang/reflect/Proxy.java @@ -897,7 +897,8 @@ public class Proxy implements java.io.Serializable { * of interfaces but in a different order will result in two distinct * proxy classes. * - * @param loader the class loader to define the proxy class + * @param loader the class loader to define the proxy class, may be + * {@code null} to represent the bootstrap class loader * @param interfaces the list of interfaces for the proxy class * to implement * @param h the invocation handler to dispatch method invocations to diff --git a/src/java.base/share/classes/java/lang/reflect/TypeVariable.java b/src/java.base/share/classes/java/lang/reflect/TypeVariable.java index 01746e34385..85e99fd3f92 100644 --- a/src/java.base/share/classes/java/lang/reflect/TypeVariable.java +++ b/src/java.base/share/classes/java/lang/reflect/TypeVariable.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 @@ -72,7 +72,7 @@ public interface TypeVariable extends Type, Annota Type[] getBounds(); /** - * Returns the {@code GenericDeclaration} object representing the + * Returns a {@code GenericDeclaration} object representing the * generic declaration declared for this type variable. * * @return the generic declaration declared for this type variable. diff --git a/src/java.base/share/classes/java/lang/reflect/UndeclaredThrowableException.java b/src/java.base/share/classes/java/lang/reflect/UndeclaredThrowableException.java index 0b086330de2..25d8affa474 100644 --- a/src/java.base/share/classes/java/lang/reflect/UndeclaredThrowableException.java +++ b/src/java.base/share/classes/java/lang/reflect/UndeclaredThrowableException.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -61,7 +61,7 @@ public class UndeclaredThrowableException extends RuntimeException { * specified {@code Throwable}. * * @param undeclaredThrowable the undeclared checked exception - * that was thrown + * that was thrown, may be {@code null} */ public UndeclaredThrowableException(Throwable undeclaredThrowable) { super(null, undeclaredThrowable); // Disallow initCause @@ -72,8 +72,8 @@ public class UndeclaredThrowableException extends RuntimeException { * specified {@code Throwable} and a detail message. * * @param undeclaredThrowable the undeclared checked exception - * that was thrown - * @param s the detail message + * that was thrown, may be {@code null} + * @param s the detail message, may be {@code null} */ public UndeclaredThrowableException(Throwable undeclaredThrowable, String s) diff --git a/src/java.base/share/classes/java/lang/runtime/ExactConversionsSupport.java b/src/java.base/share/classes/java/lang/runtime/ExactConversionsSupport.java index 6e17a4b85a0..a1a93859c42 100644 --- a/src/java.base/share/classes/java/lang/runtime/ExactConversionsSupport.java +++ b/src/java.base/share/classes/java/lang/runtime/ExactConversionsSupport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -64,9 +64,12 @@ package java.lang.runtime; * floating-point type is considered exact. * * - * @jls 5.7.1 Exact Testing Conversions - * @jls 5.7.2 Unconditionally Exact Testing Conversions - * @jls 15.20.2 The instanceof Operator + * @see + * JLS 5.7.1 Exact Testing Conversions + * @see + * JLS 5.7.2 Unconditionally Exact Testing Conversions + * @see + * JLS 15.20.2 The instanceof Operator * * @implNote Some exactness checks describe a test which can be redirected * safely through one of the existing methods. Those are omitted too (i.e., diff --git a/src/java.base/share/classes/java/net/Authenticator.java b/src/java.base/share/classes/java/net/Authenticator.java index fb33596feed..d7b64d4bc73 100644 --- a/src/java.base/share/classes/java/net/Authenticator.java +++ b/src/java.base/share/classes/java/net/Authenticator.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 diff --git a/src/java.base/share/classes/java/net/HostPortrange.java b/src/java.base/share/classes/java/net/HostPortrange.java index 289846841c9..f7a5b911633 100644 --- a/src/java.base/share/classes/java/net/HostPortrange.java +++ b/src/java.base/share/classes/java/net/HostPortrange.java @@ -60,6 +60,11 @@ class HostPortrange { } HostPortrange(String scheme, String host) { + // Defensive validation first + if (host == null || host.isEmpty()) { + throw new IllegalArgumentException("Invalid URL authority"); + } + // Parse the host name. A name has up to three components, the // hostname, a port number, or two numbers representing a port // range. "www.example.com:8080-9090" is a valid host name. diff --git a/src/java.base/share/classes/java/net/URLPermission.java b/src/java.base/share/classes/java/net/URLPermission.java index bf87cad8077..ac51d6c60a1 100644 --- a/src/java.base/share/classes/java/net/URLPermission.java +++ b/src/java.base/share/classes/java/net/URLPermission.java @@ -527,6 +527,9 @@ public final class URLPermission extends Permission { HostPortrange p; Authority(String scheme, String authority) { + if (authority == null || authority.isEmpty()) { + throw new IllegalArgumentException("Invalid URL: authority is empty"); + } int at = authority.indexOf('@'); if (at == -1) { p = new HostPortrange(scheme, authority); diff --git a/src/java.base/share/classes/java/net/doc-files/net-properties.html b/src/java.base/share/classes/java/net/doc-files/net-properties.html index 54d4cd55a9b..f7767655235 100644 --- a/src/java.base/share/classes/java/net/doc-files/net-properties.html +++ b/src/java.base/share/classes/java/net/doc-files/net-properties.html @@ -1,6 +1,6 @@ <-------second part-------> - // index: 0 2 4 6 i2 i3 - // operands: | 6 | i2 | i3 | bsm1 | bsm2 | bsm3 | - // - count = getOperandOffsetAt(operands, 0) / 2; + if (offsets != null) { + count = offsets.length(); } if (DEBUG) { System.err.println("ConstantPool.getBootstrapMethodsCount: count = " + count); @@ -463,12 +461,12 @@ public class ConstantPool extends Metadata implements ClassConstants { } public int getBootstrapMethodArgsCount(int bsmIndex) { - U2Array operands = getOperands(); + U4Array offs = getOffsets(); + U2Array bsms = getBootstrapMethods(); if (Assert.ASSERTS_ENABLED) { - Assert.that(operands != null, "Operands is not present"); + Assert.that(offs != null && bsms != null, "BSM attribute is not present"); } - int bsmOffset = getOperandOffsetAt(operands, bsmIndex); - int argc = operands.at(bsmOffset + INDY_ARGC_OFFSET); + int argc = bsms.at(offs.at(bsmIndex) + INDY_ARGC_OFFSET); if (DEBUG) { System.err.println("ConstantPool.getBootstrapMethodArgsCount: bsm index = " + bsmIndex + ", args count = " + argc); } @@ -476,15 +474,16 @@ public class ConstantPool extends Metadata implements ClassConstants { } public short[] getBootstrapMethodAt(int bsmIndex) { - U2Array operands = getOperands(); - if (operands == null) return null; // safety first - int basePos = getOperandOffsetAt(operands, bsmIndex); + U4Array offs = getOffsets(); + U2Array bsms = getBootstrapMethods(); + if (offs == null || bsms == null) return null; // safety first + int basePos = offs.at(bsmIndex); int argv = basePos + INDY_ARGV_OFFSET; - int argc = operands.at(basePos + INDY_ARGC_OFFSET); + int argc = getBootstrapMethodArgsCount(bsmIndex); int endPos = argv + argc; short[] values = new short[endPos - basePos]; for (int j = 0; j < values.length; j++) { - values[j] = operands.at(basePos+j); + values[j] = bsms.at(basePos+j); } return values; } @@ -773,8 +772,7 @@ public class ConstantPool extends Metadata implements ClassConstants { // Return the offset of the requested Bootstrap Method in the operands array private int getOperandOffsetAt(U2Array operands, int bsmIndex) { - return VM.getVM().buildIntFromShorts(operands.at(bsmIndex * 2), - operands.at(bsmIndex * 2 + 1)); + return 0; } } diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/InstanceKlass.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/InstanceKlass.java index 0cd743372d5..785bb85e640 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/InstanceKlass.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/InstanceKlass.java @@ -62,6 +62,16 @@ public class InstanceKlass extends Klass { private static int CLASS_STATE_FULLY_INITIALIZED; private static int CLASS_STATE_INITIALIZATION_ERROR; + public long getAccessFlags() { return accessFlags.getValue(this); } + // Convenience routine + public AccessFlags getAccessFlagsObj() { return new AccessFlags(getAccessFlags()); } + + public boolean isPublic() { return getAccessFlagsObj().isPublic(); } + public boolean isFinal() { return getAccessFlagsObj().isFinal(); } + public boolean isInterface() { return getAccessFlagsObj().isInterface(); } + public boolean isAbstract() { return getAccessFlagsObj().isAbstract(); } + public boolean isSuper() { return getAccessFlagsObj().isSuper(); } + public boolean isSynthetic() { return getAccessFlagsObj().isSynthetic(); } private static synchronized void initialize(TypeDataBase db) throws WrongTypeException { Type type = db.lookupType("InstanceKlass"); @@ -88,6 +98,7 @@ public class InstanceKlass extends Klass { breakpoints = type.getAddressField("_breakpoints"); } headerSize = type.getSize(); + accessFlags = new CIntField(type.getCIntegerField("_access_flags"), 0); // read internal field flags constants FIELD_FLAG_IS_INITIALIZED = db.lookupIntConstant("FieldInfo::FieldFlags::_ff_initialized"); @@ -150,6 +161,7 @@ public class InstanceKlass extends Klass { private static CIntField initState; private static CIntField itableLen; private static CIntField nestHostIndex; + private static CIntField accessFlags; private static AddressField breakpoints; // type safe enum for ClassState from instanceKlass.hpp @@ -499,7 +511,7 @@ public class InstanceKlass extends Klass { } } - public boolean implementsInterface(Klass k) { + public boolean implementsInterface(InstanceKlass k) { if (Assert.ASSERTS_ENABLED) { Assert.that(k.isInterface(), "should not reach here"); } @@ -511,7 +523,7 @@ public class InstanceKlass extends Klass { return false; } - boolean computeSubtypeOf(Klass k) { + boolean computeSubtypeOf(InstanceKlass k) { if (k.isInterface()) { return implementsInterface(k); } else { @@ -535,6 +547,7 @@ public class InstanceKlass extends Klass { visitor.doCInt(nonstaticOopMapSize, true); visitor.doCInt(initState, true); visitor.doCInt(itableLen, true); + visitor.doCInt(accessFlags, true); } /* diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Klass.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Klass.java index 1b8a9d0ef69..561c4683d29 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Klass.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Klass.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -57,7 +57,6 @@ public class Klass extends Metadata implements ClassConstants { superField = new MetadataField(type.getAddressField("_super"), 0); layoutHelper = new IntField(type.getJIntField("_layout_helper"), 0); name = type.getAddressField("_name"); - accessFlags = new CIntField(type.getCIntegerField("_access_flags"), 0); try { traceIDField = type.getField("_trace_id"); } catch(Exception e) { @@ -95,7 +94,6 @@ public class Klass extends Metadata implements ClassConstants { private static MetadataField superField; private static IntField layoutHelper; private static AddressField name; - private static CIntField accessFlags; private static MetadataField subklass; private static MetadataField nextSibling; private static MetadataField nextLink; @@ -117,9 +115,6 @@ public class Klass extends Metadata implements ClassConstants { public Klass getJavaSuper() { return null; } public int getLayoutHelper() { return layoutHelper.getValue(this); } public Symbol getName() { return getSymbol(name); } - public long getAccessFlags() { return accessFlags.getValue(this); } - // Convenience routine - public AccessFlags getAccessFlagsObj(){ return new AccessFlags(getAccessFlags()); } public Klass getSubklassKlass() { return (Klass) subklass.getValue(this); } public Klass getNextSiblingKlass() { return (Klass) nextSibling.getValue(this); } public Klass getNextLinkKlass() { return (Klass) nextLink.getValue(this); } @@ -175,7 +170,6 @@ public class Klass extends Metadata implements ClassConstants { visitor.doMetadata(superField, true); visitor.doInt(layoutHelper, true); // visitor.doOop(name, true); - visitor.doCInt(accessFlags, true); visitor.doMetadata(subklass, true); visitor.doMetadata(nextSibling, true); visitor.doCInt(vtableLen, true); @@ -205,12 +199,4 @@ public class Klass extends Metadata implements ClassConstants { // The subclasses override this to produce the correct form, eg // Ljava/lang/String; For ArrayKlasses getName itself is the signature. public String signature() { return getName().asString(); } - - // Convenience routines - public boolean isPublic() { return getAccessFlagsObj().isPublic(); } - public boolean isFinal() { return getAccessFlagsObj().isFinal(); } - public boolean isInterface() { return getAccessFlagsObj().isInterface(); } - public boolean isAbstract() { return getAccessFlagsObj().isAbstract(); } - public boolean isSuper() { return getAccessFlagsObj().isSuper(); } - public boolean isSynthetic() { return getAccessFlagsObj().isSynthetic(); } } diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ObjectHeap.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ObjectHeap.java index a4cdb671959..c71cb3156ea 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ObjectHeap.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ObjectHeap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -107,7 +107,7 @@ public class ObjectHeap { /** iterate objects of given Klass. param 'includeSubtypes' tells whether to * include objects of subtypes or not */ - public void iterateObjectsOfKlass(HeapVisitor visitor, final Klass k, boolean includeSubtypes) { + public void iterateObjectsOfKlass(HeapVisitor visitor, final InstanceKlass k, boolean includeSubtypes) { if (includeSubtypes) { if (k.isFinal()) { // do the simpler "exact" klass loop @@ -124,7 +124,7 @@ public class ObjectHeap { } /** iterate objects of given Klass (objects of subtypes included) */ - public void iterateObjectsOfKlass(HeapVisitor visitor, final Klass k) { + public void iterateObjectsOfKlass(HeapVisitor visitor, final InstanceKlass k) { iterateObjectsOfKlass(visitor, k, true); } diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/ConcurrentLocksPrinter.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/ConcurrentLocksPrinter.java index fa28a96e333..c4abb3e946d 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/ConcurrentLocksPrinter.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/ConcurrentLocksPrinter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -64,7 +64,7 @@ public class ConcurrentLocksPrinter { private void fillLocks() { VM vm = VM.getVM(); SystemDictionary sysDict = vm.getSystemDictionary(); - Klass absOwnSyncKlass = sysDict.getAbstractOwnableSynchronizerKlass(); + InstanceKlass absOwnSyncKlass = sysDict.getAbstractOwnableSynchronizerKlass(); ObjectHeap heap = vm.getObjectHeap(); // may be not loaded at all if (absOwnSyncKlass != null) { diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/Frame.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/Frame.java index 27efb631f79..ee9e0ecdafd 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/Frame.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/Frame.java @@ -87,7 +87,7 @@ public abstract class Frame implements Cloneable { CodeBlob cb = VM.getVM().getCodeCache().findBlob(pc); if (cb != null && cb.isJavaMethod()) { NMethod nm = (NMethod) cb; - if (pc.equals(nm.deoptHandlerBegin())) { + if (pc.equals(nm.deoptHandlerEntry())) { if (Assert.ASSERTS_ENABLED) { Assert.that(this.getUnextendedSP() != null, "null SP in Java frame"); } diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/StackValueCollection.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/StackValueCollection.java index a2808bfff31..f6d33d3c7e8 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/StackValueCollection.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/StackValueCollection.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/ThreadLocalAllocBuffer.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/ThreadLocalAllocBuffer.java index e23e63806bd..11f03a6003e 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/ThreadLocalAllocBuffer.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/ThreadLocalAllocBuffer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -76,9 +76,10 @@ public class ThreadLocalAllocBuffer extends VMObject { private long endReserve() { long labAlignmentReserve = VM.getVM().getLabAlignmentReserve(); + long reserveForAllocationPrefetch = VM.getVM().getReserveForAllocationPrefetch(); long heapWordSize = VM.getVM().getHeapWordSize(); - return labAlignmentReserve * heapWordSize; + return Math.max(labAlignmentReserve, reserveForAllocationPrefetch) * heapWordSize; } /** Support for iteration over heap -- not sure how this will diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/VM.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/VM.java index 1607563150a..dc27a4fc59e 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/VM.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/VM.java @@ -123,6 +123,7 @@ public class VM { private int invocationEntryBCI; private ReversePtrs revPtrs; private VMRegImpl vmregImpl; + private int reserveForAllocationPrefetch; private int labAlignmentReserve; // System.getProperties from debuggee VM @@ -446,6 +447,8 @@ public class VM { boolType = (CIntegerType) db.lookupType("bool"); Type threadLocalAllocBuffer = db.lookupType("ThreadLocalAllocBuffer"); + CIntegerField reserveForAllocationPrefetchField = threadLocalAllocBuffer.getCIntegerField("_reserve_for_allocation_prefetch"); + reserveForAllocationPrefetch = (int)reserveForAllocationPrefetchField.getCInteger(intType); Type collectedHeap = db.lookupType("CollectedHeap"); CIntegerField labAlignmentReserveField = collectedHeap.getCIntegerField("_lab_alignment_reserve"); @@ -912,6 +915,10 @@ public class VM { return vmInternalInfo; } + public int getReserveForAllocationPrefetch() { + return reserveForAllocationPrefetch; + } + public int getLabAlignmentReserve() { return labAlignmentReserve; } diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/aarch64/AARCH64Frame.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/aarch64/AARCH64Frame.java index 5ae4cb703b3..7233d508cbc 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/aarch64/AARCH64Frame.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/aarch64/AARCH64Frame.java @@ -272,9 +272,7 @@ public class AARCH64Frame extends Frame { if (cb != null) { if (cb.isUpcallStub()) { return senderForUpcallStub(map, (UpcallStub)cb); - } else if (cb.isContinuationStub()) { - return senderForContinuationStub(map, cb); - } else { + } else if (cb.getFrameSize() > 0) { return senderForCompiledFrame(map, cb); } } @@ -389,7 +387,11 @@ public class AARCH64Frame extends Frame { if (Assert.ASSERTS_ENABLED) { Assert.that(cb.getFrameSize() > 0, "must have non-zero frame size"); } - Address senderSP = getUnextendedSP().addOffsetTo(cb.getFrameSize()); + + // TODO: senderSP should consider not only PreserveFramePointer but also _sp_is_trusted. + Address senderSP = !VM.getVM().getCommandLineBooleanFlag("PreserveFramePointer") + ? getUnextendedSP().addOffsetTo(cb.getFrameSize()) + : getSenderSP(); // The return_address is always the word on the stack Address senderPC = stripPAC(senderSP.getAddressAt(-1 * VM.getVM().getAddressSize())); diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/amd64/AMD64CurrentFrameGuess.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/amd64/AMD64CurrentFrameGuess.java index e85973773dc..030d502075d 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/amd64/AMD64CurrentFrameGuess.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/amd64/AMD64CurrentFrameGuess.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 diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/amd64/AMD64Frame.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/amd64/AMD64Frame.java index 360f62a253d..fa9d50160e1 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/amd64/AMD64Frame.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/amd64/AMD64Frame.java @@ -272,9 +272,7 @@ public class AMD64Frame extends Frame { if (cb != null) { if (cb.isUpcallStub()) { return senderForUpcallStub(map, (UpcallStub)cb); - } else if (cb.isContinuationStub()) { - return senderForContinuationStub(map, cb); - } else { + } else if (cb.getFrameSize() > 0) { return senderForCompiledFrame(map, cb); } } diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/amd64/AMD64RegisterMap.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/amd64/AMD64RegisterMap.java index b1ae1e8a001..dd8834f3d38 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/amd64/AMD64RegisterMap.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/amd64/AMD64RegisterMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/bsd_amd64/BsdAMD64JavaThreadPDAccess.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/bsd_amd64/BsdAMD64JavaThreadPDAccess.java index a2cdac72add..483c3e5dfde 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/bsd_amd64/BsdAMD64JavaThreadPDAccess.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/bsd_amd64/BsdAMD64JavaThreadPDAccess.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2020, 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 diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/linux_amd64/LinuxAMD64JavaThreadPDAccess.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/linux_amd64/LinuxAMD64JavaThreadPDAccess.java index f8672066189..5eb39ed66a8 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/linux_amd64/LinuxAMD64JavaThreadPDAccess.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/linux_amd64/LinuxAMD64JavaThreadPDAccess.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2020, 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 diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/win32_amd64/Win32AMD64JavaThreadPDAccess.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/win32_amd64/Win32AMD64JavaThreadPDAccess.java index a573b9a383a..9e47bf57602 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/win32_amd64/Win32AMD64JavaThreadPDAccess.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/win32_amd64/Win32AMD64JavaThreadPDAccess.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2020, 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 diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/tools/ClassLoaderStats.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/tools/ClassLoaderStats.java index b7c470e3477..0e65a41c571 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/tools/ClassLoaderStats.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/tools/ClassLoaderStats.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 @@ -90,7 +90,7 @@ public class ClassLoaderStats extends Tool { VM vm = VM.getVM(); ObjectHeap heap = vm.getObjectHeap(); - Klass classLoaderKlass = vm.getSystemDictionary().getClassLoaderKlass(); + InstanceKlass classLoaderKlass = vm.getSystemDictionary().getClassLoaderKlass(); try { heap.iterateObjectsOfKlass(new DefaultHeapVisitor() { public boolean doObj(Oop oop) { diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/tools/PStack.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/tools/PStack.java index 9865fbbe0f2..29d2954efdc 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/tools/PStack.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/tools/PStack.java @@ -70,7 +70,6 @@ public class PStack extends Tool { if (cdbg != null) { ConcurrentLocksPrinter concLocksPrinter = null; // compute and cache java Vframes. - initJFrameCache(); if (concurrentLocks) { concLocksPrinter = new ConcurrentLocksPrinter(out); } @@ -96,6 +95,7 @@ public class PStack extends Tool { return; } final boolean cdbgCanDemangle = cdbg.canDemangle(); + Map proxyToThread = createProxyToThread();; String fillerForAddress = " ".repeat(2 + 2 * (int) VM.getVM().getAddressSize()) + "\t"; for (Iterator itr = l.iterator() ; itr.hasNext();) { ThreadProxy th = itr.next(); @@ -109,6 +109,7 @@ public class PStack extends Tool { jthread.printThreadInfoOn(out); } while (f != null) { + Address senderSP = null; Address senderFP = null; Address senderPC = null; ClosestSymbol sym = f.closestSymbolToPC(); @@ -131,7 +132,7 @@ public class PStack extends Tool { // check interpreter frame Interpreter interp = VM.getVM().getInterpreter(); if (interp.contains(pc)) { - nameInfo = getJavaNames(th, f.localVariableBase()); + nameInfo = getJavaNames(jthread, f); // print codelet name if we can't determine method if (nameInfo == null || nameInfo.names() == null || nameInfo.names().length == 0) { out.print(" "); @@ -156,7 +157,7 @@ public class PStack extends Tool { } out.println(" (Native method)"); } else { - nameInfo = getJavaNames(th, f.localVariableBase()); + nameInfo = getJavaNames(jthread, f); // just print compiled code, if can't determine method if (nameInfo == null || nameInfo.names() == null || nameInfo.names().length == 0) { out.println(""); @@ -164,6 +165,12 @@ public class PStack extends Tool { } } else { out.println("<" + cb.getName() + ">"); + if (cb.getFrameSize() > 0) { + Frame senderFrame = f.toFrame().sender(jthread.newRegisterMap(true)); + senderSP = senderFrame.getSP(); + senderFP = senderFrame.getFP(); + senderPC = senderFrame.getPC(); + } } } else { printUnknown(out); @@ -180,11 +187,12 @@ public class PStack extends Tool { out.println(nameInfo.names()[i]); } } + senderSP = nameInfo.senderSP(); senderFP = nameInfo.senderFP(); senderPC = nameInfo.senderPC(); } } - f = f.sender(th, senderFP, senderPC); + f = f.sender(th, senderSP, senderFP, senderPC); } } catch (Exception exp) { exp.printStackTrace(); @@ -212,95 +220,74 @@ public class PStack extends Tool { } // -- Internals only below this point - private Map jframeCache; - private Map proxyToThread; private PrintStream out; private boolean verbose; private boolean concurrentLocks; - private void initJFrameCache() { - // cache frames for subsequent reference - jframeCache = new HashMap<>(); - proxyToThread = new HashMap<>(); + private Map createProxyToThread() { + Map proxyToThread = new HashMap<>(); Threads threads = VM.getVM().getThreads(); for (int i = 0; i < threads.getNumberOfThreads(); i++) { - JavaThread cur = threads.getJavaThreadAt(i); - List tmp = new ArrayList<>(10); - try { - for (JavaVFrame vf = cur.getLastJavaVFrameDbg(); vf != null; vf = vf.javaSender()) { - tmp.add(vf); - } - } catch (Exception exp) { - // may be we may get frames for other threads, continue - // after printing stack trace. - exp.printStackTrace(); - } - JavaVFrame[] jvframes = tmp.toArray(new JavaVFrame[0]); - jframeCache.put(cur.getThreadProxy(), jvframes); - proxyToThread.put(cur.getThreadProxy(), cur); + JavaThread jthread = threads.getJavaThreadAt(i); + proxyToThread.put(jthread.getThreadProxy(), jthread); } + return proxyToThread; } private void printUnknown(PrintStream out) { out.println("\t????????"); } - private static record JavaNameInfo(String[] names, Address senderFP, Address senderPC) {}; - - private JavaNameInfo getJavaNames(ThreadProxy th, Address fp) { - if (fp == null) { - return null; - } - JavaVFrame[] jvframes = jframeCache.get(th); - if (jvframes == null) return null; // not a java thread + private static record JavaNameInfo(String[] names, Address senderSP, Address senderFP, Address senderPC) {}; + private JavaNameInfo getJavaNames(JavaThread jthread, CFrame f) { List names = new ArrayList<>(10); - JavaVFrame bottomJVFrame = null; - for (int fCount = 0; fCount < jvframes.length; fCount++) { - JavaVFrame vf = jvframes[fCount]; - Frame f = vf.getFrame(); - if (fp.equals(f.getFP())) { - bottomJVFrame = vf; - StringBuilder sb = new StringBuilder(); - Method method = vf.getMethod(); - // a special char to identify java frames in output - sb.append("* "); - sb.append(method.externalNameAndSignature()); - sb.append(" bci:").append(vf.getBCI()); - int lineNumber = method.getLineNumberFromBCI(vf.getBCI()); - if (lineNumber != -1) { - sb.append(" line:").append(lineNumber); - } - - if (verbose) { - sb.append(" Method*:").append(method.getAddress()); - } - - if (vf.isCompiledFrame()) { - sb.append(" (Compiled frame"); - if (vf.isDeoptimized()) { - sb.append(" [deoptimized]"); - } - } else if (vf.isInterpretedFrame()) { - sb.append(" (Interpreted frame"); - } - if (vf.mayBeImpreciseDbg()) { - sb.append("; information may be imprecise"); - } - sb.append(")"); - names.add(sb.toString()); - } - } - + Address senderSP = null; Address senderFP = null; Address senderPC = null; - if (bottomJVFrame != null) { - Frame senderFrame = bottomJVFrame.getFrame().sender((RegisterMap)bottomJVFrame.getRegisterMap().clone()); + VFrame vf = VFrame.newVFrame(f.toFrame(), jthread.newRegisterMap(true), jthread, true, true); + while (vf != null && vf.isJavaFrame()) { + StringBuilder sb = new StringBuilder(); + Method method = ((JavaVFrame)vf).getMethod(); + // a special char to identify java frames in output + sb.append("* "); + sb.append(method.externalNameAndSignature()); + sb.append(" bci:").append(((JavaVFrame)vf).getBCI()); + int lineNumber = method.getLineNumberFromBCI(((JavaVFrame)vf).getBCI()); + if (lineNumber != -1) { + sb.append(" line:").append(lineNumber); + } + + if (verbose) { + sb.append(" Method*:").append(method.getAddress()); + } + + if (vf.isCompiledFrame()) { + sb.append(" (Compiled frame"); + if (vf.isDeoptimized()) { + sb.append(" [deoptimized]"); + } + } else if (vf.isInterpretedFrame()) { + sb.append(" (Interpreted frame"); + } + if (vf.mayBeImpreciseDbg()) { + sb.append("; information may be imprecise"); + } + sb.append(")"); + names.add(sb.toString()); + + // Keep registers in sender Frame + Frame senderFrame = vf.getFrame() + .sender((RegisterMap)vf.getRegisterMap().clone()); + senderSP = senderFrame.getSP(); senderFP = senderFrame.getFP(); senderPC = senderFrame.getPC(); + + // Get sender VFrame for next stack walking + vf = vf.sender(true); } - return new JavaNameInfo(names.toArray(new String[0]), senderFP, senderPC); + return new JavaNameInfo(names.toArray(new String[0]), senderSP, senderFP, senderPC); } public void setVerbose(boolean verbose) { diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/ui/AnnotatedMemoryPanel.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/ui/AnnotatedMemoryPanel.java index 76b8595e3b2..05044ec8afc 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/ui/AnnotatedMemoryPanel.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/ui/AnnotatedMemoryPanel.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2022, 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 diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/PlatformInfo.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/PlatformInfo.java index 53fa5204349..2ca720dd573 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/PlatformInfo.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/PlatformInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2022, 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 diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/U4Array.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/U4Array.java new file mode 100644 index 00000000000..9836614d2c9 --- /dev/null +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/U4Array.java @@ -0,0 +1,64 @@ +/* + * 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 sun.jvm.hotspot.utilities; + +import sun.jvm.hotspot.debugger.Address; +import sun.jvm.hotspot.runtime.VM; +import sun.jvm.hotspot.types.Type; +import sun.jvm.hotspot.types.TypeDataBase; +import sun.jvm.hotspot.types.WrongTypeException; +import sun.jvm.hotspot.utilities.Observable; +import sun.jvm.hotspot.utilities.Observer; + +public class U4Array extends GenericArray { + static { + VM.registerVMInitializedObserver(new Observer() { + public void update(Observable o, Object data) { + initialize(VM.getVM().getTypeDataBase()); + } + }); + } + + private static synchronized void initialize(TypeDataBase db) throws WrongTypeException { + elemType = db.lookupType("u4"); + Type type = db.lookupType("Array"); + dataFieldOffset = type.getAddressField("_data").getOffset(); + } + + private static long dataFieldOffset; + protected static Type elemType; + + public U4Array(Address addr) { + super(addr, dataFieldOffset); + } + + public int at(int i) { + return (int)getIntegerAt(i); + } + + public Type getElemType() { + return elemType; + } +} diff --git a/src/jdk.hotspot.agent/windows/native/libsaproc/sawindbg.cpp b/src/jdk.hotspot.agent/windows/native/libsaproc/sawindbg.cpp index 74be5db0d79..f2994c0d827 100644 --- a/src/jdk.hotspot.agent/windows/native/libsaproc/sawindbg.cpp +++ b/src/jdk.hotspot.agent/windows/native/libsaproc/sawindbg.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2024, 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 diff --git a/src/jdk.httpserver/share/classes/com/sun/net/httpserver/Authenticator.java b/src/jdk.httpserver/share/classes/com/sun/net/httpserver/Authenticator.java index ddef30f0c5c..6bb97e0799a 100644 --- a/src/jdk.httpserver/share/classes/com/sun/net/httpserver/Authenticator.java +++ b/src/jdk.httpserver/share/classes/com/sun/net/httpserver/Authenticator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2006, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -40,7 +40,7 @@ public abstract class Authenticator { /** * Constructor for subclasses to call. */ - protected Authenticator () { } + protected Authenticator() { } /** * Base class for return type from {@link #authenticate(HttpExchange)} method. @@ -50,7 +50,7 @@ public abstract class Authenticator { /** * Constructor for subclasses to call. */ - protected Result () {} + protected Result() {} } /** @@ -67,7 +67,7 @@ public abstract class Authenticator { * @param responseCode the response code to associate with this * {@code Failure} instance */ - public Failure (int responseCode) { + public Failure(int responseCode) { this.responseCode = responseCode; } @@ -94,7 +94,7 @@ public abstract class Authenticator { * * @param p the authenticated user you wish to set as {@code Principal} */ - public Success (HttpPrincipal p) { + public Success(HttpPrincipal p) { principal = p; } @@ -126,7 +126,7 @@ public abstract class Authenticator { * @param responseCode the response code to associate with this * {@code Retry} instance */ - public Retry (int responseCode) { + public Retry(int responseCode) { this.responseCode = responseCode; } @@ -158,5 +158,5 @@ public abstract class Authenticator { * @param exch the {@code HttpExchange} upon which authenticate is called * @return the result */ - public abstract Result authenticate (HttpExchange exch); + public abstract Result authenticate(HttpExchange exch); } diff --git a/src/jdk.httpserver/share/classes/com/sun/net/httpserver/BasicAuthenticator.java b/src/jdk.httpserver/share/classes/com/sun/net/httpserver/BasicAuthenticator.java index b2cf78dd992..8c306888370 100644 --- a/src/jdk.httpserver/share/classes/com/sun/net/httpserver/BasicAuthenticator.java +++ b/src/jdk.httpserver/share/classes/com/sun/net/httpserver/BasicAuthenticator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2006, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -35,7 +35,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; /** * BasicAuthenticator provides an implementation of HTTP Basic * authentication. It is an abstract class and must be extended - * to provide an implementation of {@link #checkCredentials(String,String)} + * to provide an implementation of {@link #checkCredentials(String, String)} * which is called to verify each incoming request. * * @since 1.6 @@ -104,34 +104,34 @@ public abstract class BasicAuthenticator extends Authenticator { * * @return the authenticator's realm string */ - public String getRealm () { + public String getRealm() { return realm; } - public Result authenticate (HttpExchange t) + public Result authenticate(HttpExchange t) { Headers rmap = t.getRequestHeaders(); /* * look for auth token */ - String auth = rmap.getFirst ("Authorization"); + String auth = rmap.getFirst("Authorization"); if (auth == null) { setAuthHeader(t); - return new Authenticator.Retry (401); + return new Authenticator.Retry(401); } - int sp = auth.indexOf (' '); + int sp = auth.indexOf(' '); if (sp == -1 || !auth.substring(0, sp).equalsIgnoreCase("Basic")) { - return new Authenticator.Failure (401); + return new Authenticator.Failure(401); } byte[] b = Base64.getDecoder().decode(auth.substring(sp+1)); - String userpass = new String (b, charset); - int colon = userpass.indexOf (':'); - String uname = userpass.substring (0, colon); - String pass = userpass.substring (colon+1); + String userpass = new String(b, charset); + int colon = userpass.indexOf(':'); + String uname = userpass.substring(0, colon); + String pass = userpass.substring(colon+1); - if (checkCredentials (uname, pass)) { - return new Authenticator.Success ( - new HttpPrincipal ( + if (checkCredentials(uname, pass)) { + return new Authenticator.Success( + new HttpPrincipal( uname, realm ) ); @@ -146,7 +146,7 @@ public abstract class BasicAuthenticator extends Authenticator { Headers map = t.getResponseHeaders(); var authString = "Basic realm=" + "\"" + realm + "\"" + (isUTF8 ? ", charset=\"UTF-8\"" : ""); - map.set ("WWW-Authenticate", authString); + map.set("WWW-Authenticate", authString); } /** @@ -159,6 +159,6 @@ public abstract class BasicAuthenticator extends Authenticator { * @param password the password from the request * @return {@code true} if the credentials are valid, {@code false} otherwise */ - public abstract boolean checkCredentials (String username, String password); + public abstract boolean checkCredentials(String username, String password); } diff --git a/src/jdk.httpserver/share/classes/com/sun/net/httpserver/Filter.java b/src/jdk.httpserver/share/classes/com/sun/net/httpserver/Filter.java index 7dacefcb59b..906e8448c54 100644 --- a/src/jdk.httpserver/share/classes/com/sun/net/httpserver/Filter.java +++ b/src/jdk.httpserver/share/classes/com/sun/net/httpserver/Filter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -53,7 +53,7 @@ public abstract class Filter { /** * Constructor for subclasses to call. */ - protected Filter () {} + protected Filter() {} /** * A chain of filters associated with a {@link HttpServer}. @@ -76,7 +76,7 @@ public abstract class Filter { * @param handler the {@link HttpHandler} that will be invoked after * the final {@code Filter} has finished */ - public Chain (List filters, HttpHandler handler) { + public Chain(List filters, HttpHandler handler) { iter = filters.listIterator(); this.handler = handler; } @@ -93,12 +93,12 @@ public abstract class Filter { * @throws IOException if an I/O error occurs * @throws NullPointerException if exchange is {@code null} */ - public void doFilter (HttpExchange exchange) throws IOException { + public void doFilter(HttpExchange exchange) throws IOException { if (!iter.hasNext()) { - handler.handle (exchange); + handler.handle(exchange); } else { Filter f = iter.next(); - f.doFilter (exchange, this); + f.doFilter(exchange, this); } } } @@ -135,14 +135,14 @@ public abstract class Filter { * must be rethrown again * @throws NullPointerException if either exchange or chain are {@code null} */ - public abstract void doFilter (HttpExchange exchange, Chain chain) + public abstract void doFilter(HttpExchange exchange, Chain chain) throws IOException; /** * Returns a short description of this {@code Filter}. * * @return a {@code String} describing the {@code Filter} */ - public abstract String description (); + public abstract String description(); /** * Returns a pre-processing {@code Filter} with the given description and diff --git a/src/jdk.httpserver/share/classes/com/sun/net/httpserver/Headers.java b/src/jdk.httpserver/share/classes/com/sun/net/httpserver/Headers.java index 84535027eb4..2decd48c806 100644 --- a/src/jdk.httpserver/share/classes/com/sun/net/httpserver/Headers.java +++ b/src/jdk.httpserver/share/classes/com/sun/net/httpserver/Headers.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -62,9 +62,9 @@ import sun.net.httpserver.UnmodifiableHeaders; *

    *
  • {@link #getFirst(String)} returns a single valued header or the first * value of a multi-valued header. - *
  • {@link #add(String,String)} adds the given header value to the list + *
  • {@link #add(String, String)} adds the given header value to the list * for the given key. - *
  • {@link #set(String,String)} sets the given header field to the single + *
  • {@link #set(String, String)} sets the given header field to the single * value given overwriting any existing values in the value list. *
* @@ -80,9 +80,9 @@ import sun.net.httpserver.UnmodifiableHeaders; * {@code null} keys will never be present in HTTP request or response headers. * @since 1.6 */ -public class Headers implements Map> { +public class Headers implements Map> { - HashMap> map; + HashMap> map; /** * Creates an empty instance of {@code Headers}. @@ -99,7 +99,7 @@ public class Headers implements Map> { * null. * @since 18 */ - public Headers(Map> headers) { + public Headers(Map> headers) { Objects.requireNonNull(headers); var h = headers.entrySet().stream() .collect(Collectors.toUnmodifiableMap( @@ -109,29 +109,69 @@ public class Headers implements Map> { } /** - * Normalize the key by converting to following form. - * First {@code char} upper case, rest lower case. - * key is presumed to be {@code ASCII}. + * {@return the normalized header name of the following form: the first + * character in upper-case, the rest in lower-case} + * The input header name is assumed to be encoded in ASCII. + * + * @implSpec + * This method is performance-sensitive; update with care. + * + * @param key an ASCII-encoded header name + * @throws NullPointerException on null {@code key} + * @throws IllegalArgumentException if {@code key} contains {@code \r} or {@code \n} */ - private String normalize(String key) { + private static String normalize(String key) { + + // Fast path for the empty key Objects.requireNonNull(key); - int len = key.length(); - if (len == 0) { + int l = key.length(); + if (l == 0) { return key; } - char[] b = key.toCharArray(); - if (b[0] >= 'a' && b[0] <= 'z') { - b[0] = (char)(b[0] - ('a' - 'A')); - } else if (b[0] == '\r' || b[0] == '\n') - throw new IllegalArgumentException("illegal character in key"); - for (int i=1; i= 'A' && b[i] <= 'Z') { - b[i] = (char) (b[i] + ('a' - 'A')); - } else if (b[i] == '\r' || b[i] == '\n') - throw new IllegalArgumentException("illegal character in key"); + // Find the first non-normalized `char` + int i = 0; + char c = key.charAt(i); + if (!(c == '\r' || c == '\n' || (c >= 'a' && c <= 'z'))) { + i++; + for (; i < l; i++) { + c = key.charAt(i); + if (c == '\r' || c == '\n' || (c >= 'A' && c <= 'Z')) { + break; + } + } } - return new String(b); + + // Fast path for the already normalized key + if (i == l) { + return key; + } + + // Upper-case the first `char` + char[] cs = key.toCharArray(); + int o = 'a' - 'A'; + if (i == 0) { + if (c == '\r' || c == '\n') { + throw new IllegalArgumentException("illegal character in key at index " + i); + } + if (c >= 'a' && c <= 'z') { + cs[0] = (char) (c - o); + } + i++; + } + + // Lower-case the secondary `char`s + for (; i < l; i++) { + c = cs[i]; + if (c >= 'A' && c <= 'Z') { + cs[i] = (char) (c + o); + } else if (c == '\r' || c == '\n') { + throw new IllegalArgumentException("illegal character in key at index " + i); + } + } + + return new String(cs); + } @Override @@ -194,7 +234,7 @@ public class Headers implements Map> { List l = map.get(k); if (l == null) { l = new LinkedList<>(); - map.put(k,l); + map.put(k, l); } l.add(value); } @@ -332,7 +372,7 @@ public class Headers implements Map> { * null. * @since 18 */ - public static Headers of(Map> headers) { + public static Headers of(Map> headers) { return new UnmodifiableHeaders(new Headers(headers)); } } diff --git a/src/jdk.httpserver/share/classes/com/sun/net/httpserver/HttpContext.java b/src/jdk.httpserver/share/classes/com/sun/net/httpserver/HttpContext.java index 12529d88385..1c9e41e866c 100644 --- a/src/jdk.httpserver/share/classes/com/sun/net/httpserver/HttpContext.java +++ b/src/jdk.httpserver/share/classes/com/sun/net/httpserver/HttpContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2022, 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 @@ -90,7 +90,7 @@ public abstract class HttpContext { * * @return a {@code Map} containing the attributes of this context */ - public abstract Map getAttributes() ; + public abstract Map getAttributes() ; /** * Returns this context's {@link List} of {@linkplain Filter filters}. This diff --git a/src/jdk.httpserver/share/classes/com/sun/net/httpserver/HttpExchange.java b/src/jdk.httpserver/share/classes/com/sun/net/httpserver/HttpExchange.java index 7ea43c0418e..a56c20b53af 100644 --- a/src/jdk.httpserver/share/classes/com/sun/net/httpserver/HttpExchange.java +++ b/src/jdk.httpserver/share/classes/com/sun/net/httpserver/HttpExchange.java @@ -48,7 +48,7 @@ import java.net.URI; * should be closed. *
  • {@link #getResponseHeaders()} to set any response headers, except * content-length. - *
  • {@link #sendResponseHeaders(int,long)} to send the response headers. + *
  • {@link #sendResponseHeaders(int, long)} to send the response headers. * Must be called before next step. *
  • {@link #getResponseBody()} to get a {@link OutputStream} to * send the response body. When the response body has been written, the @@ -73,7 +73,7 @@ public abstract class HttpExchange implements AutoCloseable, Request { /* * Symbolic values for the responseLength parameter of - * sendResponseHeaders(int,long) + * sendResponseHeaders(int, long) */ /** @@ -163,7 +163,7 @@ public abstract class HttpExchange implements AutoCloseable, Request { /** * Returns a stream to which the response body must be - * written. {@link #sendResponseHeaders(int,long)}) must be called prior to + * written. {@link #sendResponseHeaders(int, long)}) must be called prior to * calling this method. Multiple calls to this method (for the same exchange) * will return the same stream. In order to correctly terminate each exchange, * the output stream must be closed, even if no response body is being sent. diff --git a/src/jdk.httpserver/share/classes/com/sun/net/httpserver/HttpHandler.java b/src/jdk.httpserver/share/classes/com/sun/net/httpserver/HttpHandler.java index 1f1fc129c9d..447001a4f53 100644 --- a/src/jdk.httpserver/share/classes/com/sun/net/httpserver/HttpHandler.java +++ b/src/jdk.httpserver/share/classes/com/sun/net/httpserver/HttpHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2013, 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 @@ -44,5 +44,5 @@ public interface HttpHandler { * @throws NullPointerException if exchange is {@code null} * @throws IOException if an I/O error occurs */ - public abstract void handle (HttpExchange exchange) throws IOException; + public abstract void handle(HttpExchange exchange) throws IOException; } diff --git a/src/jdk.httpserver/share/classes/com/sun/net/httpserver/HttpServer.java b/src/jdk.httpserver/share/classes/com/sun/net/httpserver/HttpServer.java index 8ed0172690f..a0271fed146 100644 --- a/src/jdk.httpserver/share/classes/com/sun/net/httpserver/HttpServer.java +++ b/src/jdk.httpserver/share/classes/com/sun/net/httpserver/HttpServer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -44,7 +44,7 @@ import java.util.concurrent.Executor; * a root URI path which represents the location of the application or service * on this server. The mapping of a handler to a {@code HttpServer} is * encapsulated by a {@link HttpContext} object. HttpContexts are created by - * calling {@link #createContext(String,HttpHandler)}. + * calling {@link #createContext(String, HttpHandler)}. * Any request for which no handler can be found is rejected with a 404 response. * Management of threads can be done external to this object by providing a * {@link java.util.concurrent.Executor} object. If none is provided a default @@ -117,13 +117,13 @@ public abstract class HttpServer { * Creates a {@code HttpServer} instance which is initially not bound to any * local address/port. The {@code HttpServer} is acquired from the currently * installed {@link HttpServerProvider}. The server must be bound using - * {@link #bind(InetSocketAddress,int)} before it can be used. + * {@link #bind(InetSocketAddress, int)} before it can be used. * * @throws IOException if an I/O error occurs * @return an instance of {@code HttpServer} */ public static HttpServer create() throws IOException { - return create (null, 0); + return create(null, 0); } /** @@ -149,7 +149,7 @@ public abstract class HttpServer { public static HttpServer create(InetSocketAddress addr, int backlog) throws IOException { HttpServerProvider provider = HttpServerProvider.provider(); - return provider.createHttpServer (addr, backlog); + return provider.createHttpServer(addr, backlog); } /** diff --git a/src/jdk.httpserver/share/classes/com/sun/net/httpserver/HttpsConfigurator.java b/src/jdk.httpserver/share/classes/com/sun/net/httpserver/HttpsConfigurator.java index 8cd9e3affa2..7f329e02f47 100644 --- a/src/jdk.httpserver/share/classes/com/sun/net/httpserver/HttpsConfigurator.java +++ b/src/jdk.httpserver/share/classes/com/sun/net/httpserver/HttpsConfigurator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2017, 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 @@ -38,11 +38,11 @@ import javax.net.ssl.SSLParameters; *

    The following example shows how this may be done: * *

    - * SSLContext sslContext = SSLContext.getInstance (....);
    + * SSLContext sslContext = SSLContext.getInstance(....);
      * HttpsServer server = HttpsServer.create();
      *
    - * server.setHttpsConfigurator (new HttpsConfigurator(sslContext) {
    - *     public void configure (HttpsParameters params) {
    + * server.setHttpsConfigurator(new HttpsConfigurator(sslContext) {
    + *     public void configure(HttpsParameters params) {
      *
      *         // get the remote address if needed
      *         InetSocketAddress remote = params.getClientAddress();
    @@ -51,7 +51,7 @@ import javax.net.ssl.SSLParameters;
      *
      *         // get the default parameters
      *         SSLParameters sslparams = c.getDefaultSSLParameters();
    - *         if (remote.equals (...) ) {
    + *         if (remote.equals(...)) {
      *             // modify the default set for client x
      *         }
      *
    @@ -74,7 +74,7 @@ public class HttpsConfigurator {
          */
         public HttpsConfigurator(SSLContext context) {
             if (context == null) {
    -            throw new NullPointerException ("null SSLContext");
    +            throw new NullPointerException("null SSLContext");
             }
             this.context = context;
         }
    @@ -107,6 +107,6 @@ public class HttpsConfigurator {
         * @since 1.6
         */
         public void configure(HttpsParameters params) {
    -        params.setSSLParameters (getSSLContext().getDefaultSSLParameters());
    +        params.setSSLParameters(getSSLContext().getDefaultSSLParameters());
         }
     }
    diff --git a/src/jdk.httpserver/share/classes/com/sun/net/httpserver/HttpsServer.java b/src/jdk.httpserver/share/classes/com/sun/net/httpserver/HttpsServer.java
    index 7b3dafd5582..192b8c3d697 100644
    --- a/src/jdk.httpserver/share/classes/com/sun/net/httpserver/HttpsServer.java
    +++ b/src/jdk.httpserver/share/classes/com/sun/net/httpserver/HttpsServer.java
    @@ -1,5 +1,5 @@
     /*
    - * Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved.
    + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved.
      * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
      *
      * This code is free software; you can redistribute it and/or modify it
    @@ -57,7 +57,7 @@ public abstract class HttpsServer extends HttpServer {
          * Creates a {@code HttpsServer} instance which is initially not bound to any
          * local address/port. The {@code HttpsServer} is acquired from the currently
          * installed {@link HttpServerProvider}. The server must be bound using
    -     * {@link #bind(InetSocketAddress,int)} before it can be used. The server
    +     * {@link #bind(InetSocketAddress, int)} before it can be used. The server
          * must also have a {@code HttpsConfigurator} established with
          * {@link #setHttpsConfigurator(HttpsConfigurator)}.
          *
    @@ -80,7 +80,7 @@ public abstract class HttpsServer extends HttpServer {
          * established with {@link #setHttpsConfigurator(HttpsConfigurator)}.
          *
          * @param addr the address to listen on, if {@code null} then
    -     *             {@link #bind(InetSocketAddress,int)} must be called to set
    +     *             {@link #bind(InetSocketAddress, int)} must be called to set
          *             the address
          * @param backlog the socket backlog. If this value is less than or equal to
          *               zero, then a system default value is used.
    diff --git a/src/jdk.httpserver/share/classes/com/sun/net/httpserver/SimpleFileServer.java b/src/jdk.httpserver/share/classes/com/sun/net/httpserver/SimpleFileServer.java
    index 551f24d3cf0..ba93b81db5a 100644
    --- a/src/jdk.httpserver/share/classes/com/sun/net/httpserver/SimpleFileServer.java
    +++ b/src/jdk.httpserver/share/classes/com/sun/net/httpserver/SimpleFileServer.java
    @@ -1,5 +1,5 @@
     /*
    - * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved.
    + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved.
      * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
      *
      * This code is free software; you can redistribute it and/or modify it
    @@ -55,7 +55,7 @@ import sun.net.httpserver.simpleserver.OutputFilter;
      *
      * 

    Simple file server

    * - *

    The {@link #createFileServer(InetSocketAddress,Path,OutputLevel) createFileServer} + *

    The {@link #createFileServer(InetSocketAddress, Path, OutputLevel) createFileServer} * static factory method returns an {@link HttpServer HttpServer} that is a * simple out-of-the-box file server. The server comes with an initial handler * that serves files from a given directory path (and its subdirectories). diff --git a/src/jdk.httpserver/share/classes/com/sun/net/httpserver/package-info.java b/src/jdk.httpserver/share/classes/com/sun/net/httpserver/package-info.java index 3b9de3520fb..466b76a6c4a 100644 --- a/src/jdk.httpserver/share/classes/com/sun/net/httpserver/package-info.java +++ b/src/jdk.httpserver/share/classes/com/sun/net/httpserver/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -117,26 +117,25 @@ The following code shows how the SSLContext is then used in a HttpsConfigurator and how the SSLContext and HttpsConfigurator are linked to the HttpsServer.

    -    server.setHttpsConfigurator (new HttpsConfigurator(sslContext) {
    -        public void configure (HttpsParameters params) {
    +   server.setHttpsConfigurator(new HttpsConfigurator(sslContext) {
    +       public void configure(HttpsParameters params) {
     
    -        // get the remote address if needed
    -        InetSocketAddress remote = params.getClientAddress();
    +           // get the remote address if needed
    +           InetSocketAddress remote = params.getClientAddress();
     
    -        SSLContext c = getSSLContext();
    +           SSLContext c = getSSLContext();
     
    -        // get the default parameters
    -        SSLParameters sslparams = c.getDefaultSSLParameters();
    -        if (remote.equals (...) ) {
    -            // modify the default set for client x
    -        }
    +           // get the default parameters
    +           SSLParameters sslparams = c.getDefaultSSLParameters();
    +           if (remote.equals(...)) {
    +               // modify the default set for client x
    +           }
     
    -        params.setSSLParameters(sslparams);
    -        // statement above could throw IAE if any params invalid.
    -        // eg. if app has a UI and parameters supplied by a user.
    -
    -        }
    -    });
    +           params.setSSLParameters(sslparams);
    +           // statement above could throw IAE if any params invalid.
    +           // eg. if app has a UI and parameters supplied by a user.
    +       }
    +   });
        
    @since 1.6 */ diff --git a/src/jdk.httpserver/share/classes/com/sun/net/httpserver/spi/HttpServerProvider.java b/src/jdk.httpserver/share/classes/com/sun/net/httpserver/spi/HttpServerProvider.java index d025e90cf87..9d73f0e7340 100644 --- a/src/jdk.httpserver/share/classes/com/sun/net/httpserver/spi/HttpServerProvider.java +++ b/src/jdk.httpserver/share/classes/com/sun/net/httpserver/spi/HttpServerProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -152,7 +152,7 @@ public abstract class HttpServerProvider { * * @return The system-wide default HttpServerProvider */ - public static HttpServerProvider provider () { + public static HttpServerProvider provider() { synchronized (lock) { if (provider != null) return provider; diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/AuthFilter.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/AuthFilter.java index 468fa19fe16..4641325af92 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/AuthFilter.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/AuthFilter.java @@ -33,48 +33,48 @@ public class AuthFilter extends Filter { private Authenticator authenticator; - public AuthFilter (Authenticator authenticator) { + public AuthFilter(Authenticator authenticator) { this.authenticator = authenticator; } - public String description () { + public String description() { return "Authentication filter"; } - public void setAuthenticator (Authenticator a) { + public void setAuthenticator(Authenticator a) { authenticator = a; } - public void consumeInput (HttpExchange t) throws IOException { + public void consumeInput(HttpExchange t) throws IOException { InputStream i = t.getRequestBody(); byte[] b = new byte [4096]; - while (i.read (b) != -1); - i.close (); + while (i.read(b) != -1); + i.close(); } /** * The filter's implementation, which is invoked by the server */ - public void doFilter (HttpExchange t, Filter.Chain chain) throws IOException + public void doFilter(HttpExchange t, Filter.Chain chain) throws IOException { if (authenticator != null) { - Authenticator.Result r = authenticator.authenticate (t); + Authenticator.Result r = authenticator.authenticate(t); if (r instanceof Authenticator.Success) { Authenticator.Success s = (Authenticator.Success)r; - ExchangeImpl e = ExchangeImpl.get (t); - e.setPrincipal (s.getPrincipal()); - chain.doFilter (t); + ExchangeImpl e = ExchangeImpl.get(t); + e.setPrincipal(s.getPrincipal()); + chain.doFilter(t); } else if (r instanceof Authenticator.Retry) { Authenticator.Retry ry = (Authenticator.Retry)r; - consumeInput (t); - t.sendResponseHeaders (ry.getResponseCode(), RSPBODY_EMPTY); + consumeInput(t); + t.sendResponseHeaders(ry.getResponseCode(), RSPBODY_EMPTY); } else if (r instanceof Authenticator.Failure) { Authenticator.Failure f = (Authenticator.Failure)r; - consumeInput (t); - t.sendResponseHeaders (f.getResponseCode(), RSPBODY_EMPTY); + consumeInput(t); + t.sendResponseHeaders(f.getResponseCode(), RSPBODY_EMPTY); } } else { - chain.doFilter (t); + chain.doFilter(t); } } } diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/ChunkedInputStream.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/ChunkedInputStream.java index 34ac72e1fee..0c003378d77 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/ChunkedInputStream.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/ChunkedInputStream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,7 +31,7 @@ import com.sun.net.httpserver.*; import com.sun.net.httpserver.spi.*; class ChunkedInputStream extends LeftOverInputStream { - ChunkedInputStream (ExchangeImpl t, InputStream src) { + ChunkedInputStream(ExchangeImpl t, InputStream src) { super (t, src); } @@ -48,7 +48,7 @@ class ChunkedInputStream extends LeftOverInputStream { */ private static final int MAX_CHUNK_HEADER_SIZE = 2050; - private int numeric (char[] arr, int nchars) throws IOException { + private int numeric(char[] arr, int nchars) throws IOException { assert arr.length >= nchars; int len = 0; for (int i=0; i='A' && c<= 'F') { val = c - 'A' + 10; } else { - throw new IOException ("invalid chunk length"); + throw new IOException("invalid chunk length"); } len = len * 16 + val; } @@ -71,10 +71,10 @@ class ChunkedInputStream extends LeftOverInputStream { /* read the chunk header line and return the chunk length * any chunk extensions are ignored */ - private int readChunkHeader () throws IOException { + private int readChunkHeader() throws IOException { boolean gotCR = false; int c; - char[] len_arr = new char [16]; + char[] len_arr = new char[16]; int len_size = 0; boolean end_of_len = false; int read = 0; @@ -85,11 +85,11 @@ class ChunkedInputStream extends LeftOverInputStream { if ((len_size == len_arr.length -1) || (read > MAX_CHUNK_HEADER_SIZE)) { - throw new IOException ("invalid chunk header"); + throw new IOException("invalid chunk header"); } if (gotCR) { if (ch == LF) { - int l = numeric (len_arr, len_size); + int l = numeric(len_arr, len_size); return l; } else { gotCR = false; @@ -107,10 +107,10 @@ class ChunkedInputStream extends LeftOverInputStream { } } } - throw new IOException ("end of stream reading chunk header"); + throw new IOException("end of stream reading chunk header"); } - protected int readImpl (byte[]b, int off, int len) throws IOException { + protected int readImpl(byte[] b, int off, int len) throws IOException { if (eof) { return -1; } @@ -119,7 +119,7 @@ class ChunkedInputStream extends LeftOverInputStream { if (remaining == 0) { eof = true; consumeCRLF(); - t.getServerImpl().requestCompleted (t.getConnection()); + t.getServerImpl().requestCompleted(t.getConnection()); return -1; } needToReadHeader = false; @@ -140,15 +140,15 @@ class ChunkedInputStream extends LeftOverInputStream { return n; } - private void consumeCRLF () throws IOException { + private void consumeCRLF() throws IOException { char c; c = (char)in.read(); /* CR */ if (c != CR) { - throw new IOException ("invalid chunk end"); + throw new IOException("invalid chunk end"); } c = (char)in.read(); /* LF */ if (c != LF) { - throw new IOException ("invalid chunk end"); + throw new IOException("invalid chunk end"); } } @@ -158,7 +158,7 @@ class ChunkedInputStream extends LeftOverInputStream { * limitation for the moment. It only affects potential efficiency * rather than correctness. */ - public int available () throws IOException { + public int available() throws IOException { if (eof || closed) { return 0; } @@ -170,17 +170,17 @@ class ChunkedInputStream extends LeftOverInputStream { * have been read from the underlying channel * and buffered internally */ - public boolean isDataBuffered () throws IOException { + public boolean isDataBuffered() throws IOException { assert eof; return in.available() > 0; } - public boolean markSupported () {return false;} + public boolean markSupported() {return false;} - public void mark (int l) { + public void mark(int l) { } - public void reset () throws IOException { - throw new IOException ("mark/reset not supported"); + public void reset() throws IOException { + throw new IOException("mark/reset not supported"); } } diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/ChunkedOutputStream.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/ChunkedOutputStream.java index e8e84eb8edd..fd1a940c0a5 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/ChunkedOutputStream.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/ChunkedOutputStream.java @@ -57,14 +57,14 @@ class ChunkedOutputStream extends FilterOutputStream private byte[] buf = new byte [CHUNK_SIZE+OFFSET+2]; ExchangeImpl t; - ChunkedOutputStream (ExchangeImpl t, OutputStream src) { - super (src); + ChunkedOutputStream(ExchangeImpl t, OutputStream src) { + super(src); this.t = t; } - public void write (int b) throws IOException { + public void write(int b) throws IOException { if (closed) { - throw new StreamClosedException (); + throw new StreamClosedException(); } buf [pos++] = (byte)b; count ++; @@ -74,23 +74,23 @@ class ChunkedOutputStream extends FilterOutputStream assert count < CHUNK_SIZE; } - public void write (byte[]b, int off, int len) throws IOException { + public void write(byte[] b, int off, int len) throws IOException { Objects.checkFromIndexSize(off, len, b.length); if (len == 0) { return; } if (closed) { - throw new StreamClosedException (); + throw new StreamClosedException(); } int remain = CHUNK_SIZE - count; if (len > remain) { - System.arraycopy (b,off,buf,pos,remain); + System.arraycopy(b, off, buf, pos, remain); count = CHUNK_SIZE; writeChunk(); len -= remain; off += remain; while (len >= CHUNK_SIZE) { - System.arraycopy (b,off,buf,OFFSET,CHUNK_SIZE); + System.arraycopy(b, off, buf, OFFSET, CHUNK_SIZE); len -= CHUNK_SIZE; off += CHUNK_SIZE; count = CHUNK_SIZE; @@ -98,7 +98,7 @@ class ChunkedOutputStream extends FilterOutputStream } } if (len > 0) { - System.arraycopy (b,off,buf,pos,len); + System.arraycopy(b, off, buf, pos, len); count += len; pos += len; } @@ -112,8 +112,8 @@ class ChunkedOutputStream extends FilterOutputStream * chunk does not have to be CHUNK_SIZE bytes * count must == number of user bytes (<= CHUNK_SIZE) */ - private void writeChunk () throws IOException { - char[] c = Integer.toHexString (count).toCharArray(); + private void writeChunk() throws IOException { + char[] c = Integer.toHexString(count).toCharArray(); int clen = c.length; int startByte = 4 - clen; int i; @@ -124,12 +124,12 @@ class ChunkedOutputStream extends FilterOutputStream buf[startByte + (i++)] = '\n'; buf[startByte + (i++) + count] = '\r'; buf[startByte + (i++) + count] = '\n'; - out.write (buf, startByte, i+count); + out.write(buf, startByte, i+count); count = 0; pos = OFFSET; } - public void close () throws IOException { + public void close() throws IOException { if (closed) { return; } @@ -156,12 +156,12 @@ class ChunkedOutputStream extends FilterOutputStream } Event e = new Event.WriteFinished(t); - t.getHttpContext().getServerImpl().addEvent (e); + t.getHttpContext().getServerImpl().addEvent(e); } - public void flush () throws IOException { + public void flush() throws IOException { if (closed) { - throw new StreamClosedException (); + throw new StreamClosedException(); } if (count > 0) { writeChunk(); diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/Code.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/Code.java index 6b86b8fa5bd..c5391343f96 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/Code.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/Code.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2006, 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 @@ -64,7 +64,7 @@ class Code { public static final int HTTP_GATEWAY_TIMEOUT = 504; public static final int HTTP_VERSION = 505; - static String msg (int code) { + static String msg(int code) { switch (code) { case HTTP_OK: return " OK"; diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/ContextList.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/ContextList.java index cbedb6ca860..96b55575928 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/ContextList.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/ContextList.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,30 +31,30 @@ class ContextList { private final LinkedList list = new LinkedList<>(); - public synchronized void add (HttpContextImpl ctx) { + public synchronized void add(HttpContextImpl ctx) { assert ctx.getPath() != null; if (contains(ctx)) { - throw new IllegalArgumentException ("cannot add context to list"); + throw new IllegalArgumentException("cannot add context to list"); } - list.add (ctx); + list.add(ctx); } boolean contains(HttpContextImpl ctx) { return findContext(ctx.getProtocol(), ctx.getPath(), true) != null; } - public synchronized int size () { + public synchronized int size() { return list.size(); } /* initially contexts are located only by protocol:path. * Context with longest prefix matches (currently case-sensitive) */ - synchronized HttpContextImpl findContext (String protocol, String path) { - return findContext (protocol, path, false); + synchronized HttpContextImpl findContext(String protocol, String path) { + return findContext(protocol, path, false); } - synchronized HttpContextImpl findContext (String protocol, String path, boolean exact) { + synchronized HttpContextImpl findContext(String protocol, String path, boolean exact) { protocol = protocol.toLowerCase(Locale.ROOT); String longest = ""; HttpContextImpl lc = null; @@ -63,7 +63,7 @@ class ContextList { continue; } String cpath = ctx.getPath(); - if (exact && !cpath.equals (path)) { + if (exact && !cpath.equals(path)) { continue; } else if (!exact && !path.startsWith(cpath)) { continue; @@ -76,25 +76,25 @@ class ContextList { return lc; } - public synchronized void remove (String protocol, String path) + public synchronized void remove(String protocol, String path) throws IllegalArgumentException { - HttpContextImpl ctx = findContext (protocol, path, true); + HttpContextImpl ctx = findContext(protocol, path, true); if (ctx == null) { - throw new IllegalArgumentException ("cannot remove element from list"); + throw new IllegalArgumentException("cannot remove element from list"); } - list.remove (ctx); + list.remove(ctx); } - public synchronized void remove (HttpContextImpl context) + public synchronized void remove(HttpContextImpl context) throws IllegalArgumentException { for (HttpContextImpl ctx: list) { - if (ctx.equals (context)) { - list.remove (ctx); + if (ctx.equals(context)) { + list.remove(ctx); return; } } - throw new IllegalArgumentException ("no such context in list"); + throw new IllegalArgumentException("no such context in list"); } } diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/DefaultHttpServerProvider.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/DefaultHttpServerProvider.java index fe746befb9f..7b54ef45e72 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/DefaultHttpServerProvider.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/DefaultHttpServerProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,11 +31,11 @@ import com.sun.net.httpserver.*; import com.sun.net.httpserver.spi.*; public class DefaultHttpServerProvider extends HttpServerProvider { - public HttpServer createHttpServer (InetSocketAddress addr, int backlog) throws IOException { - return new HttpServerImpl (addr, backlog); + public HttpServer createHttpServer(InetSocketAddress addr, int backlog) throws IOException { + return new HttpServerImpl(addr, backlog); } - public HttpsServer createHttpsServer (InetSocketAddress addr, int backlog) throws IOException { - return new HttpsServerImpl (addr, backlog); + public HttpsServer createHttpsServer(InetSocketAddress addr, int backlog) throws IOException { + return new HttpsServerImpl(addr, backlog); } } diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/ExchangeImpl.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/ExchangeImpl.java index 17dff13d4e5..8da98cdcfa5 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/ExchangeImpl.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/ExchangeImpl.java @@ -82,12 +82,12 @@ class ExchangeImpl { PlaceholderOutputStream uos_orig; boolean sentHeaders; /* true after response headers sent */ - final Map attributes; + final Map attributes; int rcode = -1; HttpPrincipal principal; ServerImpl server; - ExchangeImpl ( + ExchangeImpl( String m, URI u, Request req, long len, HttpConnection connection ) throws IOException { this.req = req; @@ -107,23 +107,23 @@ class ExchangeImpl { server.startExchange(); } - public Headers getRequestHeaders () { + public Headers getRequestHeaders() { return reqHdrs; } - public Headers getResponseHeaders () { + public Headers getResponseHeaders() { return rspHdrs; } - public URI getRequestURI () { + public URI getRequestURI() { return uri; } - public String getRequestMethod (){ + public String getRequestMethod() { return method; } - public HttpContextImpl getHttpContext (){ + public HttpContextImpl getHttpContext() { return connection.getHttpContext(); } @@ -131,7 +131,7 @@ class ExchangeImpl { return HEAD.equals(getRequestMethod()); } - public void close () { + public void close() { if (closed) { return; } @@ -160,38 +160,38 @@ class ExchangeImpl { } } - public InputStream getRequestBody () { + public InputStream getRequestBody() { if (uis != null) { return uis; } if (reqContentLen == -1L) { - uis_orig = new ChunkedInputStream (this, ris); + uis_orig = new ChunkedInputStream(this, ris); uis = uis_orig; } else { - uis_orig = new FixedLengthInputStream (this, ris, reqContentLen); + uis_orig = new FixedLengthInputStream(this, ris, reqContentLen); uis = uis_orig; } return uis; } - LeftOverInputStream getOriginalInputStream () { + LeftOverInputStream getOriginalInputStream() { return uis_orig; } - public int getResponseCode () { + public int getResponseCode() { return rcode; } - public OutputStream getResponseBody () { + public OutputStream getResponseBody() { /* TODO. Change spec to remove restriction below. Filters * cannot work with this restriction * * if (!sentHeaders) { - * throw new IllegalStateException ("headers not sent"); + * throw new IllegalStateException("headers not sent"); * } */ if (uos == null) { - uos_orig = new PlaceholderOutputStream (null); + uos_orig = new PlaceholderOutputStream(null); uos = uos_orig; } return uos; @@ -202,37 +202,37 @@ class ExchangeImpl { * returned from the 1st call to getResponseBody() * The "real" ouputstream is then placed inside this */ - PlaceholderOutputStream getPlaceholderResponseBody () { + PlaceholderOutputStream getPlaceholderResponseBody() { getResponseBody(); return uos_orig; } - public void sendResponseHeaders (int rCode, long contentLen) + public void sendResponseHeaders(int rCode, long contentLen) throws IOException { final Logger logger = server.getLogger(); if (sentHeaders) { - throw new IOException ("headers already sent"); + throw new IOException("headers already sent"); } this.rcode = rCode; - String statusLine = "HTTP/1.1 "+rCode+Code.msg(rCode)+"\r\n"; + String statusLine = "HTTP/1.1 " + rCode + Code.msg(rCode) + "\r\n"; ByteArrayOutputStream tmpout = new ByteArrayOutputStream(); PlaceholderOutputStream o = getPlaceholderResponseBody(); - tmpout.write (bytes(statusLine, 0), 0, statusLine.length()); + tmpout.write(bytes(statusLine, 0), 0, statusLine.length()); boolean noContentToSend = false; // assume there is content boolean noContentLengthHeader = false; // must not send Content-length is set rspHdrs.set("Date", FORMATTER.format(Instant.now())); /* check for response type that is not allowed to send a body */ - if ((rCode>=100 && rCode <200) /* informational */ + if ((rCode >= 100 && rCode < 200) /* informational */ ||(rCode == 204) /* no content */ ||(rCode == 304)) /* not modified */ { if (contentLen != RSPBODY_EMPTY) { - String msg = "sendResponseHeaders: rCode = "+ rCode + String msg = "sendResponseHeaders: rCode = " + rCode + ": forcing contentLen = RSPBODY_EMPTY"; - logger.log (Level.WARNING, msg); + logger.log(Level.WARNING, msg); } contentLen = RSPBODY_EMPTY; noContentLengthHeader = (rCode != 304); @@ -245,19 +245,19 @@ class ExchangeImpl { if (contentLen >= 0) { String msg = "sendResponseHeaders: being invoked with a content length for a HEAD request"; - logger.log (Level.WARNING, msg); + logger.log(Level.WARNING, msg); } noContentToSend = true; contentLen = 0; - o.setWrappedStream (new FixedLengthOutputStream (this, ros, contentLen)); + o.setWrappedStream(new FixedLengthOutputStream(this, ros, contentLen)); } else { /* not a HEAD request or 304 response */ if (contentLen == RSPBODY_CHUNKED) { if (http10) { - o.setWrappedStream (new UndefLengthOutputStream (this, ros)); + o.setWrappedStream(new UndefLengthOutputStream(this, ros)); close = true; } else { - rspHdrs.set ("Transfer-encoding", "chunked"); - o.setWrappedStream (new ChunkedOutputStream (this, ros)); + rspHdrs.set("Transfer-encoding", "chunked"); + o.setWrappedStream(new ChunkedOutputStream(this, ros)); } } else { if (contentLen == RSPBODY_EMPTY) { @@ -267,7 +267,7 @@ class ExchangeImpl { if (!noContentLengthHeader) { rspHdrs.set("Content-length", Long.toString(contentLen)); } - o.setWrappedStream (new FixedLengthOutputStream (this, ros, contentLen)); + o.setWrappedStream(new FixedLengthOutputStream(this, ros, contentLen)); } } @@ -280,12 +280,12 @@ class ExchangeImpl { Optional.ofNullable(rspHdrs.get("Connection")) .map(List::stream).orElse(Stream.empty()); if (conheader.anyMatch("close"::equalsIgnoreCase)) { - logger.log (Level.DEBUG, "Connection: close requested by handler"); + logger.log(Level.DEBUG, "Connection: close requested by handler"); close = true; } } - write (rspHdrs, tmpout); + write(rspHdrs, tmpout); this.rspContentLen = contentLen; tmpout.writeTo(ros); sentHeaders = true; @@ -294,33 +294,33 @@ class ExchangeImpl { ros.flush(); close(); } - server.logReply (rCode, req.requestLine(), null); + server.logReply(rCode, req.requestLine(), null); } - void write (Headers map, OutputStream os) throws IOException { - Set>> entries = map.entrySet(); - for (Map.Entry> entry : entries) { + void write(Headers map, OutputStream os) throws IOException { + Set>> entries = map.entrySet(); + for (Map.Entry> entry : entries) { String key = entry.getKey(); byte[] buf; List values = entry.getValue(); for (String val : values) { int i = key.length(); - buf = bytes (key, 2); + buf = bytes(key, 2); buf[i++] = ':'; buf[i++] = ' '; - os.write (buf, 0, i); - buf = bytes (val, 2); + os.write(buf, 0, i); + buf = bytes(val, 2); i = val.length(); buf[i++] = '\r'; buf[i++] = '\n'; - os.write (buf, 0, i); + os.write(buf, 0, i); } } - os.write ('\r'); - os.write ('\n'); + os.write('\r'); + os.write('\n'); } - private byte[] rspbuf = new byte [128]; // used by bytes() + private byte[] rspbuf = new byte[128]; // used by bytes() /** * convert string to byte[], using rspbuf @@ -328,7 +328,7 @@ class ExchangeImpl { * of rspbuf. Reallocate rspbuf if not big enough. * caller must check return value to see if rspbuf moved */ - private byte[] bytes (String s, int extra) { + private byte[] bytes(String s, int extra) { int slen = s.length(); if (slen+extra > rspbuf.length) { int diff = slen + extra - rspbuf.length; @@ -341,27 +341,27 @@ class ExchangeImpl { return rspbuf; } - public InetSocketAddress getRemoteAddress (){ + public InetSocketAddress getRemoteAddress() { Socket s = connection.getChannel().socket(); InetAddress ia = s.getInetAddress(); int port = s.getPort(); - return new InetSocketAddress (ia, port); + return new InetSocketAddress(ia, port); } - public InetSocketAddress getLocalAddress (){ + public InetSocketAddress getLocalAddress() { Socket s = connection.getChannel().socket(); InetAddress ia = s.getLocalAddress(); int port = s.getLocalPort(); - return new InetSocketAddress (ia, port); + return new InetSocketAddress(ia, port); } - public String getProtocol (){ + public String getProtocol() { String reqline = req.requestLine(); - int index = reqline.lastIndexOf (' '); - return reqline.substring (index+1); + int index = reqline.lastIndexOf(' '); + return reqline.substring(index+1); } - public SSLSession getSSLSession () { + public SSLSession getSSLSession() { SSLEngine e = connection.getSSLEngine(); if (e == null) { return null; @@ -369,11 +369,11 @@ class ExchangeImpl { return e.getSession(); } - public Object getAttribute (String name) { + public Object getAttribute(String name) { return attributes.get(Objects.requireNonNull(name, "null name parameter")); } - public void setAttribute (String name, Object value) { + public void setAttribute(String name, Object value) { var key = Objects.requireNonNull(name, "null name parameter"); if (value != null) { attributes.put(key, value); @@ -382,7 +382,7 @@ class ExchangeImpl { } } - public void setStreams (InputStream i, OutputStream o) { + public void setStreams(InputStream i, OutputStream o) { assert uis != null; if (i != null) { uis = i; @@ -395,23 +395,23 @@ class ExchangeImpl { /** * PP */ - HttpConnection getConnection () { + HttpConnection getConnection() { return connection; } - ServerImpl getServerImpl () { + ServerImpl getServerImpl() { return getHttpContext().getServerImpl(); } - public HttpPrincipal getPrincipal () { + public HttpPrincipal getPrincipal() { return principal; } - void setPrincipal (HttpPrincipal principal) { + void setPrincipal(HttpPrincipal principal) { this.principal = principal; } - static ExchangeImpl get (HttpExchange t) { + static ExchangeImpl get(HttpExchange t) { if (t instanceof HttpExchangeImpl) { return ((HttpExchangeImpl)t).getExchangeImpl(); } else { @@ -432,37 +432,37 @@ class PlaceholderOutputStream extends java.io.OutputStream { OutputStream wrapped; - PlaceholderOutputStream (OutputStream os) { + PlaceholderOutputStream(OutputStream os) { wrapped = os; } - void setWrappedStream (OutputStream os) { + void setWrappedStream(OutputStream os) { wrapped = os; } - boolean isWrapped () { + boolean isWrapped() { return wrapped != null; } - private void checkWrap () throws IOException { + private void checkWrap() throws IOException { if (wrapped == null) { - throw new IOException ("response headers not sent yet"); + throw new IOException("response headers not sent yet"); } } public void write(int b) throws IOException { checkWrap(); - wrapped.write (b); + wrapped.write(b); } public void write(byte b[]) throws IOException { checkWrap(); - wrapped.write (b); + wrapped.write(b); } public void write(byte b[], int off, int len) throws IOException { checkWrap(); - wrapped.write (b, off, len); + wrapped.write(b, off, len); } public void flush() throws IOException { diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/FixedLengthInputStream.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/FixedLengthInputStream.java index ac6fb7be005..100ba9e1b3b 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/FixedLengthInputStream.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/FixedLengthInputStream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2017, 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 @@ -39,15 +39,15 @@ import com.sun.net.httpserver.spi.*; class FixedLengthInputStream extends LeftOverInputStream { private long remaining; - FixedLengthInputStream (ExchangeImpl t, InputStream src, long len) { - super (t, src); + FixedLengthInputStream(ExchangeImpl t, InputStream src, long len) { + super(t, src); if (len < 0) { throw new IllegalArgumentException("Content-Length: " + len); } this.remaining = len; } - protected int readImpl (byte[]b, int off, int len) throws IOException { + protected int readImpl(byte[] b, int off, int len) throws IOException { eof = (remaining == 0L); if (eof) { @@ -60,7 +60,7 @@ class FixedLengthInputStream extends LeftOverInputStream { if (n > -1) { remaining -= n; if (remaining == 0) { - t.getServerImpl().requestCompleted (t.getConnection()); + t.getServerImpl().requestCompleted(t.getConnection()); } } if (n < 0 && !eof) @@ -68,7 +68,7 @@ class FixedLengthInputStream extends LeftOverInputStream { return n; } - public int available () throws IOException { + public int available() throws IOException { if (eof) { return 0; } @@ -76,12 +76,12 @@ class FixedLengthInputStream extends LeftOverInputStream { return n < remaining? n: (int)remaining; } - public boolean markSupported () {return false;} + public boolean markSupported() {return false;} - public void mark (int l) { + public void mark(int l) { } - public void reset () throws IOException { - throw new IOException ("mark/reset not supported"); + public void reset() throws IOException { + throw new IOException("mark/reset not supported"); } } diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/FixedLengthOutputStream.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/FixedLengthOutputStream.java index f800bdaba18..95de03d27fa 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/FixedLengthOutputStream.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/FixedLengthOutputStream.java @@ -42,7 +42,7 @@ class FixedLengthOutputStream extends FilterOutputStream private boolean closed = false; ExchangeImpl t; - FixedLengthOutputStream (ExchangeImpl t, OutputStream src, long len) { + FixedLengthOutputStream(ExchangeImpl t, OutputStream src, long len) { super (src); if (len < 0) { throw new IllegalArgumentException("Content-Length: " + len); @@ -51,9 +51,9 @@ class FixedLengthOutputStream extends FilterOutputStream this.remaining = len; } - public void write (int b) throws IOException { + public void write(int b) throws IOException { if (closed) { - throw new IOException ("stream closed"); + throw new IOException("stream closed"); } if (remaining == 0) { throw new StreamClosedException(); @@ -62,30 +62,30 @@ class FixedLengthOutputStream extends FilterOutputStream remaining --; } - public void write (byte[]b, int off, int len) throws IOException { + public void write(byte[] b, int off, int len) throws IOException { Objects.checkFromIndexSize(off, len, b.length); if (len == 0) { return; } if (closed) { - throw new IOException ("stream closed"); + throw new IOException("stream closed"); } if (len > remaining) { // stream is still open, caller can retry - throw new IOException ("too many bytes to write to stream"); + throw new IOException("too many bytes to write to stream"); } out.write(b, off, len); remaining -= len; } - public void close () throws IOException { + public void close() throws IOException { if (closed) { return; } closed = true; if (remaining > 0) { t.close(); - throw new IOException ("insufficient bytes written to stream"); + throw new IOException("insufficient bytes written to stream"); } flush(); LeftOverInputStream is = t.getOriginalInputStream(); @@ -95,7 +95,7 @@ class FixedLengthOutputStream extends FilterOutputStream } catch (IOException e) {} } Event e = new Event.WriteFinished(t); - t.getHttpContext().getServerImpl().addEvent (e); + t.getHttpContext().getServerImpl().addEvent(e); } // flush is a pass-through diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/HttpConnection.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/HttpConnection.java index 38aabe1136e..2be4a37d432 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/HttpConnection.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/HttpConnection.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2022, 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 @@ -73,14 +73,14 @@ class HttpConnection { return sb.toString(); } - HttpConnection () { + HttpConnection() { } - void setChannel (SocketChannel c) { + void setChannel(SocketChannel c) { chan = c; } - void setContext (HttpContextImpl ctx) { + void setContext(HttpContextImpl ctx) { context = ctx; } @@ -88,11 +88,11 @@ class HttpConnection { return state; } - void setState (State s) { + void setState(State s) { state = s; } - void setParameters ( + void setParameters( InputStream in, OutputStream rawout, SocketChannel chan, SSLEngine engine, SSLStreams sslStreams, SSLContext sslContext, String protocol, HttpContextImpl context, InputStream raw @@ -110,21 +110,21 @@ class HttpConnection { this.logger = context.getLogger(); } - SocketChannel getChannel () { + SocketChannel getChannel() { return chan; } - synchronized void close () { + synchronized void close() { if (closed) { return; } closed = true; if (logger != null && chan != null) { - logger.log (Level.TRACE, "Closing connection: " + chan.toString()); + logger.log(Level.TRACE, "Closing connection: " + chan.toString()); } if (!chan.isOpen()) { - ServerImpl.dprint ("Channel already closed"); + ServerImpl.dprint("Channel already closed"); return; } try { @@ -133,65 +133,65 @@ class HttpConnection { raw.close(); } } catch (IOException e) { - ServerImpl.dprint (e); + ServerImpl.dprint(e); } try { if (rawout != null) { rawout.close(); } } catch (IOException e) { - ServerImpl.dprint (e); + ServerImpl.dprint(e); } try { if (sslStreams != null) { sslStreams.close(); } } catch (IOException e) { - ServerImpl.dprint (e); + ServerImpl.dprint(e); } try { chan.close(); } catch (IOException e) { - ServerImpl.dprint (e); + ServerImpl.dprint(e); } } /* remaining is the number of bytes left on the lowest level inputstream * after the exchange is finished */ - void setRemaining (int r) { + void setRemaining(int r) { remaining = r; } - int getRemaining () { + int getRemaining() { return remaining; } - SelectionKey getSelectionKey () { + SelectionKey getSelectionKey() { return selectionKey; } - InputStream getInputStream () { + InputStream getInputStream() { return i; } - OutputStream getRawOutputStream () { + OutputStream getRawOutputStream() { return rawout; } - String getProtocol () { + String getProtocol() { return protocol; } - SSLEngine getSSLEngine () { + SSLEngine getSSLEngine() { return engine; } - SSLContext getSSLContext () { + SSLContext getSSLContext() { return sslContext; } - HttpContextImpl getHttpContext () { + HttpContextImpl getHttpContext() { return context; } } diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/HttpContextImpl.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/HttpContextImpl.java index f1583954115..10cd116a3a0 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/HttpContextImpl.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/HttpContextImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -35,7 +35,7 @@ import com.sun.net.httpserver.*; * to a {@link HttpHandler} which is invoked to handle requests destined * for the protocol/path on the associated HttpServer. *

    - * HttpContext instances are created by {@link HttpServer#createContext(String,String,HttpHandler,Object)} + * HttpContext instances are created by {@link HttpServer#createContext(String, String, HttpHandler, Object)} *

    */ class HttpContextImpl extends HttpContext { @@ -44,7 +44,7 @@ class HttpContextImpl extends HttpContext { private final String protocol; private final ServerImpl server; private final AuthFilter authfilter; - private final Map attributes = new ConcurrentHashMap<>(); + private final Map attributes = new ConcurrentHashMap<>(); /* system filters, not visible to applications */ private final List sfilters = new CopyOnWriteArrayList<>(); /* user filters, set by applications */ @@ -55,35 +55,35 @@ class HttpContextImpl extends HttpContext { /** * constructor is package private. */ - HttpContextImpl (String protocol, String path, HttpHandler cb, ServerImpl server) { + HttpContextImpl(String protocol, String path, HttpHandler cb, ServerImpl server) { if (path == null || protocol == null || path.length() < 1 || path.charAt(0) != '/') { - throw new IllegalArgumentException ("Illegal value for path or protocol"); + throw new IllegalArgumentException("Illegal value for path or protocol"); } this.protocol = protocol.toLowerCase(Locale.ROOT); this.path = path; - if (!this.protocol.equals ("http") && !this.protocol.equals ("https")) { - throw new IllegalArgumentException ("Illegal value for protocol"); + if (!this.protocol.equals("http") && !this.protocol.equals("https")) { + throw new IllegalArgumentException("Illegal value for protocol"); } this.handler = cb; this.server = server; authfilter = new AuthFilter(null); - sfilters.add (authfilter); + sfilters.add(authfilter); } /** * returns the handler for this context * @return the HttpHandler for this context */ - public HttpHandler getHandler () { + public HttpHandler getHandler() { return handler; } - public void setHandler (HttpHandler h) { + public void setHandler(HttpHandler h) { if (h == null) { - throw new NullPointerException ("Null handler parameter"); + throw new NullPointerException("Null handler parameter"); } if (handler != null) { - throw new IllegalArgumentException ("handler already set"); + throw new IllegalArgumentException("handler already set"); } handler = h; } @@ -100,11 +100,11 @@ class HttpContextImpl extends HttpContext { * returns the server this context was created with * @return this context's server */ - public HttpServer getServer () { + public HttpServer getServer() { return server.getWrapper(); } - ServerImpl getServerImpl () { + ServerImpl getServerImpl() { return server; } @@ -124,29 +124,29 @@ class HttpContextImpl extends HttpContext { * Every attribute stored in this Map will be visible to * every HttpExchange processed by this context */ - public Map getAttributes() { + public Map getAttributes() { return attributes; } - public List getFilters () { + public List getFilters() { return ufilters; } - List getSystemFilters () { + List getSystemFilters() { return sfilters; } - public Authenticator setAuthenticator (Authenticator auth) { + public Authenticator setAuthenticator(Authenticator auth) { Authenticator old = authenticator; authenticator = auth; - authfilter.setAuthenticator (auth); + authfilter.setAuthenticator(auth); return old; } - public Authenticator getAuthenticator () { + public Authenticator getAuthenticator() { return authenticator; } - Logger getLogger () { + Logger getLogger() { return server.getLogger(); } } diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/HttpError.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/HttpError.java index de9dfba8cfb..b201c822193 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/HttpError.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/HttpError.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2008, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,7 +31,7 @@ package sun.net.httpserver; class HttpError extends RuntimeException { private static final long serialVersionUID = 8769596371344178179L; - public HttpError (String msg) { - super (msg); + public HttpError(String msg) { + super(msg); } } diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/HttpExchangeImpl.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/HttpExchangeImpl.java index 420ef3d9e64..d4606019c56 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/HttpExchangeImpl.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/HttpExchangeImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2006, 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 @@ -38,82 +38,82 @@ class HttpExchangeImpl extends HttpExchange { ExchangeImpl impl; - HttpExchangeImpl (ExchangeImpl impl) { + HttpExchangeImpl(ExchangeImpl impl) { this.impl = impl; } - public Headers getRequestHeaders () { + public Headers getRequestHeaders() { return impl.getRequestHeaders(); } - public Headers getResponseHeaders () { + public Headers getResponseHeaders() { return impl.getResponseHeaders(); } - public URI getRequestURI () { + public URI getRequestURI() { return impl.getRequestURI(); } - public String getRequestMethod (){ + public String getRequestMethod() { return impl.getRequestMethod(); } - public HttpContextImpl getHttpContext (){ + public HttpContextImpl getHttpContext() { return impl.getHttpContext(); } - public void close () { + public void close() { impl.close(); } - public InputStream getRequestBody () { + public InputStream getRequestBody() { return impl.getRequestBody(); } - public int getResponseCode () { + public int getResponseCode() { return impl.getResponseCode(); } - public OutputStream getResponseBody () { + public OutputStream getResponseBody() { return impl.getResponseBody(); } - public void sendResponseHeaders (int rCode, long contentLen) + public void sendResponseHeaders(int rCode, long contentLen) throws IOException { - impl.sendResponseHeaders (rCode, contentLen); + impl.sendResponseHeaders(rCode, contentLen); } - public InetSocketAddress getRemoteAddress (){ + public InetSocketAddress getRemoteAddress() { return impl.getRemoteAddress(); } - public InetSocketAddress getLocalAddress (){ + public InetSocketAddress getLocalAddress() { return impl.getLocalAddress(); } - public String getProtocol (){ + public String getProtocol() { return impl.getProtocol(); } - public Object getAttribute (String name) { - return impl.getAttribute (name); + public Object getAttribute(String name) { + return impl.getAttribute(name); } - public void setAttribute (String name, Object value) { - impl.setAttribute (name, value); + public void setAttribute(String name, Object value) { + impl.setAttribute(name, value); } - public void setStreams (InputStream i, OutputStream o) { - impl.setStreams (i, o); + public void setStreams(InputStream i, OutputStream o) { + impl.setStreams(i, o); } - public HttpPrincipal getPrincipal () { + public HttpPrincipal getPrincipal() { return impl.getPrincipal(); } - ExchangeImpl getExchangeImpl () { + ExchangeImpl getExchangeImpl() { return impl; } } diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/HttpServerImpl.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/HttpServerImpl.java index dd09957c0c6..93ace4e7bd2 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/HttpServerImpl.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/HttpServerImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -35,50 +35,50 @@ public class HttpServerImpl extends HttpServer { ServerImpl server; - HttpServerImpl () throws IOException { - this (new InetSocketAddress(80), 0); + HttpServerImpl() throws IOException { + this(new InetSocketAddress(80), 0); } - HttpServerImpl ( + HttpServerImpl( InetSocketAddress addr, int backlog ) throws IOException { - server = new ServerImpl (this, "http", addr, backlog); + server = new ServerImpl(this, "http", addr, backlog); } - public void bind (InetSocketAddress addr, int backlog) throws IOException { - server.bind (addr, backlog); + public void bind(InetSocketAddress addr, int backlog) throws IOException { + server.bind(addr, backlog); } - public void start () { + public void start() { server.start(); } - public void setExecutor (Executor executor) { + public void setExecutor(Executor executor) { server.setExecutor(executor); } - public Executor getExecutor () { + public Executor getExecutor() { return server.getExecutor(); } - public void stop (int delay) { - server.stop (delay); + public void stop(int delay) { + server.stop(delay); } - public HttpContextImpl createContext (String path, HttpHandler handler) { - return server.createContext (path, handler); + public HttpContextImpl createContext(String path, HttpHandler handler) { + return server.createContext(path, handler); } - public HttpContextImpl createContext (String path) { - return server.createContext (path); + public HttpContextImpl createContext(String path) { + return server.createContext(path); } - public void removeContext (String path) throws IllegalArgumentException { - server.removeContext (path); + public void removeContext(String path) throws IllegalArgumentException { + server.removeContext(path); } - public void removeContext (HttpContext context) throws IllegalArgumentException { - server.removeContext (context); + public void removeContext(HttpContext context) throws IllegalArgumentException { + server.removeContext(context); } public InetSocketAddress getAddress() { diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/HttpsExchangeImpl.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/HttpsExchangeImpl.java index f7f43d7feca..939b88d3928 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/HttpsExchangeImpl.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/HttpsExchangeImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2006, 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 @@ -38,86 +38,86 @@ class HttpsExchangeImpl extends HttpsExchange { ExchangeImpl impl; - HttpsExchangeImpl (ExchangeImpl impl) throws IOException { + HttpsExchangeImpl(ExchangeImpl impl) throws IOException { this.impl = impl; } - public Headers getRequestHeaders () { + public Headers getRequestHeaders() { return impl.getRequestHeaders(); } - public Headers getResponseHeaders () { + public Headers getResponseHeaders() { return impl.getResponseHeaders(); } - public URI getRequestURI () { + public URI getRequestURI() { return impl.getRequestURI(); } - public String getRequestMethod (){ + public String getRequestMethod() { return impl.getRequestMethod(); } - public HttpContextImpl getHttpContext (){ + public HttpContextImpl getHttpContext() { return impl.getHttpContext(); } - public void close () { + public void close() { impl.close(); } - public InputStream getRequestBody () { + public InputStream getRequestBody() { return impl.getRequestBody(); } - public int getResponseCode () { + public int getResponseCode() { return impl.getResponseCode(); } - public OutputStream getResponseBody () { + public OutputStream getResponseBody() { return impl.getResponseBody(); } - public void sendResponseHeaders (int rCode, long contentLen) + public void sendResponseHeaders(int rCode, long contentLen) throws IOException { - impl.sendResponseHeaders (rCode, contentLen); + impl.sendResponseHeaders(rCode, contentLen); } - public InetSocketAddress getRemoteAddress (){ + public InetSocketAddress getRemoteAddress() { return impl.getRemoteAddress(); } - public InetSocketAddress getLocalAddress (){ + public InetSocketAddress getLocalAddress() { return impl.getLocalAddress(); } - public String getProtocol (){ + public String getProtocol() { return impl.getProtocol(); } - public SSLSession getSSLSession () { - return impl.getSSLSession (); + public SSLSession getSSLSession() { + return impl.getSSLSession(); } - public Object getAttribute (String name) { - return impl.getAttribute (name); + public Object getAttribute(String name) { + return impl.getAttribute(name); } - public void setAttribute (String name, Object value) { - impl.setAttribute (name, value); + public void setAttribute(String name, Object value) { + impl.setAttribute(name, value); } - public void setStreams (InputStream i, OutputStream o) { - impl.setStreams (i, o); + public void setStreams(InputStream i, OutputStream o) { + impl.setStreams(i, o); } - public HttpPrincipal getPrincipal () { + public HttpPrincipal getPrincipal() { return impl.getPrincipal(); } - ExchangeImpl getExchangeImpl () { + ExchangeImpl getExchangeImpl() { return impl; } } diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/HttpsServerImpl.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/HttpsServerImpl.java index 97312ec29e6..8ef179c1194 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/HttpsServerImpl.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/HttpsServerImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -35,58 +35,58 @@ public class HttpsServerImpl extends HttpsServer { ServerImpl server; - HttpsServerImpl () throws IOException { - this (new InetSocketAddress(443), 0); + HttpsServerImpl() throws IOException { + this(new InetSocketAddress(443), 0); } - HttpsServerImpl ( + HttpsServerImpl( InetSocketAddress addr, int backlog ) throws IOException { - server = new ServerImpl (this, "https", addr, backlog); + server = new ServerImpl(this, "https", addr, backlog); } - public void setHttpsConfigurator (HttpsConfigurator config) { - server.setHttpsConfigurator (config); + public void setHttpsConfigurator(HttpsConfigurator config) { + server.setHttpsConfigurator(config); } - public HttpsConfigurator getHttpsConfigurator () { + public HttpsConfigurator getHttpsConfigurator() { return server.getHttpsConfigurator(); } - public void bind (InetSocketAddress addr, int backlog) throws IOException { - server.bind (addr, backlog); + public void bind(InetSocketAddress addr, int backlog) throws IOException { + server.bind(addr, backlog); } - public void start () { + public void start() { server.start(); } - public void setExecutor (Executor executor) { + public void setExecutor(Executor executor) { server.setExecutor(executor); } - public Executor getExecutor () { + public Executor getExecutor() { return server.getExecutor(); } - public void stop (int delay) { - server.stop (delay); + public void stop(int delay) { + server.stop(delay); } - public HttpContextImpl createContext (String path, HttpHandler handler) { - return server.createContext (path, handler); + public HttpContextImpl createContext(String path, HttpHandler handler) { + return server.createContext(path, handler); } - public HttpContextImpl createContext (String path) { - return server.createContext (path); + public HttpContextImpl createContext(String path) { + return server.createContext(path); } - public void removeContext (String path) throws IllegalArgumentException { - server.removeContext (path); + public void removeContext(String path) throws IllegalArgumentException { + server.removeContext(path); } - public void removeContext (HttpContext context) throws IllegalArgumentException { - server.removeContext (context); + public void removeContext(HttpContext context) throws IllegalArgumentException { + server.removeContext(context); } public InetSocketAddress getAddress() { diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/LeftOverInputStream.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/LeftOverInputStream.java index 772fa621076..ea904835770 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/LeftOverInputStream.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/LeftOverInputStream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2023, 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 @@ -45,47 +45,47 @@ abstract class LeftOverInputStream extends FilterInputStream { final ServerImpl server; protected boolean closed = false; protected boolean eof = false; - byte[] one = new byte [1]; + byte[] one = new byte[1]; private static final int MAX_SKIP_BUFFER_SIZE = 2048; - public LeftOverInputStream (ExchangeImpl t, InputStream src) { - super (src); + public LeftOverInputStream(ExchangeImpl t, InputStream src) { + super(src); this.t = t; this.server = t.getServerImpl(); } /** * if bytes are left over buffered on *the UNDERLYING* stream */ - public boolean isDataBuffered () throws IOException { + public boolean isDataBuffered() throws IOException { assert eof; return super.available() > 0; } - public void close () throws IOException { + public void close() throws IOException { if (closed) { return; } closed = true; if (!eof) { - eof = drain (ServerConfig.getDrainAmount()); + eof = drain(ServerConfig.getDrainAmount()); } } - public boolean isClosed () { + public boolean isClosed() { return closed; } - public boolean isEOF () { + public boolean isEOF() { return eof; } - protected abstract int readImpl (byte[]b, int off, int len) throws IOException; + protected abstract int readImpl(byte[] b, int off, int len) throws IOException; - public synchronized int read () throws IOException { + public synchronized int read() throws IOException { if (closed) { - throw new IOException ("Stream is closed"); + throw new IOException("Stream is closed"); } - int c = readImpl (one, 0, 1); + int c = readImpl(one, 0, 1); if (c == -1 || c == 0) { return c; } else { @@ -93,11 +93,11 @@ abstract class LeftOverInputStream extends FilterInputStream { } } - public synchronized int read (byte[]b, int off, int len) throws IOException { + public synchronized int read(byte[] b, int off, int len) throws IOException { if (closed) { - throw new IOException ("Stream is closed"); + throw new IOException("Stream is closed"); } - return readImpl (b, off, len); + return readImpl(b, off, len); } @Override @@ -132,7 +132,7 @@ abstract class LeftOverInputStream extends FilterInputStream { * is at eof (ie. all bytes were read) or false if not * (still bytes to be read) */ - public boolean drain (long l) throws IOException { + public boolean drain(long l) throws IOException { while (l > 0) { long skip = skip(l); if (skip <= 0) break; // might return 0 if isFinishing or EOF diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/Request.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/Request.java index 6c0c2c271ed..75eeb29f015 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/Request.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/Request.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,6 +25,7 @@ package sun.net.httpserver; +import java.net.ProtocolException; import java.nio.*; import java.io.*; import java.nio.channels.*; @@ -39,15 +40,18 @@ class Request { static final int BUF_LEN = 2048; static final byte CR = 13; static final byte LF = 10; + static final byte FIRST_CHAR = 32; private String startLine; private SocketChannel chan; private InputStream is; private OutputStream os; private final int maxReqHeaderSize; + private final boolean firstClearRequest; - Request (InputStream rawInputStream, OutputStream rawout) throws IOException { + Request(InputStream rawInputStream, OutputStream rawout, boolean firstClearRequest) throws IOException { this.maxReqHeaderSize = ServerConfig.getMaxReqHeaderSize(); + this.firstClearRequest = firstClearRequest; is = rawInputStream; os = rawout; do { @@ -57,15 +61,15 @@ class Request { } - char[] buf = new char [BUF_LEN]; + char[] buf = new char[BUF_LEN]; int pos; StringBuffer lineBuf; - public InputStream inputStream () { + public InputStream inputStream() { return is; } - public OutputStream outputStream () { + public OutputStream outputStream() { return os; } @@ -74,10 +78,29 @@ class Request { * Not used for reading headers. */ - public String readLine () throws IOException { + public String readLine() throws IOException { boolean gotCR = false, gotLF = false; pos = 0; lineBuf = new StringBuffer(); long lsize = 32; + + // For the first request that comes on a clear connection + // we will check that the first non CR/LF char on the + // request line is eligible. This should be the first char + // of a method name, so it should be at least greater or equal + // to 32 (FIRST_CHAR) which is the space character. + // The main goal here is to fail fast if we receive 0x16 (22) which + // happens to be the first byte of a TLS handshake record. + // This is typically what would be received if a TLS client opened + // a TLS connection on a non-TLS server. + // If we receive 0x16 we should close the connection immediately as + // it indicates we're receiving a ClientHello on a clear + // connection, and we will never receive the expected CRLF that + // terminates the first request line. + // Though we could check only for 0x16, any characters < 32 + // (excluding CRLF) is not expected at this position in a + // request line, so we can still fail here early if any of + // those are detected. + int offset = 0; while (!gotLF) { int c = is.read(); if (c == -1) { @@ -88,15 +111,27 @@ class Request { gotLF = true; } else { gotCR = false; - consume (CR); - consume (c); + consume(CR); + if (firstClearRequest && offset == 0) { + if (c < FIRST_CHAR) { + throw new ProtocolException("Unexpected start of request line"); + } + offset++; + } + consume(c); lsize = lsize + 2; } } else { if (c == CR) { gotCR = true; } else { - consume (c); + if (firstClearRequest && offset == 0) { + if (c < FIRST_CHAR) { + throw new ProtocolException("Unexpected start of request line"); + } + offset++; + } + consume(c); lsize = lsize + 1; } } @@ -106,13 +141,13 @@ class Request { ServerConfig.getMaxReqHeaderSize() + "."); } } - lineBuf.append (buf, 0, pos); - return new String (lineBuf); + lineBuf.append(buf, 0, pos); + return new String(lineBuf); } - private void consume (int c) throws IOException { + private void consume(int c) throws IOException { if (pos == BUF_LEN) { - lineBuf.append (buf); + lineBuf.append(buf); pos = 0; } buf[pos++] = (char)c; @@ -121,13 +156,13 @@ class Request { /** * returns the request line (first line of a request) */ - public String requestLine () { + public String requestLine() { return startLine; } Headers hdrs = null; @SuppressWarnings("fallthrough") - Headers headers () throws IOException { + Headers headers() throws IOException { if (hdrs != null) { return hdrs; } @@ -239,7 +274,7 @@ class Request { if (k == null) { // Headers disallows null keys, use empty string k = ""; // instead to represent invalid key } - hdrs.add (k,v); + hdrs.add(k, v); len = 0; } return hdrs; @@ -262,21 +297,21 @@ class Request { ServerImpl server; static final int BUFSIZE = 8 * 1024; - public ReadStream (ServerImpl server, SocketChannel chan) throws IOException { + public ReadStream(ServerImpl server, SocketChannel chan) throws IOException { this.channel = chan; this.server = server; - chanbuf = ByteBuffer.allocate (BUFSIZE); + chanbuf = ByteBuffer.allocate(BUFSIZE); chanbuf.clear(); one = new byte[1]; closed = marked = reset = false; } - public synchronized int read (byte[] b) throws IOException { - return read (b, 0, b.length); + public synchronized int read(byte[] b) throws IOException { + return read(b, 0, b.length); } - public synchronized int read () throws IOException { - int result = read (one, 0, 1); + public synchronized int read() throws IOException { + int result = read(one, 0, 1); if (result == 1) { return one[0] & 0xFF; } else { @@ -284,12 +319,12 @@ class Request { } } - public synchronized int read (byte[] b, int off, int srclen) throws IOException { + public synchronized int read(byte[] b, int off, int srclen) throws IOException { int canreturn, willreturn; if (closed) - throw new IOException ("Stream closed"); + throw new IOException("Stream closed"); if (eof) { return -1; @@ -300,30 +335,30 @@ class Request { Objects.checkFromIndexSize(off, srclen, b.length); if (reset) { /* satisfy from markBuf */ - canreturn = markBuf.remaining (); + canreturn = markBuf.remaining(); willreturn = canreturn>srclen ? srclen : canreturn; markBuf.get(b, off, willreturn); if (canreturn == willreturn) { reset = false; } } else { /* satisfy from channel */ - chanbuf.clear (); + chanbuf.clear(); if (srclen < BUFSIZE) { - chanbuf.limit (srclen); + chanbuf.limit(srclen); } do { - willreturn = channel.read (chanbuf); + willreturn = channel.read(chanbuf); } while (willreturn == 0); if (willreturn == -1) { eof = true; return -1; } - chanbuf.flip (); + chanbuf.flip(); chanbuf.get(b, off, willreturn); if (marked) { /* copy into markBuf */ try { - markBuf.put (b, off, willreturn); + markBuf.put(b, off, willreturn); } catch (BufferOverflowException e) { marked = false; } @@ -332,14 +367,14 @@ class Request { return willreturn; } - public boolean markSupported () { + public boolean markSupported() { return true; } /* Does not query the OS socket */ - public synchronized int available () throws IOException { + public synchronized int available() throws IOException { if (closed) - throw new IOException ("Stream is closed"); + throw new IOException("Stream is closed"); if (eof) return -1; @@ -350,31 +385,31 @@ class Request { return chanbuf.remaining(); } - public void close () throws IOException { + public void close() throws IOException { if (closed) { return; } - channel.close (); + channel.close(); closed = true; } - public synchronized void mark (int readlimit) { + public synchronized void mark(int readlimit) { if (closed) return; this.readlimit = readlimit; - markBuf = ByteBuffer.allocate (readlimit); + markBuf = ByteBuffer.allocate(readlimit); marked = true; reset = false; } - public synchronized void reset () throws IOException { + public synchronized void reset() throws IOException { if (closed ) return; if (!marked) - throw new IOException ("Stream not marked"); + throw new IOException("Stream not marked"); marked = false; reset = true; - markBuf.flip (); + markBuf.flip(); } } @@ -386,50 +421,50 @@ class Request { byte[] one; ServerImpl server; - public WriteStream (ServerImpl server, SocketChannel channel) throws IOException { + public WriteStream(ServerImpl server, SocketChannel channel) throws IOException { this.channel = channel; this.server = server; assert channel.isBlocking(); closed = false; one = new byte [1]; - buf = ByteBuffer.allocate (4096); + buf = ByteBuffer.allocate(4096); } - public synchronized void write (int b) throws IOException { + public synchronized void write(int b) throws IOException { one[0] = (byte)b; write (one, 0, 1); } - public synchronized void write (byte[] b) throws IOException { + public synchronized void write(byte[] b) throws IOException { write (b, 0, b.length); } - public synchronized void write (byte[] b, int off, int len) throws IOException { + public synchronized void write(byte[] b, int off, int len) throws IOException { int l = len; if (closed) - throw new IOException ("stream is closed"); + throw new IOException("stream is closed"); int cap = buf.capacity(); if (cap < len) { int diff = len - cap; - buf = ByteBuffer.allocate (2*(cap+diff)); + buf = ByteBuffer.allocate(2*(cap+diff)); } buf.clear(); - buf.put (b, off, len); - buf.flip (); + buf.put(b, off, len); + buf.flip(); int n; - while ((n = channel.write (buf)) < l) { + while ((n = channel.write(buf)) < l) { l -= n; if (l == 0) return; } } - public void close () throws IOException { + public void close() throws IOException { if (closed) return; - //server.logStackTrace ("Request.OS.close: isOpen="+channel.isOpen()); - channel.close (); + //server.logStackTrace("Request.OS.close: isOpen="+channel.isOpen()); + channel.close(); closed = true; } } diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/SSLStreams.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/SSLStreams.java index 5cc16befa96..5f5eef0c684 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/SSLStreams.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/SSLStreams.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -53,35 +53,35 @@ class SSLStreams { /* held by thread doing the hand-shake on this connection */ Lock handshaking = new ReentrantLock(); - SSLStreams (ServerImpl server, SSLContext sslctx, SocketChannel chan) throws IOException { + SSLStreams(ServerImpl server, SSLContext sslctx, SocketChannel chan) throws IOException { this.server = server; this.sslctx= sslctx; this.chan= chan; InetSocketAddress addr = (InetSocketAddress)chan.socket().getRemoteSocketAddress(); - engine = sslctx.createSSLEngine (addr.getHostName(), addr.getPort()); - engine.setUseClientMode (false); + engine = sslctx.createSSLEngine(addr.getHostName(), addr.getPort()); + engine.setUseClientMode(false); HttpsConfigurator cfg = server.getHttpsConfigurator(); - configureEngine (cfg, addr); - wrapper = new EngineWrapper (chan, engine); + configureEngine(cfg, addr); + wrapper = new EngineWrapper(chan, engine); } @SuppressWarnings("deprecation") - private void configureEngine(HttpsConfigurator cfg, InetSocketAddress addr){ + private void configureEngine(HttpsConfigurator cfg, InetSocketAddress addr) { if (cfg != null) { - Parameters params = new Parameters (cfg, addr); + Parameters params = new Parameters(cfg, addr); //BEGIN_TIGER_EXCLUDE - cfg.configure (params); + cfg.configure(params); SSLParameters sslParams = params.getSSLParameters(); if (sslParams != null) { - engine.setSSLParameters (sslParams); + engine.setSSLParameters(sslParams); } else //END_TIGER_EXCLUDE { /* tiger compatibility */ if (params.getCipherSuites() != null) { try { - engine.setEnabledCipherSuites ( + engine.setEnabledCipherSuites( params.getCipherSuites() ); } catch (IllegalArgumentException e) { /* LOG */} @@ -93,7 +93,7 @@ class SSLStreams { } if (params.getProtocols() != null) { try { - engine.setEnabledProtocols ( + engine.setEnabledProtocols( params.getProtocols() ); } catch (IllegalArgumentException e) { /* LOG */} @@ -106,11 +106,11 @@ class SSLStreams { InetSocketAddress addr; HttpsConfigurator cfg; - Parameters (HttpsConfigurator cfg, InetSocketAddress addr) { + Parameters(HttpsConfigurator cfg, InetSocketAddress addr) { this.addr = addr; this.cfg = cfg; } - public InetSocketAddress getClientAddress () { + public InetSocketAddress getClientAddress() { return addr; } public HttpsConfigurator getHttpsConfigurator() { @@ -118,10 +118,10 @@ class SSLStreams { } //BEGIN_TIGER_EXCLUDE SSLParameters params; - public void setSSLParameters (SSLParameters p) { + public void setSSLParameters(SSLParameters p) { params = p; } - SSLParameters getSSLParameters () { + SSLParameters getSSLParameters() { return params; } //END_TIGER_EXCLUDE @@ -130,14 +130,14 @@ class SSLStreams { /** * cleanup resources allocated inside this object */ - void close () throws IOException { + void close() throws IOException { wrapper.close(); } /** * return the SSL InputStream */ - InputStream getInputStream () throws IOException { + InputStream getInputStream() throws IOException { if (is == null) { is = new InputStream(); } @@ -147,14 +147,14 @@ class SSLStreams { /** * return the SSL OutputStream */ - OutputStream getOutputStream () throws IOException { + OutputStream getOutputStream() throws IOException { if (os == null) { os = new OutputStream(); } return os; } - SSLEngine getSSLEngine () { + SSLEngine getSSLEngine() { return engine; } @@ -183,11 +183,11 @@ class SSLStreams { PACKET, APPLICATION }; - private ByteBuffer allocate (BufType type) { - return allocate (type, -1); + private ByteBuffer allocate(BufType type) { + return allocate(type, -1); } - private ByteBuffer allocate (BufType type, int len) { + private ByteBuffer allocate(BufType type, int len) { assert engine != null; synchronized (this) { int size; @@ -210,7 +210,7 @@ class SSLStreams { } size = app_buf_size; } - return ByteBuffer.allocate (size); + return ByteBuffer.allocate(size); } } @@ -222,10 +222,10 @@ class SSLStreams { * flip is set to true if the old buffer needs to be flipped * before it is copied. */ - private ByteBuffer realloc (ByteBuffer b, boolean flip, BufType type) { + private ByteBuffer realloc(ByteBuffer b, boolean flip, BufType type) { synchronized (this) { int nsize = 2 * b.capacity(); - ByteBuffer n = allocate (type, nsize); + ByteBuffer n = allocate(type, nsize); if (flip) { b.flip(); } @@ -252,7 +252,7 @@ class SSLStreams { boolean closed = false; int u_remaining; // the number of bytes left in unwrap_src after an unwrap() - EngineWrapper (SocketChannel chan, SSLEngine engine) throws IOException { + EngineWrapper(SocketChannel chan, SSLEngine engine) throws IOException { this.chan = chan; this.engine = engine; wrapLock = new Object(); @@ -261,7 +261,7 @@ class SSLStreams { wrap_dst = allocate(BufType.PACKET); } - void close () throws IOException { + void close() throws IOException { } /* try to wrap and send the data in src. Handles OVERFLOW. @@ -275,17 +275,17 @@ class SSLStreams { WrapperResult wrapAndSendX(ByteBuffer src, boolean ignoreClose) throws IOException { if (closed && !ignoreClose) { - throw new IOException ("Engine is closed"); + throw new IOException("Engine is closed"); } Status status; WrapperResult r = new WrapperResult(); synchronized (wrapLock) { wrap_dst.clear(); do { - r.result = engine.wrap (src, wrap_dst); + r.result = engine.wrap(src, wrap_dst); status = r.result.getStatus(); if (status == Status.BUFFER_OVERFLOW) { - wrap_dst = realloc (wrap_dst, true, BufType.PACKET); + wrap_dst = realloc(wrap_dst, true, BufType.PACKET); } } while (status == Status.BUFFER_OVERFLOW); if (status == Status.CLOSED && !ignoreClose) { @@ -296,7 +296,7 @@ class SSLStreams { int l = wrap_dst.remaining(); assert l == r.result.bytesProduced(); while (l>0) { - l -= chan.write (wrap_dst); + l -= chan.write(wrap_dst); } } } @@ -313,7 +313,7 @@ class SSLStreams { WrapperResult r = new WrapperResult(); r.buf = dst; if (closed) { - throw new IOException ("Engine is closed"); + throw new IOException("Engine is closed"); } boolean needData; if (u_remaining > 0) { @@ -329,19 +329,19 @@ class SSLStreams { do { if (needData) { do { - x = chan.read (unwrap_src); + x = chan.read(unwrap_src); } while (x == 0); if (x == -1) { - throw new IOException ("connection closed for reading"); + throw new IOException("connection closed for reading"); } unwrap_src.flip(); } - r.result = engine.unwrap (unwrap_src, r.buf); + r.result = engine.unwrap(unwrap_src, r.buf); status = r.result.getStatus(); if (status == Status.BUFFER_UNDERFLOW) { if (unwrap_src.limit() == unwrap_src.capacity()) { /* buffer not big enough */ - unwrap_src = realloc ( + unwrap_src = realloc( unwrap_src, false, BufType.PACKET ); } else { @@ -349,12 +349,12 @@ class SSLStreams { * data off the channel. Reset pointers * for reading off SocketChannel */ - unwrap_src.position (unwrap_src.limit()); - unwrap_src.limit (unwrap_src.capacity()); + unwrap_src.position(unwrap_src.limit()); + unwrap_src.limit(unwrap_src.capacity()); } needData = true; } else if (status == Status.BUFFER_OVERFLOW) { - r.buf = realloc (r.buf, true, BufType.APPLICATION); + r.buf = realloc(r.buf, true, BufType.APPLICATION); needData = false; } else if (status == Status.CLOSED) { closed = true; @@ -374,13 +374,13 @@ class SSLStreams { * all of the given user data has been sent and any handshake has been * completed. Caller should check if engine has been closed. */ - public WrapperResult sendData (ByteBuffer src) throws IOException { + public WrapperResult sendData(ByteBuffer src) throws IOException { WrapperResult r=null; while (src.remaining() > 0) { r = wrapper.wrapAndSend(src); Status status = r.result.getStatus(); if (status == Status.CLOSED) { - doClosure (); + doClosure(); return r; } HandshakeStatus hs_status = r.result.getHandshakeStatus(); @@ -399,16 +399,16 @@ class SSLStreams { * and returned. This call handles handshaking automatically. * Caller should check if engine has been closed. */ - public WrapperResult recvData (ByteBuffer dst) throws IOException { + public WrapperResult recvData(ByteBuffer dst) throws IOException { /* we wait until some user data arrives */ WrapperResult r = null; assert dst.position() == 0; while (dst.position() == 0) { - r = wrapper.recvAndUnwrap (dst); + r = wrapper.recvAndUnwrap(dst); dst = (r.buf != dst) ? r.buf: dst; Status status = r.result.getStatus(); if (status == Status.CLOSED) { - doClosure (); + doClosure(); return r; } @@ -416,7 +416,7 @@ class SSLStreams { if (hs_status != HandshakeStatus.FINISHED && hs_status != HandshakeStatus.NOT_HANDSHAKING) { - doHandshake (hs_status); + doHandshake(hs_status); } } dst.flip(); @@ -426,7 +426,7 @@ class SSLStreams { /* we've received a close notify. Need to call wrap to send * the response */ - void doClosure () throws IOException { + void doClosure() throws IOException { try { handshaking.lock(); ByteBuffer tmp = allocate(BufType.APPLICATION); @@ -435,8 +435,8 @@ class SSLStreams { HandshakeStatus hs; do { tmp.clear(); - tmp.flip (); - r = wrapper.wrapAndSendX (tmp, true); + tmp.flip(); + r = wrapper.wrapAndSendX(tmp, true); hs = r.result.getHandshakeStatus(); st = r.result.getStatus(); } while (st != Status.CLOSED && @@ -452,7 +452,7 @@ class SSLStreams { * is called with no data to send then there must be no problem */ @SuppressWarnings("fallthrough") - void doHandshake (HandshakeStatus hs_status) throws IOException { + void doHandshake(HandshakeStatus hs_status) throws IOException { try { handshaking.lock(); ByteBuffer tmp = allocate(BufType.APPLICATION); @@ -478,7 +478,7 @@ class SSLStreams { case NEED_UNWRAP: tmp.clear(); - r = wrapper.recvAndUnwrap (tmp); + r = wrapper.recvAndUnwrap(tmp); if (r.buf != tmp) { tmp = r.buf; } @@ -507,27 +507,27 @@ class SSLStreams { boolean needData = true; - InputStream () { - bbuf = allocate (BufType.APPLICATION); + InputStream() { + bbuf = allocate(BufType.APPLICATION); } - public int read (byte[] buf, int off, int len) throws IOException { + public int read(byte[] buf, int off, int len) throws IOException { if (closed) { - throw new IOException ("SSL stream is closed"); + throw new IOException("SSL stream is closed"); } if (eof) { return -1; } - int available=0; + int available = 0; if (!needData) { available = bbuf.remaining(); - needData = (available==0); + needData = (available == 0); } if (needData) { bbuf.clear(); - WrapperResult r = recvData (bbuf); - bbuf = r.buf== bbuf? bbuf: r.buf; - if ((available=bbuf.remaining()) == 0) { + WrapperResult r = recvData(bbuf); + bbuf = r.buf == bbuf ? bbuf: r.buf; + if ((available = bbuf.remaining()) == 0) { eof = true; return -1; } else { @@ -538,26 +538,26 @@ class SSLStreams { if (len > available) { len = available; } - bbuf.get (buf, off, len); + bbuf.get(buf, off, len); return len; } - public int available () throws IOException { + public int available() throws IOException { return bbuf.remaining(); } - public boolean markSupported () { + public boolean markSupported() { return false; /* not possible with SSLEngine */ } - public void reset () throws IOException { - throw new IOException ("mark/reset not supported"); + public void reset() throws IOException { + throw new IOException("mark/reset not supported"); } - public long skip (long s) throws IOException { + public long skip(long s) throws IOException { int n = (int)s; if (closed) { - throw new IOException ("SSL stream is closed"); + throw new IOException("SSL stream is closed"); } if (eof) { return 0; @@ -565,13 +565,13 @@ class SSLStreams { int ret = n; while (n > 0) { if (bbuf.remaining() >= n) { - bbuf.position (bbuf.position()+n); + bbuf.position(bbuf.position()+n); return ret; } else { n -= bbuf.remaining(); bbuf.clear(); - WrapperResult r = recvData (bbuf); - bbuf = r.buf==bbuf? bbuf: r.buf; + WrapperResult r = recvData(bbuf); + bbuf = r.buf == bbuf ? bbuf: r.buf; } } return ret; /* not reached */ @@ -582,22 +582,22 @@ class SSLStreams { * before this is called. Otherwise an exception will be thrown. * [Note. May need to revisit this. not quite the normal close() semantics */ - public void close () throws IOException { + public void close() throws IOException { eof = true; - engine.closeInbound (); + engine.closeInbound(); } - public int read (byte[] buf) throws IOException { - return read (buf, 0, buf.length); + public int read(byte[] buf) throws IOException { + return read(buf, 0, buf.length); } byte single[] = new byte [1]; - public int read () throws IOException { + public int read() throws IOException { if (eof) { return -1; } - int n = read (single, 0, 1); + int n = read(single, 0, 1); if (n <= 0) { return -1; } else { @@ -622,28 +622,28 @@ class SSLStreams { public void write(int b) throws IOException { single[0] = (byte)b; - write (single, 0, 1); + write(single, 0, 1); } public void write(byte b[]) throws IOException { - write (b, 0, b.length); + write(b, 0, b.length); } public void write(byte b[], int off, int len) throws IOException { if (closed) { - throw new IOException ("output stream is closed"); + throw new IOException("output stream is closed"); } while (len > 0) { int l = len > buf.capacity() ? buf.capacity() : len; buf.clear(); - buf.put (b, off, l); + buf.put(b, off, l); len -= l; off += l; buf.flip(); - WrapperResult r = sendData (buf); + WrapperResult r = sendData(buf); if (r.result.getStatus() == Status.CLOSED) { closed = true; if (len > 0) { - throw new IOException ("output stream is closed"); + throw new IOException("output stream is closed"); } } } @@ -654,13 +654,13 @@ class SSLStreams { } public void close() throws IOException { - WrapperResult r=null; + WrapperResult r = null; engine.closeOutbound(); closed = true; HandshakeStatus stat = HandshakeStatus.NEED_WRAP; buf.clear(); while (stat == HandshakeStatus.NEED_WRAP) { - r = wrapper.wrapAndSend (buf); + r = wrapper.wrapAndSend(buf); stat = r.result.getHandshakeStatus(); } assert r.result.getStatus() == Status.CLOSED diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java index 217ae86f5d3..e8c8d336e03 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java @@ -45,6 +45,7 @@ import java.lang.System.Logger; import java.lang.System.Logger.Level; import java.net.BindException; import java.net.InetSocketAddress; +import java.net.ProtocolException; import java.net.ServerSocket; import java.net.URI; import java.net.URISyntaxException; @@ -128,61 +129,61 @@ class ServerImpl { private final Logger logger; private Thread dispatcherThread; - ServerImpl ( + ServerImpl( HttpServer wrapper, String protocol, InetSocketAddress addr, int backlog ) throws IOException { this.protocol = protocol; this.wrapper = wrapper; - this.logger = System.getLogger ("com.sun.net.httpserver"); - ServerConfig.checkLegacyProperties (logger); - https = protocol.equalsIgnoreCase ("https"); + this.logger = System.getLogger("com.sun.net.httpserver"); + ServerConfig.checkLegacyProperties(logger); + https = protocol.equalsIgnoreCase("https"); this.address = addr; contexts = new ContextList(); schan = ServerSocketChannel.open(); if (addr != null) { ServerSocket socket = schan.socket(); - socket.bind (addr, backlog); + socket.bind(addr, backlog); bound = true; } - selector = Selector.open (); - schan.configureBlocking (false); - listenerKey = schan.register (selector, SelectionKey.OP_ACCEPT); + selector = Selector.open(); + schan.configureBlocking(false); + listenerKey = schan.register(selector, SelectionKey.OP_ACCEPT); dispatcher = new Dispatcher(); - idleConnections = Collections.synchronizedSet (new HashSet()); - allConnections = Collections.synchronizedSet (new HashSet()); - reqConnections = Collections.synchronizedSet (new HashSet()); - rspConnections = Collections.synchronizedSet (new HashSet()); + idleConnections = Collections.synchronizedSet(new HashSet()); + allConnections = Collections.synchronizedSet(new HashSet()); + reqConnections = Collections.synchronizedSet(new HashSet()); + rspConnections = Collections.synchronizedSet(new HashSet()); newlyAcceptedConnections = Collections.synchronizedSet(new HashSet<>()); - timer = new Timer ("idle-timeout-task", true); - timer.schedule (new IdleTimeoutTask(), IDLE_TIMER_TASK_SCHEDULE, IDLE_TIMER_TASK_SCHEDULE); + timer = new Timer("idle-timeout-task", true); + timer.schedule(new IdleTimeoutTask(), IDLE_TIMER_TASK_SCHEDULE, IDLE_TIMER_TASK_SCHEDULE); if (reqRspTimeoutEnabled) { - timer1 = new Timer ("req-rsp-timeout-task", true); - timer1.schedule (new ReqRspTimeoutTask(), REQ_RSP_TIMER_SCHEDULE, REQ_RSP_TIMER_SCHEDULE); + timer1 = new Timer("req-rsp-timeout-task", true); + timer1.schedule(new ReqRspTimeoutTask(), REQ_RSP_TIMER_SCHEDULE, REQ_RSP_TIMER_SCHEDULE); logger.log(Level.DEBUG, "HttpServer request/response timeout task schedule ms: ", REQ_RSP_TIMER_SCHEDULE); - logger.log (Level.DEBUG, "MAX_REQ_TIME: "+MAX_REQ_TIME); - logger.log (Level.DEBUG, "MAX_RSP_TIME: "+MAX_RSP_TIME); + logger.log(Level.DEBUG, "MAX_REQ_TIME: "+MAX_REQ_TIME); + logger.log(Level.DEBUG, "MAX_RSP_TIME: "+MAX_RSP_TIME); } events = new ArrayList<>(); - logger.log (Level.DEBUG, "HttpServer created "+protocol+" "+ addr); + logger.log(Level.DEBUG, "HttpServer created "+protocol+" "+ addr); } - public void bind (InetSocketAddress addr, int backlog) throws IOException { + public void bind(InetSocketAddress addr, int backlog) throws IOException { if (bound) { - throw new BindException ("HttpServer already bound"); + throw new BindException("HttpServer already bound"); } if (addr == null) { - throw new NullPointerException ("null address"); + throw new NullPointerException("null address"); } ServerSocket socket = schan.socket(); - socket.bind (addr, backlog); + socket.bind(addr, backlog); bound = true; } - public void start () { + public void start() { if (!bound || started || finished()) { - throw new IllegalStateException ("server in wrong state"); + throw new IllegalStateException("server in wrong state"); } if (executor == null) { executor = new DefaultExecutor(); @@ -192,39 +193,39 @@ class ServerImpl { dispatcherThread.start(); } - public void setExecutor (Executor executor) { + public void setExecutor(Executor executor) { if (started) { - throw new IllegalStateException ("server already started"); + throw new IllegalStateException("server already started"); } this.executor = executor; } private static class DefaultExecutor implements Executor { - public void execute (Runnable task) { + public void execute(Runnable task) { task.run(); } } - public Executor getExecutor () { + public Executor getExecutor() { return executor; } - public void setHttpsConfigurator (HttpsConfigurator config) { + public void setHttpsConfigurator(HttpsConfigurator config) { if (config == null) { - throw new NullPointerException ("null HttpsConfigurator"); + throw new NullPointerException("null HttpsConfigurator"); } if (started) { - throw new IllegalStateException ("server already started"); + throw new IllegalStateException("server already started"); } this.httpsConfig = config; sslContext = config.getSSLContext(); } - public HttpsConfigurator getHttpsConfigurator () { + public HttpsConfigurator getHttpsConfigurator() { return httpsConfig; } - private final boolean finished(){ + private final boolean finished() { // if the latch is 0, the server is finished return finishedLatch.getCount() == 0; } @@ -242,9 +243,9 @@ class ServerImpl { * * @param delay maximum delay to wait for exchanges completion, in seconds */ - public void stop (int delay) { + public void stop(int delay) { if (delay < 0) { - throw new IllegalArgumentException ("negative delay parameter"); + throw new IllegalArgumentException("negative delay parameter"); } logger.log(Level.TRACE, "stopping"); @@ -298,49 +299,49 @@ class ServerImpl { Dispatcher dispatcher; - public synchronized HttpContextImpl createContext (String path, HttpHandler handler) { + public synchronized HttpContextImpl createContext(String path, HttpHandler handler) { if (handler == null || path == null) { - throw new NullPointerException ("null handler, or path parameter"); + throw new NullPointerException("null handler, or path parameter"); } - HttpContextImpl context = new HttpContextImpl (protocol, path, handler, this); - contexts.add (context); - logger.log (Level.DEBUG, "context created: " + path); + HttpContextImpl context = new HttpContextImpl(protocol, path, handler, this); + contexts.add(context); + logger.log(Level.DEBUG, "context created: " + path); return context; } - public synchronized HttpContextImpl createContext (String path) { + public synchronized HttpContextImpl createContext(String path) { if (path == null) { - throw new NullPointerException ("null path parameter"); + throw new NullPointerException("null path parameter"); } - HttpContextImpl context = new HttpContextImpl (protocol, path, null, this); - contexts.add (context); - logger.log (Level.DEBUG, "context created: " + path); + HttpContextImpl context = new HttpContextImpl(protocol, path, null, this); + contexts.add(context); + logger.log(Level.DEBUG, "context created: " + path); return context; } - public synchronized void removeContext (String path) throws IllegalArgumentException { + public synchronized void removeContext(String path) throws IllegalArgumentException { if (path == null) { - throw new NullPointerException ("null path parameter"); + throw new NullPointerException("null path parameter"); } - contexts.remove (protocol, path); - logger.log (Level.DEBUG, "context removed: " + path); + contexts.remove(protocol, path); + logger.log(Level.DEBUG, "context removed: " + path); } - public synchronized void removeContext (HttpContext context) throws IllegalArgumentException { + public synchronized void removeContext(HttpContext context) throws IllegalArgumentException { if (!(context instanceof HttpContextImpl)) { - throw new IllegalArgumentException ("wrong HttpContext type"); + throw new IllegalArgumentException("wrong HttpContext type"); } - contexts.remove ((HttpContextImpl)context); - logger.log (Level.DEBUG, "context removed: " + context.getPath()); + contexts.remove((HttpContextImpl)context); + logger.log(Level.DEBUG, "context removed: " + context.getPath()); } public InetSocketAddress getAddress() { return (InetSocketAddress) schan.socket().getLocalSocketAddress(); } - void addEvent (Event r) { + void addEvent(Event r) { synchronized (lolock) { - events.add (r); + events.add(r); selector.wakeup(); } } @@ -413,7 +414,7 @@ class ServerImpl { */ class Dispatcher implements Runnable { - private void handleEvent (Event r) { + private void handleEvent(Event r) { // Stopping marking the state as finished if stop is requested, // termination is in progress and exchange count is 0 @@ -450,22 +451,22 @@ class ServerImpl { requestCompleted(c); } } - responseCompleted (c); + responseCompleted(c); if (t.close) { c.close(); - allConnections.remove (c); + allConnections.remove(c); } else { if (is.isDataBuffered()) { /* don't re-enable the interestops, just handle it */ - requestStarted (c); - handle (c.getChannel(), c); + requestStarted(c); + handle(c.getChannel(), c); } else { - connsToRegister.add (c); + connsToRegister.add(c); } } } } catch (IOException e) { - logger.log ( + logger.log( Level.TRACE, "Dispatcher (1)", e ); c.close(); @@ -474,18 +475,18 @@ class ServerImpl { final ArrayList connsToRegister = new ArrayList<>(); - void reRegister (HttpConnection c) { + void reRegister(HttpConnection c) { /* re-register with selector */ try { SocketChannel chan = c.getChannel(); - chan.configureBlocking (false); - SelectionKey key = chan.register (selector, SelectionKey.OP_READ); - key.attach (c); + chan.configureBlocking(false); + SelectionKey key = chan.register(selector, SelectionKey.OP_READ); + key.attach(c); c.selectionKey = key; markIdle(c); } catch (IOException e) { dprint(e); - logger.log (Level.TRACE, "Dispatcher(8)", e); + logger.log(Level.TRACE, "Dispatcher (8)", e); c.close(); } } @@ -504,7 +505,7 @@ class ServerImpl { if (list != null) { for (Event r: list) { - handleEvent (r); + handleEvent(r); } } @@ -525,7 +526,7 @@ class ServerImpl { for (final SelectionKey key : selected.toArray(SelectionKey[]::new)) { // remove the key from the original selected keys (live) Set selected.remove(key); - if (key.equals (listenerKey)) { + if (key.equals(listenerKey)) { if (terminating) { continue; } @@ -546,15 +547,15 @@ class ServerImpl { if (ServerConfig.noDelay()) { chan.socket().setTcpNoDelay(true); } - chan.configureBlocking (false); + chan.configureBlocking(false); SelectionKey newkey = - chan.register (selector, SelectionKey.OP_READ); - HttpConnection c = new HttpConnection (); + chan.register(selector, SelectionKey.OP_READ); + HttpConnection c = new HttpConnection(); c.selectionKey = newkey; - c.setChannel (chan); - newkey.attach (c); + c.setChannel(chan); + newkey.attach(c); markNewlyAccepted(c); - allConnections.add (c); + allConnections.add(c); } } else { try { @@ -563,7 +564,7 @@ class ServerImpl { HttpConnection conn = (HttpConnection)key.attachment(); key.cancel(); - chan.configureBlocking (true); + chan.configureBlocking(true); // check if connection is being closed if (newlyAcceptedConnections.remove(conn) || idleConnections.remove(conn)) { @@ -571,7 +572,7 @@ class ServerImpl { // connection. In either case, we mark that the request // has now started on this connection. requestStarted(conn); - handle (chan, conn); + handle(chan, conn); } } else { assert false : "Unexpected non-readable key:" + key; @@ -586,56 +587,56 @@ class ServerImpl { // call the selector just to process the cancelled keys selector.selectNow(); } catch (IOException e) { - logger.log (Level.TRACE, "Dispatcher (4)", e); + logger.log(Level.TRACE, "Dispatcher (4)", e); } catch (Exception e) { - logger.log (Level.TRACE, "Dispatcher (7)", e); + logger.log(Level.TRACE, "Dispatcher (7)", e); } } - try {selector.close(); } catch (Exception e) {} + try { selector.close(); } catch (Exception e) {} } - private void handleException (SelectionKey key, Exception e) { + private void handleException(SelectionKey key, Exception e) { HttpConnection conn = (HttpConnection)key.attachment(); if (e != null) { - logger.log (Level.TRACE, "Dispatcher (2)", e); + logger.log(Level.TRACE, "Dispatcher (2)", e); } closeConnection(conn); } - public void handle (SocketChannel chan, HttpConnection conn) + public void handle(SocketChannel chan, HttpConnection conn) { try { - Exchange t = new Exchange (chan, protocol, conn); - executor.execute (t); + Exchange t = new Exchange(chan, protocol, conn); + executor.execute(t); } catch (HttpError e1) { - logger.log (Level.TRACE, "Dispatcher (4)", e1); + logger.log(Level.TRACE, "Dispatcher (4)", e1); closeConnection(conn); } catch (IOException e) { - logger.log (Level.TRACE, "Dispatcher (5)", e); + logger.log(Level.TRACE, "Dispatcher (5)", e); closeConnection(conn); } catch (Throwable e) { - logger.log (Level.TRACE, "Dispatcher (6)", e); + logger.log(Level.TRACE, "Dispatcher (6)", e); closeConnection(conn); } } } - static boolean debug = ServerConfig.debugEnabled (); + static boolean debug = ServerConfig.debugEnabled(); - static synchronized void dprint (String s) { + static synchronized void dprint(String s) { if (debug) { - System.out.println (s); + System.out.println(s); } } - static synchronized void dprint (Exception e) { + static synchronized void dprint(Exception e) { if (debug) { - System.out.println (e); + System.out.println(e); e.printStackTrace(); } } - Logger getLogger () { + Logger getLogger() { return logger; } @@ -675,13 +676,13 @@ class ServerImpl { HttpContextImpl ctx; boolean rejected = false; - Exchange (SocketChannel chan, String protocol, HttpConnection conn) throws IOException { + Exchange(SocketChannel chan, String protocol, HttpConnection conn) throws IOException { this.chan = chan; this.connection = conn; this.protocol = protocol; } - public void run () { + public void run() { /* context will be null for new connections */ logger.log(Level.TRACE, "exchange started"); @@ -702,7 +703,7 @@ class ServerImpl { String requestLine = null; SSLStreams sslStreams = null; try { - if (context != null ) { + if (context != null) { this.rawin = connection.getInputStream(); this.rawout = connection.getRawOutputStream(); newconnection = false; @@ -711,21 +712,21 @@ class ServerImpl { newconnection = true; if (https) { if (sslContext == null) { - logger.log (Level.WARNING, + logger.log(Level.WARNING, "SSL connection received. No https context created"); - throw new HttpError ("No SSL context established"); + throw new HttpError("No SSL context established"); } - sslStreams = new SSLStreams (ServerImpl.this, sslContext, chan); + sslStreams = new SSLStreams(ServerImpl.this, sslContext, chan); rawin = sslStreams.getInputStream(); rawout = sslStreams.getOutputStream(); engine = sslStreams.getSSLEngine(); connection.sslStreams = sslStreams; } else { rawin = new BufferedInputStream( - new Request.ReadStream ( + new Request.ReadStream( ServerImpl.this, chan )); - rawout = new Request.WriteStream ( + rawout = new Request.WriteStream( ServerImpl.this, chan ); } @@ -733,7 +734,16 @@ class ServerImpl { connection.raw = rawin; connection.rawout = rawout; } - Request req = new Request (rawin, rawout); + + Request req; + try { + req = new Request(rawin, rawout, newconnection && !https); + } catch (ProtocolException pe) { + logger.log(Level.DEBUG, "closing due to: " + pe); + reject(Code.HTTP_BAD_REQUEST, "", pe.getMessage()); + return; + } + requestLine = req.requestLine(); if (requestLine == null) { /* connection closed */ @@ -742,31 +752,31 @@ class ServerImpl { return; } logger.log(Level.DEBUG, "Exchange request line: {0}", requestLine); - int space = requestLine.indexOf (' '); + int space = requestLine.indexOf(' '); if (space == -1) { - reject (Code.HTTP_BAD_REQUEST, + reject(Code.HTTP_BAD_REQUEST, requestLine, "Bad request line"); return; } - String method = requestLine.substring (0, space); + String method = requestLine.substring(0, space); int start = space+1; space = requestLine.indexOf(' ', start); if (space == -1) { - reject (Code.HTTP_BAD_REQUEST, + reject(Code.HTTP_BAD_REQUEST, requestLine, "Bad request line"); return; } - String uriStr = requestLine.substring (start, space); + String uriStr = requestLine.substring(start, space); URI uri; try { - uri = new URI (uriStr); + uri = new URI(uriStr); } catch (URISyntaxException e3) { reject(Code.HTTP_BAD_REQUEST, requestLine, "URISyntaxException thrown"); return; } start = space+1; - String version = requestLine.substring (start); + String version = requestLine.substring(start); Headers headers = req.headers(); /* check key for illegal characters */ for (var k : headers.keySet()) { @@ -817,32 +827,32 @@ class ServerImpl { requestCompleted(connection); } } - ctx = contexts.findContext (protocol, uri.getPath()); + ctx = contexts.findContext(protocol, uri.getPath()); if (ctx == null) { - reject (Code.HTTP_NOT_FOUND, + reject(Code.HTTP_NOT_FOUND, requestLine, "No context found for request"); return; } - connection.setContext (ctx); + connection.setContext(ctx); if (ctx.getHandler() == null) { - reject (Code.HTTP_INTERNAL_ERROR, + reject(Code.HTTP_INTERNAL_ERROR, requestLine, "No handler for context"); return; } - tx = new ExchangeImpl ( + tx = new ExchangeImpl( method, uri, req, clen, connection ); String chdr = headers.getFirst("Connection"); Headers rheaders = tx.getResponseHeaders(); - if (chdr != null && chdr.equalsIgnoreCase ("close")) { + if (chdr != null && chdr.equalsIgnoreCase("close")) { tx.close = true; } - if (version.equalsIgnoreCase ("http/1.0")) { + if (version.equalsIgnoreCase("http/1.0")) { tx.http10 = true; if (chdr == null) { tx.close = true; - rheaders.set ("Connection", "close"); + rheaders.set("Connection", "close"); } else if (chdr.equalsIgnoreCase("keep-alive")) { rheaders.set("Connection", "keep-alive"); int idleSeconds = (int) (ServerConfig.getIdleIntervalMillis() / 1000); @@ -852,7 +862,7 @@ class ServerImpl { } if (newconnection) { - connection.setParameters ( + connection.setParameters( rawin, rawout, chan, engine, sslStreams, sslContext, protocol, ctx, rawin ); @@ -863,9 +873,9 @@ class ServerImpl { * be involved in this process. */ String exp = headers.getFirst("Expect"); - if (exp != null && exp.equalsIgnoreCase ("100-continue")) { - logReply (100, requestLine, null); - sendReply ( + if (exp != null && exp.equalsIgnoreCase("100-continue")) { + logReply(100, requestLine, null); + sendReply( Code.HTTP_CONTINUE, false, null ); } @@ -880,19 +890,19 @@ class ServerImpl { final List uf = ctx.getFilters(); final Filter.Chain sc = new Filter.Chain(sf, ctx.getHandler()); - final Filter.Chain uc = new Filter.Chain(uf, new LinkHandler (sc)); + final Filter.Chain uc = new Filter.Chain(uf, new LinkHandler(sc)); /* set up the two stream references */ tx.getRequestBody(); tx.getResponseBody(); if (https) { - uc.doFilter (new HttpsExchangeImpl (tx)); + uc.doFilter(new HttpsExchangeImpl(tx)); } else { - uc.doFilter (new HttpExchangeImpl (tx)); + uc.doFilter(new HttpExchangeImpl(tx)); } } catch (Exception e) { - logger.log (Level.TRACE, "ServerImpl.Exchange", e); + logger.log(Level.TRACE, "ServerImpl.Exchange", e); if (tx == null || !tx.writefinished) { closeConnection(connection); } @@ -907,59 +917,59 @@ class ServerImpl { class LinkHandler implements HttpHandler { Filter.Chain nextChain; - LinkHandler (Filter.Chain nextChain) { + LinkHandler(Filter.Chain nextChain) { this.nextChain = nextChain; } - public void handle (HttpExchange exchange) throws IOException { - nextChain.doFilter (exchange); + public void handle(HttpExchange exchange) throws IOException { + nextChain.doFilter(exchange); } } - void reject (int code, String requestStr, String message) { + void reject(int code, String requestStr, String message) { rejected = true; - logReply (code, requestStr, message); - sendReply ( + logReply(code, requestStr, message); + sendReply( code, true, "

    "+code+Code.msg(code)+"

    "+message ); } - void sendReply ( + void sendReply( int code, boolean closeNow, String text) { try { - StringBuilder builder = new StringBuilder (512); - builder.append ("HTTP/1.1 ") - .append (code).append (Code.msg(code)).append ("\r\n"); + StringBuilder builder = new StringBuilder(512); + builder.append("HTTP/1.1 ") + .append(code).append(Code.msg(code)).append("\r\n"); if (text != null && text.length() != 0) { - builder.append ("Content-Length: ") - .append (text.length()).append ("\r\n") - .append ("Content-Type: text/html\r\n"); + builder.append("Content-Length: ") + .append(text.length()).append("\r\n") + .append("Content-Type: text/html\r\n"); } else { - builder.append ("Content-Length: 0\r\n"); + builder.append("Content-Length: 0\r\n"); text = ""; } if (closeNow) { - builder.append ("Connection: close\r\n"); + builder.append("Connection: close\r\n"); } - builder.append ("\r\n").append (text); + builder.append("\r\n").append(text); String s = builder.toString(); byte[] b = s.getBytes(ISO_8859_1); - rawout.write (b); + rawout.write(b); rawout.flush(); if (closeNow) { closeConnection(connection); } } catch (IOException e) { - logger.log (Level.TRACE, "ServerImpl.sendReply", e); + logger.log(Level.TRACE, "ServerImpl.sendReply", e); closeConnection(connection); } } } - void logReply (int code, String requestStr, String text) { + void logReply(int code, String requestStr, String text) { if (!logger.isLoggable(Level.DEBUG)) { return; } @@ -968,18 +978,18 @@ class ServerImpl { } String r; if (requestStr.length() > 80) { - r = requestStr.substring (0, 80) + ""; + r = requestStr.substring(0, 80) + ""; } else { r = requestStr; } String message = r + " [" + code + " " + Code.msg(code) + "] ("+text+")"; - logger.log (Level.DEBUG, message); + logger.log(Level.DEBUG, message); } private int exchangeCount = 0; - synchronized void startExchange () { + synchronized void startExchange() { exchangeCount ++; } @@ -987,20 +997,20 @@ class ServerImpl { return exchangeCount; } - synchronized int endExchange () { + synchronized int endExchange() { exchangeCount --; assert exchangeCount >= 0; return exchangeCount; } - HttpServer getWrapper () { + HttpServer getWrapper() { return wrapper; } - void requestStarted (HttpConnection c) { + void requestStarted(HttpConnection c) { c.reqStartedTime = System.currentTimeMillis(); - c.setState (State.REQUEST); - reqConnections.add (c); + c.setState(State.REQUEST); + reqConnections.add(c); } void markIdle(HttpConnection c) { @@ -1037,21 +1047,21 @@ class ServerImpl { // that ensures the client reads the response in a timely // fashion. - void requestCompleted (HttpConnection c) { + void requestCompleted(HttpConnection c) { State s = c.getState(); assert s == State.REQUEST : "State is not REQUEST ("+s+")"; - reqConnections.remove (c); + reqConnections.remove(c); c.rspStartedTime = System.currentTimeMillis(); - rspConnections.add (c); - c.setState (State.RESPONSE); + rspConnections.add(c); + c.setState(State.RESPONSE); } // called after response has been sent - void responseCompleted (HttpConnection c) { + void responseCompleted(HttpConnection c) { State s = c.getState(); assert s == State.RESPONSE : "State is not RESPONSE ("+s+")"; - rspConnections.remove (c); - c.setState (State.IDLE); + rspConnections.remove(c); + c.setState(State.IDLE); } /** @@ -1059,7 +1069,7 @@ class ServerImpl { * TimerTask run every CLOCK_TICK ms */ class IdleTimeoutTask extends TimerTask { - public void run () { + public void run() { closeConnections(idleConnections, IDLE_INTERVAL); // if any newly accepted connection has been idle (i.e. no byte has been sent on that // connection during the configured idle timeout period) then close it as well @@ -1095,20 +1105,20 @@ class ServerImpl { class ReqRspTimeoutTask extends TimerTask { // runs every TIMER_MILLIS - public void run () { + public void run() { ArrayList toClose = new ArrayList<>(); final long currentTime = System.currentTimeMillis(); synchronized (reqConnections) { if (MAX_REQ_TIME != -1) { for (HttpConnection c : reqConnections) { if (currentTime - c.reqStartedTime >= MAX_REQ_TIME) { - toClose.add (c); + toClose.add(c); } } for (HttpConnection c : toClose) { - logger.log (Level.DEBUG, "closing: no request: " + c); - reqConnections.remove (c); - allConnections.remove (c); + logger.log(Level.DEBUG, "closing: no request: " + c); + reqConnections.remove(c); + allConnections.remove(c); c.close(); } } @@ -1118,13 +1128,13 @@ class ServerImpl { if (MAX_RSP_TIME != -1) { for (HttpConnection c : rspConnections) { if (currentTime - c.rspStartedTime >= MAX_RSP_TIME) { - toClose.add (c); + toClose.add(c); } } for (HttpConnection c : toClose) { - logger.log (Level.DEBUG, "closing: no response: " + c); - rspConnections.remove (c); - allConnections.remove (c); + logger.log(Level.DEBUG, "closing: no response: " + c); + rspConnections.remove(c); + allConnections.remove(c); c.close(); } } diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/UndefLengthOutputStream.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/UndefLengthOutputStream.java index 7bfc39c84a1..ecda32ecc31 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/UndefLengthOutputStream.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/UndefLengthOutputStream.java @@ -40,30 +40,30 @@ class UndefLengthOutputStream extends FilterOutputStream private boolean closed = false; ExchangeImpl t; - UndefLengthOutputStream (ExchangeImpl t, OutputStream src) { - super (src); + UndefLengthOutputStream(ExchangeImpl t, OutputStream src) { + super(src); this.t = t; } - public void write (int b) throws IOException { + public void write(int b) throws IOException { if (closed) { - throw new IOException ("stream closed"); + throw new IOException("stream closed"); } out.write(b); } - public void write (byte[]b, int off, int len) throws IOException { + public void write(byte[] b, int off, int len) throws IOException { Objects.checkFromIndexSize(off, len, b.length); if (len == 0) { return; } if (closed) { - throw new IOException ("stream closed"); + throw new IOException("stream closed"); } out.write(b, off, len); } - public void close () throws IOException { + public void close() throws IOException { if (closed) { return; } @@ -76,7 +76,7 @@ class UndefLengthOutputStream extends FilterOutputStream } catch (IOException e) {} } Event e = new Event.WriteFinished(t); - t.getHttpContext().getServerImpl().addEvent (e); + t.getHttpContext().getServerImpl().addEvent(e); } // flush is a pass-through diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/UnmodifiableHeaders.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/UnmodifiableHeaders.java index 91bfc186828..503004b35e0 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/UnmodifiableHeaders.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/UnmodifiableHeaders.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -62,32 +62,32 @@ public class UnmodifiableHeaders extends Headers { @Override public List put(String key, List value) { - throw new UnsupportedOperationException ("unsupported operation"); + throw new UnsupportedOperationException("unsupported operation"); } @Override public void add(String key, String value) { - throw new UnsupportedOperationException ("unsupported operation"); + throw new UnsupportedOperationException("unsupported operation"); } @Override public void set(String key, String value) { - throw new UnsupportedOperationException ("unsupported operation"); + throw new UnsupportedOperationException("unsupported operation"); } @Override public List remove(Object key) { - throw new UnsupportedOperationException ("unsupported operation"); + throw new UnsupportedOperationException("unsupported operation"); } @Override public void putAll(Map> t) { - throw new UnsupportedOperationException ("unsupported operation"); + throw new UnsupportedOperationException("unsupported operation"); } @Override public void clear() { - throw new UnsupportedOperationException ("unsupported operation"); + throw new UnsupportedOperationException("unsupported operation"); } @Override @@ -106,19 +106,19 @@ public class UnmodifiableHeaders extends Headers { @Override public boolean replace(String key, List oldValue, List newValue) { - throw new UnsupportedOperationException ("unsupported operation"); + throw new UnsupportedOperationException("unsupported operation"); } @Override public void replaceAll(BiFunction, ? extends List> function) { - throw new UnsupportedOperationException ("unsupported operation"); + throw new UnsupportedOperationException("unsupported operation"); } @Override - public boolean equals(Object o) {return headers.equals(o);} + public boolean equals(Object o) { return headers.equals(o); } @Override - public int hashCode() {return headers.hashCode();} + public int hashCode() { return headers.hashCode(); } @Override public String toString() { diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/FileServerHandler.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/FileServerHandler.java index 3a3654d4a73..cbf032e8398 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/FileServerHandler.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/FileServerHandler.java @@ -367,7 +367,7 @@ public final class FileServerHandler implements HttpHandler { // A non-exhaustive map of reserved-HTML and special characters to their // equivalent entity. - private static final Map RESERVED_CHARS = Map.of( + private static final Map RESERVED_CHARS = Map.of( (int) '&' , "&" , (int) '<' , "<" , (int) '>' , ">" , diff --git a/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/AbstractVector.java b/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/AbstractVector.java index 45773cd29db..350931cd443 100644 --- a/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/AbstractVector.java +++ b/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/AbstractVector.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/Float16.java b/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/Float16.java index 335cbef2331..dfdd2a304c6 100644 --- a/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/Float16.java +++ b/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/Float16.java @@ -260,6 +260,9 @@ public final class Float16 * Float#toString(float)} in the handling of special values * (signed zeros, infinities, and NaN) and the generation of a * decimal string that will convert back to the argument value. + * However, the range for plain notation is defined to be the interval + * [10-3, 103) rather than the interval used + * for {@code float} and {@code double}. * * @param f16 the {@code Float16} to be converted. * @return a string representation of the argument. @@ -2106,7 +2109,7 @@ public final class Float16 int h = (int) (f * 107_375L >>> 30); int l = f - 10_000 * h; - if (0 < e && e <= 7) { + if (0 < e && e <= 3) { return toChars1(h, l, e); } if (-3 < e && e <= 0) { diff --git a/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/Vector.java b/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/Vector.java index 0e4516efa86..68b4a35067c 100644 --- a/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/Vector.java +++ b/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/Vector.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/VectorMask.java b/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/VectorMask.java index 5695dff62fb..eb231597e85 100644 --- a/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/VectorMask.java +++ b/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/VectorMask.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/VectorMath.java b/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/VectorMath.java index 67c9387dfd6..814ca2e2043 100644 --- a/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/VectorMath.java +++ b/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/VectorMath.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/VectorOperators.java b/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/VectorOperators.java index f4da4f42934..34ab05046e0 100644 --- a/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/VectorOperators.java +++ b/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/VectorOperators.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/VectorSpecies.java b/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/VectorSpecies.java index e14cdecbc4a..e80bbf231ea 100644 --- a/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/VectorSpecies.java +++ b/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/VectorSpecies.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/package-info.java b/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/package-info.java index 859c66c5f63..f257c222eca 100644 --- a/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/package-info.java +++ b/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.internal.jvmstat/macosx/classes/sun/jvmstat/PlatformSupportImpl.java b/src/jdk.internal.jvmstat/macosx/classes/sun/jvmstat/PlatformSupportImpl.java new file mode 100644 index 00000000000..f858fde4dce --- /dev/null +++ b/src/jdk.internal.jvmstat/macosx/classes/sun/jvmstat/PlatformSupportImpl.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, BELLSOFT. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.jvmstat; + +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +/* + * macOS specific implementation of the PlatformSupport routines + * providing temporary directory support. + */ +public class PlatformSupportImpl extends PlatformSupport { + + private static final String VAR_FOLDERS_PATH = "/var/folders"; + private static final String USER_NAME_SYSTEM_PROPERTY = "user.name"; + private static final String USER_NAME_ROOT = "root"; + private static final String DIRHELPER_TEMP_STR = "T"; + + private static final boolean isCurrentUserRoot = + System.getProperty(USER_NAME_SYSTEM_PROPERTY).equals(USER_NAME_ROOT); + + public PlatformSupportImpl() { + super(); + } + + /* + * Return a list of the temporary directories that the VM uses + * for the attach and perf data files. + * + * This function returns the traditional temp directory. Additionally, + * when called by root, it returns other temporary directories of non-root + * users. + * + * macOS per-user temp directories are located under /var/folders + * and have the form /var/folders///T + */ + @Override + public List getTemporaryDirectories(int pid) { + if (!isCurrentUserRoot) { + // early exit for non-root + return List.of(PlatformSupport.getTemporaryDirectory()); + } + List result = new ArrayList<>(); + try (DirectoryStream bs = Files.newDirectoryStream(Path.of(VAR_FOLDERS_PATH))) { + for (Path bucket : bs) { + try (DirectoryStream encUuids = Files.newDirectoryStream(bucket)) { + for (Path encUuid : encUuids) { + try { + Path tempDir = encUuid.resolve(DIRHELPER_TEMP_STR); + if (Files.isDirectory(tempDir) && Files.isReadable(tempDir)) { + result.add(tempDir.toString()); + } + } catch (Exception ignore) { // ignored unreadable bucket/encUuid, continue + } + } + } catch (IOException ignore) { // IOException ignored, continue to the next bucket + } + } + } catch (Exception ignore) { // var/folders directory is inaccessible / other errors + } + return result.isEmpty() ? List.of(PlatformSupport.getTemporaryDirectory()) : result; + } +} diff --git a/src/jdk.internal.jvmstat/share/classes/module-info.java b/src/jdk.internal.jvmstat/share/classes/module-info.java index 9e12c98b016..d9e1bc68008 100644 --- a/src/jdk.internal.jvmstat/share/classes/module-info.java +++ b/src/jdk.internal.jvmstat/share/classes/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2017, 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 @@ -40,6 +40,8 @@ module jdk.internal.jvmstat { jdk.jstatd; exports sun.jvmstat.perfdata.monitor to jdk.jstatd; + exports sun.jvmstat to + jdk.attach; uses sun.jvmstat.monitor.MonitoredHostService; diff --git a/src/jdk.internal.jvmstat/share/classes/sun/jvmstat/perfdata/monitor/v1_0/PerfDataBuffer.java b/src/jdk.internal.jvmstat/share/classes/sun/jvmstat/perfdata/monitor/v1_0/PerfDataBuffer.java index ba02e792f3e..9a0e31e4c10 100644 --- a/src/jdk.internal.jvmstat/share/classes/sun/jvmstat/perfdata/monitor/v1_0/PerfDataBuffer.java +++ b/src/jdk.internal.jvmstat/share/classes/sun/jvmstat/perfdata/monitor/v1_0/PerfDataBuffer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.internal.jvmstat/share/classes/sun/jvmstat/perfdata/resources/aliasmap b/src/jdk.internal.jvmstat/share/classes/sun/jvmstat/perfdata/resources/aliasmap index 7d9ba8d2251..f8cc98268a0 100644 --- a/src/jdk.internal.jvmstat/share/classes/sun/jvmstat/perfdata/resources/aliasmap +++ b/src/jdk.internal.jvmstat/share/classes/sun/jvmstat/perfdata/resources/aliasmap @@ -1,6 +1,6 @@ /* * -* Copyright (c) 2004, 2024, Oracle and/or its affiliates. All rights reserved. +* Copyright (c) 2004, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.internal.opt/share/classes/jdk/internal/joptsimple/util/InetAddressConverter.java b/src/jdk.internal.opt/share/classes/jdk/internal/joptsimple/util/InetAddressConverter.java index 46c4b8e3168..ba891b184e8 100644 --- a/src/jdk.internal.opt/share/classes/jdk/internal/joptsimple/util/InetAddressConverter.java +++ b/src/jdk.internal.opt/share/classes/jdk/internal/joptsimple/util/InetAddressConverter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.internal.opt/share/classes/module-info.java b/src/jdk.internal.opt/share/classes/module-info.java index ba6987f1ea9..728c2de500d 100644 --- a/src/jdk.internal.opt/share/classes/module-info.java +++ b/src/jdk.internal.opt/share/classes/module-info.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 @@ -32,6 +32,7 @@ module jdk.internal.opt { exports jdk.internal.joptsimple to jdk.jlink, jdk.jshell, + jdk.jpackage, jdk.jdeps; exports jdk.internal.opt to jdk.compiler, diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/code/BytecodeFrame.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/code/BytecodeFrame.java index 8c698b4ee3d..7b0d3d75387 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/code/BytecodeFrame.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/code/BytecodeFrame.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/code/VirtualObject.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/code/VirtualObject.java index 1033fa559f8..6f91bfb6c1f 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/code/VirtualObject.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/code/VirtualObject.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2019, 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 diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/code/site/Site.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/code/site/Site.java index 7d1239fea0e..d12a78e5106 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/code/site/Site.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/code/site/Site.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 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 diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/CompilerToVM.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/CompilerToVM.java index b25f7a09256..ee578cfd5f1 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/CompilerToVM.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/CompilerToVM.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotCompiledCode.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotCompiledCode.java index 6d1f16d5bd2..dffd54e3117 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotCompiledCode.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotCompiledCode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotCompiledCodeStream.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotCompiledCodeStream.java index 2cd5e839ee8..565b38de96e 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotCompiledCodeStream.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotCompiledCodeStream.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 diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotCompiledNmethod.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotCompiledNmethod.java index e0e93b0f8bc..f532617a09e 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotCompiledNmethod.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotCompiledNmethod.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotJavaType.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotJavaType.java index 792c7bc00a2..d8fcb61079c 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotJavaType.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotJavaType.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotObjectConstant.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotObjectConstant.java index bed38f91988..ca54e8325f3 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotObjectConstant.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotObjectConstant.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotObjectConstantImpl.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotObjectConstantImpl.java index 1dcc969b3e6..9a067375245 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotObjectConstantImpl.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotObjectConstantImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedJavaFieldImpl.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedJavaFieldImpl.java index b75edb9df08..ba29cad7849 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedJavaFieldImpl.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedJavaFieldImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedJavaMethodImpl.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedJavaMethodImpl.java index 224e8b1a070..3c3aaf830fa 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedJavaMethodImpl.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedJavaMethodImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedJavaType.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedJavaType.java index ab634568a84..e8c7107cd89 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedJavaType.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedJavaType.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. * 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/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedObjectType.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedObjectType.java index 1fc5f1bfd6a..5cad3d05bf5 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedObjectType.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedObjectType.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedObjectTypeImpl.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedObjectTypeImpl.java index 46e53411dee..6074b2b32dc 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedObjectTypeImpl.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedObjectTypeImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -165,8 +165,12 @@ final class HotSpotResolvedObjectTypeImpl extends HotSpotResolvedJavaType implem } public int getAccessFlags() { - HotSpotVMConfig config = config(); - return UNSAFE.getInt(getKlassPointer() + config.klassAccessFlagsOffset); + if (isArray()) { + return 0; // Array Metadata doesn't set access_flags + } else { + HotSpotVMConfig config = config(); + return UNSAFE.getInt(getKlassPointer() + config.instanceKlassAccessFlagsOffset); + } } public int getMiscFlags() { diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotSpeculationLog.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotSpeculationLog.java index 4849750337c..bf9b1875663 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotSpeculationLog.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotSpeculationLog.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 diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotVMConfig.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotVMConfig.java index e4e23c6d8b8..c058f785715 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotVMConfig.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotVMConfig.java @@ -84,7 +84,6 @@ class HotSpotVMConfig extends HotSpotVMConfigAccess { */ final int javaMirrorOffset = getFieldOffset("Klass::_java_mirror", Integer.class, "OopHandle"); - final int klassAccessFlagsOffset = getFieldOffset("Klass::_access_flags", Integer.class, "AccessFlags"); final int klassLayoutHelperOffset = getFieldOffset("Klass::_layout_helper", Integer.class, "jint"); final int klassLayoutHelperNeutralValue = getConstant("Klass::_lh_neutral_value", Integer.class); @@ -93,6 +92,7 @@ class HotSpotVMConfig extends HotSpotVMConfigAccess { final int vtableEntrySize = getFieldValue("CompilerToVM::Data::sizeof_vtableEntry", Integer.class, "int"); final int vtableEntryMethodOffset = getFieldOffset("vtableEntry::_method", Integer.class, "Method*"); + final int instanceKlassAccessFlagsOffset = getFieldOffset("InstanceKlass::_access_flags", Integer.class, "AccessFlags"); final int instanceKlassInitStateOffset = getFieldOffset("InstanceKlass::_init_state", Integer.class, "InstanceKlass::ClassState"); final int instanceKlassConstantsOffset = getFieldOffset("InstanceKlass::_constants", Integer.class, "ConstantPool*"); final int instanceKlassFieldInfoStreamOffset = getFieldOffset("InstanceKlass::_fieldinfo_stream", Integer.class, "Array*"); diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotVMConfigAccess.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotVMConfigAccess.java index 15bd58a5a5d..5f921c767dd 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotVMConfigAccess.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotVMConfigAccess.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/VMField.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/VMField.java index 21755445ca0..da9db1ddc32 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/VMField.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/VMField.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ConstantPool.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ConstantPool.java index 0b7fd70762d..ebd0df403a3 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ConstantPool.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ConstantPool.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/EncodedSpeculationReason.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/EncodedSpeculationReason.java index 2e72896b31d..02f91082fd5 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/EncodedSpeculationReason.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/EncodedSpeculationReason.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ResolvedJavaMethod.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ResolvedJavaMethod.java index f401bc30f83..dcd80170ba9 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ResolvedJavaMethod.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ResolvedJavaMethod.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ResolvedJavaType.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ResolvedJavaType.java index cdb24c5f1ad..4e11cce4f68 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ResolvedJavaType.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ResolvedJavaType.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/services/JVMCIServiceLocator.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/services/JVMCIServiceLocator.java index d5ff0bba9b3..98856337cec 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/services/JVMCIServiceLocator.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/services/JVMCIServiceLocator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2021, 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 diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/services/Services.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/services/Services.java index 22f8eec7d12..c2bccb1bac1 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/services/Services.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/services/Services.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 diff --git a/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java b/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java index f2c76058434..12967972a88 100644 --- a/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java +++ b/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java @@ -167,6 +167,7 @@ public class Main { char[] storepass; // keystore password boolean protectedPath; // protected authentication path String storetype; // keystore type + String realStoreType; String providerName; // provider name List providers = null; // list of provider names List providerClasses = null; // list of provider classes @@ -240,6 +241,7 @@ public class Main { private boolean signerSelfSigned = false; private boolean allAliasesFound = true; private boolean hasMultipleManifests = false; + private boolean weakKeyStore = false; private Throwable chainNotValidatedReason = null; private Throwable tsaChainNotValidatedReason = null; @@ -1482,6 +1484,12 @@ public class Main { warnings.add(rb.getString("external.file.attributes.detected")); } + if (weakKeyStore) { + warnings.add(String.format(rb.getString( + "jks.storetype.warning"), + realStoreType, keystore)); + } + if ((strict) && (!errors.isEmpty())) { result = isSigning ? rb.getString("jar.signed.with.signer.errors.") @@ -2422,6 +2430,23 @@ public class Main { is.close(); } } + + File storeFile = new File(keyStoreName); + if (storeFile.exists()) { + // Probe for real type. A JKS can be loaded as PKCS12 because + // DualFormat support, vice versa. + try { + KeyStore keyStore = KeyStore.getInstance(storeFile, storepass); + realStoreType = keyStore.getType(); + if (realStoreType.equalsIgnoreCase("JKS") + || realStoreType.equalsIgnoreCase("JCEKS")) { + weakKeyStore = true; + } + } catch (KeyStoreException e) { + // Probing not supported, therefore cannot be JKS or JCEKS. + // Skip the legacy type warning at all. + } + } } Enumeration aliases = store.aliases(); while (aliases.hasMoreElements()) { diff --git a/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/resources/jarsigner.properties b/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/resources/jarsigner.properties index b490d386e59..a16420daf8f 100644 --- a/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/resources/jarsigner.properties +++ b/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/resources/jarsigner.properties @@ -222,3 +222,5 @@ entry.1.is.signed.in.jarinputstream.but.is.not.signed.in.jarfile=Entry %s is sig jar.contains.internal.inconsistencies.result.in.different.contents.via.jarfile.and.jarinputstream=This JAR file contains internal inconsistencies that may result in different contents when reading via JarFile and JarInputStream: signature.verification.failed.on.entry.1.when.reading.via.jarinputstream=Signature verification failed on entry %s when reading via JarInputStream signature.verification.failed.on.entry.1.when.reading.via.jarfile=Signature verification failed on entry %s when reading via JarFile +jks.storetype.warning=%1$s uses outdated cryptographic algorithms and will be removed in a future release. Migrate to PKCS12 using:\n\ +keytool -importkeystore -srckeystore %2$s -destkeystore %2$s -deststoretype pkcs12 diff --git a/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/resources/jarsigner_de.properties b/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/resources/jarsigner_de.properties index c5d9f41ee85..2b76f1408fa 100644 --- a/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/resources/jarsigner_de.properties +++ b/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/resources/jarsigner_de.properties @@ -220,3 +220,4 @@ entry.1.is.signed.in.jarinputstream.but.is.not.signed.in.jarfile=Eintrag %s ist jar.contains.internal.inconsistencies.result.in.different.contents.via.jarfile.and.jarinputstream=Diese JAR-Datei enthält interne Inkonsistenzen, die zu anderem Inhalt beim Lesen über JarFile als beim Lesen über JarInputStream führen können: signature.verification.failed.on.entry.1.when.reading.via.jarinputstream=Signaturverifizierung war für Eintrag %s beim Lesen über JarInputStream nicht erfolgreich signature.verification.failed.on.entry.1.when.reading.via.jarfile=Signaturverifizierung war für Eintrag %s beim Lesen über JarFile nicht erfolgreich +jks.storetype.warning=%1$s verwendet veraltete kryptografische Algorithmen und wird in einer zukünftigen Version entfernt. Migrieren Sie zu PKCS12 mit:\nkeytool -importkeystore -srckeystore %2$s -destkeystore %2$s -deststoretype pkcs12 diff --git a/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/resources/jarsigner_ja.properties b/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/resources/jarsigner_ja.properties index 97ab6a918cb..f2a5ce39be3 100644 --- a/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/resources/jarsigner_ja.properties +++ b/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/resources/jarsigner_ja.properties @@ -220,3 +220,4 @@ entry.1.is.signed.in.jarinputstream.but.is.not.signed.in.jarfile=エントリ%s jar.contains.internal.inconsistencies.result.in.different.contents.via.jarfile.and.jarinputstream=このJARファイルには内部的な不整合があるため、JarFileとJarInputStreamから読み取る場合にコンテンツが異なる可能性があります: signature.verification.failed.on.entry.1.when.reading.via.jarinputstream=JarInputStreamを介して読み取るときに署名検証がエントリ%sで失敗しました signature.verification.failed.on.entry.1.when.reading.via.jarfile=JarFileを介して読み取るときに署名検証がエントリ%sで失敗しました +jks.storetype.warning=%1$sは古い暗号化アルゴリズムを使用しているため、将来のリリースで削除されます。次を使用してPKCS12に移行します:\nkeytool -importkeystore -srckeystore %2$s -destkeystore %2$s -deststoretype pkcs12 diff --git a/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/resources/jarsigner_zh_CN.properties b/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/resources/jarsigner_zh_CN.properties index 378cc3ba9fc..f780bd1f1c3 100644 --- a/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/resources/jarsigner_zh_CN.properties +++ b/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/resources/jarsigner_zh_CN.properties @@ -220,3 +220,4 @@ entry.1.is.signed.in.jarinputstream.but.is.not.signed.in.jarfile=条目 %s 已 jar.contains.internal.inconsistencies.result.in.different.contents.via.jarfile.and.jarinputstream=此 JAR 文件包含内部不一致,通过 JarFile 和 JarInputStream 读取时可能会导致内容不同: signature.verification.failed.on.entry.1.when.reading.via.jarinputstream=通过 JarInputStream 读取时,条目 %s 的签名验证失败 signature.verification.failed.on.entry.1.when.reading.via.jarfile=通过 JarFile 读取时,条目 %s 的签名验证失败 +jks.storetype.warning=%1$s 使用的加密算法已过时,将在未来发行版中删除。请使用以下命令迁移到 PKCS12:\nkeytool -importkeystore -srckeystore %2$s -destkeystore %2$s -deststoretype pkcs12 diff --git a/src/jdk.jartool/share/classes/sun/tools/jar/resources/jar_de.properties b/src/jdk.jartool/share/classes/sun/tools/jar/resources/jar_de.properties index 2a5786e10b4..292ec9c963d 100644 --- a/src/jdk.jartool/share/classes/sun/tools/jar/resources/jar_de.properties +++ b/src/jdk.jartool/share/classes/sun/tools/jar/resources/jar_de.properties @@ -80,6 +80,8 @@ error.validator.info.opens.notequal=module-info.class in einem versionierten Ver error.validator.info.provides.notequal=module-info.class in einem versionierten Verzeichnis enthält unterschiedliche "provides" error.validator.info.version.notequal={0}: module-info.class in einem versionierten Verzeichnis enthält unterschiedlichen "version"-Wert error.validator.info.manclass.notequal={0}: module-info.class in einem versionierten Verzeichnis enthält unterschiedlichen "main-class"-Wert +error.validator.metainf.wrong.position=Eintrag META-INF/ an Position 0 erwartet, aber an Position {0} gefunden +error.validator.manifest.wrong.position=Eintrag META-INF/MANIFEST.MF an Position 0 oder 1 erwartet, aber an Position {0} gefunden warn.validator.identical.entry=Warnung: Eintrag {0} enthält eine Klasse, die mit\neinem bereits in der JAR-Datei enthaltenen Eintrag identisch ist warn.validator.resources.with.same.name=Warnung: Eintrag {0}, mehrere Ressourcen mit demselben Namen warn.validator.concealed.public.class=Warnung: Eintrag {0} ist eine öffentliche Klasse\nin einem verdeckten Package. Wenn Sie diese JAR-Datei in den Classpath einfügen, kommt es\nzu nicht kompatiblen öffentlichen Schnittstellen diff --git a/src/jdk.jartool/share/classes/sun/tools/jar/resources/jar_ja.properties b/src/jdk.jartool/share/classes/sun/tools/jar/resources/jar_ja.properties index c7d7c14613a..0d0f91ad791 100644 --- a/src/jdk.jartool/share/classes/sun/tools/jar/resources/jar_ja.properties +++ b/src/jdk.jartool/share/classes/sun/tools/jar/resources/jar_ja.properties @@ -80,6 +80,8 @@ error.validator.info.opens.notequal=バージョニング・ディレクトリ error.validator.info.provides.notequal=バージョニングされたディレクトリのmodule-info.classに異なる"provides"が含まれています error.validator.info.version.notequal={0}: バージョニングされたディレクトリのmodule-info.classに異なる"version"が含まれています error.validator.info.manclass.notequal={0}: バージョニングされたディレクトリのmodule-info.classに異なる"main-class"が含まれています +error.validator.metainf.wrong.position=エントリMETA-INF/は0の位置にある必要がありますが、見つかりました: {0} +error.validator.manifest.wrong.position=エントリMETA-INF/MANIFEST.MFは0または1の位置にある必要がありますが、位置: {0}で見つかりました warn.validator.identical.entry=警告 : エントリ{0}には、jarにすでに存在する\nエントリと同じクラスが含まれます warn.validator.resources.with.same.name=警告 : エントリ{0}、同じ名前を持つ複数のリソース warn.validator.concealed.public.class=警告 : エントリ{0}は、隠しパッケージ内のpublicクラスです。\nクラスパスにこのjarを配置すると、互換性のない\npublicインタフェースが生成されます diff --git a/src/jdk.jartool/share/classes/sun/tools/jar/resources/jar_zh_CN.properties b/src/jdk.jartool/share/classes/sun/tools/jar/resources/jar_zh_CN.properties index 1979f3e2386..41833d28bfc 100644 --- a/src/jdk.jartool/share/classes/sun/tools/jar/resources/jar_zh_CN.properties +++ b/src/jdk.jartool/share/classes/sun/tools/jar/resources/jar_zh_CN.properties @@ -80,6 +80,8 @@ error.validator.info.opens.notequal=版本化目录中的 module-info.class 包 error.validator.info.provides.notequal=版本化目录中的 module-info.class 包含不同的 "provides" error.validator.info.version.notequal={0}: 版本化目录中的 module-info.class 包含不同的 "version" error.validator.info.manclass.notequal={0}: 版本化目录中的 module-info.class 包含不同的 "main-class" +error.validator.metainf.wrong.position=条目 META-INF/ 应位于位置 0 处,但发现:{0} +error.validator.manifest.wrong.position=条目 META-INF/MANIFEST.MF 应位于位置 0 或 1 处,但发现该条目位于位置 {0} 处 warn.validator.identical.entry=警告: 条目 {0} 包含与 jar 中的\n现有条目相同的类 warn.validator.resources.with.same.name=警告: 条目 {0}, 多个资源具有相同名称 warn.validator.concealed.public.class=警告: 条目 {0} 是已隐藏程序包中的\n公共类, 将此 jar 放置在类路径中\n将导致公共接口不兼容 diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AbstractTreeWriter.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AbstractTreeWriter.java index 6ad1d2107c6..edc2b4f1f3f 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AbstractTreeWriter.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AbstractTreeWriter.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 diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AllClassesIndexWriter.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AllClassesIndexWriter.java index 60c99dfa76a..c91ee348c11 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AllClassesIndexWriter.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AllClassesIndexWriter.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 diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDocletWriter.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDocletWriter.java index 6896b86279f..86ac3a892fd 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDocletWriter.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDocletWriter.java @@ -243,8 +243,12 @@ public abstract class HtmlDocletWriter { if (generating) { writeGenerating(); } + CURRENT_PATH.set(path.getPath()); } + /** Temporary workaround to share current path with taglets, see 8373909 */ + public static final ThreadLocal CURRENT_PATH = new ThreadLocal<>(); + /** * The top-level method to generate and write the page represented by this writer. * @@ -908,12 +912,13 @@ public abstract class HtmlDocletWriter { * @param refMemName the name of the member being referenced. This should * be null or empty string if no member is being referenced. * @param label the label for the external link. + * @param title the title for the link * @param style optional style for the link. * @param code true if the label should be code font. * @return the link */ public Content getCrossClassLink(TypeElement classElement, String refMemName, - Content label, HtmlStyle style, boolean code) { + Content label, String title, HtmlStyle style, boolean code) { if (classElement != null) { String className = utils.getSimpleName(classElement); PackageElement packageElement = utils.containingPackage(classElement); @@ -931,9 +936,7 @@ public abstract class HtmlDocletWriter { DocLink link = configuration.extern.getExternalLink(packageElement, pathToRoot, className + ".html", refMemName); return links.createLink(link, - (label == null) || label.isEmpty() ? defaultLabel : label, style, - resources.getText("doclet.Href_Class_Or_Interface_Title", - getLocalizedPackageName(packageElement)), true); + (label == null) || label.isEmpty() ? defaultLabel : label, style, title, true); } } return null; diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlLinkFactory.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlLinkFactory.java index 4dbbd5e172a..21b3a07b70b 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlLinkFactory.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlLinkFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -305,7 +305,7 @@ public class HtmlLinkFactory { } else { Content crossLink = m_writer.getCrossClassLink( typeElement, linkInfo.getFragment(), - label, linkInfo.getStyle(), true); + label, linkInfo.getTitle(), linkInfo.getStyle(), true); if (crossLink != null) { link.add(crossLink); addSuperscript(link, flags, null, typeElement, previewTarget, restrictedTarget); @@ -361,7 +361,7 @@ public class HtmlLinkFactory { if (fileName != null) { return m_writer.links.createLink(fileName.fragment(id.name()), label); } else if (typeElement != null) { - return (m_writer.getCrossClassLink(typeElement, id.name(), label, null, false)); + return (m_writer.getCrossClassLink(typeElement, id.name(), label, null, null, false)); } else { return label; } diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/NestedClassWriter.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/NestedClassWriter.java index 1847ddf979f..b8acc4d40fe 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/NestedClassWriter.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/NestedClassWriter.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 diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SerializedFormWriter.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SerializedFormWriter.java index dbc0c7ceaf3..e81ad2f39af 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SerializedFormWriter.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SerializedFormWriter.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 diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/Table.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/Table.java index 37edd800328..383bde4a63d 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/Table.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/Table.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlStyles.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlStyles.java index 7f33ebedfa4..9b59cb0cb47 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlStyles.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlStyles.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 diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/Links.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/Links.java index 0af29135654..285ab260e0e 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/Links.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/Links.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 @@ -197,7 +197,7 @@ public class Links { if (style != null) { l.setStyle(style); } - if (title != null && title.length() != 0) { + if (title != null && !title.isEmpty()) { l.put(HtmlAttr.TITLE, title); } if (isExternal) { diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard.properties b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard.properties index df3c2fc3a53..4366295477b 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard.properties +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard.properties @@ -51,7 +51,6 @@ doclet.Href_Annotation_Interface_Title=annotation interface in {0} doclet.Href_Enum_Title=enum in {0} doclet.Href_Enum_Class_Title=enum class in {0} doclet.Href_Type_Param_Title=type parameter in {0} -doclet.Href_Class_Or_Interface_Title=class or interface in {0} doclet.Summary=Summary: doclet.Detail=Detail: doclet.Module_Sub_Nav=Module: diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard_de.properties b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard_de.properties index 2669aa9bdc0..a380b29d553 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard_de.properties +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard_de.properties @@ -51,7 +51,6 @@ doclet.Href_Annotation_Interface_Title=Annotationsschnittstelle in {0} doclet.Href_Enum_Title=Enum in {0} doclet.Href_Enum_Class_Title=Enum-Klasse in {0} doclet.Href_Type_Param_Title=Typparameter in {0} -doclet.Href_Class_Or_Interface_Title=Klasse oder Schnittstelle in {0} doclet.Summary=Übersicht: doclet.Detail=Details: doclet.Module_Sub_Nav=Modul: @@ -191,6 +190,10 @@ doclet.Window_Help_title=API-Hilfe doclet.references={0} Referenzen doclet.Window_Search_title=Suchen doclet.search.main_heading=Suchen +doclet.theme.select_theme=Theme auswählen +doclet.theme.light=Hell +doclet.theme.dark=Dunkel +doclet.theme.system=Systemeinstellung # label for link/button element to show the information below doclet.search.show_more=Zusätzliche Ressourcen @@ -540,3 +543,5 @@ doclet.NoFrames_specified=Die Option --no-frames wird nicht mehr benötigt und w # L10N: do not localize the option name -footer doclet.footer_specified=Die Option -footer wird nicht mehr unterstützt und wird ignoriert.\nSie wird möglicherweise in einem zukünftigen Release entfernt. + +doclet.selectModule=Wählen Sie das Modul aus, in dem gesucht werden soll. diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard_en.properties b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard_en.properties new file mode 100644 index 00000000000..e1312e77aa4 --- /dev/null +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard_en.properties @@ -0,0 +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. +# + +# Empty resource bundle to be used with English default bundle as parent. +# This is necessary to make English resources available on systems using +# one of the supported non-English locales as default locale. diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard_ja.properties b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard_ja.properties index 1694dc980bc..69cdc862b4c 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard_ja.properties +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard_ja.properties @@ -51,7 +51,6 @@ doclet.Href_Annotation_Interface_Title={0}内の注釈インタフェース doclet.Href_Enum_Title={0}内の列挙型 doclet.Href_Enum_Class_Title={0}の列挙クラス doclet.Href_Type_Param_Title={0}内の型パラメータ -doclet.Href_Class_Or_Interface_Title={0}内のクラスまたはインタフェース doclet.Summary=概要: doclet.Detail=詳細: doclet.Module_Sub_Nav=モジュール: @@ -191,6 +190,10 @@ doclet.Window_Help_title=APIヘルプ doclet.references={0}の参照 doclet.Window_Search_title=検索 doclet.search.main_heading=検索 +doclet.theme.select_theme=テーマを選択 +doclet.theme.light=明るい +doclet.theme.dark=暗い +doclet.theme.system=システム設定 # label for link/button element to show the information below doclet.search.show_more=その他のリソース @@ -540,3 +543,5 @@ doclet.NoFrames_specified=--no-framesオプションは必須ではなくなり # L10N: do not localize the option name -footer doclet.footer_specified=-footerオプションはサポートされなくなったため、無視されます。\n将来のリリースで削除される可能性があります。 + +doclet.selectModule=検索するモジュールを選択します。 diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard_zh_CN.properties b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard_zh_CN.properties index 8881171351e..b3a0a3a1197 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard_zh_CN.properties +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard_zh_CN.properties @@ -51,7 +51,6 @@ doclet.Href_Annotation_Interface_Title={0} 中的批注接口 doclet.Href_Enum_Title={0}中的枚举 doclet.Href_Enum_Class_Title={0} 中的枚举类 doclet.Href_Type_Param_Title={0}中的类型参数 -doclet.Href_Class_Or_Interface_Title={0}中的类或接口 doclet.Summary=概要: doclet.Detail=详细资料: doclet.Module_Sub_Nav=模块: @@ -191,6 +190,10 @@ doclet.Window_Help_title=API 帮助 doclet.references={0} 个引用 doclet.Window_Search_title=搜索 doclet.search.main_heading=搜索 +doclet.theme.select_theme=选择主题 +doclet.theme.light=浅色 +doclet.theme.dark=深色 +doclet.theme.system=系统设置 # label for link/button element to show the information below doclet.search.show_more=其他资源 @@ -540,3 +543,5 @@ doclet.NoFrames_specified=--no-frames 选项不再是必需的,可能\n会在 # L10N: do not localize the option name -footer doclet.footer_specified=-footer 选项不再受支持并将被忽略。\n可能会在未来发行版中删除此选项。 + +doclet.selectModule=选择要在其中搜索的模块。 diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/LinkTaglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/LinkTaglet.java index 914f70ced47..e239feed0e7 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/LinkTaglet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/LinkTaglet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -50,6 +50,7 @@ import jdk.javadoc.internal.doclets.toolkit.util.CommentHelper; import jdk.javadoc.internal.doclets.toolkit.util.DocLink; import jdk.javadoc.internal.doclets.toolkit.util.VisibleMemberTable; import jdk.javadoc.internal.html.Content; +import jdk.javadoc.internal.html.HtmlId; import jdk.javadoc.internal.html.HtmlTree; import jdk.javadoc.internal.html.Text; @@ -159,6 +160,10 @@ public class LinkTaglet extends BaseTaglet { Optional.of(refSignature)); } refFragment = refFragment.substring(1); + if (ref == null && refSignature.startsWith("##")) { + // Unqualified local anchor link in doc-file + return htmlWriter.links.createLink(HtmlId.of(refFragment), labelContent); + } } if (refClass == null) { ModuleElement refModule = ch.getReferencedModule(ref); diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/CommentUtils.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/CommentUtils.java index f415a673345..cda8c2fa740 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/CommentUtils.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/CommentUtils.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 diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/WorkArounds.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/WorkArounds.java index b1266516586..c26e507279e 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/WorkArounds.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/WorkArounds.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 diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets_de.properties b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets_de.properties index 0ee34071c93..a6dbf050bf3 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets_de.properties +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets_de.properties @@ -65,6 +65,7 @@ doclet.JavaScript_in_comment=JavaScript in Dokumentationskommentar gefunden.\nVe doclet.JavaScript_in_option=Option {0} enthält JavaScript.\nVerwenden Sie --allow-script-in-comments, um die Verwendung von JavaScript zuzulassen. doclet.Link_icon=Linksymbol doclet.Link_to_section=Link zu diesem Abschnitt +doclet.Toggle_member_listing=Zwischen kurzer und detaillierter Listenansicht umschalten doclet.Packages=Packages doclet.All_Packages=Alle Packages doclet.Modules=Module @@ -114,7 +115,7 @@ doclet.inheritDocWithinInappropriateTag=@inheritDoc kann in diesem Tag nicht ver doclet.inheritDocNoDoc=überschriebene Methoden dokumentieren Ausnahmetyp {0} nicht doclet.throwsInheritDocUnsupported=@inheritDoc wird für Parameter vom Typ Ausnahme, die nicht von einer Methode deklariert werden, nicht unterstützt. Dokumentieren Sie solche Ausnahmetypen direkt. doclet.noInheritedDoc=@inheritDoc wurde verwendet, aber mit {0} wird keine Methode außer Kraft gesetzt oder implementiert. -doclet.tag_misuse=Tag {0} kann nicht in {1}-Dokumentation verwendet werden. Es kann nur in folgenden Dokumentationstypen verwendet werden: {2}. +doclet.tag_misuse=Tag {0} kann nicht in Dokumentation {1} verwendet werden. Es kann nur in folgenden Dokumentationstypen verwendet werden: {2}. doclet.Package_Summary=Packageübersicht doclet.Requires_Summary=Erfordernisse doclet.Indirect_Requires_Summary=Indirekte Erfordernisse @@ -226,7 +227,7 @@ doclet.search=Suchen doclet.search_placeholder=In Dokumentation suchen ("/" eingeben) doclet.search_in_documentation=In Dokumentation suchen doclet.search_reset=Zurücksetzen -doclet.Member=Mitglied +doclet.Member=Member doclet.Field=Feld doclet.Property=Eigenschaft doclet.Constructor=Konstruktor @@ -240,11 +241,14 @@ doclet.Description=Beschreibung doclet.ConstantField=Konstantenfeld doclet.Value=Wert doclet.table_of_contents=Inhaltsverzeichnis +doclet.Sort_lexicographically=Member-Details lexikographisch sortieren +doclet.Sort_by_source_order=Member-Details nach Quellreihenfolge sortieren doclet.hide_sidebar=Randleiste ausblenden doclet.show_sidebar=Randleiste einblenden doclet.filter_label=Inhalt filtern ("." eingeben) doclet.filter_table_of_contents=Inhaltsverzeichnis filtern doclet.filter_reset=Zurücksetzen +doclet.sort_table_of_contents=Member-Details in lexikographischer Reihenfolge sortieren doclet.linkMismatch_PackagedLinkedtoModule=Der Code, der dokumentiert wird, verwendet Packages im unbenannten Modul, aber die in {0} definierten Packages befinden sich in benannten Modulen. doclet.linkMismatch_ModuleLinkedtoPackage=Der Code, der dokumentiert wird, verwendet Module, aber die in {0} definierten Packages befinden sich im unbenannten Modul. doclet.urlRedirected=URL {0} wurde umgeleitet an {1} - Aktualisieren Sie die Befehlszeilenoptionen, um diese Warnung zu unterdrücken. diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets_en.properties b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets_en.properties new file mode 100644 index 00000000000..e1312e77aa4 --- /dev/null +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets_en.properties @@ -0,0 +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. +# + +# Empty resource bundle to be used with English default bundle as parent. +# This is necessary to make English resources available on systems using +# one of the supported non-English locales as default locale. diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets_ja.properties b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets_ja.properties index 2b10f4e6e9a..1970203da38 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets_ja.properties +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets_ja.properties @@ -65,6 +65,7 @@ doclet.JavaScript_in_comment=ドキュメント・コメントにJavaScriptが doclet.JavaScript_in_option=オプション{0}にJavaScriptが含まれています。\n--allow-script-in-commentsを使用して、JavaScriptの使用を許可してください。 doclet.Link_icon=リンク・アイコン doclet.Link_to_section=このセクションにリンク +doclet.Toggle_member_listing=短いリスト・ビューと詳細リスト・ビューの切替え doclet.Packages=パッケージ doclet.All_Packages=すべてのパッケージ doclet.Modules=モジュール @@ -240,11 +241,14 @@ doclet.Description=説明 doclet.ConstantField=定数フィールド doclet.Value=値 doclet.table_of_contents=目次 +doclet.Sort_lexicographically=メンバー詳細を辞書順にソート +doclet.Sort_by_source_order=メンバー詳細をソース順序にソート doclet.hide_sidebar=サイドバーの非表示 doclet.show_sidebar=サイドバーの表示 doclet.filter_label=コンテンツのフィルタ(.と入力) doclet.filter_table_of_contents=目次のフィルタ doclet.filter_reset=リセット +doclet.sort_table_of_contents=メンバー詳細を辞書順にソート doclet.linkMismatch_PackagedLinkedtoModule=ドキュメント化しようとしているコードでは名前のないモジュールのパッケージが使用されていますが、{0}で定義されているパッケージは名前のあるモジュールのものです。 doclet.linkMismatch_ModuleLinkedtoPackage=ドキュメント化しようとしているコードではモジュールが使用されていますが、{0}で定義されているパッケージは名前のないモジュールのものです。 doclet.urlRedirected=URL {0}は{1}にリダイレクトされました -- コマンドライン・オプションを更新してこの警告を表示しないようにしてください。 diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets_zh_CN.properties b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets_zh_CN.properties index f2c8762b283..62e51c2c1c4 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets_zh_CN.properties +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets_zh_CN.properties @@ -65,6 +65,7 @@ doclet.JavaScript_in_comment=文档注释中发现 JavaScript。\n使用 --allow doclet.JavaScript_in_option=选项 {0} 包含 JavaScript。\n使用 --allow-script-in-comments 可允许使用 JavaScript。 doclet.Link_icon=链接图标 doclet.Link_to_section=链接到此节 +doclet.Toggle_member_listing=在简短列表视图和详细列表视图之间切换 doclet.Packages=程序包 doclet.All_Packages=所有程序包 doclet.Modules=模块 @@ -240,11 +241,14 @@ doclet.Description=说明 doclet.ConstantField=常量字段 doclet.Value=值 doclet.table_of_contents=目录 +doclet.Sort_lexicographically=按字典顺序对成员详细信息进行排序 +doclet.Sort_by_source_order=按源顺序对成员详细信息进行排序 doclet.hide_sidebar=隐藏子工具栏 doclet.show_sidebar=显示子工具栏 doclet.filter_label=筛选内容(键入 .) doclet.filter_table_of_contents=筛选目录 doclet.filter_reset=重置 +doclet.sort_table_of_contents=按字典顺序对成员详细信息进行排序 doclet.linkMismatch_PackagedLinkedtoModule=进行文档化的代码使用了未命名模块中的程序包,但在 {0} 中定义的程序包在命名模块中。 doclet.linkMismatch_ModuleLinkedtoPackage=进行文档化的代码使用了模块,但在 {0} 中定义的程序包在未命名模块中。 doclet.urlRedirected=URL {0} 已重定向到 {1} — 更新命令行选项以隐藏此警告。 diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/DocFile.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/DocFile.java index 26e9635b9db..a25879f4052 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/DocFile.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/DocFile.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 diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclint/Checker.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclint/Checker.java index 3669c800a8e..b5399d0fef9 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclint/Checker.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclint/Checker.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -1004,12 +1004,15 @@ public class Checker extends DocTreePathScanner { @Override @DefinedBy(Api.COMPILER_TREE) public Void visitReference(ReferenceTree tree, Void ignore) { - Element e = env.trees.getElement(getCurrentPath()); - if (e == null) { - reportBadReference(tree); - } else if ((inLink || inSee) - && e.getKind() == ElementKind.CLASS && e.asType().getKind() != TypeKind.DECLARED) { - reportBadReference(tree); + // Exclude same-file anchor links from reference checks + if (!tree.getSignature().startsWith("##")) { + Element e = env.trees.getElement(getCurrentPath()); + if (e == null) { + reportBadReference(tree); + } else if ((inLink || inSee) + && e.getKind() == ElementKind.CLASS && e.asType().getKind() != TypeKind.DECLARED) { + reportBadReference(tree); + } } return super.visitReference(tree, ignore); } diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/html/Content.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/html/Content.java index f582dbdc76a..9a6cae7e141 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/html/Content.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/html/Content.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 diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/html/ContentBuilder.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/html/ContentBuilder.java index abcd8e199bb..5aab7cb56c9 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/html/ContentBuilder.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/html/ContentBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/html/Entity.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/html/Entity.java index 5d080c19c67..a37a4d95565 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/html/Entity.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/html/Entity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/html/RawHtml.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/html/RawHtml.java index 38be7799c4e..bcd71006cbd 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/html/RawHtml.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/html/RawHtml.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 diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/resources/javadoc_de.properties b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/resources/javadoc_de.properties index 7829d2d7e27..57137ffe75d 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/resources/javadoc_de.properties +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/resources/javadoc_de.properties @@ -52,7 +52,7 @@ main.opt.package.desc=Zeigt Package-/geschützte/öffentliche Typen und Mitglied main.opt.private.desc=Zeigt alle Typen und Mitglieder. Zeigt bei benannten Modulen\nalle Packages und alle Moduldetails. main.opt.show.members.arg= -main.opt.show.members.desc=Gibt an, welche Member (Felder, Methoden oder Konstruktoren) dokumentiert\nwerden, wobei der Wert "public", "protected",\n"package" oder "private" lauten kann. Der Standardwert ist "protected"\nund zeigt öffentliche und geschützte Member an. "public" zeigt nur\nöffentliche Member, "package" zeigt öffentliche, geschützte und\nPackage-Member, und "private" zeigt alle Member an. +main.opt.show.members.desc=Gibt an, welche Members (Felder, Methoden oder Konstruktoren) dokumentiert\nwerden, wobei der Wert "public", "protected",\n"package" oder "private" lauten kann. Der Standardwert ist "protected"\nund zeigt öffentliche und geschützte Members an. "public" zeigt nur\nöffentliche Members, "package" zeigt öffentliche, geschützte und\nPackage-Members, und "private" zeigt alle Members an. main.opt.show.types.arg= main.opt.show.types.desc=Gibt an, welche Typen (Klassen, Schnittstellen usw.) dokumentiert\nwerden, wobei der Wert "public", "protected",\n"package" oder "private" lauten kann. Der Standardwert ist "protected"\nund zeigt öffentliche und geschützte Typen, "public" zeigt nur\nöffentliche Typen, "package" zeigt öffentliche, geschützte und\nPackagetypen, und "private" zeigt alle Typen. diff --git a/src/jdk.jcmd/share/classes/sun/tools/jstat/ExpressionExecuter.java b/src/jdk.jcmd/share/classes/sun/tools/jstat/ExpressionExecuter.java index 860fd832197..649e878d340 100644 --- a/src/jdk.jcmd/share/classes/sun/tools/jstat/ExpressionExecuter.java +++ b/src/jdk.jcmd/share/classes/sun/tools/jstat/ExpressionExecuter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.jcmd/share/classes/sun/tools/jstat/ExpressionResolver.java b/src/jdk.jcmd/share/classes/sun/tools/jstat/ExpressionResolver.java index f0e1bc371e3..9e43b079793 100644 --- a/src/jdk.jcmd/share/classes/sun/tools/jstat/ExpressionResolver.java +++ b/src/jdk.jcmd/share/classes/sun/tools/jstat/ExpressionResolver.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.jcmd/share/classes/sun/tools/jstat/Parser.java b/src/jdk.jcmd/share/classes/sun/tools/jstat/Parser.java index cf1790d65e6..06de56533a6 100644 --- a/src/jdk.jcmd/share/classes/sun/tools/jstat/Parser.java +++ b/src/jdk.jcmd/share/classes/sun/tools/jstat/Parser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.jcmd/share/classes/sun/tools/jstat/resources/jstat_options b/src/jdk.jcmd/share/classes/sun/tools/jstat/resources/jstat_options index 6bf67b9bdc4..1589526db0e 100644 --- a/src/jdk.jcmd/share/classes/sun/tools/jstat/resources/jstat_options +++ b/src/jdk.jcmd/share/classes/sun/tools/jstat/resources/jstat_options @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.jdeps/share/classes/com/sun/tools/jnativescan/Main.java b/src/jdk.jdeps/share/classes/com/sun/tools/jnativescan/Main.java index 9aa77a947a9..7d645025630 100644 --- a/src/jdk.jdeps/share/classes/com/sun/tools/jnativescan/Main.java +++ b/src/jdk.jdeps/share/classes/com/sun/tools/jnativescan/Main.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.jdi/share/classes/com/sun/jdi/request/EventRequestManager.java b/src/jdk.jdi/share/classes/com/sun/jdi/request/EventRequestManager.java index 3bd363af56e..de13561a4fc 100644 --- a/src/jdk.jdi/share/classes/com/sun/jdi/request/EventRequestManager.java +++ b/src/jdk.jdi/share/classes/com/sun/jdi/request/EventRequestManager.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 @@ -251,13 +251,13 @@ public interface EventRequestManager extends Mirror { * after the first step is detected. Thus a next line method * would do the following: *
    {@code
    -     *     EventRequestManager mgr = myVM.{@link VirtualMachine#eventRequestManager eventRequestManager}();
    +     *     EventRequestManager mgr = myVM.eventRequestManager();
          *     StepRequest request = mgr.createStepRequest(myThread,
    -     *                                                 StepRequest.{@link StepRequest#STEP_LINE STEP_LINE},
    -     *                                                 StepRequest.{@link StepRequest#STEP_OVER STEP_OVER});
    -     *     request.{@link EventRequest#addCountFilter addCountFilter}(1);  // next step only
    +     *                                                 StepRequest.STEP_LINE,
    +     *                                                 StepRequest.STEP_OVER);
    +     *     request.addCountFilter(1);  // next step only
          *     request.enable();
    -     *     myVM.{@link VirtualMachine#resume resume}();
    +     *     myVM.resume();
          * }
    * * @param thread the thread in which to step @@ -382,7 +382,7 @@ public interface EventRequestManager extends Mirror { * Iterator iter = requestManager.stepRequests().iterator(); * while (iter.hasNext()) { * requestManager.deleteEventRequest(iter.next()); - * } + * } *
    * may cause a {@link java.util.ConcurrentModificationException}. * Instead use diff --git a/src/jdk.jdi/share/classes/com/sun/tools/jdi/EventSetImpl.java b/src/jdk.jdi/share/classes/com/sun/tools/jdi/EventSetImpl.java index 19e8b45c491..9a13eb3dca5 100644 --- a/src/jdk.jdi/share/classes/com/sun/tools/jdi/EventSetImpl.java +++ b/src/jdk.jdi/share/classes/com/sun/tools/jdi/EventSetImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2021, 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 @@ -37,6 +37,7 @@ import com.sun.jdi.InternalException; import com.sun.jdi.Locatable; import com.sun.jdi.Location; import com.sun.jdi.Method; +import com.sun.jdi.ObjectCollectedException; import com.sun.jdi.ObjectReference; import com.sun.jdi.ReferenceType; import com.sun.jdi.ThreadReference; @@ -206,6 +207,19 @@ public class EventSetImpl extends ArrayList implements EventSet { } + /* Safely fetch the thread name in case there is an ObjectCollectedException. + * This can happen when dealing with SUSPEND_NONE events. + */ + private static String getThreadName(ThreadReference thread) { + String name; + try { + name = thread.name(); + } catch (ObjectCollectedException oce) { + name = ""; + } + return name; + } + abstract class ThreadedEventImpl extends EventImpl { private ThreadReference thread; @@ -220,7 +234,7 @@ public class EventSetImpl extends ArrayList implements EventSet { } public String toString() { - return eventName() + " in thread " + thread.name(); + return eventName() + " in thread " + getThreadName(thread); } } @@ -249,7 +263,7 @@ public class EventSetImpl extends ArrayList implements EventSet { public String toString() { return eventName() + "@" + ((location() == null) ? " null" : location().toString()) + - " in thread " + thread().name(); + " in thread " + getThreadName(thread()); } } diff --git a/src/jdk.jdi/share/classes/com/sun/tools/jdi/ThreadReferenceImpl.java b/src/jdk.jdi/share/classes/com/sun/tools/jdi/ThreadReferenceImpl.java index e5dce0718f4..766c65ea195 100644 --- a/src/jdk.jdi/share/classes/com/sun/tools/jdi/ThreadReferenceImpl.java +++ b/src/jdk.jdi/share/classes/com/sun/tools/jdi/ThreadReferenceImpl.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 diff --git a/src/jdk.jdi/windows/native/libdt_shmem/shmem_md.c b/src/jdk.jdi/windows/native/libdt_shmem/shmem_md.c index df07b753f0e..3badeb74207 100644 --- a/src/jdk.jdi/windows/native/libdt_shmem/shmem_md.c +++ b/src/jdk.jdi/windows/native/libdt_shmem/shmem_md.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.jdwp.agent/share/native/libjdwp/EventRequestImpl.c b/src/jdk.jdwp.agent/share/native/libjdwp/EventRequestImpl.c index 3eddb01eaa5..2bc6fa5259e 100644 --- a/src/jdk.jdwp.agent/share/native/libjdwp/EventRequestImpl.c +++ b/src/jdk.jdwp.agent/share/native/libjdwp/EventRequestImpl.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2022, 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 @@ -84,12 +84,10 @@ readAndSetFilters(JNIEnv *env, PacketInputStream *in, HandlerNode *node, } case JDWP_REQUEST_MODIFIER(LocationOnly): { - jbyte tag; jclass clazz; jmethodID method; jlocation location; - tag = inStream_readByte(in); /* not currently used */ - tag = tag; /* To shut up lint */ + (void)inStream_readByte(in); /* not currently used */ if ( (serror = inStream_error(in)) != JDWP_ERROR(NONE) ) break; clazz = inStream_readClassRef(env, in); diff --git a/src/jdk.jdwp.agent/share/native/libjdwp/SDE.c b/src/jdk.jdwp.agent/share/native/libjdwp/SDE.c index b1f18dd10be..4334b4cbc63 100644 --- a/src/jdk.jdwp.agent/share/native/libjdwp/SDE.c +++ b/src/jdk.jdwp.agent/share/native/libjdwp/SDE.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -718,19 +718,6 @@ private jboolean isValid(void); lineTable[lti].jplsLineInc)); } - private int fileTableIndex(int sti, int fileId) { - int i; - int fileIndexStart = stratumTable[sti].fileIndex; - /* one past end */ - int fileIndexEnd = stratumTable[sti+1].fileIndex; - for (i = fileIndexStart; i < fileIndexEnd; ++i) { - if (fileTable[i].fileId == fileId) { - return i; - } - } - return -1; - } - private jboolean isValid(void) { return sourceMapIsValid; } diff --git a/src/jdk.jdwp.agent/share/native/libjdwp/debugInit.c b/src/jdk.jdwp.agent/share/native/libjdwp/debugInit.c index 2bed6b6bb28..1911ff1bed4 100644 --- a/src/jdk.jdwp.agent/share/native/libjdwp/debugInit.c +++ b/src/jdk.jdwp.agent/share/native/libjdwp/debugInit.c @@ -181,11 +181,11 @@ DEF_Agent_OnLoad(JavaVM *vm, char *options, void *reserved) vmInitialized = JNI_FALSE; gdata->vmDead = JNI_FALSE; - jvmtiCompileTimeMajorVersion = ( JVMTI_VERSION & JVMTI_VERSION_MASK_MAJOR ) + jvmtiCompileTimeMajorVersion = ((unsigned)JVMTI_VERSION & JVMTI_VERSION_MASK_MAJOR) >> JVMTI_VERSION_SHIFT_MAJOR; - jvmtiCompileTimeMinorVersion = ( JVMTI_VERSION & JVMTI_VERSION_MASK_MINOR ) + jvmtiCompileTimeMinorVersion = ((unsigned)JVMTI_VERSION & JVMTI_VERSION_MASK_MINOR) >> JVMTI_VERSION_SHIFT_MINOR; - jvmtiCompileTimeMicroVersion = ( JVMTI_VERSION & JVMTI_VERSION_MASK_MICRO ) + jvmtiCompileTimeMicroVersion = ((unsigned)JVMTI_VERSION & JVMTI_VERSION_MASK_MICRO) >> JVMTI_VERSION_SHIFT_MICRO; /* Get the JVMTI Env, IMPORTANT: Do this first! For jvmtiAllocate(). */ diff --git a/src/jdk.jdwp.agent/share/native/libjdwp/error_messages.c b/src/jdk.jdwp.agent/share/native/libjdwp/error_messages.c index a37b88e70df..6ddc6fe739a 100644 --- a/src/jdk.jdwp.agent/share/native/libjdwp/error_messages.c +++ b/src/jdk.jdwp.agent/share/native/libjdwp/error_messages.c @@ -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 @@ -61,6 +61,8 @@ * NOTE: This function is at the lowest level of the call tree. * Do not use the ERROR* macros here. */ + +ATTRIBUTE_PRINTF(4, 0) static void vprint_message(FILE *fp, const char *prefix, const char *suffix, const char *format, va_list ap) @@ -84,6 +86,7 @@ vprint_message(FILE *fp, const char *prefix, const char *suffix, * NOTE: This function is at the lowest level of the call tree. * Do not use the ERROR* macros here. */ +ATTRIBUTE_PRINTF(4, 5) void print_message(FILE *fp, const char *prefix, const char *suffix, const char *format, ...) @@ -96,6 +99,7 @@ print_message(FILE *fp, const char *prefix, const char *suffix, } /* Generate error message */ +ATTRIBUTE_PRINTF(1, 2) void error_message(const char *format, ...) { @@ -110,6 +114,7 @@ error_message(const char *format, ...) } /* Print plain message to stdout. */ +ATTRIBUTE_PRINTF(1, 2) void tty_message(const char *format, ...) { diff --git a/src/jdk.jdwp.agent/share/native/libjdwp/eventFilter.c b/src/jdk.jdwp.agent/share/native/libjdwp/eventFilter.c index 3ba875e88cd..6fe01044a2c 100644 --- a/src/jdk.jdwp.agent/share/native/libjdwp/eventFilter.c +++ b/src/jdk.jdwp.agent/share/native/libjdwp/eventFilter.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -976,7 +976,6 @@ eventFilter_setSourceNameMatchFilter(HandlerNode *node, jvmtiError eventFilter_setPlatformThreadsOnlyFilter(HandlerNode *node, jint index) { - PlatformThreadsFilter *filter = &FILTER(node, index).u.PlatformThreadsOnly; if (index >= FILTER_COUNT(node)) { return AGENT_ERROR_ILLEGAL_ARGUMENT; } diff --git a/src/jdk.jdwp.agent/share/native/libjdwp/inStream.c b/src/jdk.jdwp.agent/share/native/libjdwp/inStream.c index 232da2cb348..de424853175 100644 --- a/src/jdk.jdwp.agent/share/native/libjdwp/inStream.c +++ b/src/jdk.jdwp.agent/share/native/libjdwp/inStream.c @@ -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 @@ -470,6 +470,7 @@ inStream_readValue(PacketInputStream *stream) break; default: stream->error = JDWP_ERROR(INVALID_TAG); + value.j = 0L; // to make compiler happy break; } } diff --git a/src/jdk.jdwp.agent/share/native/libjdwp/log_messages.c b/src/jdk.jdwp.agent/share/native/libjdwp/log_messages.c index ebc39febff9..e24fcd57156 100644 --- a/src/jdk.jdwp.agent/share/native/libjdwp/log_messages.c +++ b/src/jdk.jdwp.agent/share/native/libjdwp/log_messages.c @@ -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 @@ -155,6 +155,7 @@ standard_logging_format(FILE *fp, } /* End a log entry */ +ATTRIBUTE_PRINTF(1, 2) void log_message_end(const char *format, ...) { diff --git a/src/jdk.jdwp.agent/share/native/libjdwp/threadControl.c b/src/jdk.jdwp.agent/share/native/libjdwp/threadControl.c index bfeffe85678..7b0adab8ca5 100644 --- a/src/jdk.jdwp.agent/share/native/libjdwp/threadControl.c +++ b/src/jdk.jdwp.agent/share/native/libjdwp/threadControl.c @@ -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 @@ -70,6 +70,7 @@ typedef struct ThreadNode { unsigned int popFrameEvent : 1; unsigned int popFrameProceed : 1; unsigned int popFrameThread : 1; + unsigned int frameGeneration_accessed:1; /* true if frameGeneration accessed to produce a FrameID */ EventIndex current_ei; /* Used to determine if we are currently handling an event on this thread. */ jobject pendingStop; /* Object we are throwing to stop the thread (ThreadReferenceImpl.stop). */ jint suspendCount; /* Number of outstanding suspends from the debugger. */ @@ -137,6 +138,9 @@ typedef struct { static DeferredEventModeList deferredEventModes; +static ThreadNode * +insertThread(JNIEnv *env, ThreadList *list, jthread thread); + /* Get the state of the thread direct from JVMTI */ static jvmtiError threadState(jthread thread, jint *pstate) @@ -154,11 +158,24 @@ setThreadLocalStorage(jthread thread, ThreadNode *node) error = JVMTI_FUNC_PTR(gdata->jvmti,SetThreadLocalStorage) (gdata->jvmti, thread, (void*)node); - if ( error == JVMTI_ERROR_THREAD_NOT_ALIVE && node == NULL) { - /* Just return. This can happen when clearing the TLS. */ + if (error == JVMTI_ERROR_THREAD_NOT_ALIVE) { + if (node == NULL) { + // Just return. This can happen when clearing the TLS. + return; + } + if (isVThread(thread)) { + // Just return. This can happen with a vthread that is running and we + // had to create a ThreadNode for it. By the time we get here, it may + // have already terminated. + return; + } + } + if (error == JVMTI_ERROR_WRONG_PHASE && gdata->vmDead && isVThread(thread)) { + // Just return. This can happen with vthreads when the vm is exiting. return; - } else if ( error != JVMTI_ERROR_NONE ) { - /* The jthread object must be valid, so this must be a fatal error */ + } + if (error != JVMTI_ERROR_NONE) { + // The jthread object must be valid, so this must be a fatal error. EXIT_ERROR(error, "cannot set thread local storage"); } } @@ -248,9 +265,10 @@ findThread(ThreadList *list, jthread thread) * Otherwise the thread should not be on the runningThreads. */ if ( !gdata->jvmtiCallBacksCleared ) { - /* The thread better not be on either list if the TLS lookup failed. */ + // The thread better not be on the runningThreads list if the TLS lookup failed. + // It might be on the runningVThreads list because of how ThreadNodes for vthreads + // can be recreated just before terminating, so we don't check runningVThreads. JDI_ASSERT(!nonTlsSearch(getEnv(), &runningThreads, thread)); - JDI_ASSERT(!nonTlsSearch(getEnv(), &runningVThreads, thread)); } else { /* * Search the runningThreads and runningVThreads lists. The TLS lookup may have @@ -277,6 +295,35 @@ findThread(ThreadList *list, jthread thread) return node; } +/* Creates a new ThreadNode for a vthread if one is needed. */ +static ThreadNode * +createVThreadNodeIfNeeded(jthread thread) { + ThreadNode *node = findThread(&otherThreads, thread); + if (node != NULL) { + //tty_message("createVThreadNodeIfNeeded: thread is on otherThreads"); + // Don't create a new node if it is on otherThreads list. Also don't return + // the existing node because if it is on otherThreads, it is not running. + return NULL; + } + + // See if we have a vthread that is alive. If we do, create a ThreadNode + // for it. Otherwise just return NULL. + jint vthread_state = 0; + jvmtiError error = threadState(thread, &vthread_state); + if (error != JVMTI_ERROR_NONE) { + EXIT_ERROR(error, "getting vthread state"); + } + if ((vthread_state & JVMTI_THREAD_STATE_ALIVE) == 0) { + return NULL; // Don't create a new ThreadNode if thread is not alive + } + node = insertThread(getEnv(), &runningVThreads, thread); + if (node->suspendCount > 0 && !node->suspendOnStart) { + node->toBeResumed = JNI_TRUE; + } + + return node; +} + /* Search for a running thread, including vthreads. */ static ThreadNode * findRunningThread(jthread thread) @@ -284,6 +331,16 @@ findRunningThread(jthread thread) ThreadNode *node; if (isVThread(thread)) { node = findThread(&runningVThreads, thread); + if (node == NULL && !gdata->includeVThreads) { + // Unlike platform threads, we don't always have a ThreadNode for all vthreads. + // They can be freed if not holding on to any relevant state info. It's also + // possible that the vthread was created before the debugger attached. Also + // in the future we won't be enabling VIRTUAL_THREAD_START events in some + // cases, which means we won't be creating a ThreadNode when the vthread is + // created. If for any of the above reasons the ThreadNode lookup failed, + // we'll create one for the vthread now, but only if really needed. + node = createVThreadNodeIfNeeded(thread); + } } else { node = findThread(&runningThreads, thread); } @@ -370,6 +427,23 @@ insertThread(JNIEnv *env, ThreadList *list, jthread thread) EXIT_ERROR(AGENT_ERROR_OUT_OF_MEMORY,"thread table entry"); return NULL; } + +#ifdef DEBUG_THREADNAME + { + /* Set the thread name */ + jvmtiThreadInfo info; + jvmtiError error; + + memset(&info, 0, sizeof(info)); + error = JVMTI_FUNC_PTR(gdata->jvmti,GetThreadInfo) + (gdata->jvmti, node->thread, &info); + if (info.name != NULL) { + strncpy(node->name, info.name, sizeof(node->name) - 1); + jvmtiDeallocate(info.name); + } + } +#endif + if (!is_vthread) { if (threadControl_isDebugThread(node->thread)) { /* Remember if it is a debug thread */ @@ -418,22 +492,6 @@ insertThread(JNIEnv *env, ThreadList *list, jthread thread) node->eventBag = eventBag; addNode(list, node); -#ifdef DEBUG_THREADNAME - { - /* Set the thread name */ - jvmtiThreadInfo info; - jvmtiError error; - - memset(&info, 0, sizeof(info)); - error = JVMTI_FUNC_PTR(gdata->jvmti,GetThreadInfo) - (gdata->jvmti, node->thread, &info); - if (info.name != NULL) { - strncpy(node->name, info.name, sizeof(node->name) - 1); - jvmtiDeallocate(info.name); - } - } -#endif - /* Set thread local storage for quick thread -> node access. * Threads that are not yet started do not allow setting of TLS. These * threads go on the otherThreads list and have their TLS set @@ -491,8 +549,7 @@ removeResumed(JNIEnv *env, ThreadList *list) static void removeVThreads(JNIEnv *env) { - ThreadList *list = &runningVThreads; - ThreadNode *node = list->first; + ThreadNode *node = runningVThreads.first; while (node != NULL) { ThreadNode *temp = node->next; removeNode(node); @@ -501,6 +558,95 @@ removeVThreads(JNIEnv *env) } } +/* + * Free (garbage collect) a vthread node if it is unused. This can only be called when + * locks are held so we don't need to worry about other threads changing the state + * of the ThreadNode while we are looking at it. We also need to make sure the + * ThreadNode is not in use for operations like single stepping or invoking. + */ +static void +freeUnusedVThreadNode(JNIEnv *env, ThreadNode* node) +{ + if (gdata->includeVThreads) { + return; + } + + /* + * node->suspendCount requires special handling to see if it triggers having + * to keep the node around. It's possible for it to be 0 yet we still need to + * keep the node around. Also, it's possbile for it to be non-zero yet we + * don't need to keep the node around. More details in the comments below. + */ + if (node->suspendCount == 0) { + /* + * Normally a suspendCount of 0 does not result in having to keep the + * node. However, if the suspendAllCount is not 0, then we do. Otherwise + * when the node is recreated later it will end up assuming suspendAllCount + * as its suspendCount rather than 0, which would be incorrect. This + * mismatch in suspend counts happens when there is a suspendAll in place, + * and ThreadReference.resume() is used to decrement the thread's + * suspendCount to 0. + */ + if (suspendAllCount > 0) { + return; + } + } else { + /* + * Although at first it might seem that a non-zero suspendCount would require + * keeping the node, we don't have to if node->suspendCount == suspendAllCount, + * because when the node is recreated it will get suspendAllCount assigned to it. + * So we only worry about keeping the node around if the two counts are not equal. + */ + if (node->suspendCount != suspendAllCount) { + return; + } + + } + + // All of the following conditions must be met to free this node. Note + // suspendCount checks were already made above, so are not included below. If + // we got here, then suspendCount checks passed w.r.t. being able to free the node. + // Also note we don't need to check node->toBeResumed. If it is set, that implies + // node->suspendCount > 0, and that will trigger toBeResumed getting set when + // the ThreadNode is recreated. See createVThreadNodeIfNeeded(). + if (!node->suspendOnStart && + node->current_ei == 0 && + node->cleInfo.ei == 0 && + !node->currentInvoke.pending && + !node->currentInvoke.started && + !node->currentInvoke.available && + !node->currentStep.pending && + node->instructionStepMode != JVMTI_ENABLE && + !node->pendingInterrupt && + !node->popFrameEvent && + !node->popFrameProceed && + !node->popFrameThread && + !node->frameGeneration_accessed && + node->pendingStop == NULL) + { + removeNode(node); + clearThread(env, node); + } +} + +/* + * Free (garbage collect) vthread nodes if unused. See freeUnusedVThreadNode() above. + */ +static void +freeUnusedVThreadNodes(JNIEnv *env) +{ + if (gdata->includeVThreads) { + return; + } + + ThreadNode *node = runningVThreads.first; + while (node != NULL) { + ThreadNode *temp = node->next; + freeUnusedVThreadNode(env, node); + node = temp; + } +} + static void moveNode(ThreadList *source, ThreadList *dest, ThreadNode *node) { @@ -1139,6 +1285,7 @@ commonResumeList(JNIEnv *env) /* * This function must be called after preSuspend and before postSuspend. + * Only called for platform threads. */ static jvmtiError commonSuspendList(JNIEnv *env, jint initCount, jthread *initList) @@ -1160,15 +1307,17 @@ commonSuspendList(JNIEnv *env, jint initCount, jthread *initList) */ for (i = 0; i < initCount; i++) { ThreadNode *node; + jthread thread = initList[i]; + JDI_ASSERT(!isVThread(thread)); /* * If the thread is not between its start and end events, we should * still suspend it. To keep track of things, add the thread * to a separate list of threads so that we'll resume it later. */ - node = findThread(&runningThreads, initList[i]); + node = findThread(&runningThreads, thread); if (node == NULL) { - node = insertThread(env, &otherThreads, initList[i]); + node = insertThread(env, &otherThreads, thread); } if (node->isDebugThread) { @@ -1187,7 +1336,7 @@ commonSuspendList(JNIEnv *env, jint initCount, jthread *initList) if (node->suspendCount == 0) { /* thread is not suspended yet so put it on the request list */ - reqList[reqCnt++] = initList[i]; + reqList[reqCnt++] = thread; } } @@ -1255,10 +1404,16 @@ commonResume(jthread thread) ThreadNode *node; /* - * The thread is normally between its start and end events, but if - * not, check the auxiliary list used by threadControl_suspendThread. + * We need to call findRunningThread(thread) here instead of just calling + * findThread(NULL, thread) because it's possible that there is not currently a + * ThreadNode for the thread, and findRunningThread() will create one in that case. */ - node = findThread(NULL, thread); + node = findRunningThread(thread); + if (node == NULL) { + // The thread is normally between its start and end events, but if not, + // check the auxiliary list used by commonSuspend() and commonSuspendList(). + node = findThread(&otherThreads, thread); + } #if 0 tty_message("commonResume: node(%p) suspendCount(%d) %s", node, node->suspendCount, node->name); #endif @@ -1336,24 +1491,9 @@ threadControl_suspendCount(jthread thread, jint *count) } else { /* * If the node is in neither list, the debugger never suspended - * this thread, so the suspend count is 0, unless it is a vthread. + * this thread, so the suspend count is 0. */ - if (isVThread(thread)) { - jint vthread_state = 0; - jvmtiError error = threadState(thread, &vthread_state); - if (error != JVMTI_ERROR_NONE) { - EXIT_ERROR(error, "getting vthread state"); - } - if (vthread_state == 0) { - // If state == 0, then this is a new vthread that has not been started yet. - *count = 0; - } else { - // This is a started vthread that we are not tracking. Use suspendAllCount. - *count = suspendAllCount; - } - } else { - *count = 0; - } + *count = 0; } debugMonitorExit(threadLock); @@ -1406,9 +1546,6 @@ threadControl_suspendAll(void) { jvmtiError error; JNIEnv *env; -#if 0 - tty_message("threadControl_suspendAll: suspendAllCount(%d)", suspendAllCount); -#endif env = getEnv(); @@ -1416,6 +1553,8 @@ threadControl_suspendAll(void) preSuspend(); + //tty_message("threadControl_suspendAll: suspendAllCount(%d)", suspendAllCount); + /* * Get a list of all threads and suspend them. */ @@ -1425,6 +1564,11 @@ threadControl_suspendAll(void) jint count; if (gdata->vthreadsSupported) { + // Now is a good time to garbage collect vthread nodes. We want to do it before + // any suspendAll because it will prevent the suspended nodes from being freed. + if (!gdata->includeVThreads) { + freeUnusedVThreadNodes(env); + } /* Tell JVMTI to suspend all virtual threads. */ if (suspendAllCount == 0) { error = JVMTI_FUNC_PTR(gdata->jvmti, SuspendAllVirtualThreads) @@ -1528,9 +1672,6 @@ threadControl_resumeAll(void) { jvmtiError error; JNIEnv *env; -#if 0 - tty_message("threadControl_resumeAll: suspendAllCount(%d)", suspendAllCount); -#endif env = getEnv(); @@ -1539,6 +1680,8 @@ threadControl_resumeAll(void) eventHandler_lock(); /* for proper lock order */ debugMonitorEnter(threadLock); + //tty_message("threadControl_resumeAll: suspendAllCount(%d)", suspendAllCount); + if (gdata->vthreadsSupported) { if (suspendAllCount == 1) { jint excludeCnt = 0; @@ -2092,7 +2235,7 @@ threadControl_onEventHandlerEntry(jbyte sessionID, EventInfo *evinfo, jobject cu processDeferredEventModes(env, thread, node); } if (ei == EI_THREAD_END) { - // If the node was previously freed, then it was just recreated and we need + // If the node was previously freed and was just now recreated, we need // to mark it as started. node->isStarted = JNI_TRUE; } @@ -2148,17 +2291,30 @@ threadControl_onEventHandlerExit(EventIndex ei, jthread thread, log_debugee_location("threadControl_onEventHandlerExit()", thread, NULL, 0); - if (ei == EI_THREAD_END) { + if (ei == EI_THREAD_END || ei == EI_THREAD_START) { eventHandler_lock(); /* for proper lock order - see removeThread() call below */ debugMonitorEnter(threadLock); node = findRunningThread(thread); if (node == NULL) { EXIT_ERROR(AGENT_ERROR_NULL_POINTER,"thread list corrupted"); } - removeThread(env, node); // Grabs handlerLock, thus the reason for first grabbing it above. - node = NULL; // We exiting the threadLock. No longer safe to access. - debugMonitorExit(threadLock); - eventHandler_unlock(); + // Remove nodes for exiting threads, but also remove nodes for vthreads + // that are just starting to help prevent their accumulation. There can't + // possibly be any state related information in the node at this point. A new + // one will be created later if necessary, such as when a new event arrives. + if (ei == EI_THREAD_END || (node->is_vthread && !gdata->includeVThreads)) { + removeThread(env, node); // Grabs handlerLock, thus the reason for first grabbing it above. + node = NULL; // Node has been freed. No longer safe to access. + } else { + // EI_THREAD_START for a platform thread, or we are tracking vthreads. + // In either case we are not removing the thread node. + // Just clear these two fields. Others are not set yet. Also no need to + // worry about pending tasks like we do below for other event types. + node->eventBag = eventBag; + node->current_ei = 0; + } + debugMonitorExit(threadLock); + eventHandler_unlock(); } else { debugMonitorEnter(threadLock); node = findRunningThread(thread); @@ -2173,7 +2329,7 @@ threadControl_onEventHandlerExit(EventIndex ei, jthread thread, node->pendingStop = NULL; node->eventBag = eventBag; node->current_ei = 0; - node = NULL; // We exiting the threadLock. No longer safe to access. + node = NULL; // We're exiting the threadLock. No longer safe to access. // doPendingTasks() may do an upcall to java, and we don't want to hold any // locks when doing that. Thus we got all our node updates done first // and can now exit the threadLock. @@ -2189,6 +2345,8 @@ threadControl_onEventHandlerExit(EventIndex ei, jthread thread, // until now. Otherwise there are complaints when JNI IsVirtualThread is called. JNI_FUNC_PTR(env,Throw)(env, currentException); } + // Note: Threads that were just created or are about to die don't have pending + // tasks, which is why this is the only code path where we call doPendingTasks(). doPendingTasks(env, thread, pendingInterrupt, pendingStop); if (pendingStop != NULL) { tossGlobalRef(env, &pendingStop); @@ -2207,6 +2365,7 @@ threadControl_applicationThreadStatus(jthread thread, log_debugee_location("threadControl_applicationThreadStatus()", thread, NULL, 0); + eventHandler_lock(); // Needed to safely access HANDLING_EVENT(node) debugMonitorEnter(threadLock); error = threadState(thread, &state); @@ -2229,6 +2388,7 @@ threadControl_applicationThreadStatus(jthread thread, } debugMonitorExit(threadLock); + eventHandler_unlock(); return error; } @@ -2334,6 +2494,7 @@ threadControl_stop(jthread thread, jobject throwable) log_debugee_location("threadControl_stop()", thread, NULL, 0); + eventHandler_lock(); // Needed to safely access HANDLING_EVENT(node) debugMonitorEnter(threadLock); node = findThread(&runningThreads, thread); @@ -2351,6 +2512,7 @@ threadControl_stop(jthread thread, jobject throwable) } debugMonitorExit(threadLock); + eventHandler_unlock(); return error; } @@ -2505,12 +2667,16 @@ threadControl_setEventMode(jvmtiEventMode mode, EventIndex ei, jthread thread) /* * Returns the current thread. + * Returns NULL on error (JVMTI_ERROR_WRONG_PHASE, JVMTI_ERROR_UNATTACHED_THREAD). */ jthread threadControl_currentThread(void) { jthread thread = NULL; jvmtiError error = JVMTI_FUNC_PTR(gdata->jvmti,GetCurrentThread)(gdata->jvmti, &thread); + if (error != JVMTI_ERROR_NONE) { + return NULL; + } return thread; } @@ -2527,6 +2693,7 @@ threadControl_getFrameGeneration(jthread thread) if (node != NULL) { frameGeneration = node->frameGeneration; + node->frameGeneration_accessed = JNI_TRUE; } } debugMonitorExit(threadLock); @@ -2537,11 +2704,9 @@ threadControl_getFrameGeneration(jthread thread) jthread * threadControl_allVThreads(jint *numVThreads) { - JNIEnv *env; ThreadNode *node; jthread* vthreads; - env = getEnv(); debugMonitorEnter(threadLock); *numVThreads = numRunningVThreads; @@ -2579,7 +2744,7 @@ threadControl_dumpAllThreads() tty_message("suspendAllCount: %d", suspendAllCount); tty_message("Dumping runningThreads:"); dumpThreadList(&runningThreads); - tty_message("\nDumping runningVThreads:"); + tty_message("\nDumping runningVThreads(numRunningVThreads=%d):", numRunningVThreads); dumpThreadList(&runningVThreads); tty_message("\nDumping otherThreads:"); dumpThreadList(&otherThreads); @@ -2650,9 +2815,11 @@ dumpThread(ThreadNode *node) { // kept small so it doesn't generate too much output. tty_message("\tsuspendCount: %d", node->suspendCount); #if 0 + tty_message("\ttoBeResumed: %d", node->toBeResumed); + tty_message("\tisStarted: %d", node->isStarted); + tty_message("\tsuspendOnStart: %d", node->suspendOnStart); tty_message("\tsuspendAllCount: %d", suspendAllCount); tty_message("\tthreadState: 0x%x", getThreadState(node)); - tty_message("\ttoBeResumed: %d", node->toBeResumed); tty_message("\tis_vthread: %d", node->is_vthread); tty_message("\tpendingInterrupt: %d", node->pendingInterrupt); tty_message("\tcurrentInvoke.pending: %d", node->currentInvoke.pending); diff --git a/src/jdk.jdwp.agent/share/native/libjdwp/utf_util.c b/src/jdk.jdwp.agent/share/native/libjdwp/utf_util.c index f5573930d34..6bae02fce54 100644 --- a/src/jdk.jdwp.agent/share/native/libjdwp/utf_util.c +++ b/src/jdk.jdwp.agent/share/native/libjdwp/utf_util.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2022, 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 @@ -173,7 +173,7 @@ int JNICALL utf8mToUtf8sLength(jbyte *string, int length) { newLength = 0; for ( i = 0 ; i < length ; i++ ) { - unsigned byte1, byte2, byte3, byte4, byte5, byte6; + unsigned byte1, byte2, byte4, byte5, byte6; byte1 = (unsigned char)string[i]; if ( (byte1 & 0x80) == 0 ) { /* 1byte encoding */ @@ -196,7 +196,7 @@ int JNICALL utf8mToUtf8sLength(jbyte *string, int length) { break; /* Error condition */ } byte2 = (unsigned char)string[++i]; - byte3 = (unsigned char)string[++i]; + ++i; // byte3 is not used newLength += 3; /* Possible process a second 3byte encoding */ if ( (i+3) < length && byte1 == 0xED && (byte2 & 0xF0) == 0xA0 ) { diff --git a/src/jdk.jdwp.agent/share/native/libjdwp/util.h b/src/jdk.jdwp.agent/share/native/libjdwp/util.h index a48c8ba2c09..0975ddeb4fd 100644 --- a/src/jdk.jdwp.agent/share/native/libjdwp/util.h +++ b/src/jdk.jdwp.agent/share/native/libjdwp/util.h @@ -59,6 +59,14 @@ #include "error_messages.h" #include "debugInit.h" +/* To handle "format string is not a string literal" warning. */ +#if !defined(_MSC_VER) + #define ATTRIBUTE_PRINTF(fmt_pos_num, vargs_pos_num) \ + __attribute__((format(printf, fmt_pos_num, vargs_pos_num))) +#else + #define ATTRIBUTE_PRINTF(fmt_pos_num, vargs_pos_num) +#endif + /* Definition of a CommonRef tracked by the backend for the frontend */ typedef struct RefNode { jlong seqNum; /* ID of reference, also key for hash table */ diff --git a/src/jdk.jdwp.agent/windows/native/libjdwp/proc_md.h b/src/jdk.jdwp.agent/windows/native/libjdwp/proc_md.h index 6c86473625f..e8877f862f8 100644 --- a/src/jdk.jdwp.agent/windows/native/libjdwp/proc_md.h +++ b/src/jdk.jdwp.agent/windows/native/libjdwp/proc_md.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2005, 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 diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java index eaba86e6327..1180ebd6ea2 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java @@ -34,6 +34,7 @@ import jdk.jfr.internal.periodic.PeriodicEvents; import jdk.jfr.internal.util.ImplicitFields; import jdk.jfr.internal.util.TimespanRate; import jdk.jfr.internal.util.Utils; +import jdk.jfr.internal.settings.CPUThrottleSetting; import jdk.jfr.internal.settings.Throttler; import jdk.jfr.internal.tracing.Modification; @@ -60,7 +61,7 @@ public final class PlatformEventType extends Type { private boolean stackTraceEnabled = true; private long thresholdTicks = 0; private long period = 0; - private TimespanRate cpuRate; + private TimespanRate cpuRate = TimespanRate.of(CPUThrottleSetting.DEFAULT_VALUE); private boolean hasHook; private boolean beginChunk; diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecording.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecording.java index b07a71dd4d5..64454fc3cb4 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecording.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecording.java @@ -34,6 +34,7 @@ import static jdk.jfr.internal.LogTag.JFR; import java.io.IOException; import java.io.InputStream; import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.StandardOpenOption; @@ -744,7 +745,14 @@ public final class PlatformRecording implements AutoCloseable { } private void transferChunks(WriteablePath path) throws IOException { - try (ChunksChannel cc = new ChunksChannel(chunks); FileChannel fc = FileChannel.open(path.getReal(), StandardOpenOption.WRITE, StandardOpenOption.APPEND)) { + // Before writing, wipe the file if it already exists. + try (ChunksChannel cc = new ChunksChannel(chunks); FileChannel fc = FileChannel.open(path.getReal(), StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE)) { + // Mitigate races against other processes + FileLock l = fc.tryLock(); + if (l == null) { + Logger.log(LogTag.JFR, LogLevel.INFO, "Dump operation skipped for recording \"" + name + "\" (" + id + "). File " + path.getRealPathText() + " is locked by other dump operation or activity."); + return; + } long bytes = cc.transferTo(fc); Logger.log(LogTag.JFR, LogLevel.INFO, "Transferred " + bytes + " bytes from the disk repository"); // No need to force if no data was transferred, which avoids IOException when device is /dev/null diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ConstantMap.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ConstantMap.java index 3b6859304b8..b6f1273eb9e 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ConstantMap.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ConstantMap.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 diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/Dispatcher.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/Dispatcher.java index 61072e03e79..266756d49e9 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/Dispatcher.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/Dispatcher.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/query/FieldBuilder.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/query/FieldBuilder.java index df6fb9f2a9a..64791b1976a 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/query/FieldBuilder.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/query/FieldBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. * 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/jdk.jfr/share/classes/jdk/jfr/internal/query/QueryResolver.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/query/QueryResolver.java index 1b68a038a17..1bc21131cc1 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/query/QueryResolver.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/query/QueryResolver.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. * 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/jdk.jfr/share/classes/jdk/jfr/internal/query/view.ini b/src/jdk.jfr/share/classes/jdk/jfr/internal/query/view.ini index 91decb1aa20..5d72a9f85dc 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/query/view.ini +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/query/view.ini @@ -110,6 +110,15 @@ table = "COLUMN 'Time', 'Requested By', 'Operation', 'Classes' GROUP BY redefinitionId ORDER BY duration DESC" +[jvm.classes-by-source] + label = "Classes by Source" + table = "COLUMN 'Source', 'Count' + FORMAT truncate-beginning, none + SELECT source, COUNT(*) AS C + FROM jdk.ClassDefine + GROUP BY source + ORDER BY C DESC" + [jvm.compiler-configuration] label = "Compiler Configuration" form = "SELECT LAST(threadCount), LAST(dynamicCompilerThreadCount), LAST(tieredCompilation) diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/CPUThrottleSetting.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/CPUThrottleSetting.java index 944827f6d6f..7ea0ace21bb 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/CPUThrottleSetting.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/CPUThrottleSetting.java @@ -65,7 +65,7 @@ public final class CPUThrottleSetting extends SettingControl { } } } - return Objects.requireNonNullElse(highestRate.toString(), DEFAULT_VALUE); + return highestRate == null ? DEFAULT_VALUE : highestRate.toString(); } @Override diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Query.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Query.java index ec0407a6f04..280bef55af4 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Query.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Query.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. * 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/jdk.jfr/share/classes/jdk/jfr/internal/tracing/Instrumentation.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/Instrumentation.java index dbafca4ed3c..e06d361b203 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/Instrumentation.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/Instrumentation.java @@ -58,7 +58,13 @@ final class Instrumentation { } public void addMethod(long methodId, String name, String signature, int modification) { - modificationMap.put(name + signature, new Method(methodId, Modification.valueOf(modification), className + "::" + name)); + Method method = new Method( + methodId, + Modification.valueOf(modification), + name.equals(""), + className + "::" + name + ); + modificationMap.put(name + signature, method); } public List getMethods() { @@ -71,7 +77,7 @@ final class Instrumentation { ClassModel classModel = classFile.parse(bytecode); byte[] generated = classFile.build(classModel.thisClass().asSymbol(), classBuilder -> { for (var ce : classModel) { - if (modifyClassElement(classBuilder, ce)) { + if (modifyClassElement(classModel, classBuilder, ce)) { modified[0] = true; } else { classBuilder.with(ce); @@ -93,7 +99,7 @@ final class Instrumentation { } } - private boolean modifyClassElement(ClassBuilder classBuilder, ClassElement ce) { + private boolean modifyClassElement(ClassModel classModel, ClassBuilder classBuilder, ClassElement ce) { if (ce instanceof MethodModel mm) { String method = mm.methodName().stringValue(); String signature = mm.methodType().stringValue(); @@ -102,15 +108,15 @@ final class Instrumentation { if (tm != null) { Modification m = tm.modification(); if (m.tracing() || m.timing()) { - return modifyMethod(classBuilder, mm, tm); + return modifyMethod(classModel, classBuilder, mm, tm); } } } return false; } - private boolean modifyMethod(ClassBuilder classBuilder, MethodModel m, Method method) { - var code = m.code(); + private boolean modifyMethod(ClassModel classModel, ClassBuilder classBuilder, MethodModel methodModel, Method method) { + var code = methodModel.code(); if (code.isPresent()) { if (classLoader == null && ExcludeList.containsMethod(method.name())) { String msg = "Risk of recursion, skipping bytecode generation of " + method.name(); @@ -118,9 +124,9 @@ final class Instrumentation { return false; } MethodTransform s = MethodTransform.ofStateful( - () -> MethodTransform.transformingCode(new Transform(method)) + () -> MethodTransform.transformingCode(new Transform(classModel, code.get(), method)) ); - classBuilder.transformMethod(m, s); + classBuilder.transformMethod(methodModel, s); return true; } return false; diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/Method.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/Method.java index d685083153d..d85e458e9d5 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/Method.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/Method.java @@ -31,7 +31,7 @@ import jdk.jfr.internal.Logger; /** * Class that holds information about an instrumented method. */ -record Method(long methodId, Modification modification, String name) { +record Method(long methodId, Modification modification, boolean constructor, String name) { @Override public String toString() { return name + (modification.timing() ? " +timing" : " -timing") + (modification.tracing() ? " +tracing" : " -tracing") + " (Method ID: " + String.format("0x%08X)", methodId); diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/Transform.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/Transform.java index cd65a119cee..377eede7925 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/Transform.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/Transform.java @@ -24,13 +24,19 @@ */ package jdk.jfr.internal.tracing; +import java.lang.classfile.ClassModel; import java.lang.classfile.CodeBuilder; import java.lang.classfile.CodeElement; +import java.lang.classfile.CodeModel; import java.lang.classfile.CodeTransform; +import java.lang.classfile.Label; import java.lang.classfile.TypeKind; +import java.lang.classfile.instruction.InvokeInstruction; import java.lang.classfile.instruction.ReturnInstruction; import java.lang.classfile.instruction.ThrowInstruction; import java.lang.constant.ClassDesc; +import java.util.ArrayList; +import java.util.List; import jdk.jfr.internal.util.Bytecode; import jdk.jfr.internal.util.Bytecode.MethodDesc; @@ -43,59 +49,208 @@ import jdk.jfr.tracing.MethodTracer; * The method ID is determined by native code. */ final class Transform implements CodeTransform { + private static class TryBlock { + Label start; + Label end; + } private static final ClassDesc METHOD_TRACER_CLASS = ClassDesc.of(MethodTracer.class.getName()); private static final MethodDesc TRACE_METHOD = MethodDesc.of("trace", "(JJ)V"); private static final MethodDesc TIMING_METHOD = MethodDesc.of("timing", "(JJ)V"); private static final MethodDesc TRACE_TIMING_METHOD = MethodDesc.of("traceTiming", "(JJ)V"); private static final MethodDesc TIMESTAMP_METHOD = MethodDesc.of("timestamp", "()J"); + private final List tryBlocks = new ArrayList<>(); + private final boolean simplifiedInstrumentation; + private final ClassModel classModel; private final Method method; private int timestampSlot = -1; - Transform(Method method) { + Transform(ClassModel classModel, CodeModel model, Method method) { this.method = method; + this.classModel = classModel; + // The JVMS (not the JLS) allows multiple mutually exclusive super/this. + // invocations in a constructor body as long as only one lies on any given + // execution path. For example, this is valid bytecode: + // + // Foo(boolean value) { + // if (value) { + // staticMethodThatMayThrow(); + // super(); + // } else { + // try { + // if (value == 0) { + // throw new Exception(""); + // } + // } catch (Throwable t) { + // throw t; + // } + // super(); + // } + // } + // + // If such a method is found, instrumentation falls back to instrumenting only + // RET and ATHROW. This can cause exceptions to be missed or counted twice. + // + // An effect of this heuristic is that constructors like the one below + // will also trigger simplified instrumentation. + // + // class Bar { + // } + // + // class Foo extends Bar { + // Foo() { + // new Bar(); + // } + // } + // + // java.lang.Object:: with zero constructor invocations should use simplified instrumentation + this.simplifiedInstrumentation = method.constructor() && constructorInvocations(model.elementList()) != 1; + } + + private int constructorInvocations(List elementList) { + int count = 0; + for (CodeElement e : elementList) { + if (isConstructorInvocation(e)) { + count++; + } + } + return count; + } + + private boolean isConstructorInvocation(CodeElement element) { + if (element instanceof InvokeInstruction inv && inv.name().equalsString("")) { + if (classModel.thisClass().equals(inv.owner())) { + return true; + } + if (classModel.superclass().isPresent()) { + return classModel.superclass().get().equals(inv.owner()); + } + } + return false; } @Override - public final void accept(CodeBuilder builder, CodeElement element) { + public void accept(CodeBuilder builder, CodeElement element) { + if (simplifiedInstrumentation) { + acceptSimplifiedInstrumentation(builder, element); + return; + } + if (method.constructor()) { + acceptConstructor(builder, element, isConstructorInvocation(element)); + } else { + acceptMethod(builder, element); + } + } + + @Override + public void atEnd(CodeBuilder builder) { + endTryBlock(builder); + for (TryBlock block : tryBlocks) { + addCatchHandler(block, builder); + } + } + + private void acceptConstructor(CodeBuilder builder, CodeElement element, boolean isConstructorInvocation) { + if (timestampSlot == -1) { + timestampSlot = invokeTimestamp(builder); + builder.lstore(timestampSlot); + if (!isConstructorInvocation) { + beginTryBlock(builder); + } + } + if (isConstructorInvocation) { + endTryBlock(builder); + builder.with(element); + beginTryBlock(builder); + return; + } + if (element instanceof ReturnInstruction) { + addTracing(builder); + } + builder.with(element); + } + + private void endTryBlock(CodeBuilder builder) { + if (tryBlocks.isEmpty()) { + return; + } + TryBlock last = tryBlocks.getLast(); + if (last.end == null) { + last.end = builder.newBoundLabel(); + } + } + + private void beginTryBlock(CodeBuilder builder) { + TryBlock block = new TryBlock(); + block.start = builder.newBoundLabel(); + tryBlocks.add(block); + } + + private void acceptSimplifiedInstrumentation(CodeBuilder builder, CodeElement element) { if (timestampSlot == -1) { timestampSlot = invokeTimestamp(builder); builder.lstore(timestampSlot); } if (element instanceof ReturnInstruction || element instanceof ThrowInstruction) { - builder.lload(timestampSlot); - builder.ldc(method.methodId()); - Modification modification = method.modification(); - boolean objectInit = method.name().equals("java.lang.Object::"); - String suffix = objectInit ? "ObjectInit" : ""; - if (modification.timing()) { - if (modification.tracing()) { - invokeTraceTiming(builder, suffix); - } else { - invokeTiming(builder, suffix); - } - } else { - if (modification.tracing()) { - invokeTrace(builder, suffix); - } - } + addTracing(builder); } builder.with(element); } - public static void invokeTiming(CodeBuilder builder, String suffix) { + private void acceptMethod(CodeBuilder builder, CodeElement element) { + if (timestampSlot == -1) { + timestampSlot = invokeTimestamp(builder); + builder.lstore(timestampSlot); + beginTryBlock(builder); + } + if (element instanceof ReturnInstruction) { + addTracing(builder); + } + builder.with(element); + } + + private void addCatchHandler(TryBlock block, CodeBuilder builder) { + Label catchHandler = builder.newBoundLabel(); + int exceptionSlot = builder.allocateLocal(TypeKind.REFERENCE); + builder.astore(exceptionSlot); + addTracing(builder); + builder.aload(exceptionSlot); + builder.athrow(); + builder.exceptionCatchAll(block.start, block.end, catchHandler); + } + + private void addTracing(CodeBuilder builder) { + builder.lload(timestampSlot); + builder.ldc(method.methodId()); + Modification modification = method.modification(); + boolean objectInit = method.name().equals("java.lang.Object::"); + String suffix = objectInit ? "ObjectInit" : ""; + if (modification.timing()) { + if (modification.tracing()) { + invokeTraceTiming(builder, suffix); + } else { + invokeTiming(builder, suffix); + } + } else { + if (modification.tracing()) { + invokeTrace(builder, suffix); + } + } + } + + private static void invokeTiming(CodeBuilder builder, String suffix) { builder.invokestatic(METHOD_TRACER_CLASS, TIMING_METHOD.name() + suffix, TIMING_METHOD.descriptor()); } - public static void invokeTrace(CodeBuilder builder, String suffix) { + private static void invokeTrace(CodeBuilder builder, String suffix) { builder.invokestatic(METHOD_TRACER_CLASS, TRACE_METHOD.name() + suffix, TRACE_METHOD.descriptor()); } - public static void invokeTraceTiming(CodeBuilder builder, String suffix) { + private static void invokeTraceTiming(CodeBuilder builder, String suffix) { builder.invokestatic(METHOD_TRACER_CLASS, TRACE_TIMING_METHOD.name() + suffix, TRACE_TIMING_METHOD.descriptor()); } - public static int invokeTimestamp(CodeBuilder builder) { + private static int invokeTimestamp(CodeBuilder builder) { Bytecode.invokestatic(builder, METHOD_TRACER_CLASS, TIMESTAMP_METHOD); return builder.allocateLocal(TypeKind.LONG); } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Rate.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Rate.java index 2632cd63848..0a7b14965cb 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Rate.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Rate.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -34,7 +34,7 @@ public record Rate(long amount, TimespanUnit unit) { String value = splitted[0].strip(); String unit = splitted[1].strip(); TimespanUnit tu = TimespanUnit.fromText(unit); - if (unit == null) { + if (tu == null) { return null; } try { diff --git a/src/jdk.jfr/share/man/jfr.md b/src/jdk.jfr/share/man/jfr.md index f7ff1b62775..b3b7ab1c0b4 100644 --- a/src/jdk.jfr/share/man/jfr.md +++ b/src/jdk.jfr/share/man/jfr.md @@ -1,5 +1,5 @@ --- -# Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.jlink/share/classes/jdk/tools/jimage/resources/jimage.properties b/src/jdk.jlink/share/classes/jdk/tools/jimage/resources/jimage.properties index 3042823130c..ac13505a0d9 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jimage/resources/jimage.properties +++ b/src/jdk.jlink/share/classes/jdk/tools/jimage/resources/jimage.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2015, 2018, 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 diff --git a/src/jdk.jlink/share/classes/jdk/tools/jimage/resources/jimage_de.properties b/src/jdk.jlink/share/classes/jdk/tools/jimage/resources/jimage_de.properties index fd30f5385e8..62bfd36181f 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jimage/resources/jimage_de.properties +++ b/src/jdk.jlink/share/classes/jdk/tools/jimage/resources/jimage_de.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2015, 2018, 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 diff --git a/src/jdk.jlink/share/classes/jdk/tools/jimage/resources/jimage_ja.properties b/src/jdk.jlink/share/classes/jdk/tools/jimage/resources/jimage_ja.properties index 66471e23b38..7ab6d7f655f 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jimage/resources/jimage_ja.properties +++ b/src/jdk.jlink/share/classes/jdk/tools/jimage/resources/jimage_ja.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2015, 2018, 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 diff --git a/src/jdk.jlink/share/classes/jdk/tools/jimage/resources/jimage_zh_CN.properties b/src/jdk.jlink/share/classes/jdk/tools/jimage/resources/jimage_zh_CN.properties index 8355ed5595e..37954b9b743 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jimage/resources/jimage_zh_CN.properties +++ b/src/jdk.jlink/share/classes/jdk/tools/jimage/resources/jimage_zh_CN.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2015, 2018, 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 diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java index 928b9a47934..825672cfe6d 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.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 @@ -27,9 +27,11 @@ package jdk.tools.jlink.internal; import static jdk.tools.jlink.internal.TaskHelper.JLINK_BUNDLE; import java.io.BufferedInputStream; +import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.io.PrintWriter; import java.io.UncheckedIOException; import java.lang.module.Configuration; @@ -56,6 +58,7 @@ import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.NoSuchElementException; import java.util.Objects; import java.util.Optional; import java.util.Set; @@ -238,6 +241,27 @@ public class JlinkTask { } public static final String OPTIONS_RESOURCE = "jdk/tools/jlink/internal/options"; + // Release information stored in the java.base module + private static final String JDK_RELEASE_RESOURCE = "jdk/internal/misc/resources/release.txt"; + + /** + * Read the release.txt from the module. + */ + private static Optional getReleaseInfo(ModuleReference mref) { + try { + Optional release = mref.open().open(JDK_RELEASE_RESOURCE); + + if (release.isEmpty()) { + return Optional.empty(); + } + + try (var r = new BufferedReader(new InputStreamReader(release.get()))) { + return Optional.of(r.readLine()); + } + } catch (IOException ioe) { + throw new UncheckedIOException(ioe); + } + } int run(String[] args) { if (log == null) { @@ -410,7 +434,8 @@ public class JlinkTask { // Sanity check version if we use JMODs if (!isLinkFromRuntime) { - checkJavaBaseVersion(finder); + assert(finder.find("java.base").isPresent()); + checkJavaBaseVersion(finder.find("java.base").get()); } // Determine the roots set @@ -561,32 +586,34 @@ public class JlinkTask { return finder; } + private static String getCurrentRuntimeVersion() { + ModuleReference current = ModuleLayer.boot() + .configuration() + .findModule("java.base") + .get() + .reference(); + // This jlink runtime should always have the release.txt + return getReleaseInfo(current).get(); + } + /* - * Checks the version of the module descriptor of java.base for compatibility - * with the current runtime version. + * Checks the release information of the java.base used for target image + * for compatibility with the java.base used by jlink. * - * @throws IllegalArgumentException the descriptor of java.base has no - * version or the java.base version is not the same as the current runtime's - * version. + * @throws IllegalArgumentException If the `java.base` module reference `target` + * is not compatible with this jlink. */ - private static void checkJavaBaseVersion(ModuleFinder finder) { - assert finder.find("java.base").isPresent(); + private static void checkJavaBaseVersion(ModuleReference target) { + String currentRelease = getCurrentRuntimeVersion(); - // use the version of java.base module, if present, as - // the release version for multi-release JAR files - ModuleDescriptor.Version v = finder.find("java.base").get() - .descriptor().version().orElseThrow(() -> - new IllegalArgumentException("No version in java.base descriptor") - ); + String targetRelease = getReleaseInfo(target).orElseThrow(() -> new IllegalArgumentException( + taskHelper.getMessage("err.jlink.version.missing", currentRelease))); - Runtime.Version version = Runtime.Version.parse(v.toString()); - if (Runtime.version().feature() != version.feature() || - Runtime.version().interim() != version.interim()) { - // jlink version and java.base version do not match. - // We do not (yet) support this mode. + if (!currentRelease.equals(targetRelease)) { + // Current runtime image and the target runtime image are not compatible build throw new IllegalArgumentException(taskHelper.getMessage("err.jlink.version.mismatch", - Runtime.version().feature(), Runtime.version().interim(), - version.feature(), version.interim())); + currentRelease, + targetRelease)); } } diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/Snippets.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/Snippets.java index d662e297f9a..c5bfeb08d33 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/Snippets.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/Snippets.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/TaskHelper.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/TaskHelper.java index d53589dc388..dca4d57f764 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/TaskHelper.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/TaskHelper.java @@ -362,10 +362,13 @@ public final class TaskHelper { if (plugin instanceof DefaultCompressPlugin) { plugOption - = new PluginOption(false, + = new PluginOption(true, (task, opt, arg) -> { Map m = addArgumentMap(plugin); - m.put(plugin.getName(), DefaultCompressPlugin.LEVEL_2); + String level = (arg != null && !arg.isEmpty()) + ? arg + :"zip-6"; + m.put(plugin.getName(), level); }, false, "--compress", "-c"); mainOptions.add(plugOption); } else if (plugin instanceof DefaultStripDebugPlugin) { diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/ModuleDescriptorBuilder.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/ModuleDescriptorBuilder.java index 87f4ddcdbc6..2fdb37410ae 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/ModuleDescriptorBuilder.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/ModuleDescriptorBuilder.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 diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/ReleaseInfoPlugin.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/ReleaseInfoPlugin.java index b4301ea8e45..25789b4d172 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/ReleaseInfoPlugin.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/ReleaseInfoPlugin.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 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 @@ -25,11 +25,13 @@ package jdk.tools.jlink.internal.plugins; import java.io.ByteArrayOutputStream; -import java.io.FileInputStream; import java.io.IOException; import java.io.PrintWriter; +import java.io.Reader; import java.io.UncheckedIOException; import java.lang.module.ModuleDescriptor; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.EnumSet; import java.util.HashMap; import java.util.Map; @@ -107,8 +109,8 @@ public final class ReleaseInfoPlugin extends AbstractPlugin { default: { // --release-info Properties props = new Properties(); - try (FileInputStream fis = new FileInputStream(operation)) { - props.load(fis); + try (Reader reader = Files.newBufferedReader(Path.of(operation))) { + props.load(reader); // Use reader API so as to read in as UTF-8 } catch (IOException exp) { throw new UncheckedIOException(exp); } diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/StripJavaDebugAttributesPlugin.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/StripJavaDebugAttributesPlugin.java index 4a6b54a3b6c..72b20cd2d43 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/StripJavaDebugAttributesPlugin.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/StripJavaDebugAttributesPlugin.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 diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/SystemModulesPlugin.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/SystemModulesPlugin.java index 1c126d7321c..533c216c735 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/SystemModulesPlugin.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/SystemModulesPlugin.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 diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink.properties b/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink.properties index 080d51506a6..374ed78f608 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink.properties +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink.properties @@ -130,7 +130,9 @@ err.runtime.link.patched.module=jlink does not support linking from the run-time err.no.module.path=--module-path option must be specified with --add-modules ALL-MODULE-PATH err.empty.module.path=No module found in module path ''{0}'' with --add-modules ALL-MODULE-PATH err.limit.modules=--limit-modules not allowed with --add-modules ALL-MODULE-PATH -err.jlink.version.mismatch=jlink version {0}.{1} does not match target java.base version {2}.{3} +err.jlink.version.mismatch=jlink build ''{0}'' does not match target java.base build ''{1}'' +err.jlink.version.missing=jlink build ''{0}'' cannot find the build signature\ +\ in the java.base specified on module path, likely from an earlier build. err.automatic.module:automatic module cannot be used with jlink: {0} from {1} err.unknown.byte.order:unknown byte order {0} err.launcher.main.class.empty:launcher main class name cannot be empty: {0} diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink_de.properties b/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink_de.properties index 9b776745c66..40022a5532b 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink_de.properties +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink_de.properties @@ -78,7 +78,8 @@ err.runtime.link.patched.module=jlink unterstützt keine Verknüpfung vom Laufze err.no.module.path=Option --module-path muss mit --add-modules ALL-MODULE-PATH angegeben werden err.empty.module.path=Kein Modul im Modulpfad "{0}" mit --add-modules ALL-MODULE-PATH gefunden err.limit.modules=--limit-modules nicht mit --add-modules ALL-MODULE-PATH zulässig -err.jlink.version.mismatch=jlink-Version {0}.{1} stimmt nicht mit Ziel-java.base-Version {2}.{3} überein +err.jlink.version.mismatch=jlink-Build "{0}" stimmt nicht mit dem java.base-Ziel-Build "{1}" überein +err.jlink.version.missing=jlink-Build "{0}" kann die Build-Signatur nicht im Modul java.base finden, das im Modulpfad angegebenen wird. Wahrscheinlich stammt es aus einem früheren Build. err.automatic.module:automatisches Modul kann nicht mit jlink verwendet werden: {0} aus {1} err.unknown.byte.order:unbekannte Bytereihenfolge {0} err.launcher.main.class.empty:Launcher-Hauptklassenname darf nicht leer sein: {0} diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink_ja.properties b/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink_ja.properties index c925f250c41..9519a24c96e 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink_ja.properties +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink_ja.properties @@ -78,7 +78,8 @@ err.runtime.link.patched.module=--patch-moduleを使用してパッチ済ラン err.no.module.path=--module-pathオプションは--add-modules ALL-MODULE-PATHで指定する必要があります err.empty.module.path=モジュール・パス''{0}''に--add-modules ALL-MODULE-PATHを使用したモジュールが見つかりません err.limit.modules=--limit-modulesは--add-modules ALL-MODULE-PATHとともに指定できません -err.jlink.version.mismatch=jlinkバージョン{0}.{1}がターゲットのjava.baseバージョン{2}.{3}と一致しません +err.jlink.version.mismatch=jlinkビルド''{0}''がターゲットのjava.baseビルド''{1}''と一致しません +err.jlink.version.missing=jlinkビルド''{0}''では、モジュール・パスで指定されたjava.baseにビルド署名が見つかりません(おそらく以前のビルドから)。 err.automatic.module:jlinkでは自動モジュールは使用できません: {1}からの{0} err.unknown.byte.order:不明なバイト順{0} err.launcher.main.class.empty:起動ツールのメイン・クラス名は空にできません: {0} diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink_zh_CN.properties b/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink_zh_CN.properties index b7526c9f57a..81af170ae7f 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink_zh_CN.properties +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink_zh_CN.properties @@ -78,7 +78,8 @@ err.runtime.link.patched.module=当使用 --patch-module 在打补丁的运行 err.no.module.path=--module-path 选项必须与 --add-modules ALL-MODULE-PATH 一起指定 err.empty.module.path=在随 --add-modules ALL-MODULE-PATH 提供的模块路径 ''{0}'' 中找不到模块 err.limit.modules=不允许将 --limit-modules 与 --add-modules ALL-MODULE-PATH 一起使用 -err.jlink.version.mismatch=jlink 版本 {0}.{1} 与目标 java.base 版本 {2}.{3} 不匹配 +err.jlink.version.mismatch=jlink 工作版本 ''{0}'' 与目标 java.base 工作版本 ''{1}'' 不匹配 +err.jlink.version.missing=jlink 工作版本 ''{0}'' 在模块路径中指定的 java.base 中找不到工作版本签名,可能来自早期工作版本。 err.automatic.module:自动模块不能用于来自 {1} 的 jlink: {0} err.unknown.byte.order:未知的字节顺序 {0} err.launcher.main.class.empty:启动程序主类名不能为空: {0} diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins.properties b/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins.properties index a4b780a15c3..7e3c26fa7b8 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins.properties +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -39,6 +39,7 @@ release-info.argument=|add:=:=:...|del: option is to load release properties from the supplied file.\n\ +\ The specified file is expected to be encoded in UTF-8.\n\ add: is to add properties to the 'release' file.\n\ Any number of = pairs can be passed.\n\ del: is to delete the list of keys in release file. @@ -46,7 +47,8 @@ del: is to delete the list of keys in release file. release-info.usage=\ \ --release-info |add:=:=:...|del:\n\ \ option is to load release properties from\n\ -\ the supplied file.\n\ +\ the supplied file. The specified file is expected\n\ +\ to be encoded in UTF-8.\n\ \ add: is to add properties to the 'release' file.\n\ \ Any number of = pairs can be passed.\n\ \ del: is to delete the list of keys in release file. @@ -59,14 +61,14 @@ Class optimization: convert Class.forName calls to constant loads. class-for-name.usage=\ \ --class-for-name Class optimization: convert Class.forName calls to constant loads. -compress.argument=[:filter=] +compress.argument=[:filter=] compress.description= Compression to use in compressing resources. compress.usage=\ \ --compress Compression to use in compressing resources:\n\ \ Accepted values are:\n\ -\ zip-[0-9], where zip-0 provides no compression,\n\ +\ zip-'{0-9}', where zip-0 provides no compression,\n\ \ and zip-9 provides the best compression.\n\ \ Default is zip-6. @@ -305,15 +307,15 @@ plugin.opt.disable-plugin=\ \ --disable-plugin Disable the plugin mentioned plugin.opt.compress=\ -\ --compress Compression to use in compressing resources:\n\ +\ --compress Compress all resources in the output image:\n\ \ Accepted values are:\n\ -\ zip-[0-9], where zip-0 provides no compression,\n\ +\ zip-'{0-9}', where zip-0 provides no compression,\n\ \ and zip-9 provides the best compression.\n\ \ Default is zip-6.\n\ \ Deprecated values to be removed in a future release:\n\ -\ 0: No compression. Equivalent to zip-0.\n\ +\ 0: No compression. Use zip-0 instead.\n\ \ 1: Constant String Sharing\n\ -\ 2: Equivalent to zip-6. +\ 2: ZIP. Use zip-6 instead. plugin.opt.strip-debug=\ \ -G, --strip-debug Strip debug information diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins_de.properties b/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins_de.properties index 4713eabed85..80d1ba6e05f 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins_de.properties +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins_de.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -31,9 +31,9 @@ add-options.usage=\ --add-options Stellt die angegebene release-info.argument=|add:=:=:...|del: -release-info.description=-Option lädt Releaseeigenschaften aus der angegebenen Datei.\nadd: fügt der Datei "release" Eigenschaften hinzu.\nEine beliebige Anzahl von =-Paaren kann übergeben werden.\ndel: löscht die Liste der Schlüssel in der Releasedatei. +release-info.description=Option lädt Releaseeigenschaften aus der angegebenen Datei.\n Die angegebene Datei soll erwartungsgemäß in UTF-8 codiert sein.\nadd: fügt der Datei "release" Eigenschaften hinzu.\nEine beliebige Anzahl von =-Paaren kann übergeben werden.\ndel: löscht die Liste der Schlüssel in der Releasedatei. -release-info.usage=\ --release-info |add:=:=:...|del:\n Option löscht Releaseeigenschaften aus\n der angegebenen Datei.\n add: fügt Eigenschaften der Datei "release" hinzu.\n Eine beliebige Anzahl =-Paare kann übergeben werden.\n del: löscht die Liste der Schlüssel in der Releasedatei. +release-info.usage=\ --release-info |add:=:=:...|del:\n Option lädt Releaseeigenschaften aus\n der angegebenen Datei. Die angegebene Datei soll erwartungsgemäß\n in UTF-8 codiert sein.\n add: fügt der Datei "release" Eigenschaften hinzu.\n Eine beliebige Anzahl =-Paare kann übergeben werden.\n del: löscht die Liste der Schlüssel in der Releasedatei. class-for-name.argument= @@ -41,11 +41,11 @@ class-for-name.description=Klassenoptimierung: Konvertiert Class.forName-Aufrufe class-for-name.usage=\ --class-for-name Klassenoptimierung: Konvertiert Class.forName-Aufrufe in Konstantenladevorgänge. -compress.argument=[:filter=] +compress.argument=[:filter=] compress.description= Zu verwendende Komprimierung für Ressourcen. -compress.usage=\ --compress Zu verwendende Komprimierung für Ressourcen:\n Zulässige Werte:\n zip-[0-9], wobei "zip-0" für keine Komprimierung\n und "zip-9" für die beste Komprimierung steht.\n Standardwert ist "zip-6". +compress.usage=\ --compress Zu verwendende Komprimierung für Ressourcen:\n Zulässige Werte:\n zip-'{0-9}', wobei "zip-0" für keine Komprimierung\n und "zip-9" für die beste Komprimierung steht.\n Standardwert ist "zip-6". compress.warn.argumentdeprecated=Warnung: Das Argument {0} für --compress ist veraltet und wird möglicherweise in einem zukünftigen Release entfernt @@ -170,7 +170,7 @@ plugin.opt.resources-last-sorter=\ --resources-last-sorter Das le plugin.opt.disable-plugin=\ --disable-plugin Deaktiviert das angegebene Plug-in -plugin.opt.compress=\ --compress Zu verwendende Komprimierung für Ressourcen:\n Zulässige Werte:\n zip-[0-9], wobei "zip-0" für keine Komprimierung\n und "zip-9" für die beste Komprimierung steht.\n Standardwert ist "zip-6".\n Veraltete Werte, die in einem zukünftigen Release entfernt werden:\n 0: Keine Komprimierung. Entspricht "zip-0".\n 1: Gemeinsame Verwendung konstanter Zeichenfolgen\n 2: Entspricht "zip-6". +plugin.opt.compress=\ --compress Komprimiert alle Ressourcen im Ausgabeimage:\n Zulässige Werte:\n zip-'{0-9}', wobei "zip-0" für keine Komprimierung\n und "zip-9" für die beste Komprimierung steht.\n Standardwert ist "zip-6."\n Veraltete Werte, die in einem zukünftigen Release entfernt werden:\n 0: Keine Komprimierung. Verwenden Sie stattdessen "zip-0".\n 1: Gemeinsame Verwendung konstanter Zeichenfolgen\n 2: ZIP. Verwenden Sie stattdessen "zip-6". plugin.opt.strip-debug=\ -G, --strip-debug Entfernt Debuginformationen diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins_ja.properties b/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins_ja.properties index 1cf3ba5b0c0..6ed0a486132 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins_ja.properties +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins_ja.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -31,9 +31,9 @@ add-options.usage=\ --add-options 指定した文字列を release-info.argument=|add:=:=:...|del: -release-info.description=オプションは指定されたファイルからリリース・プロパティをロードします。\nadd:はリリース・ファイルにプロパティを追加します。\n任意の数の=のペアを渡すことができます。\ndel:はリリース・ファイルのキーのリストを削除します。 +release-info.description=オプションは指定されたファイルからリリース・プロパティをロードします。\n 指定されたファイルはUTF-8でエンコードされる必要があります。\nadd:はリリース・ファイルにプロパティを追加します。\n任意の数の=のペアを渡すことができます。\ndel:はリリース・ファイルのキーのリストを削除します。 -release-info.usage=\ --release-info |add:=:=:...|del:\n オプションは指定されたファイルからリリース・プロパティを\n ロードします。\n add:はリリース・ファイルにプロパティを追加します。\n 任意の数の=ペアを渡すことができます。\n del:はリリース・ファイルのキーのリストを削除します。 +release-info.usage=\ --release-info |add:=:=:...|del:\n オプションは指定されたファイルからリリース・プロパティを\n ロードします。指定されたファイルはUTF-8で\n エンコードされる必要があります。\n add:はリリース・ファイルにプロパティを追加します。\n 任意の数の=ペアを渡すことができます。\n del:はリリース・ファイルのキーのリストを削除します。 class-for-name.argument= @@ -41,11 +41,11 @@ class-for-name.description=クラスの最適化: Class.forName呼出しを定 class-for-name.usage=\ --class-for-name クラスの最適化: Class.forName呼出しを定数のロードに変換します。 -compress.argument=[:filter=] +compress.argument=[:filter=] compress.description= リソースの圧縮に使用する圧縮。 -compress.usage=\ --compress リソースの圧縮に使用する圧縮:\n 使用可能な値は\n zip-[0-9]です。zip-0では圧縮は行われず、\n zip-9では最適な圧縮が行われます。\n デフォルトはzip-6です。 +compress.usage=\ --compress リソースの圧縮に使用する圧縮:\n 使用可能な値は\n zip-'{0-9}'です。zip-0では圧縮は行われず、\n zip-9では最適な圧縮が行われます。\n デフォルトはzip-6です。 compress.warn.argumentdeprecated=警告: --compressの{0}引数は非推奨であり、今後のリリースで削除される可能性があります @@ -170,7 +170,7 @@ plugin.opt.resources-last-sorter=\ --resources-last-sorter 最後 plugin.opt.disable-plugin=\ --disable-plugin 指定したプラグインを無効にします -plugin.opt.compress=\ --compress リソースの圧縮に使用する圧縮:\n 使用可能な値は\n zip-[0-9]です。zip-0では圧縮は行われず、\n zip-9では最適な圧縮が行われます。\n デフォルトはzip-6です。\n 今後のリリースで削除される非推奨の値:\n 0: 圧縮なし。zip-0と同等。\n 1: 定数文字列の共有\n 2: zip-6と同等。 +plugin.opt.compress=\ --compress 出力イメージ内のすべてのリソースを圧縮します:\n 使用可能な値は\n zip-'{0-9}'です。zip-0では圧縮は行われず、\n zip-9では最適な圧縮が行われます。\n デフォルトはzip-6です。\n 今後のリリースで削除される非推奨の値:\n 0: 圧縮なし。かわりにzip-0を使用。\n 1: 定数文字列の共有\n 2: ZIP。かわりにzip-6を使用。 plugin.opt.strip-debug=\ -G, --strip-debug デバッグ情報を削除します diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins_zh_CN.properties b/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins_zh_CN.properties index 819238e7d04..a0480a31fc3 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins_zh_CN.properties +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins_zh_CN.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -31,9 +31,9 @@ add-options.usage=\ --add-options 在生成的映像中调用虚拟 release-info.argument=|add:=:=:...|del: -release-info.description= 选项:从提供的文件加载 release 属性。\nadd:向 'release' 文件中添加属性。\n可以传递任意数量的 = 对。\ndel:删除 release 文件中的关键字列表。 +release-info.description= 选项:从提供的文件加载 release 属性。\n 指定的文件应采用 UTF-8 编码。\nadd:向 'release' 文件中添加属性。\n可以传递任意数量的 = 对。\ndel:删除 release 文件中的关键字列表。 -release-info.usage=\ --release-info |add:=:=:...|del:\n 选项:从提供的文件\n 加载 release 属性。\n add:向 'release' 文件中添加属性。\n 可以传递任意数量的 = 对。\n del:删除 release 文件中的关键字列表。 +release-info.usage=\ --release-info |add:=:=:...|del:\n 选项:从提供的文件\n 加载 release 属性。指定的文件\n 应采用 UTF-8 编码。\n add:向 'release' 文件中添加属性。\n 可以传递任意数量的 = 对。\n del:删除 release 文件中的关键字列表。 class-for-name.argument= @@ -41,11 +41,11 @@ class-for-name.description=类优化:将 Class.forName 调用转换为常量 class-for-name.usage=\ --class-for-name 类优化:将 Class.forName 调用转换为常量负载。 -compress.argument=[:filter=] +compress.argument=[:filter=] compress.description= 要在压缩资源时使用的压缩。 -compress.usage=\ --compress 要在压缩资源时使用的压缩:\n 接受的值为:\n zip-[0-9],其中 zip-0 表示无压缩,\n zip-9 表示最佳压缩。\n 默认值为 zip-6。 +compress.usage=\ --compress 要在压缩资源时使用的压缩:\n 接受的值为:\n zip-'{0-9}',其中 zip-0 表示无压缩,\n zip-9 表示最佳压缩。\n 默认值为 zip-6。 compress.warn.argumentdeprecated=警告:--compress 的 {0} 参数已过时,可能会在未来发行版中删除 @@ -170,7 +170,7 @@ plugin.opt.resources-last-sorter=\ --resources-last-sorter 允许 plugin.opt.disable-plugin=\ --disable-plugin 禁用所提及的插件 -plugin.opt.compress=\ --compress 要在压缩资源时使用的压缩:\n 接受的值为:\n zip-[0-9],其中 zip-0 表示无压缩,\n zip-9 表示最佳压缩。\n 默认值为 zip-6。\n 要在未来发行版中删除的已过时值:\n 0:无压缩。等同于 zip-0。\n 1:常量字符串共享\n 2:等同于 zip-6。 +plugin.opt.compress=\ --compress 在输出映像中压缩所有资源:\n 接受的值包括:\n zip-'{0-9}',其中 zip-0 表示无压缩,\n zip-9 表示最佳压缩。\n 默认值为 zip-6。\n 要在未来发行版中删除的已过时值:\n 0:无压缩。改为使用 zip-0。\n 1:常量字符串共享\n 2:ZIP。改为使用 zip-6。 plugin.opt.strip-debug=\ -G, --strip-debug 去除调试信息 diff --git a/src/jdk.jlink/share/classes/jdk/tools/jmod/JmodTask.java b/src/jdk.jlink/share/classes/jdk/tools/jmod/JmodTask.java index 5345ee4253c..d4d7171d799 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jmod/JmodTask.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jmod/JmodTask.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.jlink/share/classes/jdk/tools/jmod/resources/jmod_de.properties b/src/jdk.jlink/share/classes/jdk/tools/jmod/resources/jmod_de.properties index 126e04d0b58..386fd199d86 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jmod/resources/jmod_de.properties +++ b/src/jdk.jlink/share/classes/jdk/tools/jmod/resources/jmod_de.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2015, 2022, 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 diff --git a/src/jdk.jlink/share/man/jlink.md b/src/jdk.jlink/share/man/jlink.md index dc256af43b5..5c77202434c 100644 --- a/src/jdk.jlink/share/man/jlink.md +++ b/src/jdk.jlink/share/man/jlink.md @@ -1,5 +1,5 @@ --- -# Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2017, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -64,12 +64,16 @@ Developers are responsible for updating their custom runtime images. `--bind-services` : Link service provider modules and their dependencies. -`-c ={0|1|2}` or `--compress={0|1|2}` -: Enable compression of resources: +`-c zip-{0-9}` or `--compress=zip-{0-9}` +: Enable compression of resources. The accepted values are: + zip-{0-9}, where zip-0 provides no compression, + and zip-9 provides the best compression. Default is zip-6. - - `0`: No compression +: Deprecated values to be removed in a future release: + + - `0`: No compression. Use zip-0 instead. - `1`: Constant string sharing - - `2`: ZIP + - `2`: ZIP. Use zip-6 instead. `--disable-plugin` *pluginname* : Disables the specified plug-in. See [jlink Plug-ins] for the list of @@ -170,14 +174,19 @@ For a complete list of all available plug-ins, run the command ### Plugin `compress` Options -: `--compress=`{`0`\|`1`\|`2`}\[`:filter=`*pattern-list*\] +: `--compress=zip-`{`0`-`9`}\[`:filter=`*pattern-list*\] Description : Compresses all resources in the output image. + Accepted values are: + zip-{0-9}, where zip-0 provides no compression, + and zip-9 provides the best compression. Default is zip-6. - - Level 0: No compression +: Deprecated values to be removed in a future release: + + - Level 0: No compression. Use zip-0 instead. - Level 1: Constant string sharing - - Level 2: ZIP + - Level 2: ZIP. Use zip-6 instead. An optional *pattern-list* filter can be specified to list the pattern of files to include. 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 dbaa5e3eec6..523b6c4821c 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/DesktopIntegration.java +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/DesktopIntegration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -50,6 +50,7 @@ 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.Enquoter; import jdk.jpackage.internal.util.PathUtils; import jdk.jpackage.internal.util.XmlUtils; diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LibProvidersLookup.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LibProvidersLookup.java index 55200b908cd..6faacbca528 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LibProvidersLookup.java +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LibProvidersLookup.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,13 +27,12 @@ package jdk.jpackage.internal; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.List; import java.util.Collection; -import java.util.Objects; import java.util.Collections; -import java.util.Set; -import java.util.ArrayList; import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.Set; import java.util.function.Predicate; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -48,9 +47,6 @@ public final class LibProvidersLookup { return (new ToolValidator(TOOL_LDD).validate() == null); } - public LibProvidersLookup() { - } - LibProvidersLookup setPackageLookup(PackageLookup v) { packageLookup = v; return this; @@ -87,23 +83,20 @@ public final class LibProvidersLookup { } private static List getNeededLibsForFile(Path path) throws IOException { - List result = new ArrayList<>(); - int ret = Executor.of(TOOL_LDD, path.toString()).setOutputConsumer(lines -> { - lines.map(line -> { - Matcher matcher = LIB_IN_LDD_OUTPUT_REGEX.matcher(line); - if (matcher.find()) { - return matcher.group(1); - } - return null; - }).filter(Objects::nonNull).map(Path::of).forEach(result::add); - }).execute(); + final var result = Executor.of(TOOL_LDD, path.toString()).saveOutput().execute(); - if (ret != 0) { + if (result.getExitCode() != 0) { // objdump failed. This is OK if the tool was applied to not a binary file return Collections.emptyList(); } - return result; + return result.stdout().stream().map(line -> { + Matcher matcher = LIB_IN_LDD_OUTPUT_REGEX.matcher(line); + if (matcher.find()) { + return matcher.group(1); + } + return null; + }).filter(Objects::nonNull).map(Path::of).toList(); } private static Collection getNeededLibsForFiles(List paths) { diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxBundlingEnvironment.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxBundlingEnvironment.java new file mode 100644 index 00000000000..d2169ede461 --- /dev/null +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxBundlingEnvironment.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * 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; + +import static java.util.stream.Collectors.toMap; +import static jdk.jpackage.internal.LinuxFromOptions.createLinuxApplication; +import static jdk.jpackage.internal.LinuxPackagingPipeline.APPLICATION_LAYOUT; +import static jdk.jpackage.internal.cli.StandardBundlingOperation.CREATE_LINUX_APP_IMAGE; +import static jdk.jpackage.internal.cli.StandardBundlingOperation.CREATE_LINUX_DEB; +import static jdk.jpackage.internal.cli.StandardBundlingOperation.CREATE_LINUX_RPM; + +import java.util.Map; +import java.util.Optional; +import java.util.function.Supplier; +import java.util.stream.Stream; +import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.cli.StandardBundlingOperation; +import jdk.jpackage.internal.model.BundlingOperationDescriptor; +import jdk.jpackage.internal.model.LinuxPackage; +import jdk.jpackage.internal.model.PackageType; +import jdk.jpackage.internal.util.Result; + +public class LinuxBundlingEnvironment extends DefaultBundlingEnvironment { + + public LinuxBundlingEnvironment() { + super(build().mutate(builder -> { + + // Wrap the generic Linux system environment supplier in the run-once wrapper + // as this supplier is called from both RPM and DEB Linux system environment suppliers. + var sysEnv = runOnce(() -> { + return LinuxSystemEnvironment.create(); + }); + + Supplier> debSysEnv = () -> { + return LinuxDebSystemEnvironment.create(sysEnv.get()); + }; + + Supplier> rpmSysEnv = () -> { + return LinuxRpmSystemEnvironment.create(sysEnv.get()); + }; + + builder.defaultOperation(() -> { + return sysEnv.get().value().map(LinuxSystemEnvironment::nativePackageType).map(DESCRIPTORS::get); + }) + .bundler(CREATE_LINUX_DEB, debSysEnv, LinuxBundlingEnvironment::createDebPackage) + .bundler(CREATE_LINUX_RPM, rpmSysEnv, LinuxBundlingEnvironment::createRpmPackage); + }).bundler(CREATE_LINUX_APP_IMAGE, LinuxBundlingEnvironment::createAppImage)); + } + + private static void createDebPackage(Options options, LinuxDebSystemEnvironment sysEnv) { + + createNativePackage(options, + LinuxFromOptions.createLinuxDebPackage(options, sysEnv), + buildEnv()::create, + LinuxBundlingEnvironment::buildPipeline, + (env, pkg, outputDir) -> { + return new LinuxDebPackager(env, pkg, outputDir, sysEnv); + }); + } + + private static void createRpmPackage(Options options, LinuxRpmSystemEnvironment sysEnv) { + + createNativePackage(options, + LinuxFromOptions.createLinuxRpmPackage(options, sysEnv), + buildEnv()::create, + LinuxBundlingEnvironment::buildPipeline, + (env, pkg, outputDir) -> { + return new LinuxRpmPackager(env, pkg, outputDir, sysEnv); + }); + } + + private static void createAppImage(Options options) { + + final var app = createLinuxApplication(options); + + createApplicationImage(options, app, LinuxPackagingPipeline.build(Optional.empty())); + } + + private static PackagingPipeline.Builder buildPipeline(LinuxPackage pkg) { + return LinuxPackagingPipeline.build(Optional.of(pkg)); + } + + private static BuildEnvFromOptions buildEnv() { + return new BuildEnvFromOptions().predefinedAppImageLayout(APPLICATION_LAYOUT); + } + + private static final Map DESCRIPTORS = Stream.of( + CREATE_LINUX_DEB, + CREATE_LINUX_RPM + ).collect(toMap(StandardBundlingOperation::packageType, StandardBundlingOperation::descriptor)); +} diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebBundler.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebBundler.java deleted file mode 100644 index 76a08519b48..00000000000 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebBundler.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. - * 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; - -import java.nio.file.Path; -import java.util.Map; -import java.util.Optional; -import jdk.jpackage.internal.model.LinuxDebPackage; -import jdk.jpackage.internal.model.PackagerException; -import jdk.jpackage.internal.model.StandardPackageType; -import jdk.jpackage.internal.util.Result; - -public class LinuxDebBundler extends LinuxPackageBundler { - - public LinuxDebBundler() { - super(LinuxFromParams.DEB_PACKAGE); - } - - @Override - public String getName() { - return I18N.getString("deb.bundler.name"); - } - - @Override - public String getID() { - return "deb"; - } - - @Override - public Path execute(Map params, Path outputParentDir) throws PackagerException { - - var pkg = LinuxFromParams.DEB_PACKAGE.fetchFrom(params); - - return Packager.build().outputDir(outputParentDir) - .pkg(pkg) - .env(BuildEnvFromParams.BUILD_ENV.fetchFrom(params)) - .pipelineBuilderMutatorFactory((env, _, outputDir) -> { - return new LinuxDebPackager(env, pkg, outputDir, sysEnv.orElseThrow()); - }).execute(LinuxPackagingPipeline.build(Optional.of(pkg))); - } - - @Override - protected Result sysEnv() { - return sysEnv; - } - - @Override - public boolean isDefault() { - return sysEnv.value() - .map(LinuxSystemEnvironment::nativePackageType) - .map(StandardPackageType.LINUX_DEB::equals) - .orElse(false); - } - - private final Result sysEnv = LinuxDebSystemEnvironment.create(SYS_ENV); -} diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebPackageBuilder.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebPackageBuilder.java index 50e371d5c76..d7b6559abe1 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebPackageBuilder.java +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebPackageBuilder.java @@ -26,7 +26,6 @@ package jdk.jpackage.internal; import java.util.Objects; import java.util.Optional; -import jdk.jpackage.internal.model.ConfigException; import jdk.jpackage.internal.model.LinuxDebPackage; import jdk.jpackage.internal.model.LinuxDebPackageMixin; @@ -36,7 +35,7 @@ final class LinuxDebPackageBuilder { this.pkgBuilder = Objects.requireNonNull(pkgBuilder); } - LinuxDebPackage create() throws ConfigException { + LinuxDebPackage create() { if (pkgBuilder.category().isEmpty()) { pkgBuilder.category(DEFAULTS.category()); } diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebPackager.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebPackager.java index 64a0368e9a0..0ec6a77e683 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebPackager.java +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebPackager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,7 +25,6 @@ package jdk.jpackage.internal; -import static jdk.jpackage.internal.model.StandardPackageType.LINUX_DEB; import static jdk.jpackage.internal.util.function.ThrowingFunction.toFunction; import java.io.IOException; @@ -76,11 +75,11 @@ final class LinuxDebPackager extends LinuxPackager { try { // Try the real path first as it works better on newer Ubuntu versions - return findProvidingPackages(realPath, sysEnv.dpkg()); + return findProvidingPackages(realPath, sysEnv); } catch (IOException ex) { // Try the default path if differ if (!realPath.equals(file)) { - return findProvidingPackages(file, sysEnv.dpkg()); + return findProvidingPackages(file, sysEnv); } else { throw ex; } @@ -107,7 +106,7 @@ final class LinuxDebPackager extends LinuxPackager { properties.forEach(property -> cmdline.add(property.name)); - Map actualValues = Executor.of(cmdline.toArray(String[]::new)) + Map actualValues = Executor.of(cmdline) .saveOutput(true) .executeExpectSuccess() .getOutput().stream() @@ -158,9 +157,8 @@ final class LinuxDebPackager extends LinuxPackager { cmdline.addAll(List.of("-b", env.appImageDir().toString(), debFile.toAbsolutePath().toString())); // run dpkg - RetryExecutor.retryOnKnownErrorMessage( - "semop(1): encountered an error: Invalid argument").execute( - cmdline.toArray(String[]::new)); + Executor.of(cmdline).retryOnKnownErrorMessage( + "semop(1): encountered an error: Invalid argument").execute(); Log.verbose(I18N.format("message.output-to-location", debFile.toAbsolutePath())); } @@ -233,7 +231,7 @@ final class LinuxDebPackager extends LinuxPackager { } } - private static Stream findProvidingPackages(Path file, Path dpkg) throws IOException { + private static Stream findProvidingPackages(Path file, LinuxDebSystemEnvironment sysEnv) throws IOException { // // `dpkg -S` command does glob pattern lookup. If not the absolute path // to the file is specified it might return mltiple package names. @@ -279,9 +277,9 @@ final class LinuxDebPackager extends LinuxPackager { Set archPackages = new HashSet<>(); Set otherPackages = new HashSet<>(); - var debArch = LinuxPackageArch.getValue(LINUX_DEB); + var debArch = sysEnv.packageArch().value(); - Executor.of(dpkg.toString(), "-S", file.toString()) + Executor.of(sysEnv.dpkg().toString(), "-S", file.toString()) .saveOutput(true).executeExpectSuccess() .getOutput().forEach(line -> { Matcher matcher = PACKAGE_NAME_REGEX.matcher(line); diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebSystemEnvironment.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebSystemEnvironment.java index 5b5decb7a67..d5480361452 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebSystemEnvironment.java +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebSystemEnvironment.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,7 +28,7 @@ import static jdk.jpackage.internal.LinuxSystemEnvironment.mixin; import jdk.jpackage.internal.util.Result; -public interface LinuxDebSystemEnvironment extends LinuxSystemEnvironment, LinuxDebSystemEnvironmentMixin { +interface LinuxDebSystemEnvironment extends LinuxSystemEnvironment, LinuxDebSystemEnvironmentMixin { static Result create(Result base) { return mixin(LinuxDebSystemEnvironment.class, base, LinuxDebSystemEnvironmentMixin::create); diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebSystemEnvironmentMixin.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebSystemEnvironmentMixin.java index 8688327b353..2cf3e9e36e8 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebSystemEnvironmentMixin.java +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebSystemEnvironmentMixin.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,7 +29,7 @@ import java.util.Objects; import java.util.stream.Stream; import jdk.jpackage.internal.util.Result; -public interface LinuxDebSystemEnvironmentMixin { +interface LinuxDebSystemEnvironmentMixin { Path dpkg(); Path dpkgdeb(); Path fakeroot(); diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxFromOptions.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxFromOptions.java new file mode 100644 index 00000000000..0791c79c662 --- /dev/null +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxFromOptions.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * 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; + +import static jdk.jpackage.internal.FromOptions.buildApplicationBuilder; +import static jdk.jpackage.internal.FromOptions.createPackageBuilder; +import static jdk.jpackage.internal.LinuxPackagingPipeline.APPLICATION_LAYOUT; +import static jdk.jpackage.internal.cli.StandardOption.LINUX_APP_CATEGORY; +import static jdk.jpackage.internal.cli.StandardOption.LINUX_DEB_MAINTAINER_EMAIL; +import static jdk.jpackage.internal.cli.StandardOption.LINUX_MENU_GROUP; +import static jdk.jpackage.internal.cli.StandardOption.LINUX_PACKAGE_DEPENDENCIES; +import static jdk.jpackage.internal.cli.StandardOption.LINUX_PACKAGE_NAME; +import static jdk.jpackage.internal.cli.StandardOption.LINUX_RELEASE; +import static jdk.jpackage.internal.cli.StandardOption.LINUX_RPM_LICENSE_TYPE; +import static jdk.jpackage.internal.cli.StandardOption.LINUX_SHORTCUT_HINT; +import static jdk.jpackage.internal.model.StandardPackageType.LINUX_DEB; +import static jdk.jpackage.internal.model.StandardPackageType.LINUX_RPM; + +import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.model.Launcher; +import jdk.jpackage.internal.model.LinuxApplication; +import jdk.jpackage.internal.model.LinuxDebPackage; +import jdk.jpackage.internal.model.LinuxLauncher; +import jdk.jpackage.internal.model.LinuxLauncherMixin; +import jdk.jpackage.internal.model.LinuxRpmPackage; +import jdk.jpackage.internal.model.StandardPackageType; + +final class LinuxFromOptions { + + static LinuxApplication createLinuxApplication(Options options) { + + final var launcherFromOptions = new LauncherFromOptions().faWithDefaultDescription(); + + final var appBuilder = buildApplicationBuilder().create(options, launcherOptions -> { + + final var launcher = launcherFromOptions.create(launcherOptions); + + final var shortcut = LINUX_SHORTCUT_HINT.findIn(launcherOptions); + + return LinuxLauncher.create(launcher, new LinuxLauncherMixin.Stub(shortcut)); + + }, (LinuxLauncher linuxLauncher, Launcher launcher) -> { + return LinuxLauncher.create(launcher, linuxLauncher); + }, APPLICATION_LAYOUT); + + appBuilder.launchers().map(LinuxPackagingPipeline::normalizeShortcuts).ifPresent(appBuilder::launchers); + + return LinuxApplication.create(appBuilder.create()); + } + + static LinuxRpmPackage createLinuxRpmPackage(Options options, LinuxRpmSystemEnvironment sysEnv) { + + final var superPkgBuilder = createLinuxPackageBuilder(options, sysEnv, LINUX_RPM); + + final var pkgBuilder = new LinuxRpmPackageBuilder(superPkgBuilder); + + LINUX_RPM_LICENSE_TYPE.ifPresentIn(options, pkgBuilder::licenseType); + + return pkgBuilder.create(); + } + + static LinuxDebPackage createLinuxDebPackage(Options options, LinuxDebSystemEnvironment sysEnv) { + + final var superPkgBuilder = createLinuxPackageBuilder(options, sysEnv, LINUX_DEB); + + final var pkgBuilder = new LinuxDebPackageBuilder(superPkgBuilder); + + LINUX_DEB_MAINTAINER_EMAIL.ifPresentIn(options, pkgBuilder::maintainerEmail); + + final var pkg = pkgBuilder.create(); + + // Show warning if license file is missing + if (pkg.licenseFile().isEmpty()) { + Log.verbose(I18N.getString("message.debs-like-licenses")); + } + + return pkg; + } + + private static LinuxPackageBuilder createLinuxPackageBuilder(Options options, LinuxSystemEnvironment sysEnv, StandardPackageType type) { + + final var app = createLinuxApplication(options); + + final var superPkgBuilder = createPackageBuilder(options, app, type); + + final var pkgBuilder = new LinuxPackageBuilder(superPkgBuilder); + + pkgBuilder.arch(sysEnv.packageArch()); + + LINUX_PACKAGE_DEPENDENCIES.ifPresentIn(options, pkgBuilder::additionalDependencies); + LINUX_APP_CATEGORY.ifPresentIn(options, pkgBuilder::category); + LINUX_MENU_GROUP.ifPresentIn(options, pkgBuilder::menuGroupName); + LINUX_RELEASE.ifPresentIn(options, pkgBuilder::release); + LINUX_PACKAGE_NAME.ifPresentIn(options, pkgBuilder::literalName); + + return pkgBuilder; + } + +} diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxFromParams.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxFromParams.java deleted file mode 100644 index e9d1416b5c3..00000000000 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxFromParams.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * 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; - -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.FromParams.findLauncherShortcut; -import static jdk.jpackage.internal.LinuxPackagingPipeline.APPLICATION_LAYOUT; -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 jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.model.LinuxApplication; -import jdk.jpackage.internal.model.LinuxDebPackage; -import jdk.jpackage.internal.model.LinuxLauncher; -import jdk.jpackage.internal.model.LinuxLauncherMixin; -import jdk.jpackage.internal.model.LinuxRpmPackage; -import jdk.jpackage.internal.model.Launcher; -import jdk.jpackage.internal.model.StandardPackageType; - -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 = findLauncherShortcut(LINUX_SHORTCUT_HINT, params, launcherParams); - return LinuxLauncher.create(launcher, new LinuxLauncherMixin.Stub(shortcut)); - }), (LinuxLauncher linuxLauncher, Launcher launcher) -> { - return LinuxLauncher.create(launcher, linuxLauncher); - }, APPLICATION_LAYOUT).create(); - return LinuxApplication.create(app); - } - - private static LinuxPackageBuilder createLinuxPackageBuilder( - Map params, StandardPackageType type) throws ConfigException, IOException { - - final var app = APPLICATION.fetchFrom(params); - - final var superPkgBuilder = createPackageBuilder(params, app, type); - - final var pkgBuilder = new LinuxPackageBuilder(superPkgBuilder); - - LINUX_PACKAGE_DEPENDENCIES.copyInto(params, pkgBuilder::additionalDependencies); - LINUX_CATEGORY.copyInto(params, pkgBuilder::category); - LINUX_MENU_GROUP.copyInto(params, pkgBuilder::menuGroupName); - RELEASE.copyInto(params, pkgBuilder::release); - LINUX_PACKAGE_NAME.copyInto(params, pkgBuilder::literalName); - - return pkgBuilder; - } - - private static LinuxRpmPackage createLinuxRpmPackage( - Map params) throws ConfigException, IOException { - - final var superPkgBuilder = createLinuxPackageBuilder(params, LINUX_RPM); - - final var pkgBuilder = new LinuxRpmPackageBuilder(superPkgBuilder); - - LICENSE_TYPE.copyInto(params, pkgBuilder::licenseType); - - return pkgBuilder.create(); - } - - private static LinuxDebPackage createLinuxDebPackage( - Map params) throws ConfigException, IOException { - - final var superPkgBuilder = createLinuxPackageBuilder(params, LINUX_DEB); - - final var pkgBuilder = new LinuxDebPackageBuilder(superPkgBuilder); - - MAINTAINER_EMAIL.copyInto(params, pkgBuilder::maintainerEmail); - - final var pkg = pkgBuilder.create(); - - // Show warning if license file is missing - if (pkg.licenseFile().isEmpty()) { - Log.verbose(I18N.getString("message.debs-like-licenses")); - } - - return pkg; - } - - static final BundlerParamInfo APPLICATION = createApplicationBundlerParam( - LinuxFromParams::createLinuxApplication); - - static final BundlerParamInfo RPM_PACKAGE = createPackageBundlerParam( - LinuxFromParams::createLinuxRpmPackage); - - static final BundlerParamInfo DEB_PACKAGE = createPackageBundlerParam( - LinuxFromParams::createLinuxDebPackage); - - 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()); - - private static final BundlerParamInfo LINUX_PACKAGE_DEPENDENCIES = createStringBundlerParam( - Arguments.CLIOptions.LINUX_PACKAGE_DEPENDENCIES.getId()); - - private static final BundlerParamInfo LINUX_MENU_GROUP = createStringBundlerParam( - Arguments.CLIOptions.LINUX_MENU_GROUP.getId()); - - private static final BundlerParamInfo RELEASE = createStringBundlerParam( - Arguments.CLIOptions.RELEASE.getId()); - - private static final BundlerParamInfo LINUX_PACKAGE_NAME = createStringBundlerParam( - Arguments.CLIOptions.LINUX_BUNDLE_NAME.getId()); - - private static final BundlerParamInfo LICENSE_TYPE = createStringBundlerParam( - Arguments.CLIOptions.LINUX_RPM_LICENSE_TYPE.getId()); - - private static final BundlerParamInfo MAINTAINER_EMAIL = createStringBundlerParam( - Arguments.CLIOptions.LINUX_DEB_MAINTAINER.getId()); -} diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxLaunchersAsServices.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxLaunchersAsServices.java index b14404d67b1..40ff26bcfac 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxLaunchersAsServices.java +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxLaunchersAsServices.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -32,6 +32,7 @@ import java.util.List; import java.util.Map; import jdk.jpackage.internal.model.Launcher; import jdk.jpackage.internal.model.Package; +import jdk.jpackage.internal.util.Enquoter; /** * Helper to install launchers as services using "systemd". diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageArch.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageArch.java index 836d1fb2c37..b1df92ae312 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageArch.java +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageArch.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,18 +25,20 @@ package jdk.jpackage.internal; import java.io.IOException; -import static jdk.jpackage.internal.util.function.ThrowingSupplier.toSupplier; +import java.util.ArrayList; import jdk.jpackage.internal.model.StandardPackageType; +import jdk.jpackage.internal.util.CommandOutputControl; +import jdk.jpackage.internal.util.Result; -final class LinuxPackageArch { +record LinuxPackageArch(String value) { - static String getValue(StandardPackageType pkgType) { + static Result create(StandardPackageType pkgType) { switch (pkgType) { case LINUX_RPM -> { - return RpmPackageArch.VALUE; + return rpm().map(LinuxPackageArch::new); } case LINUX_DEB -> { - return DebPackageArch.VALUE; + return deb().map(LinuxPackageArch::new); } default -> { throw new IllegalArgumentException(); @@ -44,62 +46,51 @@ final class LinuxPackageArch { } } - private static class DebPackageArch { - - static final String VALUE = toSupplier(DebPackageArch::getValue).get(); - - private static String getValue() throws IOException { - return Executor.of("dpkg", "--print-architecture").saveOutput(true) - .executeExpectSuccess().getOutput().get(0); - } + private static Result deb() { + var exec = Executor.of("dpkg", "--print-architecture").saveOutput(true); + return Result.of(exec::executeExpectSuccess, IOException.class) + .flatMap(LinuxPackageArch::getStdoutFirstLine); } - private static class RpmPackageArch { - - /* - * Various ways to get rpm arch. Needed to address JDK-8233143. rpmbuild is mandatory for - * rpm packaging, try it first. rpm is optional and may not be available, use as the last - * resort. - */ - private static enum RpmArchReader { - Rpmbuild("rpmbuild", "--eval=%{_target_cpu}"), - Rpm("rpm", "--eval=%{_target_cpu}"); - - RpmArchReader(String... cmdline) { - this.cmdline = cmdline; + private static Result rpm() { + var errors = new ArrayList(); + for (var tool : RpmArchReader.values()) { + var result = tool.getRpmArch(); + if (result.hasValue()) { + return result; + } else { + errors.addAll(result.errors()); } - - String getRpmArch() throws IOException { - Executor exec = Executor.of(cmdline).saveOutput(true); - switch (this) { - case Rpm -> { - exec.executeExpectSuccess(); - } - case Rpmbuild -> { - if (exec.execute() != 0) { - return null; - } - } - default -> { - throw new UnsupportedOperationException(); - } - } - return exec.getOutput().get(0); - } - - private final String[] cmdline; } - static final String VALUE = toSupplier(RpmPackageArch::getValue).get(); + return Result.ofErrors(errors); + } - private static String getValue() throws IOException { - for (var rpmArchReader : RpmArchReader.values()) { - var rpmArchStr = rpmArchReader.getRpmArch(); - if (rpmArchStr != null) { - return rpmArchStr; - } - } - throw new RuntimeException("error.rpm-arch-not-detected"); + /* + * Various ways to get rpm arch. Needed to address JDK-8233143. rpmbuild is mandatory for + * rpm packaging, try it first. rpm is optional and may not be available, use as the last + * resort. + */ + private enum RpmArchReader { + RPMBUILD("rpmbuild", "--eval=%{_target_cpu}"), + RPM("rpm", "--eval=%{_target_cpu}"); + + RpmArchReader(String... cmdline) { + this.cmdline = cmdline; } + + Result getRpmArch() { + var exec = Executor.of(cmdline).saveOutput(true); + return Result.of(exec::executeExpectSuccess, IOException.class) + .flatMap(LinuxPackageArch::getStdoutFirstLine); + } + + private final String[] cmdline; + } + + private static Result getStdoutFirstLine(CommandOutputControl.Result result) { + return Result.of(() -> { + return result.stdout().stream().findFirst().orElseThrow(result::unexpected); + }, IOException.class); } } 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 4cfb8a26c8f..cd4d674432e 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBuilder.java +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -32,12 +32,11 @@ import java.util.Optional; 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.RuntimeLayout; import jdk.jpackage.internal.model.StandardPackageType; final class LinuxPackageBuilder { @@ -46,20 +45,17 @@ final class LinuxPackageBuilder { this.pkgBuilder = Objects.requireNonNull(pkgBuilder); } - LinuxPackage create() throws ConfigException { - if (literalName != null) { - pkgBuilder.name(literalName); - } else { + LinuxPackage create() { + pkgBuilder.name(Optional.ofNullable(literalName).orElseGet(() -> { // Lower case and turn spaces/underscores into dashes - pkgBuilder.name(pkgBuilder.create().packageName().toLowerCase().replaceAll("[ _]", "-")); - } + return pkgBuilder.create().packageName().toLowerCase().replaceAll("[ _]", "-"); + })); final var tmpPkg = pkgBuilder.create(); - final var stdPkgType = tmpPkg.asStandardPackageType(); - if (stdPkgType.isPresent()) { - validatePackageName(tmpPkg.packageName(), stdPkgType.orElseThrow()); - } + tmpPkg.asStandardPackageType().ifPresent(stdPkgType -> { + validatePackageName(tmpPkg.packageName(), stdPkgType); + }); final AppImageLayout relativeInstalledLayout; if (create(tmpPkg).isInstallDirInUsrTree()) { @@ -81,13 +77,13 @@ final class LinuxPackageBuilder { .create()); } - private LinuxPackage create(Package pkg) throws ConfigException { + private LinuxPackage create(Package pkg) { return LinuxPackage.create(pkg, new LinuxPackageMixin.Stub( Optional.ofNullable(menuGroupName).orElseGet(DEFAULTS::menuGroupName), category(), Optional.ofNullable(additionalDependencies), release(), - pkg.asStandardPackageType().map(LinuxPackageArch::getValue).orElseThrow())); + arch.value())); } LinuxPackageBuilder literalName(String v) { @@ -123,6 +119,11 @@ final class LinuxPackageBuilder { return Optional.ofNullable(release); } + LinuxPackageBuilder arch(LinuxPackageArch v) { + arch = v; + return this; + } + private static LinuxApplicationLayout usrTreePackageLayout(Path prefix, String packageName) { final var lib = prefix.resolve(Path.of("lib", packageName)); return LinuxApplicationLayout.create( @@ -137,8 +138,7 @@ final class LinuxPackageBuilder { lib.resolve("lib/libapplauncher.so")); } - private static void validatePackageName(String packageName, - StandardPackageType pkgType) throws ConfigException { + private static void validatePackageName(String packageName, StandardPackageType pkgType) { switch (pkgType) { case LINUX_DEB -> { // @@ -189,6 +189,7 @@ final class LinuxPackageBuilder { private String category; private String additionalDependencies; private String release; + private LinuxPackageArch arch; private final PackageBuilder pkgBuilder; diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBundler.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBundler.java deleted file mode 100644 index 1f674c0be11..00000000000 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBundler.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * 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. 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; - -import java.util.Map; -import java.util.Objects; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.model.LinuxPackage; -import jdk.jpackage.internal.util.Result; - -abstract class LinuxPackageBundler extends AbstractBundler { - - LinuxPackageBundler(BundlerParamInfo pkgParam) { - this.pkgParam = Objects.requireNonNull(pkgParam); - } - - @Override - public final boolean validate(Map params) - throws ConfigException { - - // Order is important! - pkgParam.fetchFrom(params); - BuildEnvFromParams.BUILD_ENV.fetchFrom(params); - - LinuxSystemEnvironment sysEnv; - try { - sysEnv = sysEnv().orElseThrow(); - } catch (RuntimeException ex) { - throw ConfigException.rethrowConfigException(ex); - } - - if (!isDefault()) { - Log.verbose(I18N.format( - "message.not-default-bundler-no-dependencies-lookup", - getName())); - } else if (!sysEnv.soLookupAvailable()) { - final String advice; - if ("deb".equals(getID())) { - advice = "message.deb-ldd-not-available.advice"; - } else { - advice = "message.rpm-ldd-not-available.advice"; - } - // Let user know package dependencies will not be generated. - Log.error(String.format("%s\n%s", I18N.getString( - "message.ldd-not-available"), I18N.getString(advice))); - } - - return true; - } - - @Override - public final String getBundleType() { - return "INSTALLER"; - } - - @Override - public boolean supported(boolean runtimeInstaller) { - return sysEnv().hasValue(); - } - - protected abstract Result sysEnv(); - - private final BundlerParamInfo pkgParam; - - static final Result SYS_ENV = LinuxSystemEnvironment.create(); -} diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackager.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackager.java index 806592904d1..af7f5288cc5 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackager.java +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackager.java @@ -40,7 +40,6 @@ import jdk.jpackage.internal.PackagingPipeline.PrimaryTaskID; import jdk.jpackage.internal.PackagingPipeline.TaskID; import jdk.jpackage.internal.model.ConfigException; import jdk.jpackage.internal.model.LinuxPackage; -import jdk.jpackage.internal.model.PackagerException; abstract class LinuxPackager implements Consumer { @@ -95,7 +94,7 @@ abstract class LinuxPackager implements Consumer { + // Return "true" if shortcut is not configured for the launcher. + return launcher.shortcut().isEmpty(); + }, (LinuxLauncher launcher) -> { + return launcher.shortcut().flatMap(LauncherShortcut::startupDirectory); + }, (launcher, shortcut) -> { + return LinuxLauncher.create(launcher, new LinuxLauncherMixin.Stub(Optional.of(new LauncherShortcut(shortcut)))); + }); + } + private static void writeLauncherLib( AppImageBuildEnv env) throws IOException { @@ -90,6 +106,15 @@ final class LinuxPackagingPipeline { }); } + private static final ApplicationLayout LINUX_APPLICATION_LAYOUT = ApplicationLayout.build() + .launchersDirectory("bin") + .appDirectory("lib/app") + .runtimeDirectory("lib/runtime") + .desktopIntegrationDirectory("lib") + .appModsDirectory("lib/app/mods") + .contentDirectory("lib") + .create(); + static final LinuxApplicationLayout APPLICATION_LAYOUT = LinuxApplicationLayout.create( - ApplicationLayoutUtils.PLATFORM_APPLICATION_LAYOUT, Path.of("lib/libapplauncher.so")); + LINUX_APPLICATION_LAYOUT, Path.of("lib/libapplauncher.so")); } diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmBundler.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmBundler.java deleted file mode 100644 index c134aa91d6a..00000000000 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmBundler.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. - * 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; - -import java.nio.file.Path; -import java.util.Map; -import java.util.Optional; -import jdk.jpackage.internal.model.LinuxRpmPackage; -import jdk.jpackage.internal.model.PackagerException; -import jdk.jpackage.internal.model.StandardPackageType; -import jdk.jpackage.internal.util.Result; - - -public class LinuxRpmBundler extends LinuxPackageBundler { - - public LinuxRpmBundler() { - super(LinuxFromParams.RPM_PACKAGE); - } - - @Override - public String getName() { - return I18N.getString("rpm.bundler.name"); - } - - @Override - public String getID() { - return "rpm"; - } - - @Override - public Path execute(Map params, Path outputParentDir) throws PackagerException { - - var pkg = LinuxFromParams.RPM_PACKAGE.fetchFrom(params); - - return Packager.build().outputDir(outputParentDir) - .pkg(pkg) - .env(BuildEnvFromParams.BUILD_ENV.fetchFrom(params)) - .pipelineBuilderMutatorFactory((env, _, outputDir) -> { - return new LinuxRpmPackager(env, pkg, outputDir, sysEnv.orElseThrow()); - }).execute(LinuxPackagingPipeline.build(Optional.of(pkg))); - } - - @Override - protected Result sysEnv() { - return sysEnv; - } - - @Override - public boolean isDefault() { - return sysEnv.value() - .map(LinuxSystemEnvironment::nativePackageType) - .map(StandardPackageType.LINUX_RPM::equals) - .orElse(false); - } - - private final Result sysEnv = LinuxRpmSystemEnvironment.create(SYS_ENV); -} diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmPackageBuilder.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmPackageBuilder.java index bc361eac759..f97a7741e42 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmPackageBuilder.java +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmPackageBuilder.java @@ -26,7 +26,6 @@ package jdk.jpackage.internal; import java.util.Objects; import java.util.Optional; -import jdk.jpackage.internal.model.ConfigException; import jdk.jpackage.internal.model.LinuxRpmPackage; import jdk.jpackage.internal.model.LinuxRpmPackageMixin; @@ -36,7 +35,7 @@ final class LinuxRpmPackageBuilder { this.pkgBuilder = Objects.requireNonNull(pkgBuilder); } - LinuxRpmPackage create() throws ConfigException { + LinuxRpmPackage create() { if (pkgBuilder.release().isEmpty()) { pkgBuilder.release("1"); } diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmSystemEnvironment.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmSystemEnvironment.java index 58c10668227..e56551be325 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmSystemEnvironment.java +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmSystemEnvironment.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,7 +28,7 @@ import static jdk.jpackage.internal.LinuxSystemEnvironment.mixin; import jdk.jpackage.internal.util.Result; -public interface LinuxRpmSystemEnvironment extends LinuxSystemEnvironment, LinuxRpmSystemEnvironmentMixin { +interface LinuxRpmSystemEnvironment extends LinuxSystemEnvironment, LinuxRpmSystemEnvironmentMixin { static Result create(Result base) { return mixin(LinuxRpmSystemEnvironment.class, base, LinuxRpmSystemEnvironmentMixin::create); diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmSystemEnvironmentMixin.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmSystemEnvironmentMixin.java index b741495f5ed..4cbd3ce4a9c 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmSystemEnvironmentMixin.java +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmSystemEnvironmentMixin.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -32,7 +32,7 @@ import java.util.stream.Stream; import jdk.jpackage.internal.model.DottedVersion; import jdk.jpackage.internal.util.Result; -public interface LinuxRpmSystemEnvironmentMixin { +interface LinuxRpmSystemEnvironmentMixin { Path rpm(); Path rpmbuild(); diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxSystemEnvironment.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxSystemEnvironment.java index 1a70cc938b8..e347c58ae21 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxSystemEnvironment.java +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxSystemEnvironment.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,7 +27,6 @@ package jdk.jpackage.internal; import java.io.IOException; import java.util.ArrayList; import java.util.List; -import java.util.Objects; import java.util.Optional; import java.util.function.Supplier; import jdk.jpackage.internal.model.PackageType; @@ -35,9 +34,10 @@ import jdk.jpackage.internal.model.StandardPackageType; import jdk.jpackage.internal.util.CompositeProxy; import jdk.jpackage.internal.util.Result; -public interface LinuxSystemEnvironment extends SystemEnvironment { +interface LinuxSystemEnvironment extends SystemEnvironment { boolean soLookupAvailable(); PackageType nativePackageType(); + LinuxPackageArch packageArch(); static Result create() { return detectNativePackageType().map(LinuxSystemEnvironment::create).orElseGet(() -> { @@ -45,7 +45,7 @@ public interface LinuxSystemEnvironment extends SystemEnvironment { }); } - static Optional detectNativePackageType() { + static Optional detectNativePackageType() { if (Internal.isDebian()) { return Optional.of(StandardPackageType.LINUX_DEB); } else if (Internal.isRpm()) { @@ -55,13 +55,14 @@ public interface LinuxSystemEnvironment extends SystemEnvironment { } } - static Result create(PackageType nativePackageType) { - return Result.ofValue(new Stub(LibProvidersLookup.supported(), - Objects.requireNonNull(nativePackageType))); + static Result create(StandardPackageType nativePackageType) { + return LinuxPackageArch.create(nativePackageType).map(arch -> { + return new Stub(LibProvidersLookup.supported(), nativePackageType, arch); + }); } static U createWithMixin(Class type, LinuxSystemEnvironment base, T mixin) { - return CompositeProxy.create(type, base, mixin); + return CompositeProxy.build().invokeTunnel(CompositeProxyTunnel.INSTANCE).create(type, base, mixin); } static Result mixin(Class type, @@ -79,7 +80,7 @@ public interface LinuxSystemEnvironment extends SystemEnvironment { } } - record Stub(boolean soLookupAvailable, PackageType nativePackageType) implements LinuxSystemEnvironment { + record Stub(boolean soLookupAvailable, PackageType nativePackageType, LinuxPackageArch packageArch) implements LinuxSystemEnvironment { } static final class Internal { 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 c84b5e3bbf5..3c654f604c2 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,8 @@ */ package jdk.jpackage.internal.model; +import static jdk.jpackage.internal.cli.StandardAppImageFileOption.LINUX_LAUNCHER_SHORTCUT; + import java.util.HashMap; import java.util.Map; import jdk.jpackage.internal.util.CompositeProxy; @@ -39,7 +41,7 @@ public interface LinuxLauncher extends Launcher, LinuxLauncherMixin { default Map extraAppImageFileData() { Map map = new HashMap<>(); shortcut().ifPresent(shortcut -> { - shortcut.store(SHORTCUT_ID, map::put); + shortcut.store(LINUX_LAUNCHER_SHORTCUT.getName(), map::put); }); return map; } @@ -55,6 +57,4 @@ 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/resources/LinuxResources.properties b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources.properties index a732d02c7d1..3aabe1f4ba5 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources.properties +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources.properties @@ -23,10 +23,6 @@ # questions. # # -app.bundler.name=Linux Application Image -deb.bundler.name=DEB Bundle -rpm.bundler.name=RPM Bundle - param.license-type.default=Unknown resource.deb-control-file=DEB control file @@ -58,7 +54,6 @@ message.output-to-location=Package (.deb) saved to: {0}. message.debs-like-licenses=Debian packages should specify a license. The absence of a license will cause some linux distributions to complain about the quality of the application. message.outputting-bundle-location=Generating RPM for installer to: {0}. message.output-bundle-location=Package (.rpm) saved to: {0}. -message.creating-association-with-null-extension=Creating association with null extension. message.ldd-not-available=ldd command not found. Package dependencies will not be generated. message.deb-ldd-not-available.advice=Install "libc-bin" DEB package to get ldd. diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources_de.properties b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources_de.properties index 2f8fcddff73..345ed36b7be 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources_de.properties +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources_de.properties @@ -23,12 +23,7 @@ # questions. # # -app.bundler.name=Linux-Anwendungsimage -deb.bundler.name=DEB-Bundle -rpm.bundler.name=RPM-Bundle - param.license-type.default=Unbekannt -param.menu-group.default=Unbekannt resource.deb-control-file=DEB-Kontrolldatei resource.deb-preinstall-script=DEB-Preinstall-Skript @@ -59,7 +54,6 @@ message.output-to-location=Package (.deb) gespeichert in: {0}. message.debs-like-licenses=Debian-Packages müssen eine Lizenz angeben. Bei fehlender Lizenz geben einige Linux-Distributionen eine Meldung über eine Beeinträchtigung der Anwendungsqualität aus. message.outputting-bundle-location=RPM für Installationsprogramm wird generiert in: {0}. message.output-bundle-location=Package (.rpm) gespeichert in: {0}. -message.creating-association-with-null-extension=Verknüpfung mit Nullerweiterung wird erstellt. message.ldd-not-available=ldd-Befehl nicht gefunden. Packageabhängigkeiten werden nicht generiert. message.deb-ldd-not-available.advice=Installieren Sie das DEB-Package "libc-bin", um ldd abzurufen. diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources_ja.properties b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources_ja.properties index be2cd00b42c..d0bc4f73407 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources_ja.properties +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources_ja.properties @@ -23,12 +23,7 @@ # questions. # # -app.bundler.name=Linuxアプリケーション・イメージ -deb.bundler.name=DEBバンドル -rpm.bundler.name=RPMバンドル - param.license-type.default=不明 -param.menu-group.default=不明 resource.deb-control-file=DEB制御ファイル resource.deb-preinstall-script=DEBインストール前スクリプト @@ -59,7 +54,6 @@ message.output-to-location=パッケージ(.deb)は次に保存されました: message.debs-like-licenses=Debianパッケージではライセンスを指定する必要があります。ライセンスがない場合、一部のLinuxディストリビューションでアプリケーションの品質に問題が発生する場合があります。 message.outputting-bundle-location=インストーラのRPMを次に生成しています: {0} message.output-bundle-location=パッケージ(.rpm)は次に保存されました: {0} -message.creating-association-with-null-extension=null拡張子との関連付けを作成しています。 message.ldd-not-available=lddコマンドが見つかりませんでした。パッケージ依存性は生成されません。 message.deb-ldd-not-available.advice="libc-bin" DEBパッケージをインストールしてlddを取得します。 diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources_zh_CN.properties b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources_zh_CN.properties index 5b583062ab6..f3d62675c4d 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources_zh_CN.properties +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources_zh_CN.properties @@ -23,12 +23,7 @@ # questions. # # -app.bundler.name=Linux 应用程序映像 -deb.bundler.name=DEB 包 -rpm.bundler.name=RPM 包 - param.license-type.default=未知 -param.menu-group.default=未知 resource.deb-control-file=DEB 控制文件 resource.deb-preinstall-script=DEB 安装前脚本 @@ -59,7 +54,6 @@ message.output-to-location=程序包 (.deb) 已保存到: {0}。 message.debs-like-licenses=Debian 程序包应指定许可证。缺少许可证将导致某些 Linux 分发投诉应用程序质量。 message.outputting-bundle-location=正在为安装程序生成 RPM, 位置: {0}。 message.output-bundle-location=程序包 (.rpm) 已保存到: {0}。 -message.creating-association-with-null-extension=正在使用空扩展名创建关联。 message.ldd-not-available=未找到 ldd 命令。将不生成程序包被依赖对象。 message.deb-ldd-not-available.advice=安装 "libc-bin" DEB 程序包以获取 ldd。 diff --git a/src/jdk.jpackage/linux/classes/module-info.java.extra b/src/jdk.jpackage/linux/classes/module-info.java.extra index d32314b0429..7bef2286214 100644 --- a/src/jdk.jpackage/linux/classes/module-info.java.extra +++ b/src/jdk.jpackage/linux/classes/module-info.java.extra @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,8 +23,5 @@ * questions. */ -provides jdk.jpackage.internal.Bundler with - jdk.jpackage.internal.LinuxAppBundler, - jdk.jpackage.internal.LinuxDebBundler, - jdk.jpackage.internal.LinuxRpmBundler; - +provides jdk.jpackage.internal.cli.CliBundlingEnvironment with + jdk.jpackage.internal.LinuxBundlingEnvironment; diff --git a/src/jdk.jpackage/linux/native/libapplauncher/LinuxLauncherLib.cpp b/src/jdk.jpackage/linux/native/libapplauncher/LinuxLauncherLib.cpp index 7d7c8b7213d..561e2a942bb 100644 --- a/src/jdk.jpackage/linux/native/libapplauncher/LinuxLauncherLib.cpp +++ b/src/jdk.jpackage/linux/native/libapplauncher/LinuxLauncherLib.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/AppImageInfoPListFile.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/AppImageInfoPListFile.java index 4787d1297bb..602e147a970 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/AppImageInfoPListFile.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/AppImageInfoPListFile.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,8 +24,6 @@ */ package jdk.jpackage.internal; -import static jdk.jpackage.internal.util.XmlUtils.initDocumentBuilder; - import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -50,7 +48,7 @@ record AppImageInfoPListFile(String bundleIdentifier, String bundleName, String static AppImageInfoPListFile loadFromInfoPList(Path infoPListFile) throws IOException, InvalidPlistFileException, SAXException { - final var plistReader = new PListReader(initDocumentBuilder().parse(Files.newInputStream(infoPListFile))); + final var plistReader = new PListReader(Files.readAllBytes(infoPListFile)); try { return new AppImageInfoPListFile( 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 71f87dd8705..81e04ad7ed1 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/AppImageSigner.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/AppImageSigner.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -48,6 +48,7 @@ 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.Result; import jdk.jpackage.internal.util.function.ExceptionBox; @@ -188,11 +189,9 @@ final class AppImageSigner { } private static boolean isXcodeDevToolsInstalled() { - try { - return Executor.of("/usr/bin/xcrun", "--help").setQuiet(true).execute() == 0; - } catch (IOException ex) { - return false; - } + return Result.of( + Executor.of("/usr/bin/xcrun", "--help").setQuiet(true)::executeExpectSuccess, + IOException.class).hasValue(); } private static void unsign(Path path) throws IOException { diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/AppImageSigningConfigBuilder.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/AppImageSigningConfigBuilder.java index bb043830f3c..6fc7fe004c2 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/AppImageSigningConfigBuilder.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/AppImageSigningConfigBuilder.java @@ -28,7 +28,6 @@ import java.nio.file.Path; import java.util.Objects; import java.util.Optional; import jdk.jpackage.internal.model.AppImageSigningConfig; -import jdk.jpackage.internal.model.ConfigException; import jdk.jpackage.internal.model.LauncherStartupInfo; final class AppImageSigningConfigBuilder { @@ -62,21 +61,20 @@ final class AppImageSigningConfigBuilder { return this; } - Optional create() throws ConfigException { - final var identityCfg = signingIdentityBuilder.create(); - if (identityCfg.isEmpty()) { - return Optional.empty(); - } else { + Optional create() { + return signingIdentityBuilder.create().map(cfg -> { final var validatedEntitlements = validatedEntitlements(); - return identityCfg.map(cfg -> { - return new AppImageSigningConfig.Stub(cfg.identity(), signingIdentifierPrefix, - validatedEntitlements, cfg.keychain().map(Keychain::name), - Optional.ofNullable(entitlementsResourceName).orElse("entitlements.plist")); - }); - } + return new AppImageSigningConfig.Stub( + Objects.requireNonNull(cfg.identity()), + Objects.requireNonNull(signingIdentifierPrefix), + validatedEntitlements, + cfg.keychain().map(Keychain::name), + Optional.ofNullable(entitlementsResourceName).orElse("entitlements.plist") + ); + }); } - private Optional validatedEntitlements() throws ConfigException { + private Optional validatedEntitlements() { return Optional.ofNullable(entitlements); } diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/Codesign.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/Codesign.java index 920b75df398..a7cd17b06b9 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/Codesign.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/Codesign.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -34,7 +34,6 @@ import java.util.Objects; import java.util.Optional; import java.util.function.Consumer; import java.util.function.Supplier; -import java.util.stream.Stream; public final class Codesign { @@ -94,14 +93,12 @@ public final class Codesign { public void applyTo(Path path) throws IOException, CodesignException { - var exec = Executor.of(Stream.concat( - cmdline.stream(), - Stream.of(path.toString())).toArray(String[]::new) - ).saveOutput(true); + var exec = Executor.of(cmdline).args(path.toString()).saveOutput(true); configureExecutor.ifPresent(configure -> configure.accept(exec)); - if (exec.execute() != 0) { - throw new CodesignException(exec.getOutput().toArray(String[]::new)); + var result = exec.execute(); + if (result.getExitCode() != 0) { + throw new CodesignException(result.getOutput().toArray(String[]::new)); } } diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppBundler.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppBundler.java deleted file mode 100644 index cce35ece117..00000000000 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppBundler.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. - * 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; - -import static jdk.jpackage.internal.StandardBundlerParam.OUTPUT_DIR; -import static jdk.jpackage.internal.StandardBundlerParam.SIGN_BUNDLE; - -import java.util.Map; -import java.util.Optional; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.util.function.ExceptionBox; - -public class MacAppBundler extends AppImageBundler { - public MacAppBundler() { - setAppImageSupplier((params, output) -> { - - // Order is important! - final var app = MacFromParams.APPLICATION.fetchFrom(params); - final BuildEnv env; - - if (StandardBundlerParam.hasPredefinedAppImage(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(MacBuildEnvFromParams.BUILD_ENV.fetchFrom(params), output); - MacPackagingPipeline.build(Optional.empty()) - .excludeDirFromCopying(output.getParent()) - .excludeDirFromCopying(OUTPUT_DIR.fetchFrom(params)).create().execute(env, app); - } - - }); - setParamsValidator(MacAppBundler::doValidate); - } - - private static void doValidate(Map params) - throws ConfigException { - - try { - MacFromParams.APPLICATION.fetchFrom(params); - } catch (ExceptionBox ex) { - if (ex.getCause() instanceof ConfigException cfgEx) { - throw cfgEx; - } else { - throw ex; - } - } - - if (StandardBundlerParam.hasPredefinedAppImage(params)) { - if (!Optional.ofNullable( - SIGN_BUNDLE.fetchFrom(params)).orElse(Boolean.FALSE)) { - throw new ConfigException( - I18N.getString("error.app-image.mac-sign.required"), - null); - } - } - } -} 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 226bb9e8134..5f126305aed 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacApplicationBuilder.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacApplicationBuilder.java @@ -28,17 +28,15 @@ import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.List; -import java.util.Set; import java.util.Objects; import java.util.Optional; +import java.util.Set; +import jdk.jpackage.internal.model.AppImageLayout; +import jdk.jpackage.internal.model.AppImageSigningConfig; import jdk.jpackage.internal.model.Application; -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 { @@ -92,7 +90,7 @@ final class MacApplicationBuilder { return this; } - MacApplication create() throws ConfigException { + MacApplication create() { if (externalInfoPlistFile != null) { return createCopyForExternalInfoPlistFile().create(); } @@ -136,7 +134,7 @@ final class MacApplicationBuilder { return true; } - private static void validateAppVersion(Application app) throws ConfigException { + private static void validateAppVersion(Application app) { try { CFBundleVersion.of(app.version()); } catch (IllegalArgumentException ex) { @@ -156,7 +154,7 @@ final class MacApplicationBuilder { } } - private MacApplicationBuilder createCopyForExternalInfoPlistFile() throws ConfigException { + private MacApplicationBuilder createCopyForExternalInfoPlistFile() { try { final var plistFile = AppImageInfoPListFile.loadFromInfoPList(externalInfoPlistFile); @@ -187,15 +185,11 @@ final class MacApplicationBuilder { } } - private Optional createSigningConfig() throws ConfigException { - if (signingBuilder != null) { - return signingBuilder.create(); - } else { - return Optional.empty(); - } + private Optional createSigningConfig() { + return Optional.ofNullable(signingBuilder).flatMap(AppImageSigningConfigBuilder::create); } - private String validatedBundleName() throws ConfigException { + private String validatedBundleName() { final var value = Optional.ofNullable(bundleName).orElseGet(() -> { final var appName = app.name(); // Commented out for backward compatibility @@ -214,7 +208,7 @@ final class MacApplicationBuilder { return value; } - private String validatedBundleIdentifier() throws ConfigException { + private String validatedBundleIdentifier() { final var value = Optional.ofNullable(bundleIdentifier).orElseGet(() -> { return app.mainLauncher() .flatMap(Launcher::startupInfo) @@ -238,16 +232,12 @@ final class MacApplicationBuilder { return value; } - private String validatedCategory() throws ConfigException { + private String validatedCategory() { return "public.app-category." + Optional.ofNullable(category).orElseGet(DEFAULTS::category); } - private Optional validatedIcon() throws ConfigException { - if (icon != null) { - LauncherBuilder.validateIcon(icon); - } - - return Optional.ofNullable(icon); + private Optional validatedIcon() { + return Optional.ofNullable(icon).map(LauncherBuilder::validateIcon); } private record Defaults(String category) { diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBundlingEnvironment.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBundlingEnvironment.java new file mode 100644 index 00000000000..0531559e052 --- /dev/null +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBundlingEnvironment.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * 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; + +import static jdk.jpackage.internal.MacFromOptions.createMacApplication; +import static jdk.jpackage.internal.MacPackagingPipeline.APPLICATION_LAYOUT; +import static jdk.jpackage.internal.MacPackagingPipeline.createSignAppImagePackage; +import static jdk.jpackage.internal.cli.StandardBundlingOperation.CREATE_MAC_APP_IMAGE; +import static jdk.jpackage.internal.cli.StandardBundlingOperation.CREATE_MAC_DMG; +import static jdk.jpackage.internal.cli.StandardBundlingOperation.CREATE_MAC_PKG; +import static jdk.jpackage.internal.cli.StandardBundlingOperation.SIGN_MAC_APP_IMAGE; + +import java.util.Optional; +import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.model.MacPackage; +import jdk.jpackage.internal.model.Package; + +public class MacBundlingEnvironment extends DefaultBundlingEnvironment { + + public MacBundlingEnvironment() { + super(build() + .defaultOperation(CREATE_MAC_DMG) + .bundler(SIGN_MAC_APP_IMAGE, MacBundlingEnvironment::signAppImage) + .bundler(CREATE_MAC_APP_IMAGE, MacBundlingEnvironment::createAppImage) + .bundler(CREATE_MAC_DMG, MacDmgSystemEnvironment::create, MacBundlingEnvironment::createDmdPackage) + .bundler(CREATE_MAC_PKG, MacBundlingEnvironment::createPkgPackage)); + } + + private static void createDmdPackage(Options options, MacDmgSystemEnvironment sysEnv) { + createNativePackage(options, + MacFromOptions.createMacDmgPackage(options), + buildEnv()::create, + MacBundlingEnvironment::buildPipeline, + (env, pkg, outputDir) -> { + Log.verbose(I18N.format("message.building-dmg", pkg.app().name())); + return new MacDmgPackager(env, pkg, outputDir, sysEnv); + }); + } + + private static void createPkgPackage(Options options) { + createNativePackage(options, + MacFromOptions.createMacPkgPackage(options), + buildEnv()::create, + MacBundlingEnvironment::buildPipeline, + (env, pkg, outputDir) -> { + Log.verbose(I18N.format("message.building-pkg", pkg.app().name())); + return new MacPkgPackager(env, pkg, outputDir); + }); + } + + private static void signAppImage(Options options) { + + final var app = createMacApplication(options); + + final var env = buildEnv().create(options, app); + + final var pkg = createSignAppImagePackage(app, env); + + buildPipeline(pkg).create().execute(env, pkg, env.appImageDir()); + } + + private static void createAppImage(Options options) { + + final var app = createMacApplication(options); + + createApplicationImage(options, app, MacPackagingPipeline.build(Optional.empty())); + } + + private static PackagingPipeline.Builder buildPipeline(Package pkg) { + return MacPackagingPipeline.build(Optional.of(pkg)); + } + + private static BuildEnvFromOptions buildEnv() { + return new BuildEnvFromOptions() + .predefinedAppImageLayout(APPLICATION_LAYOUT) + .predefinedRuntimeImageLayout(MacPackage::guessRuntimeLayout); + } +} diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacCertificateUtils.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacCertificateUtils.java index fe593e347fc..24a236ae15d 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacCertificateUtils.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacCertificateUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -53,7 +53,7 @@ public final class MacCertificateUtils { keychain.map(Keychain::asCliArg).ifPresent(args::add); return toSupplier(() -> { - final var output = Executor.of(args.toArray(String[]::new)) + final var output = Executor.of(args) .setQuiet(true).saveOutput(true).executeExpectSuccess() .getOutput(); diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgBundler.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgBundler.java deleted file mode 100644 index 0ddb987dbee..00000000000 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgBundler.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. - * 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; - -import java.nio.file.Path; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.model.MacDmgPackage; -import jdk.jpackage.internal.model.PackagerException; -import jdk.jpackage.internal.util.Result; - -public class MacDmgBundler extends MacBaseInstallerBundler { - - @Override - public String getName() { - return I18N.getString("dmg.bundler.name"); - } - - @Override - public String getID() { - return "dmg"; - } - - @Override - public boolean validate(Map params) - throws ConfigException { - try { - Objects.requireNonNull(params); - - MacFromParams.DMG_PACKAGE.fetchFrom(params); - - //run basic validation to ensure requirements are met - //we are not interested in return code, only possible exception - validateAppImageAndBundeler(params); - - return true; - } catch (RuntimeException re) { - if (re.getCause() instanceof ConfigException) { - throw (ConfigException) re.getCause(); - } else { - throw new ConfigException(re); - } - } - } - - @Override - public Path execute(Map params, - Path outputParentDir) throws PackagerException { - - var pkg = MacFromParams.DMG_PACKAGE.fetchFrom(params); - - Log.verbose(I18N.format("message.building-dmg", pkg.app().name())); - - return Packager.build().outputDir(outputParentDir) - .pkg(pkg) - .env(MacBuildEnvFromParams.BUILD_ENV.fetchFrom(params)) - .pipelineBuilderMutatorFactory((env, _, outputDir) -> { - return new MacDmgPackager(env, pkg, outputDir, sysEnv.orElseThrow()); - }).execute(MacPackagingPipeline.build(Optional.of(pkg))); - } - - @Override - public boolean supported(boolean runtimeInstaller) { - return sysEnv.hasValue(); - } - - @Override - public boolean isDefault() { - return true; - } - - private final Result sysEnv = MacDmgSystemEnvironment.create(); -} 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 c2b9c25f327..10754b1f1b6 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgPackageBuilder.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgPackageBuilder.java @@ -28,7 +28,6 @@ import java.nio.file.Path; import java.util.List; import java.util.Objects; import java.util.Optional; -import jdk.jpackage.internal.model.ConfigException; import jdk.jpackage.internal.model.MacDmgPackage; import jdk.jpackage.internal.model.MacDmgPackageMixin; @@ -52,7 +51,7 @@ final class MacDmgPackageBuilder { return Optional.ofNullable(dmgContent).orElseGet(List::of); } - MacDmgPackage create() throws ConfigException { + MacDmgPackage create() { final var pkg = pkgBuilder.create(); return MacDmgPackage.create(pkg, new MacDmgPackageMixin.Stub( diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgPackager.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgPackager.java index 4ccc459109f..20a687487ef 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgPackager.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgPackager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -33,11 +33,15 @@ import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; import java.text.MessageFormat; +import java.util.ArrayList; import java.util.Base64; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.concurrent.TimeUnit; import java.util.function.Consumer; +import java.util.function.Function; import jdk.jpackage.internal.PackagingPipeline.PackageTaskID; import jdk.jpackage.internal.PackagingPipeline.TaskID; import jdk.jpackage.internal.model.MacDmgPackage; @@ -105,6 +109,10 @@ record MacDmgPackager(BuildEnv env, MacDmgPackage pkg, Path outputDir, return env.configDir().resolve(pkg.app().name() + "-license.plist"); } + private Path finalDmg() { + return outputDir.resolve(pkg.packageFileNameWithSuffix()); + } + Path protoDmg() { return dmgWorkdir().resolve("proto.dmg"); } @@ -128,6 +136,10 @@ record MacDmgPackager(BuildEnv env, MacDmgPackage pkg, Path outputDir, } } + private Executor hdiutil(String... args) { + return Executor.of(sysEnv.hdiutil().toString()).args(args).storeOutputInFiles(); + } + private void prepareDMGSetupScript() throws IOException { Path dmgSetup = volumeScript(); Log.verbose(MessageFormat.format( @@ -211,13 +223,17 @@ record MacDmgPackager(BuildEnv env, MacDmgPackage pkg, Path outputDir, } } + private String hdiUtilVerbosityFlag() { + return env.verbose() ? "-verbose" : "-quiet"; + } + private void buildDMG() throws IOException { boolean copyAppImage = false; - Path protoDMG = protoDmg(); - Path finalDMG = outputDir.resolve(pkg.packageFileNameWithSuffix()); + final Path protoDMG = protoDmg(); + final Path finalDMG = finalDmg(); - Path srcFolder = env.appImageDir(); + final Path srcFolder = env.appImageDir(); Log.verbose(MessageFormat.format(I18N.getString( "message.creating-dmg-file"), finalDMG.toAbsolutePath())); @@ -233,21 +249,17 @@ record MacDmgPackager(BuildEnv env, MacDmgPackage pkg, Path outputDir, Files.createDirectories(protoDMG.getParent()); Files.createDirectories(finalDMG.getParent()); - String hdiUtilVerbosityFlag = env.verbose() ? - "-verbose" : "-quiet"; + final String hdiUtilVerbosityFlag = hdiUtilVerbosityFlag(); // create temp image - ProcessBuilder pb = new ProcessBuilder( - sysEnv.hdiutil().toString(), - "create", - hdiUtilVerbosityFlag, - "-srcfolder", normalizedAbsolutePathString(srcFolder), - "-volname", volumeName(), - "-ov", normalizedAbsolutePathString(protoDMG), - "-fs", "HFS+", - "-format", "UDRW"); try { - IOUtils.exec(pb, false, null, true, Executor.INFINITE_TIMEOUT); + hdiutil("create", + hdiUtilVerbosityFlag, + "-srcfolder", normalizedAbsolutePathString(srcFolder), + "-volname", volumeName(), + "-ov", normalizedAbsolutePathString(protoDMG), + "-fs", "HFS+", + "-format", "UDRW").executeExpectSuccess(); } catch (IOException ex) { Log.verbose(ex); // Log exception @@ -260,31 +272,26 @@ record MacDmgPackager(BuildEnv env, MacDmgPackage pkg, Path outputDir, // not be bigger, but it will able to hold additional 50 megabytes of data. // We need extra room for icons and background image. When we providing // actual files to hdiutil, it will create DMG with ~50 megabytes extra room. - pb = new ProcessBuilder( - sysEnv.hdiutil().toString(), - "create", - hdiUtilVerbosityFlag, - "-size", String.valueOf(size), - "-volname", volumeName(), - "-ov", normalizedAbsolutePathString(protoDMG), - "-fs", "HFS+"); - new RetryExecutor() - .setMaxAttemptsCount(10) - .setAttemptTimeoutMillis(3000) - .setWriteOutputToFile(true) - .execute(pb); + hdiutil( + "create", + hdiUtilVerbosityFlag, + "-size", String.valueOf(size), + "-volname", volumeName(), + "-ov", normalizedAbsolutePathString(protoDMG), + "-fs", "HFS+" + ).retry() + .setMaxAttemptsCount(10) + .setAttemptTimeout(3, TimeUnit.SECONDS) + .execute(); } + final Path mountedVolume = volumePath(); + // mount temp image - pb = new ProcessBuilder( - sysEnv.hdiutil().toString(), - "attach", + hdiutil("attach", normalizedAbsolutePathString(protoDMG), hdiUtilVerbosityFlag, - "-mountroot", protoDMG.getParent().toString()); - IOUtils.exec(pb, false, null, true, Executor.INFINITE_TIMEOUT); - - final Path mountedVolume = volumePath(); + "-mountroot", mountedVolume.getParent().toString()).executeExpectSuccess(); // Copy app image, since we did not create DMG with it, but instead we created // empty one. @@ -302,9 +309,13 @@ record MacDmgPackager(BuildEnv env, MacDmgPackage pkg, Path outputDir, // to install-dir in DMG as critical error, since it can fail in // headless environment. try { - pb = new ProcessBuilder(sysEnv.osascript().toString(), - normalizedAbsolutePathString(volumeScript())); - IOUtils.exec(pb, 180); // Wait 3 minutes. See JDK-8248248. + Executor.of( + sysEnv.osascript().toString(), + normalizedAbsolutePathString(volumeScript()) + ) + // Wait 3 minutes. See JDK-8248248. + .timeout(3, TimeUnit.MINUTES) + .executeExpectSuccess(); } catch (IOException ex) { Log.verbose(ex); } @@ -325,18 +336,18 @@ record MacDmgPackager(BuildEnv env, MacDmgPackage pkg, Path outputDir, // but it seems Finder excepts these bytes to be // "icnC" for the volume icon // (might not work on Mac 10.13 with old XCode) - pb = new ProcessBuilder( + Executor.of( sysEnv.setFileUtility().orElseThrow().toString(), "-c", "icnC", - normalizedAbsolutePathString(volumeIconFile)); - IOUtils.exec(pb); + normalizedAbsolutePathString(volumeIconFile) + ).executeExpectSuccess(); volumeIconFile.toFile().setReadOnly(); - pb = new ProcessBuilder( + Executor.of( sysEnv.setFileUtility().orElseThrow().toString(), "-a", "C", - normalizedAbsolutePathString(mountedVolume)); - IOUtils.exec(pb); + normalizedAbsolutePathString(mountedVolume) + ).executeExpectSuccess(); } catch (IOException ex) { Log.error(ex.getMessage()); Log.verbose("Cannot enable custom icon using SetFile utility"); @@ -347,85 +358,23 @@ record MacDmgPackager(BuildEnv env, MacDmgPackage pkg, Path outputDir, } finally { // Detach the temporary image - pb = new ProcessBuilder( - sysEnv.hdiutil().toString(), - "detach", - hdiUtilVerbosityFlag, - normalizedAbsolutePathString(mountedVolume)); - // "hdiutil detach" might not work right away due to resource busy error, so - // repeat detach several times. - RetryExecutor retryExecutor = new RetryExecutor(); - // Image can get detach even if we got resource busy error, so stop - // trying to detach it if it is no longer attached. - retryExecutor.setExecutorInitializer(exec -> { - if (!Files.exists(mountedVolume)) { - retryExecutor.abort(); - } - }); - try { - // 10 times with 6 second delays. - retryExecutor.setMaxAttemptsCount(10).setAttemptTimeoutMillis(6000) - .execute(pb); - } catch (IOException ex) { - if (!retryExecutor.isAborted()) { - // Now force to detach if it still attached - if (Files.exists(mountedVolume)) { - pb = new ProcessBuilder( - sysEnv.hdiutil().toString(), - "detach", - "-force", - hdiUtilVerbosityFlag, - normalizedAbsolutePathString(mountedVolume)); - IOUtils.exec(pb, false, null, true, Executor.INFINITE_TIMEOUT); - } - } - } + detachVolume(); } // Compress it to a new image - pb = new ProcessBuilder( - sysEnv.hdiutil().toString(), - "convert", - normalizedAbsolutePathString(protoDMG), - hdiUtilVerbosityFlag, - "-format", "UDZO", - "-o", normalizedAbsolutePathString(finalDMG)); - try { - new RetryExecutor() - .setMaxAttemptsCount(10) - .setAttemptTimeoutMillis(3000) - .execute(pb); - } catch (Exception ex) { - // Convert might failed if something holds file. Try to convert copy. - Path protoCopyDMG = protoCopyDmg(); - Files.copy(protoDMG, protoCopyDMG); - try { - pb = new ProcessBuilder( - sysEnv.hdiutil().toString(), - "convert", - normalizedAbsolutePathString(protoCopyDMG), - hdiUtilVerbosityFlag, - "-format", "UDZO", - "-o", normalizedAbsolutePathString(finalDMG)); - IOUtils.exec(pb, false, null, true, Executor.INFINITE_TIMEOUT); - } finally { - Files.deleteIfExists(protoCopyDMG); - } - } + convertProtoDmg(); //add license if needed if (pkg.licenseFile().isPresent()) { - pb = new ProcessBuilder( - sysEnv.hdiutil().toString(), + hdiutil( "udifrez", normalizedAbsolutePathString(finalDMG), "-xml", normalizedAbsolutePathString(licenseFile()) - ); - new RetryExecutor() - .setMaxAttemptsCount(10) - .setAttemptTimeoutMillis(3000) - .execute(pb); + ).retry() + .setMaxAttemptsCount(10) + .setAttemptTimeout(3, TimeUnit.SECONDS) + .execute(); } try { @@ -441,6 +390,69 @@ record MacDmgPackager(BuildEnv env, MacDmgPackage pkg, Path outputDir, } + private void detachVolume() throws IOException { + var mountedVolume = volumePath(); + + // "hdiutil detach" might not work right away due to resource busy error, so + // repeat detach several times. + Globals.instance().objectFactory().retryExecutor(IOException.class).setExecutable(context -> { + + List cmdline = new ArrayList<>(); + cmdline.add("detach"); + + if (context.isLastAttempt()) { + // The last attempt, force detach. + cmdline.add("-force"); + } + + cmdline.addAll(List.of( + hdiUtilVerbosityFlag(), + normalizedAbsolutePathString(mountedVolume) + )); + + // The image can get detached even if we get a resource busy error, + // so execute the detach command without checking the exit code. + var result = hdiutil(cmdline.toArray(String[]::new)).execute(); + + if (result.getExitCode() == 0 || !Files.exists(mountedVolume)) { + // Detached successfully! + return null; + } else { + throw result.unexpected(); + } + }).setMaxAttemptsCount(10).setAttemptTimeout(6, TimeUnit.SECONDS).execute(); + } + + private void convertProtoDmg() throws IOException { + + Function convert = srcDmg -> { + return hdiutil( + "convert", + normalizedAbsolutePathString(srcDmg), + hdiUtilVerbosityFlag(), + "-format", "UDZO", + "-o", normalizedAbsolutePathString(finalDmg())); + }; + + // Convert it to a new image. + try { + convert.apply(protoDmg()).retry() + .setMaxAttemptsCount(10) + .setAttemptTimeout(3, TimeUnit.SECONDS) + .execute(); + } catch (IOException ex) { + Log.verbose(ex); + // Something holds the file, try to convert a copy. + Path copyDmg = protoCopyDmg(); + Files.copy(protoDmg(), copyDmg); + try { + convert.apply(copyDmg).executeExpectSuccess(); + } finally { + Files.deleteIfExists(copyDmg); + } + } + } + // Background image name in resources private static final String DEFAULT_BACKGROUND_IMAGE = "background_dmg.tiff"; private static final String DEFAULT_DMG_SETUP_SCRIPT = "DMGsetup.scpt"; diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgSystemEnvironment.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgSystemEnvironment.java index 54eb0c6f4fe..12d105b99b5 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgSystemEnvironment.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgSystemEnvironment.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,10 +25,11 @@ package jdk.jpackage.internal; import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; +import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.stream.Collectors; import java.util.stream.Stream; import jdk.jpackage.internal.util.Result; @@ -54,41 +55,31 @@ record MacDmgSystemEnvironment(Path hdiutil, Path osascript, Optional setF // Location of SetFile utility may be different depending on MacOS version // We look for several known places and if none of them work will // try to find it - private static Optional findSetFileUtility() { - String typicalPaths[] = {"/Developer/Tools/SetFile", - "/usr/bin/SetFile", "/Developer/usr/bin/SetFile"}; + static Optional findSetFileUtility() { + return SETFILE_KNOWN_PATHS.stream().filter(setFilePath -> { + // Validate SetFile, if Xcode is not installed it will run, but exit with error code + return Result.of( + Executor.of(setFilePath.toString(), "-h").setQuiet(true)::executeExpectSuccess, + IOException.class).hasValue(); + }).findFirst().or(() -> { + // generic find attempt + final var executor = Executor.of("/usr/bin/xcrun", "-find", "SetFile").setQuiet(true).saveFirstLineOfOutput(); - final var setFilePath = Stream.of(typicalPaths).map(Path::of).filter(Files::isExecutable).findFirst(); - if (setFilePath.isPresent()) { - // Validate SetFile, if Xcode is not installed it will run, but exit with error - // code - try { - if (Executor.of(setFilePath.orElseThrow().toString(), "-h").setQuiet(true).execute() == 0) { - return setFilePath; - } - } catch (Exception ignored) { - // No need for generic find attempt. We found it, but it does not work. - // Probably due to missing xcode. - return Optional.empty(); - } - } - - // generic find attempt - try { - final var executor = Executor.of("/usr/bin/xcrun", "-find", "SetFile"); - final var code = executor.setQuiet(true).saveOutput(true).execute(); - if (code == 0 && !executor.getOutput().isEmpty()) { - final var firstLine = executor.getOutput().getFirst(); - Path f = Path.of(firstLine); - if (new ToolValidator(f).checkExistsOnly().validate() == null) { - return Optional.of(f.toAbsolutePath()); - } - } - } catch (IOException ignored) {} - - return Optional.empty(); + return Result.of(executor::executeExpectSuccess, IOException.class).flatMap(execResult -> { + return Result.of(() -> { + return execResult.stdout().stream().findFirst().map(Path::of).orElseThrow(execResult::unexpected); + }, Exception.class); + }).value().filter(v -> { + return new ToolValidator(v).checkExistsOnly().validate() == null; + }).map(Path::toAbsolutePath); + }); } + static final List SETFILE_KNOWN_PATHS = Stream.of( + "/Developer/Tools/SetFile", + "/usr/bin/SetFile", + "/Developer/usr/bin/SetFile").map(Path::of).collect(Collectors.toUnmodifiableList()); + private static final Path HDIUTIL = Path.of("/usr/bin/hdiutil"); private static final Path OSASCRIPT = Path.of("/usr/bin/osascript"); } diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFileAssociationBuilder.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFileAssociationBuilder.java index c86b3a15cbb..7b9e5478f74 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFileAssociationBuilder.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFileAssociationBuilder.java @@ -27,15 +27,13 @@ package jdk.jpackage.internal; import java.util.List; import java.util.Objects; import java.util.Optional; - -import jdk.jpackage.internal.model.ConfigException; import jdk.jpackage.internal.model.FileAssociation; import jdk.jpackage.internal.model.MacFileAssociation; import jdk.jpackage.internal.model.MacFileAssociationMixin; final class MacFileAssociationBuilder { - MacFileAssociation create(FileAssociation fa) throws ConfigException { + MacFileAssociation create(FileAssociation fa) { Objects.requireNonNull(fa); final var mixin = new MacFileAssociationMixin.Stub( diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromOptions.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromOptions.java new file mode 100644 index 00000000000..b1094331740 --- /dev/null +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromOptions.java @@ -0,0 +1,331 @@ +/* + * 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; + +import static jdk.jpackage.internal.FromOptions.buildApplicationBuilder; +import static jdk.jpackage.internal.FromOptions.createPackageBuilder; +import static jdk.jpackage.internal.MacPackagingPipeline.APPLICATION_LAYOUT; +import static jdk.jpackage.internal.MacRuntimeValidator.validateRuntimeHasJliLib; +import static jdk.jpackage.internal.MacRuntimeValidator.validateRuntimeHasNoBinDir; +import static jdk.jpackage.internal.cli.StandardBundlingOperation.SIGN_MAC_APP_IMAGE; +import static jdk.jpackage.internal.cli.StandardOption.ICON; +import static jdk.jpackage.internal.cli.StandardOption.APPCLASS; +import static jdk.jpackage.internal.cli.StandardOption.MAC_APP_CATEGORY; +import static jdk.jpackage.internal.cli.StandardOption.MAC_APP_IMAGE_SIGN_IDENTITY; +import static jdk.jpackage.internal.cli.StandardOption.MAC_APP_STORE; +import static jdk.jpackage.internal.cli.StandardOption.MAC_BUNDLE_IDENTIFIER; +import static jdk.jpackage.internal.cli.StandardOption.MAC_BUNDLE_NAME; +import static jdk.jpackage.internal.cli.StandardOption.MAC_BUNDLE_SIGNING_PREFIX; +import static jdk.jpackage.internal.cli.StandardOption.MAC_DMG_CONTENT; +import static jdk.jpackage.internal.cli.StandardOption.MAC_ENTITLEMENTS; +import static jdk.jpackage.internal.cli.StandardOption.MAC_INSTALLER_SIGN_IDENTITY; +import static jdk.jpackage.internal.cli.StandardOption.MAC_SIGN; +import static jdk.jpackage.internal.cli.StandardOption.MAC_SIGNING_KEYCHAIN; +import static jdk.jpackage.internal.cli.StandardOption.MAC_SIGNING_KEY_NAME; +import static jdk.jpackage.internal.cli.StandardOption.PREDEFINED_APP_IMAGE; +import static jdk.jpackage.internal.cli.StandardOption.PREDEFINED_RUNTIME_IMAGE; +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.ExceptionBox.toUnchecked; + +import java.nio.file.Path; +import java.util.Objects; +import java.util.Optional; +import jdk.jpackage.internal.ApplicationBuilder.MainLauncherStartupInfo; +import jdk.jpackage.internal.SigningIdentityBuilder.ExpiredCertificateException; +import jdk.jpackage.internal.SigningIdentityBuilder.StandardCertificateSelector; +import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.cli.StandardFaOption; +import jdk.jpackage.internal.model.ApplicationLaunchers; +import jdk.jpackage.internal.model.ExternalApplication; +import jdk.jpackage.internal.model.FileAssociation; +import jdk.jpackage.internal.model.Launcher; +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; +import jdk.jpackage.internal.util.Result; +import jdk.jpackage.internal.util.function.ExceptionBox; + + +final class MacFromOptions { + + static MacApplication createMacApplication(Options options) { + return createMacApplicationInternal(options).app(); + } + + static MacDmgPackage createMacDmgPackage(Options options) { + + final var app = createMacApplicationInternal(options); + + final var superPkgBuilder = createMacPackageBuilder(options, app, MAC_DMG); + + final var pkgBuilder = new MacDmgPackageBuilder(superPkgBuilder); + + MAC_DMG_CONTENT.ifPresentIn(options, pkgBuilder::dmgContent); + + return pkgBuilder.create(); + } + + static MacPkgPackage createMacPkgPackage(Options options) { + + // + // One of "MacSignTest.testExpiredCertificate" test cases expects + // two error messages about expired certificates in the output: one for + // certificate for signing an app image, another certificate for signing a PKG. + // So creation of a PKG package is a bit messy. + // + + final boolean sign = MAC_SIGN.findIn(options).orElse(false); + final boolean appStore = MAC_APP_STORE.findIn(options).orElse(false); + + final var appResult = Result.of(() -> createMacApplicationInternal(options)); + + final Optional pkgBuilder; + if (appResult.hasValue()) { + final var superPkgBuilder = createMacPackageBuilder(options, appResult.orElseThrow(), MAC_PKG); + pkgBuilder = Optional.of(new MacPkgPackageBuilder(superPkgBuilder)); + } else { + // Failed to create an app. Is it because of the expired certificate? + rethrowIfNotExpiredCertificateException(appResult); + // Yes, the certificate for signing the app image has expired. + // Keep going, try to create a signing config for the package. + pkgBuilder = Optional.empty(); + } + + if (sign) { + final var signingIdentityBuilder = createSigningIdentityBuilder(options); + MAC_INSTALLER_SIGN_IDENTITY.ifPresentIn(options, signingIdentityBuilder::signingIdentity); + MAC_SIGNING_KEY_NAME.findIn(options).ifPresent(userName -> { + final StandardCertificateSelector domain; + if (appStore) { + domain = StandardCertificateSelector.APP_STORE_PKG_INSTALLER; + } else { + domain = StandardCertificateSelector.PKG_INSTALLER; + } + + signingIdentityBuilder.certificateSelector(StandardCertificateSelector.create(userName, domain)); + }); + + if (pkgBuilder.isPresent()) { + pkgBuilder.orElseThrow().signingBuilder(signingIdentityBuilder); + } else { + // + // The certificate for signing the app image has expired. Can not create a + // package because there is no app. + // Try to create a signing config for the package and see if the certificate for + // signing the package is also expired. + // + + final var expiredAppCertException = appResult.firstError().orElseThrow(); + + final var pkgSignConfigResult = Result.of(signingIdentityBuilder::create); + try { + rethrowIfNotExpiredCertificateException(pkgSignConfigResult); + // The certificate for the package signing config is also expired! + } catch (RuntimeException ex) { + // Some error occurred trying to configure the signing config for the package. + // Ignore it, bail out with the first error. + throw toUnchecked(expiredAppCertException); + } + + Log.error(pkgSignConfigResult.firstError().orElseThrow().getMessage()); + throw toUnchecked(expiredAppCertException); + } + } + + return pkgBuilder.orElseThrow().create(); + } + + private record ApplicationWithDetails(MacApplication app, Optional externalApp) { + ApplicationWithDetails { + Objects.requireNonNull(app); + Objects.requireNonNull(externalApp); + } + } + + private static ApplicationWithDetails createMacApplicationInternal(Options options) { + + final var predefinedRuntimeLayout = PREDEFINED_RUNTIME_IMAGE.findIn(options) + .map(MacPackage::guessRuntimeLayout); + + predefinedRuntimeLayout.ifPresent(layout -> { + validateRuntimeHasJliLib(layout); + if (MAC_APP_STORE.containsIn(options)) { + validateRuntimeHasNoBinDir(layout); + } + }); + + final var launcherFromOptions = new LauncherFromOptions().faMapper(MacFromOptions::createMacFa); + + final var superAppBuilder = buildApplicationBuilder() + .runtimeLayout(RUNTIME_BUNDLE_LAYOUT) + .predefinedRuntimeLayout(predefinedRuntimeLayout.map(RuntimeLayout::unresolve).orElse(null)) + .create(options, launcherOptions -> { + var launcher = launcherFromOptions.create(launcherOptions); + return MacLauncher.create(launcher); + }, (MacLauncher _, Launcher launcher) -> { + return MacLauncher.create(launcher); + }, APPLICATION_LAYOUT); + + if (PREDEFINED_APP_IMAGE.containsIn(options)) { + // Set the main launcher start up info. + // AppImageFile assumes the main launcher start up info is available when + // it is constructed from Application instance. + // This happens when jpackage signs predefined app image. + final var appImageFileOptions = superAppBuilder.externalApplication().orElseThrow().extra(); + final var mainLauncherStartupInfo = new MainLauncherStartupInfo(APPCLASS.getFrom(appImageFileOptions)); + final var launchers = superAppBuilder.launchers().orElseThrow(); + final var mainLauncher = ApplicationBuilder.overrideLauncherStartupInfo(launchers.mainLauncher(), mainLauncherStartupInfo); + superAppBuilder.launchers(new ApplicationLaunchers(MacLauncher.create(mainLauncher), launchers.additionalLaunchers())); + } + + final var app = superAppBuilder.create(); + + final var appBuilder = new MacApplicationBuilder(app); + + PREDEFINED_APP_IMAGE.findIn(options) + .map(MacBundle::new) + .map(MacBundle::infoPlistFile) + .ifPresent(appBuilder::externalInfoPlistFile); + + ICON.ifPresentIn(options, appBuilder::icon); + MAC_BUNDLE_NAME.ifPresentIn(options, appBuilder::bundleName); + MAC_BUNDLE_IDENTIFIER.ifPresentIn(options, appBuilder::bundleIdentifier); + MAC_APP_CATEGORY.ifPresentIn(options, appBuilder::category); + + final boolean sign; + final boolean appStore; + + if (PREDEFINED_APP_IMAGE.containsIn(options) && OptionUtils.bundlingOperation(options) != SIGN_MAC_APP_IMAGE) { + final var appImageFileOptions = superAppBuilder.externalApplication().orElseThrow().extra(); + sign = MAC_SIGN.getFrom(appImageFileOptions); + appStore = MAC_APP_STORE.getFrom(appImageFileOptions); + } else { + sign = MAC_SIGN.getFrom(options); + appStore = MAC_APP_STORE.getFrom(options); + } + + appBuilder.appStore(appStore); + + if (sign) { + final var signingIdentityBuilder = createSigningIdentityBuilder(options); + MAC_APP_IMAGE_SIGN_IDENTITY.ifPresentIn(options, signingIdentityBuilder::signingIdentity); + MAC_SIGNING_KEY_NAME.findIn(options).ifPresent(userName -> { + final StandardCertificateSelector domain; + if (appStore) { + domain = StandardCertificateSelector.APP_STORE_APP_IMAGE; + } else { + domain = StandardCertificateSelector.APP_IMAGE; + } + + signingIdentityBuilder.certificateSelector(StandardCertificateSelector.create(userName, domain)); + }); + + final var signingBuilder = new AppImageSigningConfigBuilder(signingIdentityBuilder); + if (appStore) { + signingBuilder.entitlementsResourceName("sandbox.plist"); + } + + app.mainLauncher().flatMap(Launcher::startupInfo).ifPresentOrElse( + signingBuilder::signingIdentifierPrefix, + () -> { + // Runtime installer does not have the main launcher, use + // 'bundleIdentifier' as the prefix by default. + var bundleIdentifier = appBuilder.create().bundleIdentifier(); + signingBuilder.signingIdentifierPrefix(bundleIdentifier + "."); + }); + MAC_BUNDLE_SIGNING_PREFIX.ifPresentIn(options, signingBuilder::signingIdentifierPrefix); + + MAC_ENTITLEMENTS.ifPresentIn(options, signingBuilder::entitlements); + + appBuilder.signingBuilder(signingBuilder); + } + + return new ApplicationWithDetails(appBuilder.create(), superAppBuilder.externalApplication()); + } + + private static MacPackageBuilder createMacPackageBuilder(Options options, ApplicationWithDetails app, PackageType type) { + + final var builder = new MacPackageBuilder(createPackageBuilder(options, app.app(), type)); + + app.externalApp() + .map(ExternalApplication::extra) + .flatMap(MAC_SIGN::findIn) + .ifPresent(builder::predefinedAppImageSigned); + + PREDEFINED_RUNTIME_IMAGE.findIn(options) + .map(MacBundle::new) + .filter(MacBundle::isValid) + .map(MacBundle::isSigned) + .ifPresent(builder::predefinedAppImageSigned); + + return builder; + } + + private static void rethrowIfNotExpiredCertificateException(Result result) { + final var ex = result.firstError().orElseThrow(); + + if (ex instanceof ExpiredCertificateException) { + return; + } + + if (ex instanceof ExceptionBox box) { + if (box.getCause() instanceof Exception cause) { + rethrowIfNotExpiredCertificateException(Result.ofError(cause)); + } + } + + throw toUnchecked(ex); + } + + private static SigningIdentityBuilder createSigningIdentityBuilder(Options options) { + final var builder = new SigningIdentityBuilder(); + MAC_SIGNING_KEYCHAIN.findIn(options).map(Path::toString).ifPresent(builder::keychain); + return builder; + } + + private static MacFileAssociation createMacFa(Options options, FileAssociation fa) { + + final var builder = new MacFileAssociationBuilder(); + + StandardFaOption.MAC_CFBUNDLETYPEROLE.ifPresentIn(options, builder::cfBundleTypeRole); + StandardFaOption.MAC_LSHANDLERRANK.ifPresentIn(options, builder::lsHandlerRank); + StandardFaOption.MAC_NSSTORETYPEKEY.ifPresentIn(options, builder::nsPersistentStoreTypeKey); + StandardFaOption.MAC_NSDOCUMENTCLASS.ifPresentIn(options, builder::nsDocumentClass); + StandardFaOption.MAC_LSTYPEISPACKAGE.ifPresentIn(options, builder::lsTypeIsPackage); + StandardFaOption.MAC_LSDOCINPLACE.ifPresentIn(options, builder::lsSupportsOpeningDocumentsInPlace); + StandardFaOption.MAC_UIDOCBROWSER.ifPresentIn(options, builder::uiSupportsDocumentBrowser); + StandardFaOption.MAC_NSEXPORTABLETYPES.ifPresentIn(options, builder::nsExportableTypes); + StandardFaOption.MAC_UTTYPECONFORMSTO.ifPresentIn(options, builder::utTypeConformsTo); + + return builder.create(fa); + } +} diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromParams.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromParams.java deleted file mode 100644 index 72c33ef6475..00000000000 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromParams.java +++ /dev/null @@ -1,384 +0,0 @@ -/* - * 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; - -import static jdk.jpackage.internal.BundlerParamInfo.createBooleanBundlerParam; -import static jdk.jpackage.internal.BundlerParamInfo.createPathBundlerParam; -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.MacPackagingPipeline.APPLICATION_LAYOUT; -import static jdk.jpackage.internal.MacRuntimeValidator.validateRuntimeHasJliLib; -import static jdk.jpackage.internal.MacRuntimeValidator.validateRuntimeHasNoBinDir; -import static jdk.jpackage.internal.StandardBundlerParam.DMG_CONTENT; -import static jdk.jpackage.internal.StandardBundlerParam.ICON; -import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE; -import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE_FILE; -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_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.Path; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.Callable; -import java.util.function.Predicate; -import jdk.jpackage.internal.ApplicationBuilder.MainLauncherStartupInfo; -import jdk.jpackage.internal.SigningIdentityBuilder.ExpiredCertificateException; -import jdk.jpackage.internal.SigningIdentityBuilder.StandardCertificateSelector; -import jdk.jpackage.internal.model.ApplicationLaunchers; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.model.FileAssociation; -import jdk.jpackage.internal.model.Launcher; -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; -import jdk.jpackage.internal.util.function.ExceptionBox; - - -final class MacFromParams { - - private static MacApplication createMacApplication( - Map params) throws ConfigException, IOException { - - final var predefinedRuntimeLayout = PREDEFINED_RUNTIME_IMAGE.findIn(params) - .map(MacPackage::guessRuntimeLayout); - - if (predefinedRuntimeLayout.isPresent()) { - validateRuntimeHasJliLib(predefinedRuntimeLayout.orElseThrow()); - if (APP_STORE.findIn(params).orElse(false)) { - validateRuntimeHasNoBinDir(predefinedRuntimeLayout.orElseThrow()); - } - } - - 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); - }), (MacLauncher _, Launcher launcher) -> { - return MacLauncher.create(launcher); - }, APPLICATION_LAYOUT, RUNTIME_BUNDLE_LAYOUT, predefinedRuntimeLayout.map(RuntimeLayout::unresolve)); - - if (hasPredefinedAppImage(params)) { - // Set the main launcher start up info. - // AppImageFile assumes the main launcher start up info is available when - // it is constructed from Application instance. - // This happens when jpackage signs predefined app image. - final var mainLauncherStartupInfo = new MainLauncherStartupInfo(superAppBuilder.mainLauncherClassName().orElseThrow()); - final var launchers = superAppBuilder.launchers().orElseThrow(); - final var mainLauncher = ApplicationBuilder.overrideLauncherStartupInfo(launchers.mainLauncher(), mainLauncherStartupInfo); - superAppBuilder.launchers(new ApplicationLaunchers(MacLauncher.create(mainLauncher), launchers.additionalLaunchers())); - } - - final var app = superAppBuilder.create(); - - final var appBuilder = new MacApplicationBuilder(app); - - if (hasPredefinedAppImage(params)) { - appBuilder.externalInfoPlistFile(PREDEFINED_APP_IMAGE.findIn(params).map(MacBundle::new).orElseThrow().infoPlistFile()); - } - - ICON.copyInto(params, appBuilder::icon); - MAC_CF_BUNDLE_NAME.copyInto(params, appBuilder::bundleName); - MAC_CF_BUNDLE_IDENTIFIER.copyInto(params, appBuilder::bundleIdentifier); - APP_CATEGORY.copyInto(params, appBuilder::category); - - final boolean sign; - final boolean appStore; - - if (hasPredefinedAppImage(params) && PACKAGE_TYPE.findIn(params).filter(Predicate.isEqual("app-image")).isEmpty()) { - final var appImageFileExtras = new MacAppImageFileExtras(superAppBuilder.externalApplication().orElseThrow()); - sign = appImageFileExtras.signed(); - appStore = appImageFileExtras.appStore(); - } else { - sign = SIGN_BUNDLE.findIn(params).orElse(false); - appStore = APP_STORE.findIn(params).orElse(false); - } - - appBuilder.appStore(appStore); - - if (sign) { - final var signingIdentityBuilder = createSigningIdentityBuilder(params); - APP_IMAGE_SIGN_IDENTITY.copyInto(params, signingIdentityBuilder::signingIdentity); - SIGNING_KEY_USER.findIn(params).ifPresent(userName -> { - final StandardCertificateSelector domain; - if (appStore) { - domain = StandardCertificateSelector.APP_STORE_APP_IMAGE; - } else { - domain = StandardCertificateSelector.APP_IMAGE; - } - - signingIdentityBuilder.certificateSelector(StandardCertificateSelector.create(userName, domain)); - }); - - final var signingBuilder = new AppImageSigningConfigBuilder(signingIdentityBuilder); - if (appStore) { - signingBuilder.entitlementsResourceName("sandbox.plist"); - } - - final var bundleIdentifier = appBuilder.create().bundleIdentifier(); - app.mainLauncher().flatMap(Launcher::startupInfo).ifPresentOrElse( - signingBuilder::signingIdentifierPrefix, - () -> { - // Runtime installer does not have main launcher, so use - // 'bundleIdentifier' as prefix by default. - signingBuilder.signingIdentifierPrefix( - bundleIdentifier + "."); - }); - SIGN_IDENTIFIER_PREFIX.copyInto(params, signingBuilder::signingIdentifierPrefix); - - ENTITLEMENTS.copyInto(params, signingBuilder::entitlements); - - appBuilder.signingBuilder(signingBuilder); - } - - return appBuilder.create(); - } - - private static MacPackageBuilder createMacPackageBuilder( - Map params, MacApplication app, - PackageType type) throws ConfigException { - final var builder = new MacPackageBuilder(createPackageBuilder(params, app, type)); - - PREDEFINED_APP_IMAGE_FILE.findIn(params) - .map(MacAppImageFileExtras::new) - .map(MacAppImageFileExtras::signed) - .ifPresent(builder::predefinedAppImageSigned); - - PREDEFINED_RUNTIME_IMAGE.findIn(params) - .map(MacBundle::new) - .filter(MacBundle::isValid) - .map(MacBundle::isSigned) - .ifPresent(builder::predefinedAppImageSigned); - - return builder; - } - - private static MacDmgPackage createMacDmgPackage( - Map params) throws ConfigException, IOException { - - final var app = APPLICATION.fetchFrom(params); - - final var superPkgBuilder = createMacPackageBuilder(params, app, MAC_DMG); - - final var pkgBuilder = new MacDmgPackageBuilder(superPkgBuilder); - - DMG_CONTENT.copyInto(params, pkgBuilder::dmgContent); - - return pkgBuilder.create(); - } - - private record WithExpiredCertificateException(Optional obj, Optional certEx) { - WithExpiredCertificateException { - if (obj.isEmpty() == certEx.isEmpty()) { - throw new IllegalArgumentException(); - } - } - - static WithExpiredCertificateException of(Callable callable) { - try { - return new WithExpiredCertificateException<>(Optional.of(callable.call()), Optional.empty()); - } catch (ExpiredCertificateException ex) { - return new WithExpiredCertificateException<>(Optional.empty(), Optional.of(ex)); - } catch (ExceptionBox ex) { - if (ex.getCause() instanceof ExpiredCertificateException certEx) { - return new WithExpiredCertificateException<>(Optional.empty(), Optional.of(certEx)); - } - throw ex; - } catch (RuntimeException ex) { - throw ex; - } catch (Throwable t) { - throw ExceptionBox.rethrowUnchecked(t); - } - } - } - - private static MacPkgPackage createMacPkgPackage( - Map params) throws ConfigException, IOException { - - // This is over complicated to make "MacSignTest.testExpiredCertificate" test pass. - - final boolean sign = SIGN_BUNDLE.findIn(params).orElse(false); - final boolean appStore = APP_STORE.findIn(params).orElse(false); - - final var appOrExpiredCertEx = WithExpiredCertificateException.of(() -> { - return APPLICATION.fetchFrom(params); - }); - - final Optional pkgBuilder; - if (appOrExpiredCertEx.obj().isPresent()) { - final var superPkgBuilder = createMacPackageBuilder(params, appOrExpiredCertEx.obj().orElseThrow(), MAC_PKG); - pkgBuilder = Optional.of(new MacPkgPackageBuilder(superPkgBuilder)); - } else { - pkgBuilder = Optional.empty(); - } - - if (sign) { - final var signingIdentityBuilder = createSigningIdentityBuilder(params); - INSTALLER_SIGN_IDENTITY.copyInto(params, signingIdentityBuilder::signingIdentity); - SIGNING_KEY_USER.findIn(params).ifPresent(userName -> { - final StandardCertificateSelector domain; - if (appStore) { - domain = StandardCertificateSelector.APP_STORE_PKG_INSTALLER; - } else { - domain = StandardCertificateSelector.PKG_INSTALLER; - } - - signingIdentityBuilder.certificateSelector(StandardCertificateSelector.create(userName, domain)); - }); - - if (pkgBuilder.isPresent()) { - pkgBuilder.orElseThrow().signingBuilder(signingIdentityBuilder); - } else { - final var expiredPkgCert = WithExpiredCertificateException.of(() -> { - return signingIdentityBuilder.create(); - }).certEx(); - expiredPkgCert.map(ConfigException::getMessage).ifPresent(Log::error); - throw appOrExpiredCertEx.certEx().orElseThrow(); - } - } - - return pkgBuilder.orElseThrow().create(); - } - - private static SigningIdentityBuilder createSigningIdentityBuilder(Map params) { - final var builder = new SigningIdentityBuilder(); - SIGNING_KEYCHAIN.copyInto(params, builder::keychain); - return builder; - } - - private static MacFileAssociation createMacFa(FileAssociation fa, Map params) { - - final var builder = new MacFileAssociationBuilder(); - - FA_MAC_CFBUNDLETYPEROLE.copyInto(params, builder::cfBundleTypeRole); - FA_MAC_LSHANDLERRANK.copyInto(params, builder::lsHandlerRank); - FA_MAC_NSSTORETYPEKEY.copyInto(params, builder::nsPersistentStoreTypeKey); - FA_MAC_NSDOCUMENTCLASS.copyInto(params, builder::nsDocumentClass); - FA_MAC_LSTYPEISPACKAGE.copyInto(params, builder::lsTypeIsPackage); - FA_MAC_LSDOCINPLACE.copyInto(params, builder::lsSupportsOpeningDocumentsInPlace); - FA_MAC_UIDOCBROWSER.copyInto(params, builder::uiSupportsDocumentBrowser); - FA_MAC_NSEXPORTABLETYPES.copyInto(params, builder::nsExportableTypes); - FA_MAC_UTTYPECONFORMSTO.copyInto(params, builder::utTypeConformsTo); - - return toFunction(builder::create).apply(fa); - } - - static final BundlerParamInfo APPLICATION = createApplicationBundlerParam( - MacFromParams::createMacApplication); - - static final BundlerParamInfo DMG_PACKAGE = createPackageBundlerParam( - MacFromParams::createMacDmgPackage); - - static final BundlerParamInfo PKG_PACKAGE = createPackageBundlerParam( - MacFromParams::createMacPkgPackage); - - private static final BundlerParamInfo MAC_CF_BUNDLE_NAME = createStringBundlerParam( - Arguments.CLIOptions.MAC_BUNDLE_NAME.getId()); - - private static final BundlerParamInfo APP_CATEGORY = createStringBundlerParam( - Arguments.CLIOptions.MAC_CATEGORY.getId()); - - private static final BundlerParamInfo ENTITLEMENTS = createPathBundlerParam( - Arguments.CLIOptions.MAC_ENTITLEMENTS.getId()); - - private static final BundlerParamInfo MAC_CF_BUNDLE_IDENTIFIER = createStringBundlerParam( - Arguments.CLIOptions.MAC_BUNDLE_IDENTIFIER.getId()); - - private static final BundlerParamInfo SIGN_IDENTIFIER_PREFIX = createStringBundlerParam( - Arguments.CLIOptions.MAC_BUNDLE_SIGNING_PREFIX.getId()); - - private static final BundlerParamInfo APP_IMAGE_SIGN_IDENTITY = createStringBundlerParam( - Arguments.CLIOptions.MAC_APP_IMAGE_SIGN_IDENTITY.getId()); - - private static final BundlerParamInfo INSTALLER_SIGN_IDENTITY = createStringBundlerParam( - Arguments.CLIOptions.MAC_INSTALLER_SIGN_IDENTITY.getId()); - - private static final BundlerParamInfo SIGNING_KEY_USER = createStringBundlerParam( - Arguments.CLIOptions.MAC_SIGNING_KEY_NAME.getId()); - - private static final BundlerParamInfo SIGNING_KEYCHAIN = createStringBundlerParam( - Arguments.CLIOptions.MAC_SIGNING_KEYCHAIN.getId()); - - private static final BundlerParamInfo PACKAGE_TYPE = createStringBundlerParam( - Arguments.CLIOptions.PACKAGE_TYPE.getId()); - - private static final BundlerParamInfo APP_STORE = createBooleanBundlerParam( - Arguments.CLIOptions.MAC_APP_STORE.getId()); - - private static final BundlerParamInfo FA_MAC_CFBUNDLETYPEROLE = createStringBundlerParam( - Arguments.MAC_CFBUNDLETYPEROLE); - - private static final BundlerParamInfo FA_MAC_LSHANDLERRANK = createStringBundlerParam( - Arguments.MAC_LSHANDLERRANK); - - private static final BundlerParamInfo FA_MAC_NSSTORETYPEKEY = createStringBundlerParam( - Arguments.MAC_NSSTORETYPEKEY); - - private static final BundlerParamInfo FA_MAC_NSDOCUMENTCLASS = createStringBundlerParam( - Arguments.MAC_NSDOCUMENTCLASS); - - private static final BundlerParamInfo FA_MAC_LSTYPEISPACKAGE = createBooleanBundlerParam( - Arguments.MAC_LSTYPEISPACKAGE); - - private static final BundlerParamInfo FA_MAC_LSDOCINPLACE = createBooleanBundlerParam( - Arguments.MAC_LSDOCINPLACE); - - private static final BundlerParamInfo FA_MAC_UIDOCBROWSER = createBooleanBundlerParam( - Arguments.MAC_UIDOCBROWSER); - - @SuppressWarnings("unchecked") - private static final BundlerParamInfo> FA_MAC_NSEXPORTABLETYPES = - new BundlerParamInfo<>( - Arguments.MAC_NSEXPORTABLETYPES, - (Class>) (Object) List.class, - null, - (s, p) -> Arrays.asList(s.split("(,|\\s)+")) - ); - - @SuppressWarnings("unchecked") - private static final BundlerParamInfo> FA_MAC_UTTYPECONFORMSTO = - new BundlerParamInfo<>( - Arguments.MAC_UTTYPECONFORMSTO, - (Class>) (Object) List.class, - null, - (s, p) -> Arrays.asList(s.split("(,|\\s)+")) - ); -} 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 9576f6a6a99..e19f234d0d6 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackageBuilder.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackageBuilder.java @@ -29,7 +29,6 @@ import static jdk.jpackage.internal.MacPackagingPipeline.LayoutUtils.packagerLay import java.nio.file.Files; 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; @@ -49,7 +48,7 @@ final class MacPackageBuilder { return pkgBuilder; } - MacPackage create() throws ConfigException { + MacPackage create() { final var app = (MacApplication)pkgBuilder.app(); 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 b82b20c0c36..53f297282ba 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackagingPipeline.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackagingPipeline.java @@ -75,7 +75,6 @@ import jdk.jpackage.internal.model.MacFileAssociation; import jdk.jpackage.internal.model.MacPackage; import jdk.jpackage.internal.model.Package; import jdk.jpackage.internal.model.PackageType; -import jdk.jpackage.internal.model.PackagerException; import jdk.jpackage.internal.util.FileUtils; import jdk.jpackage.internal.util.PathUtils; import jdk.jpackage.internal.util.function.ThrowingConsumer; @@ -225,10 +224,12 @@ final class MacPackagingPipeline { if (!app.sign()) { throw new IllegalArgumentException(); } - return toSupplier(() -> { - return new PackageBuilder(app, SignAppImagePackageType.VALUE).predefinedAppImage( - Objects.requireNonNull(env.appImageDir())).installDir(Path.of("/foo")).create(); - }).get(); + return new PackageBuilder( + app, + SignAppImagePackageType.VALUE + ).predefinedAppImage( + Objects.requireNonNull(env.appImageDir()) + ).installDir(Path.of("/foo")).create(); } static final class LayoutUtils { @@ -268,7 +269,7 @@ final class MacPackagingPipeline { static AppImageTaskAction withBundleLayout(AppImageTaskAction action) { return new AppImageTaskAction<>() { @Override - public void execute(AppImageBuildEnv env) throws IOException, PackagerException { + public void execute(AppImageBuildEnv env) throws IOException { if (!env.envLayout().runtimeDirectory().getName(0).equals(Path.of("Contents"))) { env = LayoutUtils.fromPackagerLayout(env); } @@ -610,11 +611,20 @@ final class MacPackagingPipeline { } @Override - public void execute(TaskAction taskAction) throws IOException, PackagerException { + public void execute(TaskAction taskAction) throws IOException { delegate.execute(taskAction); } } + private static final ApplicationLayout MAC_APPLICATION_LAYOUT = ApplicationLayout.build() + .launchersDirectory("Contents/MacOS") + .appDirectory("Contents/app") + .runtimeDirectory("Contents/runtime/Contents/Home") + .desktopIntegrationDirectory("Contents/Resources") + .appModsDirectory("Contents/app/mods") + .contentDirectory("Contents") + .create(); + static final MacApplicationLayout APPLICATION_LAYOUT = MacApplicationLayout.create( - ApplicationLayoutUtils.PLATFORM_APPLICATION_LAYOUT, Path.of("Contents/runtime")); + MAC_APPLICATION_LAYOUT, Path.of("Contents/runtime")); } diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgBundler.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgBundler.java deleted file mode 100644 index e827f238db3..00000000000 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgBundler.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * 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 - * 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; - -import java.nio.file.Path; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.model.MacPkgPackage; -import jdk.jpackage.internal.model.PackagerException; - -public class MacPkgBundler extends MacBaseInstallerBundler { - - @Override - public String getName() { - return I18N.getString("pkg.bundler.name"); - } - - @Override - public String getID() { - return "pkg"; - } - - @Override - public boolean validate(Map params) - throws ConfigException { - try { - Objects.requireNonNull(params); - - final var pkg = MacFromParams.PKG_PACKAGE.fetchFrom(params); - - // run basic validation to ensure requirements are met - // we are not interested in return code, only possible exception - validateAppImageAndBundeler(params); - - // hdiutil is always available so there's no need - // to test for availability. - - return true; - } catch (RuntimeException re) { - if (re.getCause() instanceof ConfigException) { - throw (ConfigException) re.getCause(); - } else { - throw new ConfigException(re); - } - } - } - - @Override - public Path execute(Map params, - Path outputParentDir) throws PackagerException { - - var pkg = MacFromParams.PKG_PACKAGE.fetchFrom(params); - - Log.verbose(I18N.format("message.building-pkg", pkg.app().name())); - - return Packager.build().outputDir(outputParentDir) - .pkg(pkg) - .env(MacBuildEnvFromParams.BUILD_ENV.fetchFrom(params)) - .pipelineBuilderMutatorFactory((env, _, outputDir) -> { - return new MacPkgPackager(env, pkg, outputDir); - }).execute(MacPackagingPipeline.build(Optional.of(pkg))); - } - - @Override - public boolean supported(boolean runtimeInstaller) { - return true; - } - - @Override - public boolean isDefault() { - return false; - } - -} diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgInstallerScripts.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgInstallerScripts.java index ecd3d19ac17..d0313af1efa 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgInstallerScripts.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgInstallerScripts.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgPackageBuilder.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgPackageBuilder.java index 663b8b16265..b81340354e1 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgPackageBuilder.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgPackageBuilder.java @@ -26,7 +26,6 @@ package jdk.jpackage.internal; import java.util.Objects; import java.util.Optional; -import jdk.jpackage.internal.model.ConfigException; import jdk.jpackage.internal.model.MacPkgPackage; import jdk.jpackage.internal.model.MacPkgPackageMixin; import jdk.jpackage.internal.model.PkgSigningConfig; @@ -42,20 +41,16 @@ final class MacPkgPackageBuilder { return this; } - MacPkgPackage create() throws ConfigException { + MacPkgPackage create() { var pkg = MacPkgPackage.create(pkgBuilder.create(), new MacPkgPackageMixin.Stub(createSigningConfig())); validatePredefinedAppImage(pkg); return pkg; } - private Optional createSigningConfig() throws ConfigException { - if (signingBuilder != null) { - return signingBuilder.create().map(cfg -> { - return new PkgSigningConfig.Stub(cfg.identity(), cfg.keychain().map(Keychain::name)); - }); - } else { - return Optional.empty(); - } + private Optional createSigningConfig() { + return Optional.ofNullable(signingBuilder).flatMap(SigningIdentityBuilder::create).map(cfg -> { + return new PkgSigningConfig.Stub(cfg.identity(), cfg.keychain().map(Keychain::name)); + }); } private static void validatePredefinedAppImage(MacPkgPackage pkg) { diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgPackager.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgPackager.java index 127b3232661..126248e2330 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgPackager.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgPackager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -32,7 +32,6 @@ import java.io.IOException; import java.io.UncheckedIOException; import java.net.URI; import java.net.URISyntaxException; -import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.text.MessageFormat; @@ -57,6 +56,7 @@ import jdk.jpackage.internal.PackagingPipeline.PackageTaskID; import jdk.jpackage.internal.PackagingPipeline.TaskID; import jdk.jpackage.internal.model.MacPkgPackage; import jdk.jpackage.internal.resources.ResourceLocator; +import jdk.jpackage.internal.util.Enquoter; import jdk.jpackage.internal.util.XmlUtils; import org.xml.sax.SAXException; @@ -108,7 +108,7 @@ record MacPkgPackager(BuildEnv env, MacPkgPackage pkg, Optional servic cmdline.addAll(allPkgbuildArgs()); try { Files.createDirectories(path.getParent()); - IOUtils.exec(new ProcessBuilder(cmdline), false, null, true, Executor.INFINITE_TIMEOUT); + Executor.of(cmdline).executeExpectSuccess(); } catch (IOException ex) { throw new UncheckedIOException(ex); } @@ -487,15 +487,13 @@ record MacPkgPackager(BuildEnv env, MacPkgPackage pkg, Optional servic Files.createDirectories(cpl.getParent()); - final var pb = new ProcessBuilder("/usr/bin/pkgbuild", + Executor.of("/usr/bin/pkgbuild", "--root", normalizedAbsolutePathString(env.appImageDir()), "--install-location", normalizedAbsolutePathString(installLocation()), "--analyze", - normalizedAbsolutePathString(cpl)); - - IOUtils.exec(pb, false, null, true, Executor.INFINITE_TIMEOUT); + normalizedAbsolutePathString(cpl)).executeExpectSuccess(); patchCPLFile(cpl); } @@ -544,8 +542,7 @@ record MacPkgPackager(BuildEnv env, MacPkgPackage pkg, Optional servic } commandLine.add(normalizedAbsolutePathString(finalPkg)); - final var pb = new ProcessBuilder(commandLine); - IOUtils.exec(pb, false, null, true, Executor.INFINITE_TIMEOUT); + Executor.of(commandLine).executeExpectSuccess(); } private static Optional createServices(BuildEnv env, MacPkgPackage pkg) { diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacRuntimeValidator.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacRuntimeValidator.java index e7ef6a23fa7..cfab12cbcba 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacRuntimeValidator.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacRuntimeValidator.java @@ -30,12 +30,11 @@ import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.util.function.Predicate; -import jdk.jpackage.internal.model.ConfigException; import jdk.jpackage.internal.model.RuntimeLayout; final class MacRuntimeValidator { - static void validateRuntimeHasJliLib(RuntimeLayout runtimeLayout) throws ConfigException { + static void validateRuntimeHasJliLib(RuntimeLayout runtimeLayout) { final var jliName = Path.of("libjli.dylib"); try (var walk = Files.walk(runtimeLayout.runtimeDirectory().resolve("lib"))) { if (walk.map(Path::getFileName).anyMatch(Predicate.isEqual(jliName))) { @@ -51,7 +50,7 @@ final class MacRuntimeValidator { runtimeLayout.unresolve().runtimeDirectory().resolve("lib/**").resolve(jliName)).create(); } - static void validateRuntimeHasNoBinDir(RuntimeLayout runtimeLayout) throws ConfigException { + static void validateRuntimeHasNoBinDir(RuntimeLayout runtimeLayout) { if (Files.isDirectory(runtimeLayout.runtimeDirectory().resolve("bin"))) { throw I18N.buildConfigException() .message("error.invalid-runtime-image-bin-dir", runtimeLayout.rootDirectory()) diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/SigningIdentityBuilder.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/SigningIdentityBuilder.java index f90e76bb23d..0f753c07569 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/SigningIdentityBuilder.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/SigningIdentityBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -40,6 +40,7 @@ import javax.naming.ldap.Rdn; import javax.security.auth.x500.X500Principal; import jdk.jpackage.internal.MacCertificateUtils.CertificateHash; import jdk.jpackage.internal.model.ConfigException; +import jdk.jpackage.internal.model.JPackageException; import jdk.jpackage.internal.model.SigningIdentity; final class SigningIdentityBuilder { @@ -82,7 +83,7 @@ final class SigningIdentityBuilder { return this; } - Optional create() throws ConfigException { + Optional create() { if (signingIdentity == null && certificateSelector == null) { return Optional.empty(); } else { @@ -90,11 +91,11 @@ final class SigningIdentityBuilder { } } - private Optional validatedKeychain() throws ConfigException { + private Optional validatedKeychain() { return Optional.ofNullable(keychain).map(Keychain::new); } - private SigningIdentity validatedSigningIdentity() throws ConfigException { + private SigningIdentity validatedSigningIdentity() { if (signingIdentity != null) { return new SigningIdentityImpl(signingIdentity); } @@ -142,15 +143,14 @@ final class SigningIdentityBuilder { } private static X509Certificate selectSigningIdentity(List certs, - CertificateSelector certificateSelector, Optional keychain) throws ConfigException { + CertificateSelector certificateSelector, Optional keychain) { Objects.requireNonNull(certificateSelector); Objects.requireNonNull(keychain); switch (certs.size()) { case 0 -> { - Log.error(I18N.format("error.cert.not.found", certificateSelector.signingIdentities().getFirst(), + throw new JPackageException(I18N.format("error.cert.not.found", + certificateSelector.signingIdentities().getFirst(), keychain.map(Keychain::name).orElse(""))); - throw I18N.buildConfigException("error.explicit-sign-no-cert") - .advice("error.explicit-sign-no-cert.advice").create(); } case 1 -> { return certs.getFirst(); diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/TempKeychain.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/TempKeychain.java index 97709678c67..2f616aafba1 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/TempKeychain.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/TempKeychain.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,15 +27,17 @@ package jdk.jpackage.internal; import java.io.Closeable; import java.io.IOException; +import java.io.UncheckedIOException; import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.function.Consumer; import jdk.internal.util.OSVersion; -import jdk.jpackage.internal.util.function.ThrowingConsumer; final class TempKeychain implements Closeable { - static void withKeychains(ThrowingConsumer> keychainConsumer, List keychains) throws Throwable { + static void withKeychains(Consumer> keychainConsumer, List keychains) { + keychains.forEach(Objects::requireNonNull); if (keychains.isEmpty() || OSVersion.current().compareTo(new OSVersion(10, 12)) < 0) { keychainConsumer.accept(keychains); @@ -43,11 +45,14 @@ final class TempKeychain implements Closeable { // we need this for OS X 10.12+ try (var tempKeychain = new TempKeychain(keychains)) { keychainConsumer.accept(tempKeychain.keychains); + } catch (IOException ex) { + throw new UncheckedIOException(ex); } } } - static void withKeychain(ThrowingConsumer keychainConsumer, Keychain keychain) throws Throwable { + static void withKeychain(Consumer keychainConsumer, Keychain keychain) { + Objects.requireNonNull(keychainConsumer); withKeychains(keychains -> { keychainConsumer.accept(keychains.getFirst()); @@ -78,7 +83,7 @@ final class TempKeychain implements Closeable { args.addAll(missingKeychains.stream().map(Keychain::asCliArg).toList()); - Executor.of(args.toArray(String[]::new)).executeExpectSuccess(); + Executor.of(args).executeExpectSuccess(); } } @@ -89,7 +94,7 @@ final class TempKeychain implements Closeable { @Override public void close() throws IOException { if (!restoreKeychainsCmd.isEmpty()) { - Executor.of(restoreKeychainsCmd.toArray(String[]::new)).executeExpectSuccess(); + Executor.of(restoreKeychainsCmd).executeExpectSuccess(); } } diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/model/MacApplication.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/model/MacApplication.java index 04ab7042ac5..cfe10e8a012 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/model/MacApplication.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/model/MacApplication.java @@ -25,13 +25,18 @@ package jdk.jpackage.internal.model; import static java.util.stream.Collectors.joining; -import static java.util.stream.Collectors.toMap; +import static java.util.stream.Collectors.toUnmodifiableMap; +import static jdk.jpackage.internal.cli.StandardAppImageFileOption.MAC_APP_STORE; +import static jdk.jpackage.internal.cli.StandardAppImageFileOption.MAC_MAIN_CLASS; +import static jdk.jpackage.internal.cli.StandardAppImageFileOption.MAC_SIGNED; import java.nio.file.Path; import java.util.Map; +import java.util.Optional; import java.util.function.Function; import java.util.stream.IntStream; import java.util.stream.Stream; +import jdk.jpackage.internal.cli.OptionValue; import jdk.jpackage.internal.util.CompositeProxy; public interface MacApplication extends Application, MacApplicationMixin { @@ -76,7 +81,14 @@ public interface MacApplication extends Application, MacApplicationMixin { @Override default Map extraAppImageFileData() { - return Stream.of(ExtraAppImageFileField.values()).collect(toMap(ExtraAppImageFileField::fieldName, x -> x.asString(this))); + return Stream.of(ExtraAppImageFileField.values()).map(field -> { + return field.findStringValue(this).map(value -> { + return Map.entry(field.fieldName(), value); + }); + }) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue)); } public static MacApplication create(Application app, MacApplicationMixin mixin) { @@ -84,23 +96,31 @@ public interface MacApplication extends Application, MacApplicationMixin { } public enum ExtraAppImageFileField { - SIGNED("signed", app -> Boolean.toString(app.sign())), - APP_STORE("app-store", app -> Boolean.toString(app.appStore())); + SIGNED(MAC_SIGNED, app -> { + return Optional.of(Boolean.toString(app.sign())); + }), + APP_STORE(MAC_APP_STORE, app -> { + return Optional.of(Boolean.toString(app.appStore())); + }), + APP_CLASS(MAC_MAIN_CLASS, app -> { + return app.mainLauncher().flatMap(Launcher::startupInfo).map(LauncherStartupInfo::qualifiedClassName); + }), + ; - ExtraAppImageFileField(String fieldName, Function getter) { - this.fieldName = fieldName; + ExtraAppImageFileField(OptionValue option, Function> getter) { + this.fieldName = option.getName(); this.getter = getter; } - public String fieldName() { + String fieldName() { return fieldName; } - String asString(MacApplication app) { + Optional findStringValue(MacApplication app) { return getter.apply(app); } private final String fieldName; - private final Function getter; + private final Function> getter; } } diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources.properties b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources.properties index afa71d84d5c..240e82dcc9b 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources.properties +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2017, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -23,26 +23,15 @@ # questions. # # - -app.bundler.name=Mac Application Image -store.bundler.name=Mac App Store Ready Bundler -dmg.bundler.name=Mac DMG Package -pkg.bundler.name=Mac PKG Package - error.invalid-cfbundle-version.advice=Set a compatible 'app-version' value. Valid versions are one to three integers separated by dots. -error.explicit-sign-no-cert=Signature explicitly requested but no signing certificate found -error.explicit-sign-no-cert.advice=Specify a valid mac-signing-key-user-name and mac-signing-keychain -error.must-sign-app-store=Mac App Store apps must be signed, and signing has been disabled by bundler configuration -error.must-sign-app-store.advice=Use --mac-sign option with appropriate user-name and keychain -error.certificate.expired=Error: Certificate expired {0} +error.certificate.expired=Certificate expired {0} error.cert.not.found=No certificate found matching [{0}] using keychain [{1}] error.multiple.certs.found=Multiple certificates matching name [{0}] found in keychain [{1}] -error.app-image.mac-sign.required=Error: --mac-sign option is required with predefined application image and with type [app-image] -error.tool.failed.with.output=Error: "{0}" failed with following output: +error.app-image.mac-sign.required=--mac-sign option is required with predefined application image and with type [app-image] +error.tool.failed.with.output="{0}" failed with following output: error.invalid-runtime-image-missing-file=Runtime image "{0}" is missing "{1}" file error.invalid-runtime-image-bin-dir=Runtime image "{0}" should not contain "bin" folder error.invalid-runtime-image-bin-dir.advice=Use --strip-native-commands jlink option when generating runtime image used with {0} option -resource.bundle-config-file=Bundle config file resource.app-info-plist=Application Info.plist resource.app-runtime-info-plist=Embedded Java Runtime Info.plist resource.runtime-info-plist=Java Runtime Info.plist @@ -60,21 +49,15 @@ resource.pkg-background-image=pkg background image resource.pkg-pdf=project definition file resource.launchd-plist-file=launchd plist file - message.bundle-name-too-long-warning={0} is set to ''{1}'', which is longer than 16 characters. For a better Mac experience consider shortening it. message.preparing-info-plist=Preparing Info.plist: {0}. message.icon-not-icns= The specified icon "{0}" is not an ICNS file and will not be used. The default icon will be used in it's place. message.version-string-too-many-components='app-version' may have between 1 and 3 numbers: 1, 1.2, 1.2.3. message.version-string-first-number-not-zero=The first number in an app-version cannot be zero or negative. -message.creating-association-with-null-extension=Creating association with null extension. -message.ignoring.symlink=Warning: codesign is skipping the symlink {0}. -message.already.signed=File already signed: {0}. -message.keychain.error=Error: unable to get keychain list. -message.building-bundle=Building Mac App Store Package for {0}. -message.invalid-identifier=invalid mac bundle identifier [{0}]. +message.keychain.error=Unable to get keychain list. +message.invalid-identifier=Invalid mac bundle identifier [{0}]. message.invalid-identifier.advice=specify identifier with "--mac-package-identifier". message.building-dmg=Building DMG package for {0}. -message.running-script=Running shell script on application image [{0}]. message.preparing-dmg-setup=Preparing dmg setup: {0}. message.creating-dmg-file=Creating DMG file: {0}. message.dmg-cannot-be-overwritten=Dmg file exists [{0}] and can not be removed. diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources_de.properties b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources_de.properties index 7e36a260c3f..3cc56bb6cdf 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources_de.properties +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources_de.properties @@ -23,24 +23,19 @@ # questions. # # - -app.bundler.name=Mac-Anwendungsimage -store.bundler.name=Mac App Store-fähiger Bundler -dmg.bundler.name=Mac-DMG-Package -pkg.bundler.name=Mac-PKG-Package - error.invalid-cfbundle-version.advice=Legen Sie einen kompatiblen Wert für "app-version" fest. Gültige Versionsnummern sind ein bis drei durch Punkte getrennte Ganzzahlen. error.explicit-sign-no-cert=Signatur wurde explizit angefordert, doch es wurde kein Signaturzertifikat gefunden error.explicit-sign-no-cert.advice=Geben Sie gültige Werte für mac-signing-key-user-name und mac-signing-keychain an -error.must-sign-app-store=Mac App Store-Apps müssen signiert werden. Die Signierung wurde von der Bundler-Konfiguration deaktiviert -error.must-sign-app-store.advice=Verwenden Sie die Option --mac-sign mit entsprechenden Werten für user-name und keychain -error.certificate.expired=Fehler: Zertifikat abgelaufen {0} +error.certificate.expired=Zertifikat abgelaufen {0} error.cert.not.found=Kein Zertifikat gefunden, das [{0}] mit Schlüsselbund [{1}] entspricht -error.multiple.certs.found=WARNUNG: Mehrere Zertifikate gefunden, die [{0}] mit Schlüsselbund [{1}] entsprechen. Es wird das erste Zertifikat verwendet -error.app-image.mac-sign.required=Fehler: Die Option "--mac-sign" ist mit einem vordefinierten Anwendungsimage und Typ [app-image] erforderlich -error.tool.failed.with.output=Fehler: "{0}" nicht erfolgreich mit folgender Ausgabe: -resource.bundle-config-file=Bundle-Konfigurationsdatei +error.multiple.certs.found=Mehrere Zertifikate mit Namen [{0}] in Schlüsselbund [{1}] gefunden +error.app-image.mac-sign.required=Die Option --mac-sign ist mit einem vordefinierten Anwendungsimage und Typ [app-image] erforderlich +error.tool.failed.with.output="{0}" war mit folgender Ausgabe nicht erfolgreich: +error.invalid-runtime-image-missing-file=Im Laufzeitimage "{0}" fehlt die Datei "{1}" +error.invalid-runtime-image-bin-dir=Laufzeitimage "{0}" darf keinen Ordner "bin" enthalten +error.invalid-runtime-image-bin-dir.advice=Verwenden Sie die jlink-Option --strip-native-commands, wenn das Laufzeitimage mit Option {0} generiert wird resource.app-info-plist=Info.plist der Anwendung +resource.app-runtime-info-plist=Eingebettete Info.plist von Java Runtime resource.runtime-info-plist=Info.plist von Java Runtime resource.entitlements=Mac-Berechtigungen resource.dmg-setup-script=DMG-Setupskript @@ -56,21 +51,15 @@ resource.pkg-background-image=PKG-Hintergrundbild resource.pkg-pdf=Projektdefinitionsdatei resource.launchd-plist-file=launchd-PLIST-Datei - message.bundle-name-too-long-warning={0} ist auf "{1}" gesetzt. Dies ist länger als 16 Zeichen. Kürzen Sie den Wert, um die Mac-Nutzungserfahrung zu verbessern. message.preparing-info-plist=Info.plist wird vorbereitet: {0}. message.icon-not-icns= Das angegebene Symbol "{0}" ist keine ICNS-Datei und wird nicht verwendet. Stattdessen wird das Standardsymbol verwendet. message.version-string-too-many-components="app-version" darf ein bis drei Zahlen aufweisen: 1, 1.2, 1.2.3. message.version-string-first-number-not-zero=Die erste Zahl in app-version darf nicht null oder negativ sein. -message.creating-association-with-null-extension=Verknüpfung mit Nullerweiterung wird erstellt. -message.ignoring.symlink=Warnung: codesign überspringt den Symlink {0}. -message.already.signed=Datei ist bereits signiert: {0}. -message.keychain.error=Fehler: Schlüsselbundliste kann nicht abgerufen werden. -message.building-bundle=Mac App Store-Package für {0} wird erstellt. +message.keychain.error=Schlüsselbundliste kann nicht abgerufen werden. message.invalid-identifier=Ungültige Mac-Bundle-ID [{0}]. message.invalid-identifier.advice=Geben Sie die ID mit "--mac-package-identifier" an. message.building-dmg=DMG-Package für {0} wird erstellt. -message.running-script=Shellskript wird auf Anwendungsimage [{0}] ausgeführt. message.preparing-dmg-setup=DMG-Setup wird vorbereitet: {0}. message.creating-dmg-file=DMG-Datei wird erstellt: {0}. message.dmg-cannot-be-overwritten=DMG-Datei [{0}] ist vorhanden und kann nicht entfernt werden. @@ -84,3 +73,5 @@ message.codesign.failed.reason.app.content="codesign" war nicht erfolgreich, und message.codesign.failed.reason.xcode.tools=Möglicher Grund für "codesign"-Fehler ist fehlender Xcode mit Befehlszeilen-Entwicklertools. Installieren Sie Xcode mit Befehlszeilen-Entwicklertools, und prüfen Sie, ob das Problem dadurch beseitigt wird. warning.unsigned.app.image=Warnung: Nicht signiertes app-image wird zum Erstellen von signiertem {0} verwendet. warning.per.user.app.image.signed=Warnung: Konfiguration der installierten Anwendung pro Benutzer wird nicht unterstützt, da "{0}" im vordefinierten signierten Anwendungsimage fehlt. +warning.non.standard.contents.sub.dir=Warnung: Der Dateiname des Verzeichnisses "{0}", das für die Option --app-content angegeben wurde, ist kein Standardunterverzeichnisname im Verzeichnis "Contents" des Anwendungs-Bundles. Möglicherweise verläuft die Codesignierung und/oder Notarisierung im Ergebnisanwendungs-Bundle nicht erfolgreich. +warning.app.content.is.not.dir=Warnung: Der Wert "{0}" der Option --app-content ist kein Verzeichnis. Möglicherweise verläuft die Codesignierung und/oder Notarisierung im Ergebnisanwendungs-Bundle nicht erfolgreich. diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources_ja.properties b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources_ja.properties index 4384d6507f9..d3150a34a86 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources_ja.properties +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources_ja.properties @@ -23,24 +23,19 @@ # questions. # # - -app.bundler.name=Macアプリケーション・イメージ -store.bundler.name=Mac App Storeの準備完了バンドラ -dmg.bundler.name=Mac DMGパッケージ -pkg.bundler.name=Mac PKGパッケージ - error.invalid-cfbundle-version.advice=互換性のある'app-version'値を設定します。有効なバージョンは、ドットで区切られた1から3つの整数です。 error.explicit-sign-no-cert=署名が明示的に要求されましたが、署名証明書が見つかりません error.explicit-sign-no-cert.advice=有効なmac-signing-key-user-nameおよびmac-signing-keychainを指定してください -error.must-sign-app-store=Mac App Storeアプリケーションは署名されている必要がありますが、署名はバンドラ構成によって無効化されています -error.must-sign-app-store.advice=--mac-signオプションを適切なuser-nameおよびkeychain付きで使用してください -error.certificate.expired=エラー: 証明書は{0}に期限が切れました +error.certificate.expired=証明書が期限切れです{0} error.cert.not.found=キーチェーン[{1}]を使用する[{0}]と一致する証明書が見つかりません -error.multiple.certs.found=警告: キーチェーン[{1}]を使用する[{0}]と一致する複数の証明書が見つかりました。最初のものを使用します -error.app-image.mac-sign.required=エラー: --mac-signオプションは、事前定義済アプリケーション・イメージおよびタイプ[app-image]で必要です -error.tool.failed.with.output=エラー: "{0}"は次の出力で失敗しました: -resource.bundle-config-file=バンドル構成ファイル +error.multiple.certs.found=名前[{0}]に一致する複数の証明書がキーチェーン[{1}]で見つかりました +error.app-image.mac-sign.required=--mac-signオプションは、事前定義済アプリケーション・イメージおよびタイプ[app-image]で必要です +error.tool.failed.with.output="{0}"は次の出力で失敗しました: +error.invalid-runtime-image-missing-file=ランタイム・イメージ"{0}"に"{1}"ファイルがありません +error.invalid-runtime-image-bin-dir=ランタイム・イメージ"{0}"に"bin"フォルダを含めることはできません +error.invalid-runtime-image-bin-dir.advice={0}オプションとともに使用されるランタイム・イメージを生成する場合は、--strip-native-commands jlinkオプションを使用します resource.app-info-plist=アプリケーションのInfo.plist +resource.app-runtime-info-plist=埋込みJavaランタイムのInfo.plist resource.runtime-info-plist=JavaランタイムのInfo.plist resource.entitlements=Mac権限 resource.dmg-setup-script=DMG設定スクリプト @@ -56,21 +51,15 @@ resource.pkg-background-image=pkg背景イメージ resource.pkg-pdf=プロジェクト定義ファイル resource.launchd-plist-file=launchd plistファイル - message.bundle-name-too-long-warning={0}が16文字を超える''{1}''に設定されています。Macでの操作性をより良くするために短くすることを検討してください。 message.preparing-info-plist=Info.plistを準備しています: {0}。 message.icon-not-icns= 指定したアイコン"{0}"はICNSファイルではなく、使用されません。デフォルト・アイコンがその位置に使用されます。 message.version-string-too-many-components='app-version'には、1、1.2、1.2.3など1から3の数字を使用できます。 message.version-string-first-number-not-zero=pp-versionの最初の数字は、ゼロまたは負の値にできません。 -message.creating-association-with-null-extension=null拡張子との関連付けを作成しています。 -message.ignoring.symlink=警告: codesignがsymlink {0}をスキップしています -message.already.signed=ファイルはすでに署名されています: {0}。 -message.keychain.error=エラー: キーチェーン・リストを取得できません。 -message.building-bundle={0}のMac App Storeパッケージを作成しています。 +message.keychain.error=キーチェーン・リストを取得できません。 message.invalid-identifier=macバンドル識別子[{0}]が無効です。 message.invalid-identifier.advice="--mac-package-identifier"で識別子を指定してください。 message.building-dmg={0}のDMGパッケージを作成しています -message.running-script=アプリケーション・イメージ[{0}]でシェル・スクリプトを実行しています。 message.preparing-dmg-setup=dmgの設定を準備しています: {0} message.creating-dmg-file=DMGファイルを作成しています: {0} message.dmg-cannot-be-overwritten=Dmgファイルは存在し[{0}]、削除できません。 @@ -84,3 +73,5 @@ message.codesign.failed.reason.app.content="codesign"が失敗したため、追 message.codesign.failed.reason.xcode.tools="codesign"失敗の考えられる理由は、Xcodeとコマンドライン・デベロッパ・ツールの欠落です。Xcodeとコマンドライン・デベロッパ・ツールをインストールして、問題が解決されるかを確認してください。 warning.unsigned.app.image=警告: 署名されていないapp-imageを使用して署名された{0}を作成します。 warning.per.user.app.image.signed=警告: 事前定義済の署名付きアプリケーション・イメージに"{0}"がないため、インストール済アプリケーションのユーザーごとの構成はサポートされません。 +warning.non.standard.contents.sub.dir=警告: --app-contentオプションに指定されたディレクトリ"{0}"のファイル名が、アプリケーション・バンドルの"Contents"ディレクトリ内の標準サブディレクトリ名ではありません。結果アプリケーション・バンドルは、コード署名および/または公証に失敗することがあります。 +warning.app.content.is.not.dir=警告: --app-contentオプションの値"{0}"はディレクトリではありません。結果アプリケーション・バンドルは、コード署名または公証(あるいはその両方)に失敗することがあります。 diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources_zh_CN.properties b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources_zh_CN.properties index 09c6d77694a..8ca2219b72f 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources_zh_CN.properties +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources_zh_CN.properties @@ -23,24 +23,19 @@ # questions. # # - -app.bundler.name=Mac 应用程序映像 -store.bundler.name=支持 Mac App Store 的打包程序 -dmg.bundler.name=Mac DMG 程序包 -pkg.bundler.name=Mac PKG 程序包 - error.invalid-cfbundle-version.advice=设置兼容的 'app-version' 值。有效版本包含一到三个用点分隔的整数。 error.explicit-sign-no-cert=已明确请求签名,但找不到签名证书 error.explicit-sign-no-cert.advice=指定有效的 mac-signing-key-user-name 和 mac-signing-keychain -error.must-sign-app-store=Mac App Store 应用程序必须签名, 而打包程序配置已禁用签名 -error.must-sign-app-store.advice=将 --mac-sign 选项用于适当的用户名和密钥链 -error.certificate.expired=错误: 证书已失效 {0} +error.certificate.expired=证书已到期 {0} error.cert.not.found=使用密钥链 [{1}] 找不到与 [{0}] 匹配的证书 -error.multiple.certs.found=警告:使用密钥链 [{1}] 找到多个与 [{0}] 匹配的证书,将使用第一个证书 -error.app-image.mac-sign.required=错误:预定义的应用程序映像和类型 [app image] 需要 --mac-sign 选项 -error.tool.failed.with.output=错误:"{0}" 失败,显示以下输出: -resource.bundle-config-file=包配置文件 +error.multiple.certs.found=在密钥链 [{1}] 中找到多个与名称 [{0}] 匹配的证书 +error.app-image.mac-sign.required=预定义的应用程序映像和类型 [app-image] 需要 --mac-sign 选项 +error.tool.failed.with.output="{0}" 失败,显示以下输出: +error.invalid-runtime-image-missing-file=运行时映像 "{0}" 缺少 "{1}" 文件 +error.invalid-runtime-image-bin-dir=运行时映像 "{0}" 不应包含 "bin" 文件夹 +error.invalid-runtime-image-bin-dir.advice=生成与 {0} 选项一起使用的运行时映像时,使用 --strip-native-commands jlink 选项 resource.app-info-plist=应用程序 Info.plist +resource.app-runtime-info-plist=嵌入式 Java 运行时 Info.plist resource.runtime-info-plist=Java 运行时 Info.plist resource.entitlements=Mac 权利 resource.dmg-setup-script=DMG 设置脚本 @@ -56,21 +51,15 @@ resource.pkg-background-image=pkg 背景图像 resource.pkg-pdf=项目定义文件 resource.launchd-plist-file=launchd plist 文件 - message.bundle-name-too-long-warning={0}已设置为 ''{1}'', 其长度超过了 16 个字符。为了获得更好的 Mac 体验, 请考虑将其缩短。 message.preparing-info-plist=正在准备 Info.plist: {0}。 message.icon-not-icns= 指定的图标 "{0}" 不是 ICNS 文件, 不会使用。将使用默认图标代替。 message.version-string-too-many-components='app-version' 可以包含 1 到 3 个数字:1、1.2、1.2.3。 message.version-string-first-number-not-zero=app-version 中的第一个数字不能为零或负数。 -message.creating-association-with-null-extension=正在使用空扩展名创建关联。 -message.ignoring.symlink=警告: codesign 正在跳过符号链接 {0}。 -message.already.signed=文件已签名:{0}。 -message.keychain.error=错误:无法获取密钥链列表。 -message.building-bundle=正在为 {0} 构建 Mac App Store 程序包。 -message.invalid-identifier=无效的 Mac 包标识符 [{0}]。 +message.keychain.error=无法获取密钥链列表。 +message.invalid-identifier=mac 包标识符 [{0}] 无效。 message.invalid-identifier.advice=请使用 "--mac-package-identifier" 指定标识符。 message.building-dmg=正在为 {0} 构建 DMG 程序包。 -message.running-script=正在应用程序映像 [{0}] 上运行 shell 脚本。 message.preparing-dmg-setup=正在准备 dmg 设置: {0}。 message.creating-dmg-file=正在创建 DMG 文件: {0}。 message.dmg-cannot-be-overwritten=Dmg 文件已存在 [{0}] 且无法删除。 @@ -84,3 +73,5 @@ message.codesign.failed.reason.app.content="codesign" 失败,并通过 "--app- message.codesign.failed.reason.xcode.tools="codesign" 失败可能是因为缺少带命令行开发人员工具的 Xcode。请安装带命令行开发人员工具的 Xcode,看看是否可以解决问题。 warning.unsigned.app.image=警告:使用未签名的 app-image 生成已签名的 {0}。 warning.per.user.app.image.signed=警告:由于预定义的已签名应用程序映像中缺少 "{0}",不支持对已安装应用程序的每用户配置提供支持。 +warning.non.standard.contents.sub.dir=警告:为 --app-content 选项指定的目录 "{0}" 的文件名不是应用程序包的 "Contents" 目录中的标准子目录名称。结果应用程序包可能会使代码签名和/或公证失败。 +warning.app.content.is.not.dir=警告:--app-content 选项的值 "{0}" 不是目录。结果应用程序包可能会使代码签名和/或公证失败。 diff --git a/src/jdk.jpackage/macosx/classes/module-info.java.extra b/src/jdk.jpackage/macosx/classes/module-info.java.extra index 1496167cd4a..e6202dd1156 100644 --- a/src/jdk.jpackage/macosx/classes/module-info.java.extra +++ b/src/jdk.jpackage/macosx/classes/module-info.java.extra @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,8 +23,5 @@ * questions. */ -provides jdk.jpackage.internal.Bundler with - jdk.jpackage.internal.MacAppBundler, - jdk.jpackage.internal.MacDmgBundler, - jdk.jpackage.internal.MacPkgBundler; - +provides jdk.jpackage.internal.cli.CliBundlingEnvironment with + jdk.jpackage.internal.MacBundlingEnvironment; diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AddLauncherArguments.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AddLauncherArguments.java deleted file mode 100644 index 93d037c6a45..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AddLauncherArguments.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. - * 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; - -import java.nio.file.Path; -import java.util.HashMap; -import java.util.Map; -import java.util.List; -import java.util.Optional; - -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; - -/* - * AddLauncherArguments - * - * Processes a add-launcher properties file to create the Map of - * bundle params applicable to the add-launcher: - * - * BundlerParams p = (new AddLauncherArguments(file)).getLauncherMap(); - * - * A add-launcher is another executable program generated by either the - * create-app-image mode or the create-installer mode. - * The add-launcher may be the same program with different configuration, - * or a completely different program created from the same files. - * - * There may be multiple add-launchers, each created by using the - * command line arg "--add-launcher - * - * The add-launcher properties file may have any of: - * - * appVersion - * description - * module - * main-jar - * main-class - * icon - * arguments - * java-options - * launcher-as-service - * win-console - * win-shortcut - * win-menu - * linux-app-category - * linux-shortcut - * - */ -class AddLauncherArguments { - - private final String name; - private final String filename; - private Map allArgs; - private Map bundleParams; - - AddLauncherArguments(String name, String filename) { - this.name = name; - this.filename = filename; - } - - private void initLauncherMap() { - if (bundleParams != null) { - return; - } - - allArgs = Arguments.getPropertiesFromFile(filename); - allArgs.put(CLIOptions.NAME.getId(), name); - - bundleParams = new HashMap<>(); - String mainJar = getOptionValue(CLIOptions.MAIN_JAR); - String mainClass = getOptionValue(CLIOptions.APPCLASS); - String module = getOptionValue(CLIOptions.MODULE); - - if (module != null && mainClass != null) { - Arguments.putUnlessNull(bundleParams, CLIOptions.MODULE.getId(), - module + "/" + mainClass); - } else if (module != null) { - Arguments.putUnlessNull(bundleParams, CLIOptions.MODULE.getId(), - module); - } else { - Arguments.putUnlessNull(bundleParams, CLIOptions.MAIN_JAR.getId(), - mainJar); - Arguments.putUnlessNull(bundleParams, CLIOptions.APPCLASS.getId(), - mainClass); - } - - Arguments.putUnlessNull(bundleParams, CLIOptions.NAME.getId(), - getOptionValue(CLIOptions.NAME)); - - Arguments.putUnlessNull(bundleParams, CLIOptions.VERSION.getId(), - getOptionValue(CLIOptions.VERSION)); - - Arguments.putUnlessNull(bundleParams, CLIOptions.DESCRIPTION.getId(), - getOptionValue(CLIOptions.DESCRIPTION)); - - Arguments.putUnlessNull(bundleParams, CLIOptions.RELEASE.getId(), - getOptionValue(CLIOptions.RELEASE)); - - Arguments.putUnlessNull(bundleParams, CLIOptions.ICON.getId(), - Optional.ofNullable(getOptionValue(CLIOptions.ICON)).map( - Path::of).orElse(null)); - - Arguments.putUnlessNull(bundleParams, - CLIOptions.LAUNCHER_AS_SERVICE.getId(), getOptionValue( - CLIOptions.LAUNCHER_AS_SERVICE)); - - if (OperatingSystem.isWindows()) { - Arguments.putUnlessNull(bundleParams, - CLIOptions.WIN_CONSOLE_HINT.getId(), - getOptionValue(CLIOptions.WIN_CONSOLE_HINT)); - Arguments.putUnlessNull(bundleParams, CLIOptions.WIN_SHORTCUT_HINT.getId(), - getOptionValue(CLIOptions.WIN_SHORTCUT_HINT)); - 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, CLIOptions.LINUX_SHORTCUT_HINT.getId(), - getOptionValue(CLIOptions.LINUX_SHORTCUT_HINT)); - } - - // "arguments" and "java-options" even if value is null: - if (allArgs.containsKey(CLIOptions.ARGUMENTS.getId())) { - String argumentStr = getOptionValue(CLIOptions.ARGUMENTS); - bundleParams.put(CLIOptions.ARGUMENTS.getId(), - Arguments.getArgumentList(argumentStr)); - } - - if (allArgs.containsKey(CLIOptions.JAVA_OPTIONS.getId())) { - String jvmargsStr = getOptionValue(CLIOptions.JAVA_OPTIONS); - bundleParams.put(CLIOptions.JAVA_OPTIONS.getId(), - Arguments.getArgumentList(jvmargsStr)); - } - } - - private String getOptionValue(CLIOptions option) { - if (option == null || allArgs == null) { - return null; - } - - String id = option.getId(); - - if (allArgs.containsKey(id)) { - return allArgs.get(id); - } - - return null; - } - - Map getLauncherMap() { - initLauncherMap(); - return bundleParams; - } - - static Map merge( - Map original, - Map additional, String... exclude) { - Map tmp = new HashMap<>(original); - List.of(exclude).forEach(tmp::remove); - - // remove LauncherData from map so it will be re-computed - tmp.remove(LAUNCHER_DATA.getID()); - // remove "application-name" so it will be re-computed - tmp.remove(APP_NAME.getID()); - - if (additional.containsKey(CLIOptions.MODULE.getId())) { - tmp.remove(CLIOptions.MAIN_JAR.getId()); - tmp.remove(CLIOptions.APPCLASS.getId()); - } else if (additional.containsKey(CLIOptions.MAIN_JAR.getId())) { - tmp.remove(CLIOptions.MODULE.getId()); - } - if (additional.containsKey(CLIOptions.ARGUMENTS.getId())) { - // if add launcher properties file contains "arguments", even with - // null value, disregard the "arguments" from command line - tmp.remove(CLIOptions.ARGUMENTS.getId()); - } - if (additional.containsKey(CLIOptions.JAVA_OPTIONS.getId())) { - // same thing for java-options - tmp.remove(CLIOptions.JAVA_OPTIONS.getId()); - } - tmp.putAll(additional); - return tmp; - } - -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageBundler.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageBundler.java deleted file mode 100644 index 192630a5656..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageBundler.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * 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 - * 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; - -import static jdk.jpackage.internal.StandardBundlerParam.APP_NAME; -import static jdk.jpackage.internal.StandardBundlerParam.LAUNCHER_DATA; -import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.text.MessageFormat; -import java.util.Map; -import java.util.Objects; -import jdk.internal.util.OperatingSystem; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.model.PackagerException; - - -class AppImageBundler extends AbstractBundler { - - @Override - public final String getName() { - return I18N.getString("app.bundler.name"); - } - - @Override - public final String getID() { - return "app"; - } - - @Override - public final String getBundleType() { - return "IMAGE"; - } - - @Override - public final boolean validate(Map params) - throws ConfigException { - try { - Objects.requireNonNull(params); - - if (!params.containsKey(PREDEFINED_APP_IMAGE.getID()) - && !StandardBundlerParam.isRuntimeInstaller(params)) { - LAUNCHER_DATA.fetchFrom(params); - } - - if (paramsValidator != null) { - paramsValidator.validate(params); - } - } catch (RuntimeException re) { - if (re.getCause() instanceof ConfigException) { - throw (ConfigException) re.getCause(); - } else { - throw new ConfigException(re); - } - } - - return true; - } - - @Override - public final Path execute(Map params, - Path outputParentDir) throws PackagerException { - - final var predefinedAppImage = PREDEFINED_APP_IMAGE.fetchFrom(params); - - try { - if (predefinedAppImage == null) { - Path rootDirectory = createRoot(params, outputParentDir); - appImageSupplier.prepareApplicationFiles(params, rootDirectory); - return rootDirectory; - } else { - appImageSupplier.prepareApplicationFiles(params, predefinedAppImage); - return predefinedAppImage; - } - } catch (PackagerException pe) { - throw pe; - } catch (RuntimeException|IOException ex) { - Log.verbose(ex); - throw new PackagerException(ex); - } - } - - @Override - public final boolean supported(boolean runtimeInstaller) { - return true; - } - - @Override - public final boolean isDefault() { - return false; - } - - @FunctionalInterface - static interface AppImageSupplier { - - void prepareApplicationFiles(Map params, - Path root) throws PackagerException, IOException; - } - - final AppImageBundler setAppImageSupplier(AppImageSupplier v) { - appImageSupplier = v; - return this; - } - - final AppImageBundler setParamsValidator(ParamsValidator v) { - paramsValidator = v; - return this; - } - - @FunctionalInterface - interface ParamsValidator { - void validate(Map params) throws ConfigException; - } - - private Path createRoot(Map params, - Path outputDirectory) throws PackagerException, IOException { - - IOUtils.writableOutputDir(outputDirectory); - - String imageName = APP_NAME.fetchFrom(params); - if (OperatingSystem.isMacOS()) { - imageName = imageName + ".app"; - } - - Log.verbose(MessageFormat.format( - I18N.getString("message.creating-app-bundle"), - imageName, outputDirectory.toAbsolutePath())); - - // Create directory structure - Path rootDirectory = outputDirectory.resolve(imageName); - if (Files.exists(rootDirectory)) { - throw new PackagerException("error.root-exists", - rootDirectory.toAbsolutePath().toString()); - } - - Files.createDirectories(rootDirectory); - - return rootDirectory; - } - - private ParamsValidator paramsValidator; - private AppImageSupplier appImageSupplier; -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageFile.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageFile.java index 8b8a22edc56..75ead9d08ad 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageFile.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageFile.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,148 +25,166 @@ package jdk.jpackage.internal; import static java.util.stream.Collectors.joining; -import static java.util.stream.Collectors.toMap; -import static java.util.stream.Collectors.toSet; +import static java.util.stream.Collectors.toUnmodifiableMap; +import static java.util.stream.Collectors.toUnmodifiableSet; +import static jdk.jpackage.internal.cli.StandardAppImageFileOption.APP_VERSION; +import static jdk.jpackage.internal.cli.StandardAppImageFileOption.DESCRIPTION; +import static jdk.jpackage.internal.cli.StandardAppImageFileOption.LAUNCHER_AS_SERVICE; +import static jdk.jpackage.internal.cli.StandardAppImageFileOption.LAUNCHER_NAME; import static jdk.jpackage.internal.util.function.ThrowingFunction.toFunction; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; +import java.util.ArrayList; 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.Set; import java.util.stream.Stream; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import jdk.internal.util.OperatingSystem; +import jdk.jpackage.internal.cli.OptionValue; +import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.cli.StandardAppImageFileOption.AppImageFileOptionScope; +import jdk.jpackage.internal.cli.StandardAppImageFileOption.InvalidOptionValueException; +import jdk.jpackage.internal.cli.StandardAppImageFileOption.MissingMandatoryOptionException; import jdk.jpackage.internal.model.Application; import jdk.jpackage.internal.model.ApplicationLayout; -import jdk.jpackage.internal.model.ConfigException; import jdk.jpackage.internal.model.ExternalApplication; +import jdk.jpackage.internal.model.JPackageException; import jdk.jpackage.internal.model.Launcher; import jdk.jpackage.internal.util.XmlUtils; +import jdk.jpackage.internal.util.function.ExceptionBox; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.xml.sax.SAXException; -final class AppImageFile implements ExternalApplication { +final class AppImageFile { AppImageFile(Application app) { - this(new ApplicationData(app)); - } - - private AppImageFile(ApplicationData app) { - - appVersion = app.version(); - launcherName = app.mainLauncherName(); - mainClass = app.mainLauncherMainClassName(); - extra = app.extra; - creatorVersion = getVersion(); - creatorPlatform = getPlatform(); - addLauncherInfos = app.additionalLaunchers; - } - - @Override - public List getAddLaunchers() { - return addLauncherInfos; - } - - @Override - public String getAppVersion() { - return appVersion; - } - - @Override - public String getAppName() { - return launcherName; - } - - @Override - public String getLauncherName() { - return launcherName; - } - - @Override - public String getMainClass() { - return mainClass; - } - - @Override - public Map getExtra() { - return extra; + appVersion = Objects.requireNonNull(app.version()); + extra = Objects.requireNonNull(app.extraAppImageFileData()); + launcherInfos = app.launchers().stream().map(LauncherInfo::new).toList(); } /** - * Saves file with application image info in application image using values - * from this instance. + * Writes the values captured in this instance into the application image info + * file in the given application layout. + *

    + * It is an equivalent to calling + * {@link #save(ApplicationLayout, OperatingSystem)} method with + * {@code OperatingSystem.current()} for the second parameter. + * + * @param appLayout the application layout + * @throws IOException if an I/O error occurs when writing */ void save(ApplicationLayout appLayout) throws IOException { + save(appLayout, OperatingSystem.current()); + } + + /** + * Writes the values captured in this instance into the application image info + * file in the given application layout. + * + * @param appLayout the application layout + * @param os the target OS + * @throws IOException if an I/O error occurs when writing + */ + void save(ApplicationLayout appLayout, OperatingSystem os) throws IOException { XmlUtils.createXml(getPathInAppImage(appLayout), xml -> { xml.writeStartElement("jpackage-state"); - xml.writeAttribute("version", creatorVersion); - xml.writeAttribute("platform", creatorPlatform); + xml.writeAttribute("version", getVersion()); + xml.writeAttribute("platform", getPlatform(os)); xml.writeStartElement("app-version"); xml.writeCharacters(appVersion); xml.writeEndElement(); - xml.writeStartElement("main-launcher"); - xml.writeCharacters(launcherName); - xml.writeEndElement(); - - xml.writeStartElement("main-class"); - xml.writeCharacters(mainClass); - xml.writeEndElement(); - for (var extraKey : extra.keySet().stream().sorted().toList()) { xml.writeStartElement(extraKey); xml.writeCharacters(extra.get(extraKey)); xml.writeEndElement(); } - for (var li : addLauncherInfos) { - xml.writeStartElement("add-launcher"); - xml.writeAttribute("name", li.name()); - xml.writeAttribute("service", Boolean.toString(li.service())); - for (var extraKey : li.extra().keySet().stream().sorted().toList()) { - xml.writeStartElement(extraKey); - xml.writeCharacters(li.extra().get(extraKey)); - xml.writeEndElement(); - } - xml.writeEndElement(); + launcherInfos.getFirst().save(xml, "main-launcher"); + + for (var li : launcherInfos.subList(1, launcherInfos.size())) { + li.save(xml, "add-launcher"); } }); } /** - * Returns path to application image info file. - * @param appLayout - application layout + * Returns the path to the application image info file in the given application layout. + * + * @param appLayout the application layout */ static Path getPathInAppImage(ApplicationLayout appLayout) { return appLayout.appDirectory().resolve(FILENAME); } /** - * Loads application image info from application image. - * @param appImageDir - path at which to resolve the given application layout - * @param appLayout - application layout + * Loads application image info from the specified application layout. + *

    + * It is an equivalent to calling + * {@link #load(ApplicationLayout, OperatingSystem)} method with + * {@code OperatingSystem.current()} for the second parameter. + * + * @param appLayout the application layout */ - static AppImageFile load(Path appImageDir, ApplicationLayout appLayout) throws ConfigException, IOException { - var srcFilePath = getPathInAppImage(appLayout.resolveAt(appImageDir)); + static ExternalApplication load(ApplicationLayout appLayout) { + return load(appLayout, OperatingSystem.current()); + } + + /** + * Loads application image info from the specified application layout and OS. + * + * @param appLayout the application layout + * @param os the OS defining extra properties of the application and + * additional launchers + */ + static ExternalApplication load(ApplicationLayout appLayout, OperatingSystem os) { + Objects.requireNonNull(appLayout); + Objects.requireNonNull(os); + + final var appImageDir = appLayout.rootDirectory(); + final var appImageFilePath = getPathInAppImage(appLayout); + final var relativeAppImageFilePath = appImageDir.relativize(appImageFilePath); + try { - final Document doc = XmlUtils.initDocumentBuilder().parse(Files.newInputStream(srcFilePath)); + // + // Use javax.xml.parsers.DocumentBuilder#parse(java.io.InputStream). + // Don't use javax.xml.parsers.DocumentBuilder#parse(java.io.File) as this will introduce + // dependency on how the XML parser reports filesystem I/O errors. + // E.g.: the default JDK XML parser throws java.io.FileNotFoundException if the supplied + // directory is not found and throws org.xml.sax.SAXParseException if the supplied file is a directory. + // Another DOM XML parser (a different version of Xerces?) may behave differently. + // + // The use of javax.xml.parsers.DocumentBuilder#parse(java.io.InputStream) eliminates + // differences in how XML parsers handle file system I/O errors. + // Filesystem I/O is delegated to java.nio.file.Files#readAllBytes(java.nio.file.Path), + // XML parser deals with the byte stream in memory and the error handling code + // doesn't depend on how XML parser reports filesystem I/O errors because + // it reads data from memory, not from the filesystem. + // + final Document doc = XmlUtils.initDocumentBuilder().parse( + new ByteArrayInputStream(Files.readAllBytes(appImageFilePath))); final XPath xPath = XPathFactory.newInstance().newXPath(); final var isPlatformValid = XmlUtils.queryNodes(doc, xPath, "/jpackage-state/@platform").findFirst().map( - Node::getNodeValue).map(getPlatform()::equals).orElse(false); + Node::getNodeValue).map(getPlatform(os)::equals).orElse(false); if (!isPlatformValid) { throw new InvalidAppImageFileException(); } @@ -177,102 +195,73 @@ final class AppImageFile implements ExternalApplication { throw new InvalidAppImageFileException(); } - final AppImageProperties props; + final var appOptions = AppImageFileOptionScope.APP.parse(appImageFilePath, AppImageProperties.main(doc, xPath), os); + + final var mainLauncherOptions = LauncherElement.MAIN.readAll(doc, xPath).stream().reduce((_, second) -> { + return second; + }).map(launcherProps -> { + return AppImageFileOptionScope.LAUNCHER.parse(appImageFilePath, launcherProps, os); + }).orElseThrow(InvalidAppImageFileException::new); + + final var addLauncherOptions = LauncherElement.ADDITIONAL.readAll(doc, xPath).stream().map(launcherProps -> { + return AppImageFileOptionScope.LAUNCHER.parse(appImageFilePath, launcherProps, os); + }).toList(); + try { - props = AppImageProperties.main(doc, xPath); - } catch (IllegalArgumentException ex) { + return ExternalApplication.create(Options.concat(appOptions, mainLauncherOptions), addLauncherOptions, os); + } catch (NoSuchElementException ex) { throw new InvalidAppImageFileException(ex); } - final var additionalLaunchers = AppImageProperties.launchers(doc, xPath).stream().map(launcherProps -> { - try { - return new LauncherInfo(launcherProps.get("name"), - launcherProps.find("service").map(Boolean::parseBoolean).orElse(false), launcherProps.getExtra()); - } catch (IllegalArgumentException ex) { - throw new InvalidAppImageFileException(ex); - } - }).toList(); - - return new AppImageFile(new ApplicationData(props.get("app-version"), props.get("main-launcher"), - props.get("main-class"), props.getExtra(), additionalLaunchers)); - } catch (XPathExpressionException ex) { // This should never happen as XPath expressions should be correct - throw new RuntimeException(ex); + throw ExceptionBox.toUnchecked(ex); } catch (SAXException ex) { - // Exception reading input XML (probably malformed XML) - throw new IOException(ex); + // Malformed input XML + throw new JPackageException(I18N.format("error.malformed-app-image-file", relativeAppImageFilePath, appImageDir), ex); } catch (NoSuchFileException ex) { - throw I18N.buildConfigException("error.foreign-app-image", appImageDir).create(); - } catch (InvalidAppImageFileException ex) { + // Don't save the original exception as its error message is redundant. + throw new JPackageException(I18N.format("error.missing-app-image-file", relativeAppImageFilePath, appImageDir)); + } catch (InvalidAppImageFileException|InvalidOptionValueException|MissingMandatoryOptionException ex) { // Invalid input XML - throw I18N.buildConfigException("error.invalid-app-image", appImageDir, srcFilePath).create(); + throw new JPackageException(I18N.format("error.invalid-app-image-file", relativeAppImageFilePath, appImageDir), ex); + } catch (IOException ex) { + throw new JPackageException(I18N.format("error.reading-app-image-file", relativeAppImageFilePath, appImageDir), ex); } } - static boolean getBooleanExtraFieldValue(String fieldId, ExternalApplication appImageFile) { - Objects.requireNonNull(fieldId); - Objects.requireNonNull(appImageFile); - return Optional.ofNullable(appImageFile.getExtra().get(fieldId)).map(Boolean::parseBoolean).orElse(false); - } - static String getVersion() { return System.getProperty("java.version"); } - static String getPlatform() { - return PLATFORM_LABELS.get(OperatingSystem.current()); + static String getPlatform(OperatingSystem os) { + return Objects.requireNonNull(PLATFORM_LABELS.get(Objects.requireNonNull(os))); } + private static final class AppImageProperties { - private AppImageProperties(Map data, Set stdKeys) { - this.data = data; - this.stdKeys = stdKeys; + + static Map main(Document xml, XPath xPath) throws XPathExpressionException { + return queryProperties(xml.getDocumentElement(), xPath, MAIN_PROPERTIES_XPATH_QUERY); } - static AppImageProperties main(Document xml, XPath xPath) throws XPathExpressionException { - final var data = queryProperties(xml.getDocumentElement(), xPath, MAIN_PROPERTIES_XPATH_QUERY); - return new AppImageProperties(data, MAIN_ELEMENT_NAMES); - } + static Map launcher(Element launcherNode, XPath xPath) throws XPathExpressionException { + final var attrData = XmlUtils.toStream(launcherNode.getAttributes()) + .collect(toUnmodifiableMap(Node::getNodeName, Node::getNodeValue)); - static AppImageProperties launcher(Element addLauncherNode, XPath xPath) throws XPathExpressionException { - final var attrData = XmlUtils.toStream(addLauncherNode.getAttributes()) - .collect(toMap(Node::getNodeName, Node::getNodeValue)); - - final var extraData = queryProperties(addLauncherNode, xPath, LAUNCHER_PROPERTIES_XPATH_QUERY); + final var extraData = queryProperties(launcherNode, xPath, LAUNCHER_PROPERTIES_XPATH_QUERY); final Map data = new HashMap<>(attrData); data.putAll(extraData); - return new AppImageProperties(data, LAUNCHER_ATTR_NAMES); - } - - static List launchers(Document xml, XPath xPath) throws XPathExpressionException { - return XmlUtils.queryNodes(xml, xPath, "/jpackage-state/add-launcher") - .map(Element.class::cast).map(toFunction(e -> { - return launcher(e, xPath); - })).toList(); - } - - String get(String name) { - return find(name).orElseThrow(InvalidAppImageFileException::new); - } - - Optional find(String name) { - return Optional.ofNullable(data.get(name)); - } - - Map getExtra() { - Map extra = new HashMap<>(data); - stdKeys.forEach(extra::remove); - return extra; + return data; } private static Map queryProperties(Element e, XPath xPath, String xpathExpr) throws XPathExpressionException { return XmlUtils.queryNodes(e, xPath, xpathExpr) .map(Element.class::cast) - .collect(toMap(Node::getNodeName, selectedElement -> { + .collect(toUnmodifiableMap(Node::getNodeName, selectedElement -> { return selectedElement.getTextContent(); }, (a, b) -> b)); } @@ -285,13 +274,14 @@ final class AppImageFile implements ExternalApplication { return String.format("*[(%s) and not(*)]", otherElementNames); } - private final Map data; - private final Set stdKeys; - - private static final Set LAUNCHER_ATTR_NAMES = Set.of("name", "service"); + private static final Set LAUNCHER_ATTR_NAMES = Stream.of( + LAUNCHER_NAME + ).map(OptionValue::getName).collect(toUnmodifiableSet()); private static final String LAUNCHER_PROPERTIES_XPATH_QUERY = xpathQueryForExtraProperties(LAUNCHER_ATTR_NAMES); - private static final Set MAIN_ELEMENT_NAMES = Set.of("app-version", "main-launcher", "main-class"); + private static final Set MAIN_ELEMENT_NAMES = Stream.of( + APP_VERSION + ).map(OptionValue::getName).collect(toUnmodifiableSet()); private static final String MAIN_PROPERTIES_XPATH_QUERY; static { @@ -301,40 +291,65 @@ final class AppImageFile implements ExternalApplication { MAIN_PROPERTIES_XPATH_QUERY = String.format("%s|/jpackage-state/%s", nonEmptyMainElements, xpathQueryForExtraProperties(Stream.concat(MAIN_ELEMENT_NAMES.stream(), - Stream.of("add-launcher")).collect(toSet()))); + Stream.of("main-launcher", "add-launcher")).collect(toUnmodifiableSet()))); } } - private record ApplicationData(String version, String mainLauncherName, String mainLauncherMainClassName, - Map extra, List additionalLaunchers) { - ApplicationData { - Objects.requireNonNull(version); - Objects.requireNonNull(mainLauncherName); - Objects.requireNonNull(mainLauncherMainClassName); - Objects.requireNonNull(extra); - Objects.requireNonNull(additionalLaunchers); + private enum LauncherElement { + MAIN("main-launcher"), + ADDITIONAL("add-launcher"); - for (final var property : List.of(version, mainLauncherName, mainLauncherMainClassName)) { - if (property.isBlank()) { - throw new IllegalArgumentException(); - } + LauncherElement(String elementName) { + this.elementName = Objects.requireNonNull(elementName); + } + + List> readAll(Document xml, XPath xPath) throws XPathExpressionException { + return XmlUtils.queryNodes(xml, xPath, "/jpackage-state/" + elementName + "[@name]") + .map(Element.class::cast).map(toFunction(e -> { + return AppImageProperties.launcher(e, xPath); + })).toList(); + } + + private final String elementName; + } + + private record LauncherInfo(String name, Map properties) { + LauncherInfo { + Objects.requireNonNull(name); + Objects.requireNonNull(properties); + } + + LauncherInfo(Launcher launcher) { + this(launcher.name(), properties(launcher)); + } + + void save(XMLStreamWriter xml, String elementName) throws IOException, XMLStreamException { + xml.writeStartElement(elementName); + xml.writeAttribute("name", name()); + for (var key : properties().keySet().stream().sorted().toList()) { + xml.writeStartElement(key); + xml.writeCharacters(properties().get(key)); + xml.writeEndElement(); } + xml.writeEndElement(); } - ApplicationData(Application app) { - this(app, app.mainLauncher().orElseThrow()); - } + private static Map properties(Launcher launcher) { + List> standardProps = new ArrayList<>(); + if (launcher.isService()) { + standardProps.add(Map.entry(LAUNCHER_AS_SERVICE.getName(), Boolean.TRUE.toString())); + } + standardProps.add(Map.entry(DESCRIPTION.getName(), launcher.description())); - private ApplicationData(Application app, Launcher mainLauncher) { - this(app.version(), mainLauncher.name(), mainLauncher.startupInfo().orElseThrow().qualifiedClassName(), - app.extraAppImageFileData(), app.additionalLaunchers().stream().map(launcher -> { - return new LauncherInfo(launcher.name(), launcher.isService(), - launcher.extraAppImageFileData()); - }).toList()); + return Stream.concat( + standardProps.stream(), + launcher.extraAppImageFileData().entrySet().stream() + ).collect(toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue)); } } + private static class InvalidAppImageFileException extends RuntimeException { InvalidAppImageFileException() { @@ -347,13 +362,10 @@ final class AppImageFile implements ExternalApplication { private static final long serialVersionUID = 1L; } + private final String appVersion; - private final String launcherName; - private final String mainClass; private final Map extra; - private final List addLauncherInfos; - private final String creatorVersion; - private final String creatorPlatform; + private final List launcherInfos; private static final String FILENAME = ".jpackage.xml"; 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 76a5fc1a50c..6896668ffdc 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationBuilder.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationBuilder.java @@ -38,9 +38,7 @@ import java.util.function.Predicate; import jdk.jpackage.internal.model.AppImageLayout; import jdk.jpackage.internal.model.Application; import jdk.jpackage.internal.model.ApplicationLaunchers; -import jdk.jpackage.internal.model.ConfigException; import jdk.jpackage.internal.model.ExternalApplication; -import jdk.jpackage.internal.model.ExternalApplication.LauncherInfo; import jdk.jpackage.internal.model.Launcher; import jdk.jpackage.internal.model.LauncherIcon; import jdk.jpackage.internal.model.LauncherStartupInfo; @@ -49,28 +47,17 @@ import jdk.jpackage.internal.model.RuntimeBuilder; final class ApplicationBuilder { - Application create() throws ConfigException { + Application create() { Objects.requireNonNull(appImageLayout); final var launchersAsList = Optional.ofNullable(launchers).map( ApplicationLaunchers::asList).orElseGet(List::of); - final var launcherCount = launchersAsList.size(); - - if (launcherCount != launchersAsList.stream().map(Launcher::name).distinct().count()) { - throw buildConfigException("ERR_NoUniqueName").create(); - } - - final String effectiveName; - if (name != null) { - effectiveName = name; - } else if (!launchersAsList.isEmpty()) { - effectiveName = launchers.mainLauncher().name(); - } else { - throw buildConfigException("error.no.name").advice("error.no.name.advice").create(); - } - - Objects.requireNonNull(launchersAsList); + final String effectiveName = Optional.ofNullable(name).or(() -> { + return Optional.ofNullable(launchers).map(ApplicationLaunchers::mainLauncher).map(Launcher::name); + }).orElseThrow(() -> { + return buildConfigException("error.no.name").advice("error.no.name.advice").create(); + }); return new Application.Stub( effectiveName, @@ -80,7 +67,10 @@ final class ApplicationBuilder { Optional.ofNullable(copyright).orElseGet(DEFAULTS::copyright), Optional.ofNullable(srcDir), Optional.ofNullable(contentDirs).orElseGet(List::of), - appImageLayout, Optional.ofNullable(runtimeBuilder), launchersAsList, Map.of()); + appImageLayout, + Optional.ofNullable(runtimeBuilder), + launchersAsList, + Map.of()); } ApplicationBuilder runtimeBuilder(RuntimeBuilder v) { @@ -88,25 +78,8 @@ final class ApplicationBuilder { return this; } - ApplicationBuilder initFromExternalApplication(ExternalApplication app, - Function mapper) { - - externalApp = Objects.requireNonNull(app); - - if (version == null) { - version = app.getAppVersion(); - } - if (name == null) { - name = app.getAppName(); - } - runtimeBuilder = null; - - var mainLauncherInfo = new LauncherInfo(app.getLauncherName(), false, Map.of()); - - launchers = new ApplicationLaunchers( - mapper.apply(mainLauncherInfo), - app.getAddLaunchers().stream().map(mapper).toList()); - + ApplicationBuilder externalApplication(ExternalApplication v) { + externalApp = v; return this; } @@ -123,15 +96,6 @@ final class ApplicationBuilder { return Optional.ofNullable(externalApp); } - Optional mainLauncherClassName() { - return launchers() - .map(ApplicationLaunchers::mainLauncher) - .flatMap(Launcher::startupInfo) - .map(LauncherStartupInfo::qualifiedClassName).or(() -> { - return externalApplication().map(ExternalApplication::getMainClass); - }); - } - ApplicationBuilder appImageLayout(AppImageLayout v) { appImageLayout = v; return this; diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationLayoutUtils.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationLayoutUtils.java deleted file mode 100644 index 0ea72ae160c..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationLayoutUtils.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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; - -import java.nio.file.Path; -import jdk.internal.util.OperatingSystem; -import jdk.jpackage.internal.model.ApplicationLayout; - - -final class ApplicationLayoutUtils { - - public static final ApplicationLayout PLATFORM_APPLICATION_LAYOUT; - - private static final ApplicationLayout WIN_APPLICATION_LAYOUT = ApplicationLayout.build() - .setAll("") - .appDirectory("app") - .runtimeDirectory("runtime") - .appModsDirectory(Path.of("app", "mods")) - .create(); - - private static final ApplicationLayout MAC_APPLICATION_LAYOUT = ApplicationLayout.build() - .launchersDirectory("Contents/MacOS") - .appDirectory("Contents/app") - .runtimeDirectory("Contents/runtime/Contents/Home") - .desktopIntegrationDirectory("Contents/Resources") - .appModsDirectory("Contents/app/mods") - .contentDirectory("Contents") - .create(); - - private static final ApplicationLayout LINUX_APPLICATION_LAYOUT = ApplicationLayout.build() - .launchersDirectory("bin") - .appDirectory("lib/app") - .runtimeDirectory("lib/runtime") - .desktopIntegrationDirectory("lib") - .appModsDirectory("lib/app/mods") - .contentDirectory("lib") - .create(); - - static { - switch (OperatingSystem.current()) { - case WINDOWS -> PLATFORM_APPLICATION_LAYOUT = WIN_APPLICATION_LAYOUT; - case MACOS -> PLATFORM_APPLICATION_LAYOUT = MAC_APPLICATION_LAYOUT; - case LINUX -> PLATFORM_APPLICATION_LAYOUT = LINUX_APPLICATION_LAYOUT; - default -> { - throw new UnsupportedOperationException(); - } - } - } -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Arguments.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Arguments.java deleted file mode 100644 index f0323bbd841..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Arguments.java +++ /dev/null @@ -1,867 +0,0 @@ -/* - * 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 - * 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; - -import jdk.internal.util.OperatingSystem; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.model.PackagerException; -import java.io.IOException; -import java.io.Reader; -import java.nio.file.Files; -import java.nio.file.Path; -import java.text.MessageFormat; -import java.util.ArrayList; -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; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Arguments - * - * This class encapsulates and processes the command line arguments, - * in effect, implementing all the work of jpackage tool. - * - * The primary entry point, processArguments(): - * Processes and validates command line arguments, constructing DeployParams. - * Validates the DeployParams, and generate the BundleParams. - * Generates List of Bundlers from BundleParams valid for this platform. - * Executes each Bundler in the list. - */ -public class Arguments { - private static final ResourceBundle I18N = ResourceBundle.getBundle( - "jdk.jpackage.internal.resources.MainResources"); - - private static final String FA_EXTENSIONS = "extension"; - private static final String FA_CONTENT_TYPE = "mime-type"; - private static final String FA_DESCRIPTION = "description"; - private static final String FA_ICON = "icon"; - - // Mac specific file association keys - // String - public static final String MAC_CFBUNDLETYPEROLE = "mac.CFBundleTypeRole"; - public static final String MAC_LSHANDLERRANK = "mac.LSHandlerRank"; - public static final String MAC_NSSTORETYPEKEY = "mac.NSPersistentStoreTypeKey"; - public static final String MAC_NSDOCUMENTCLASS = "mac.NSDocumentClass"; - // Boolean - public static final String MAC_LSTYPEISPACKAGE = "mac.LSTypeIsPackage"; - public static final String MAC_LSDOCINPLACE = "mac.LSSupportsOpeningDocumentsInPlace"; - public static final String MAC_UIDOCBROWSER = "mac.UISupportsDocumentBrowser"; - // Array of strings - public static final String MAC_NSEXPORTABLETYPES = "mac.NSExportableTypes"; - public static final String MAC_UTTYPECONFORMSTO = "mac.UTTypeConformsTo"; - - // regexp for parsing args (for example, for additional launchers) - private static Pattern pattern = Pattern.compile( - "(?:(?:([\"'])(?:\\\\\\1|.)*?(?:\\1|$))|(?:\\\\[\"'\\s]|[^\\s]))++"); - - private DeployParams deployParams = null; - - private int pos = 0; - private List argList = null; - - private List allOptions = null; - - private boolean hasMainJar = false; - private boolean hasMainClass = false; - private boolean hasMainModule = false; - public boolean userProvidedBuildRoot = false; - - private String buildRoot = null; - private String mainJarPath = null; - - private boolean runtimeInstaller = false; - - private List addLaunchers = null; - - private static final Map argIds = new HashMap<>(); - private static final Map argShortIds = new HashMap<>(); - - static { - // init maps for parsing arguments - (EnumSet.allOf(CLIOptions.class)).forEach(option -> { - argIds.put(option.getIdWithPrefix(), option); - if (option.getShortIdWithPrefix() != null) { - argShortIds.put(option.getShortIdWithPrefix(), option); - } - }); - } - - private static final InheritableThreadLocal instance = - new InheritableThreadLocal(); - - public Arguments(String[] args) { - instance.set(this); - - argList = new ArrayList(args.length); - for (String arg : args) { - argList.add(arg); - } - - pos = 0; - - deployParams = new DeployParams(); - - allOptions = new ArrayList<>(); - - addLaunchers = new ArrayList<>(); - } - - // CLIOptions is public for DeployParamsTest - public enum CLIOptions { - PACKAGE_TYPE("type", "t", OptionCategories.PROPERTY, () -> { - var type = popArg(); - context().deployParams.setTargetFormat(type); - setOptionValue("type", type); - }), - - INPUT ("input", "i", OptionCategories.PROPERTY, () -> { - setOptionValue("input", popArg()); - }), - - OUTPUT ("dest", "d", OptionCategories.PROPERTY, () -> { - var path = Path.of(popArg()); - setOptionValue("dest", path); - }), - - DESCRIPTION ("description", OptionCategories.PROPERTY), - - VENDOR ("vendor", OptionCategories.PROPERTY), - - APPCLASS ("main-class", OptionCategories.PROPERTY, () -> { - context().hasMainClass = true; - setOptionValue("main-class", popArg()); - }), - - NAME ("name", "n", OptionCategories.PROPERTY), - - VERBOSE ("verbose", OptionCategories.PROPERTY, () -> { - setOptionValue("verbose", true); - Log.setVerbose(); - }), - - RESOURCE_DIR("resource-dir", - OptionCategories.PROPERTY, () -> { - String resourceDir = popArg(); - setOptionValue("resource-dir", resourceDir); - }), - - DMG_CONTENT ("mac-dmg-content", OptionCategories.PROPERTY, () -> { - List content = getArgumentList(popArg()); - content.forEach(a -> setOptionValue("mac-dmg-content", a)); - }), - - ARGUMENTS ("arguments", OptionCategories.PROPERTY, () -> { - List arguments = getArgumentList(popArg()); - setOptionValue("arguments", arguments); - }), - - JLINK_OPTIONS ("jlink-options", OptionCategories.PROPERTY, () -> { - List options = getArgumentList(popArg()); - setOptionValue("jlink-options", options); - }), - - ICON ("icon", OptionCategories.PROPERTY), - - COPYRIGHT ("copyright", OptionCategories.PROPERTY), - - LICENSE_FILE ("license-file", OptionCategories.PROPERTY), - - VERSION ("app-version", OptionCategories.PROPERTY), - - RELEASE ("linux-app-release", OptionCategories.PROPERTY), - - ABOUT_URL ("about-url", OptionCategories.PROPERTY), - - JAVA_OPTIONS ("java-options", OptionCategories.PROPERTY, () -> { - List args = getArgumentList(popArg()); - args.forEach(a -> setOptionValue("java-options", a)); - }), - - APP_CONTENT ("app-content", OptionCategories.PROPERTY, () -> { - getArgumentList(popArg()).forEach( - a -> setOptionValue("app-content", a)); - }), - - FILE_ASSOCIATIONS ("file-associations", - OptionCategories.PROPERTY, () -> { - Map args = new HashMap<>(); - - // load .properties file - Map initialMap = getPropertiesFromFile(popArg()); - - putUnlessNull(args, StandardBundlerParam.FA_EXTENSIONS.getID(), - initialMap.get(FA_EXTENSIONS)); - - putUnlessNull(args, StandardBundlerParam.FA_CONTENT_TYPE.getID(), - initialMap.get(FA_CONTENT_TYPE)); - - putUnlessNull(args, StandardBundlerParam.FA_DESCRIPTION.getID(), - initialMap.get(FA_DESCRIPTION)); - - putUnlessNull(args, StandardBundlerParam.FA_ICON.getID(), - initialMap.get(FA_ICON)); - - // Mac extended file association arguments - putUnlessNull(args, MAC_CFBUNDLETYPEROLE, - initialMap.get(MAC_CFBUNDLETYPEROLE)); - - putUnlessNull(args, MAC_LSHANDLERRANK, - initialMap.get(MAC_LSHANDLERRANK)); - - putUnlessNull(args, MAC_NSSTORETYPEKEY, - initialMap.get(MAC_NSSTORETYPEKEY)); - - putUnlessNull(args, MAC_NSDOCUMENTCLASS, - initialMap.get(MAC_NSDOCUMENTCLASS)); - - putUnlessNull(args, MAC_LSTYPEISPACKAGE, - initialMap.get(MAC_LSTYPEISPACKAGE)); - - putUnlessNull(args, MAC_LSDOCINPLACE, - initialMap.get(MAC_LSDOCINPLACE)); - - putUnlessNull(args, MAC_UIDOCBROWSER, - initialMap.get(MAC_UIDOCBROWSER)); - - putUnlessNull(args, MAC_NSEXPORTABLETYPES, - initialMap.get(MAC_NSEXPORTABLETYPES)); - - putUnlessNull(args, MAC_UTTYPECONFORMSTO, - initialMap.get(MAC_UTTYPECONFORMSTO)); - - ArrayList> associationList = - new ArrayList>(); - - associationList.add(args); - - // check that we really add _another_ value to the list - setOptionValue("file-associations", associationList); - - }), - - ADD_LAUNCHER ("add-launcher", - OptionCategories.PROPERTY, () -> { - String spec = popArg(); - String name = null; - String filename = spec; - if (spec.contains("=")) { - String[] values = spec.split("=", 2); - name = values[0]; - filename = values[1]; - } - context().addLaunchers.add( - new AddLauncherArguments(name, filename)); - }), - - TEMP_ROOT ("temp", OptionCategories.PROPERTY, () -> { - context().buildRoot = popArg(); - context().userProvidedBuildRoot = true; - setOptionValue("temp", context().buildRoot); - }), - - INSTALL_DIR ("install-dir", OptionCategories.PROPERTY), - - PREDEFINED_APP_IMAGE ("app-image", OptionCategories.PROPERTY), - - PREDEFINED_RUNTIME_IMAGE ("runtime-image", OptionCategories.PROPERTY), - - MAIN_JAR ("main-jar", OptionCategories.PROPERTY, () -> { - context().mainJarPath = popArg(); - context().hasMainJar = true; - setOptionValue("main-jar", context().mainJarPath); - }), - - MODULE ("module", "m", OptionCategories.MODULAR, () -> { - context().hasMainModule = true; - setOptionValue("module", popArg()); - }), - - ADD_MODULES ("add-modules", OptionCategories.MODULAR), - - MODULE_PATH ("module-path", "p", OptionCategories.MODULAR), - - LAUNCHER_AS_SERVICE ("launcher-as-service", OptionCategories.PROPERTY, () -> { - setOptionValue("launcher-as-service", true); - }), - - MAC_SIGN ("mac-sign", "s", OptionCategories.PLATFORM_MAC, () -> { - setOptionValue("mac-sign", true); - }), - - MAC_APP_STORE ("mac-app-store", OptionCategories.PLATFORM_MAC, () -> { - setOptionValue("mac-app-store", true); - }), - - MAC_CATEGORY ("mac-app-category", OptionCategories.PLATFORM_MAC), - - MAC_BUNDLE_NAME ("mac-package-name", OptionCategories.PLATFORM_MAC), - - MAC_BUNDLE_IDENTIFIER("mac-package-identifier", - OptionCategories.PLATFORM_MAC), - - MAC_BUNDLE_SIGNING_PREFIX ("mac-package-signing-prefix", - OptionCategories.PLATFORM_MAC), - - MAC_SIGNING_KEY_NAME ("mac-signing-key-user-name", - OptionCategories.PLATFORM_MAC), - - MAC_APP_IMAGE_SIGN_IDENTITY ("mac-app-image-sign-identity", - OptionCategories.PLATFORM_MAC), - - MAC_INSTALLER_SIGN_IDENTITY ("mac-installer-sign-identity", - OptionCategories.PLATFORM_MAC), - - MAC_SIGNING_KEYCHAIN ("mac-signing-keychain", - OptionCategories.PLATFORM_MAC), - - MAC_ENTITLEMENTS ("mac-entitlements", OptionCategories.PLATFORM_MAC), - - WIN_HELP_URL ("win-help-url", OptionCategories.PLATFORM_WIN), - - WIN_UPDATE_URL ("win-update-url", OptionCategories.PLATFORM_WIN), - - 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, - createArgumentWithOptionalValueAction("win-shortcut")), - - WIN_SHORTCUT_PROMPT ("win-shortcut-prompt", - OptionCategories.PLATFORM_WIN, () -> { - setOptionValue("win-shortcut-prompt", true); - }), - - WIN_PER_USER_INSTALLATION ("win-per-user-install", - OptionCategories.PLATFORM_WIN, () -> { - setOptionValue("win-per-user-install", false); - }), - - WIN_DIR_CHOOSER ("win-dir-chooser", - OptionCategories.PLATFORM_WIN, () -> { - setOptionValue("win-dir-chooser", true); - }), - - WIN_UPGRADE_UUID ("win-upgrade-uuid", - OptionCategories.PLATFORM_WIN), - - WIN_CONSOLE_HINT ("win-console", OptionCategories.PLATFORM_WIN, () -> { - setOptionValue("win-console", true); - }), - - LINUX_BUNDLE_NAME ("linux-package-name", - OptionCategories.PLATFORM_LINUX), - - LINUX_DEB_MAINTAINER ("linux-deb-maintainer", - OptionCategories.PLATFORM_LINUX), - - LINUX_CATEGORY ("linux-app-category", - OptionCategories.PLATFORM_LINUX), - - LINUX_RPM_LICENSE_TYPE ("linux-rpm-license-type", - OptionCategories.PLATFORM_LINUX), - - LINUX_PACKAGE_DEPENDENCIES ("linux-package-deps", - OptionCategories.PLATFORM_LINUX), - - LINUX_SHORTCUT_HINT ("linux-shortcut", OptionCategories.PLATFORM_LINUX, - createArgumentWithOptionalValueAction("linux-shortcut")), - - LINUX_MENU_GROUP ("linux-menu-group", OptionCategories.PLATFORM_LINUX); - - private final String id; - private final String shortId; - private final OptionCategories category; - private final Runnable action; - private static Arguments argContext; - - private CLIOptions(String id, OptionCategories category) { - this(id, null, category, null); - } - - private CLIOptions(String id, String shortId, - OptionCategories category) { - this(id, shortId, category, null); - } - - private CLIOptions(String id, - OptionCategories category, Runnable action) { - this(id, null, category, action); - } - - private CLIOptions(String id, String shortId, - OptionCategories category, Runnable action) { - this.id = id; - this.shortId = shortId; - this.action = action; - this.category = category; - } - - public static Arguments context() { - return instance.get(); - } - - public String getId() { - return this.id; - } - - String getIdWithPrefix() { - return "--" + this.id; - } - - String getShortIdWithPrefix() { - return this.shortId == null ? null : "-" + this.shortId; - } - - void execute() { - if (action != null) { - action.run(); - } else { - defaultAction(); - } - } - - private void defaultAction() { - context().deployParams.addBundleArgument(id, popArg()); - } - - private static void setOptionValue(String option, Object value) { - context().deployParams.addBundleArgument(option, value); - } - - private static String popArg() { - nextArg(); - return (context().pos >= context().argList.size()) ? - "" : context().argList.get(context().pos); - } - - private static String getArg() { - return (context().pos >= context().argList.size()) ? - "" : context().argList.get(context().pos); - } - - private static void nextArg() { - 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 { - MODULAR, - PROPERTY, - PLATFORM_MAC, - PLATFORM_WIN, - PLATFORM_LINUX; - } - - public boolean processArguments() { - try { - // parse cmd line - String arg; - CLIOptions option; - for (; CLIOptions.hasNextArg(); CLIOptions.nextArg()) { - arg = CLIOptions.getArg(); - if ((option = toCLIOption(arg)) != null) { - // found a CLI option - allOptions.add(option); - option.execute(); - } else { - throw new PackagerException("ERR_InvalidOption", arg); - } - } - - // display error for arguments that are not supported - // for current configuration. - - validateArguments(); - - List> launchersAsMap = - new ArrayList<>(); - - for (AddLauncherArguments sl : addLaunchers) { - launchersAsMap.add(sl.getLauncherMap()); - } - - deployParams.addBundleArgument( - StandardBundlerParam.ADD_LAUNCHERS.getID(), - launchersAsMap); - - // at this point deployParams should be already configured - - deployParams.validate(); - - BundleParams bp = deployParams.getBundleParams(); - - // validate name(s) - ArrayList usedNames = new ArrayList(); - usedNames.add(bp.getName()); // add main app name - - for (AddLauncherArguments sl : addLaunchers) { - Map slMap = sl.getLauncherMap(); - String slName = - (String) slMap.get(Arguments.CLIOptions.NAME.getId()); - if (slName == null) { - throw new PackagerException("ERR_NoAddLauncherName"); - } - // same rules apply to additional launcher names as app name - DeployParams.validateName(slName, false); - for (String usedName : usedNames) { - if (slName.equals(usedName)) { - throw new PackagerException("ERR_NoUniqueName"); - } - } - usedNames.add(slName); - } - - generateBundle(bp.getBundleParamsAsMap()); - return true; - } catch (Exception e) { - Log.verbose(e); - String msg1 = e.getMessage(); - Log.fatalError(msg1); - if (e.getCause() != null && e.getCause() != e) { - String msg2 = e.getCause().getMessage(); - if (msg2 != null && !msg1.contains(msg2)) { - Log.fatalError(msg2); - } - } - return false; - } - } - - private void validateArguments() throws PackagerException { - String type = deployParams.getTargetFormat(); - String ptype = (type != null) ? type : "default"; - boolean imageOnly = deployParams.isTargetAppImage(); - boolean hasAppImage = allOptions.contains( - CLIOptions.PREDEFINED_APP_IMAGE); - boolean hasRuntime = allOptions.contains( - CLIOptions.PREDEFINED_RUNTIME_IMAGE); - boolean installerOnly = !imageOnly && hasAppImage; - boolean isMac = OperatingSystem.isMacOS(); - runtimeInstaller = !imageOnly && hasRuntime && !hasAppImage && - !hasMainModule && !hasMainJar; - - for (CLIOptions option : allOptions) { - if (!ValidOptions.checkIfSupported(option)) { - // includes option valid only on different platform - throw new PackagerException("ERR_UnsupportedOption", - option.getIdWithPrefix()); - } - if ((imageOnly && !isMac) || (imageOnly && !hasAppImage && isMac)) { - if (!ValidOptions.checkIfImageSupported(option)) { - throw new PackagerException("ERR_InvalidTypeOption", - option.getIdWithPrefix(), type); - } - } else if (imageOnly && hasAppImage && isMac) { // Signing app image - if (!ValidOptions.checkIfSigningSupported(option)) { - throw new PackagerException( - "ERR_InvalidOptionWithAppImageSigning", - option.getIdWithPrefix()); - } - } else if (installerOnly || runtimeInstaller) { - if (!ValidOptions.checkIfInstallerSupported(option)) { - if (runtimeInstaller) { - throw new PackagerException("ERR_NoInstallerEntryPoint", - option.getIdWithPrefix()); - } else { - throw new PackagerException("ERR_InvalidTypeOption", - option.getIdWithPrefix(), ptype); - } - } - } - } - if (hasRuntime) { - if (hasAppImage) { - // note --runtime-image is only for image or runtime installer. - throw new PackagerException("ERR_MutuallyExclusiveOptions", - CLIOptions.PREDEFINED_RUNTIME_IMAGE.getIdWithPrefix(), - CLIOptions.PREDEFINED_APP_IMAGE.getIdWithPrefix()); - } - if (allOptions.contains(CLIOptions.ADD_MODULES)) { - throw new PackagerException("ERR_MutuallyExclusiveOptions", - CLIOptions.PREDEFINED_RUNTIME_IMAGE.getIdWithPrefix(), - CLIOptions.ADD_MODULES.getIdWithPrefix()); - } - if (allOptions.contains(CLIOptions.JLINK_OPTIONS)) { - throw new PackagerException("ERR_MutuallyExclusiveOptions", - CLIOptions.PREDEFINED_RUNTIME_IMAGE.getIdWithPrefix(), - CLIOptions.JLINK_OPTIONS.getIdWithPrefix()); - } - } - if (allOptions.contains(CLIOptions.MAC_SIGNING_KEY_NAME) && - allOptions.contains(CLIOptions.MAC_APP_IMAGE_SIGN_IDENTITY)) { - throw new PackagerException("ERR_MutuallyExclusiveOptions", - CLIOptions.MAC_SIGNING_KEY_NAME.getIdWithPrefix(), - CLIOptions.MAC_APP_IMAGE_SIGN_IDENTITY.getIdWithPrefix()); - } - if (allOptions.contains(CLIOptions.MAC_SIGNING_KEY_NAME) && - allOptions.contains(CLIOptions.MAC_INSTALLER_SIGN_IDENTITY)) { - throw new PackagerException("ERR_MutuallyExclusiveOptions", - CLIOptions.MAC_SIGNING_KEY_NAME.getIdWithPrefix(), - CLIOptions.MAC_INSTALLER_SIGN_IDENTITY.getIdWithPrefix()); - } - if (isMac && (imageOnly || "dmg".equals(type)) && - allOptions.contains(CLIOptions.MAC_INSTALLER_SIGN_IDENTITY)) { - throw new PackagerException("ERR_InvalidTypeOption", - CLIOptions.MAC_INSTALLER_SIGN_IDENTITY.getIdWithPrefix(), - type); - } - if (allOptions.contains(CLIOptions.DMG_CONTENT) - && !("dmg".equals(type))) { - throw new PackagerException("ERR_InvalidTypeOption", - CLIOptions.DMG_CONTENT.getIdWithPrefix(), ptype); - } - if (hasMainJar && hasMainModule) { - throw new PackagerException("ERR_BothMainJarAndModule"); - } - if (imageOnly && !hasAppImage && !hasMainJar && !hasMainModule) { - throw new PackagerException("ERR_NoEntryPoint"); - } - } - - private jdk.jpackage.internal.Bundler getPlatformBundler() { - boolean appImage = deployParams.isTargetAppImage(); - String type = deployParams.getTargetFormat(); - String bundleType = (appImage ? "IMAGE" : "INSTALLER"); - - for (jdk.jpackage.internal.Bundler bundler : - Bundlers.createBundlersInstance().getBundlers(bundleType)) { - if (type == null) { - if (bundler.isDefault()) { - return bundler; - } - } else { - if (appImage || type.equalsIgnoreCase(bundler.getID())) { - return bundler; - } - } - } - return null; - } - - private void generateBundle(Map params) - throws PackagerException { - - // the temp dir needs to be fetched from the params early, - // to prevent each copy of the params (such as may be used for - // additional launchers) from generating a separate temp dir when - // the default is used (the default is a new temp directory) - // The bundler.cleanup() below would not otherwise be able to - // clean these extra (and unneeded) temp directories. - StandardBundlerParam.TEMP_ROOT.fetchFrom(params); - - // determine what bundler to run - jdk.jpackage.internal.Bundler bundler = getPlatformBundler(); - - if (bundler == null || !bundler.supported(runtimeInstaller)) { - String type = Optional.ofNullable(bundler).map(Bundler::getID).orElseGet( - () -> deployParams.getTargetFormat()); - throw new PackagerException("ERR_InvalidInstallerType", type); - } - - Map localParams = new HashMap<>(params); - try { - Path result = executeBundler(bundler, params, localParams); - if (result == null) { - throw new PackagerException("MSG_BundlerFailed", - bundler.getID(), bundler.getName()); - } - Log.verbose(MessageFormat.format( - I18N.getString("message.bundle-created"), - bundler.getName())); - } catch (ConfigException e) { - Log.verbose(e); - if (e.getAdvice() != null) { - throw new PackagerException(e, "MSG_BundlerConfigException", - bundler.getName(), e.getMessage(), e.getAdvice()); - } else { - throw new PackagerException(e, - "MSG_BundlerConfigExceptionNoAdvice", - bundler.getName(), e.getMessage()); - } - } catch (RuntimeException re) { - Log.verbose(re); - throw new PackagerException(re, "MSG_BundlerRuntimeException", - bundler.getName(), re.toString()); - } finally { - if (userProvidedBuildRoot) { - Log.verbose(MessageFormat.format( - I18N.getString("message.debug-working-directory"), - (Path.of(buildRoot)).toAbsolutePath().toString())); - } else { - // always clean up the temporary directory created - // when --temp option not used. - bundler.cleanup(localParams); - } - } - } - - private static Path executeBundler(Bundler bundler, Map params, - Map localParams) throws ConfigException, PackagerException { - try { - bundler.validate(localParams); - return bundler.execute(localParams, StandardBundlerParam.OUTPUT_DIR.fetchFrom(params)); - } catch (ConfigException|PackagerException ex) { - throw ex; - } catch (RuntimeException ex) { - if (ex.getCause() instanceof ConfigException cfgEx) { - throw cfgEx; - } else if (ex.getCause() instanceof PackagerException pkgEx) { - throw pkgEx; - } else { - throw ex; - } - } - } - - static CLIOptions toCLIOption(String arg) { - CLIOptions option; - if ((option = argIds.get(arg)) == null) { - option = argShortIds.get(arg); - } - return option; - } - - static Map getPropertiesFromFile(String filename) { - Map map = new HashMap<>(); - // load properties file - Properties properties = new Properties(); - try (Reader reader = Files.newBufferedReader(Path.of(filename))) { - properties.load(reader); - } catch (IOException e) { - Log.error("Exception: " + e.getMessage()); - } - - for (final String name: properties.stringPropertyNames()) { - map.put(name, properties.getProperty(name)); - } - - return map; - } - - static List getArgumentList(String inputString) { - List list = new ArrayList<>(); - if (inputString == null || inputString.isEmpty()) { - return list; - } - - // The "pattern" regexp attempts to abide to the rule that - // strings are delimited by whitespace unless surrounded by - // quotes, then it is anything (including spaces) in the quotes. - Matcher m = pattern.matcher(inputString); - while (m.find()) { - String s = inputString.substring(m.start(), m.end()).trim(); - // Ensure we do not have an empty string. trim() will take care of - // whitespace only strings. The regex preserves quotes and escaped - // chars so we need to clean them before adding to the List - if (!s.isEmpty()) { - list.add(unquoteIfNeeded(s)); - } - } - return list; - } - - static void putUnlessNull(Map params, - String param, Object value) { - if (value != null) { - params.put(param, value); - } - } - - private static String unquoteIfNeeded(String in) { - if (in == null) { - return null; - } - - if (in.isEmpty()) { - return ""; - } - - // Use code points to preserve non-ASCII chars - StringBuilder sb = new StringBuilder(); - int codeLen = in.codePointCount(0, in.length()); - int quoteChar = -1; - for (int i = 0; i < codeLen; i++) { - int code = in.codePointAt(i); - if (code == '"' || code == '\'') { - // If quote is escaped make sure to copy it - if (i > 0 && in.codePointAt(i - 1) == '\\') { - sb.deleteCharAt(sb.length() - 1); - sb.appendCodePoint(code); - continue; - } - if (quoteChar != -1) { - if (code == quoteChar) { - // close quote, skip char - quoteChar = -1; - } else { - sb.appendCodePoint(code); - } - } else { - // opening quote, skip char - quoteChar = code; - } - } else { - sb.appendCodePoint(code); - } - } - return sb.toString(); - } -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BasicBundlers.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BasicBundlers.java deleted file mode 100644 index 7f444fe7337..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BasicBundlers.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2014, 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. 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; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.ServiceLoader; -import java.util.concurrent.CopyOnWriteArrayList; - -/** - * BasicBundlers - * - * A basic bundlers collection that loads the default bundlers. - * Loads the common bundlers. - *

      - *
    • Windows file image
    • - *
    • Mac .app
    • - *
    • Linux file image
    • - *
    • Windows MSI
    • - *
    • Windows EXE
    • - *
    • Mac DMG
    • - *
    • Mac PKG
    • - *
    • Linux DEB
    • - *
    • Linux RPM
    • - * - *
    - */ -public class BasicBundlers implements Bundlers { - - boolean defaultsLoaded = false; - - private final Collection bundlers = new CopyOnWriteArrayList<>(); - - @Override - public Collection getBundlers() { - return Collections.unmodifiableCollection(bundlers); - } - - @Override - public Collection getBundlers(String type) { - if (type == null) return Collections.emptySet(); - switch (type) { - case "NONE": - return Collections.emptySet(); - case "ALL": - return getBundlers(); - default: - return Arrays.asList(getBundlers().stream() - .filter(b -> type.equalsIgnoreCase(b.getBundleType())) - .toArray(Bundler[]::new)); - } - } - - // Loads bundlers from the META-INF/services direct - @Override - public void loadBundlersFromServices(ClassLoader cl) { - ServiceLoader loader = ServiceLoader.load(Bundler.class, cl); - for (Bundler aLoader : loader) { - bundlers.add(aLoader); - } - } -} 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 ba70913eca9..17b805b8259 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BuildEnvBuilder.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BuildEnvBuilder.java @@ -24,14 +24,13 @@ */ package jdk.jpackage.internal; -import java.io.IOException; -import java.nio.file.Files; +import static jdk.jpackage.internal.cli.StandardValidator.IS_DIRECTORY_EMPTY_OR_NON_EXISTENT_PREDICATE; + 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; @@ -41,20 +40,12 @@ final class BuildEnvBuilder { this.root = Objects.requireNonNull(root); } - BuildEnv create() throws ConfigException { - var exceptionBuilder = I18N.buildConfigException("ERR_BuildRootInvalid", root); - if (Files.isDirectory(root)) { - try (var rootDirContents = Files.list(root)) { - if (rootDirContents.findAny().isPresent()) { - // The root directory is not empty. - throw exceptionBuilder.create(); - } - } catch (IOException ioe) { - throw exceptionBuilder.cause(ioe).create(); - } - } else if (Files.exists(root)) { - // The root is not a directory. - throw exceptionBuilder.create(); + BuildEnv create() { + // The directory should be validated earlier with a proper error message. + // Here is only a sanity check. + if (!IS_DIRECTORY_EMPTY_OR_NON_EXISTENT_PREDICATE.test(root)) { + throw new UnsupportedOperationException( + String.format("Root work directory [%s] should be empty or non existent", root)); } return BuildEnv.create(root, Optional.ofNullable(resourceDir), verbose, diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BuildEnvFromOptions.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BuildEnvFromOptions.java new file mode 100644 index 00000000000..8faabe97a3d --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BuildEnvFromOptions.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. 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; + +import static jdk.jpackage.internal.cli.StandardOption.PREDEFINED_APP_IMAGE; +import static jdk.jpackage.internal.cli.StandardOption.PREDEFINED_RUNTIME_IMAGE; +import static jdk.jpackage.internal.cli.StandardOption.RESOURCE_DIR; +import static jdk.jpackage.internal.cli.StandardOption.TEMP_ROOT; +import static jdk.jpackage.internal.cli.StandardOption.VERBOSE; + +import java.nio.file.Path; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; +import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.model.Application; +import jdk.jpackage.internal.model.ApplicationLayout; +import jdk.jpackage.internal.model.Package; +import jdk.jpackage.internal.model.RuntimeLayout; + +final class BuildEnvFromOptions { + + BuildEnvFromOptions() { + predefinedRuntimeImageLayout(RuntimeLayout.DEFAULT); + } + + BuildEnvFromOptions predefinedAppImageLayout(Function v) { + predefinedAppImageLayout = v; + return this; + } + + BuildEnvFromOptions predefinedAppImageLayout(ApplicationLayout v) { + return predefinedAppImageLayout(path -> v.resolveAt(path)); + } + + BuildEnvFromOptions predefinedRuntimeImageLayout(Function v) { + predefinedRuntimeImageLayout = v; + return this; + } + + BuildEnvFromOptions predefinedRuntimeImageLayout(RuntimeLayout v) { + return predefinedRuntimeImageLayout(path -> v.resolveAt(path)); + } + + BuildEnv create(Options options, Application app) { + return create(options, app, Optional.empty()); + } + + BuildEnv create(Options options, Package pkg) { + return create(options, pkg.app(), Optional.of(pkg)); + } + + private BuildEnv create(Options options, Application app, Optional pkg) { + Objects.requireNonNull(options); + Objects.requireNonNull(app); + Objects.requireNonNull(pkg); + Objects.requireNonNull(predefinedAppImageLayout); + Objects.requireNonNull(predefinedRuntimeImageLayout); + + final var builder = new BuildEnvBuilder(TEMP_ROOT.getFrom(options)); + + RESOURCE_DIR.ifPresentIn(options, builder::resourceDir); + VERBOSE.ifPresentIn(options, builder::verbose); + + if (app.isRuntime()) { + var path = PREDEFINED_RUNTIME_IMAGE.getFrom(options); + builder.appImageLayout(predefinedRuntimeImageLayout.apply(path)); + } else if (PREDEFINED_APP_IMAGE.containsIn(options)) { + var path = PREDEFINED_APP_IMAGE.getFrom(options); + builder.appImageLayout(predefinedAppImageLayout.apply(path)); + } else { + pkg.ifPresentOrElse(builder::appImageDirFor, () -> { + builder.appImageDirFor(app); + }); + } + + return builder.create(); + } + + private Function predefinedAppImageLayout; + private Function predefinedRuntimeImageLayout; +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BuildEnvFromParams.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BuildEnvFromParams.java deleted file mode 100644 index 6fb1a342fbf..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BuildEnvFromParams.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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; - -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, - Function predefinedAppImageLayoutProvider, - Function predefinedRuntimeImageLayoutProvider) throws ConfigException { - - final var builder = new BuildEnvBuilder(TEMP_ROOT.fetchFrom(params)); - - RESOURCE_DIR.copyInto(params, builder::resourceDir); - VERBOSE.copyInto(params, builder::verbose); - - final var app = FromParams.APPLICATION.findIn(params).orElseThrow(); - - final var pkg = FromParams.getCurrentPackage(params); - - if (app.isRuntime()) { - var layout = predefinedRuntimeImageLayoutProvider.apply(PREDEFINED_RUNTIME_IMAGE.findIn(params).orElseThrow()); - builder.appImageLayout(layout); - } else if (StandardBundlerParam.hasPredefinedAppImage(params)) { - var layout = predefinedAppImageLayoutProvider.apply(PREDEFINED_APP_IMAGE.findIn(params).orElseThrow()); - builder.appImageLayout(layout); - } else if (pkg.isPresent()) { - builder.appImageDirFor(pkg.orElseThrow()); - } else { - builder.appImageDirFor(app); - } - - return builder.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/BundleParams.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BundleParams.java deleted file mode 100644 index 937a3655e8b..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BundleParams.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved. - * 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; - -import java.util.HashMap; -import java.util.Map; -import static jdk.jpackage.internal.StandardBundlerParam.APP_NAME; - -public class BundleParams { - - protected final Map params; - - /** - * create a new bundle with all default values - */ - public BundleParams() { - params = new HashMap<>(); - } - - /** - * Create a bundle params with a copy of the params - * @param params map of initial parameters to be copied in. - */ - public BundleParams(Map params) { - this.params = new HashMap<>(params); - } - - public void addAllBundleParams(Map params) { - this.params.putAll(params); - } - - // NOTE: we do not care about application parameters here - // as they will be embedded into jar file manifest and - // java launcher will take care of them! - - public Map getBundleParamsAsMap() { - return new HashMap<>(params); - } - - public String getName() { - return APP_NAME.fetchFrom(params); - } - - private void putUnlessNull(String param, Object value) { - if (value != null) { - params.put(param, value); - } - } -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Bundler.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Bundler.java deleted file mode 100644 index 0d29677e826..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Bundler.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * 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 - * 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; - -import java.nio.file.Path; -import java.util.Map; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.model.PackagerException; - -/** - * Bundler - * - * The basic interface implemented by all Bundlers. - */ -public interface Bundler { - /** - * @return User Friendly name of this bundler. - */ - String getName(); - - /** - * @return Command line identifier of the bundler. Should be unique. - */ - String getID(); - - /** - * @return The bundle type of the bundle that is created by this bundler. - */ - String getBundleType(); - - /** - * Determines if this bundler will execute with the given parameters. - * - * @param params The parameters to be validate. Validation may modify - * the map, so if you are going to be using the same map - * across multiple bundlers you should pass in a deep copy. - * @return true if valid - * @throws ConfigException If the configuration params are incorrect. The - * exception may contain advice on how to modify the params map - * to make it valid. - */ - public boolean validate(Map params) - throws ConfigException; - - /** - * Creates a bundle from existing content. - * - * If a call to {@link #validate(java.util.Map)} date} returns true with - * the parameters map, then you can expect a valid output. - * However if an exception was thrown out of validate or it returned - * false then you should not expect sensible results from this call. - * It may or may not return a value, and it may or may not throw an - * exception. But any output should not be considered valid or sane. - * - * @param params The Bundle parameters, - * Keyed by the id from the ParamInfo. Execution may - * modify the map, so if you are going to be using the - * same map across multiple bundlers you should pass - * in a deep copy. - * @param outputParentDir - * The parent dir that the returned bundle will be placed in. - * @return The resulting bundled file - * - * For a bundler that produces a single artifact file this will be the - * location of that artifact (.exe file, .deb file, etc) - * - * For a bundler that produces a specific directory format output this will - * be the location of that specific directory (.app file, etc). - * - * For a bundler that produce multiple files, this will be a parent - * directory of those files (linux and windows images), whose name is not - * relevant to the result. - * - * @throws java.lang.IllegalArgumentException for any of the following - * reasons: - *
      - *
    • A required parameter is not found in the params list, for - * example missing the main class.
    • - *
    • A parameter has the wrong type of an object, for example a - * String where a File is required
    • - *
    • Bundler specific incompatibilities with the parameters, for - * example a bad version number format or an application id with - * forward slashes.
    • - *
    - */ - public Path execute(Map params, - Path outputParentDir) throws PackagerException; - - /** - * Removes temporary files that are used for bundling. - */ - public void cleanup(Map params); - - /** - * Returns "true" if this bundler is supported on current platform. - */ - public boolean supported(boolean runtimeInstaller); - - /** - * Returns "true" if this bundler is he default for the current platform. - */ - public boolean isDefault(); -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BundlerParamInfo.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BundlerParamInfo.java deleted file mode 100644 index 81030b18f6d..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BundlerParamInfo.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * 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 - * 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; - -import java.nio.file.Path; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.function.BiFunction; -import java.util.function.Consumer; -import java.util.function.Function; -import jdk.jpackage.internal.util.function.ThrowingFunction; - -/** - * BundlerParamInfo - * - * A BundlerParamInfo encapsulates an individual bundler parameter of type . - * - * @param id The command line and hashmap name of the parameter - * - * @param valueType Type of the parameter - * - * @param defaultValueFunction If the value is not set, and no fallback value is found, the - * parameter uses the value returned by the producer. - * - * @param stringConverter An optional string converter for command line arguments. - */ -record BundlerParamInfo(String id, Class valueType, - Function, T> defaultValueFunction, - BiFunction, T> stringConverter) { - - BundlerParamInfo { - Objects.requireNonNull(id); - Objects.requireNonNull(valueType); - } - - static BundlerParamInfo createStringBundlerParam(String id) { - return new BundlerParamInfo<>(id, String.class, null, null); - } - - static BundlerParamInfo createBooleanBundlerParam(String id) { - return new BundlerParamInfo<>(id, Boolean.class, null, BundlerParamInfo::toBoolean); - } - - static BundlerParamInfo createPathBundlerParam(String id) { - return new BundlerParamInfo<>(id, Path.class, null, BundlerParamInfo::toPath); - } - - @SuppressWarnings({"unchecked", "rawtypes"}) - static BundlerParamInfo createBundlerParam(String id, Class valueType, - ThrowingFunction, U> valueCtor) { - return new BundlerParamInfo(id, valueType, ThrowingFunction.toFunction(valueCtor), null); - } - - static BundlerParamInfo createBundlerParam(Class valueType, - ThrowingFunction, U> valueCtor) { - return createBundlerParam(valueType.getName(), valueType, valueCtor); - } - - static boolean toBoolean(String value, Map params) { - if (value == null || "null".equalsIgnoreCase(value)) { - return false; - } else { - return Boolean.valueOf(value); - } - } - - static Path toPath(String value, Map params) { - return Path.of(value); - } - - String getID() { - return id; - } - - Class getValueType() { - return valueType; - } - - /** - * Returns true if value was not provided on command line for this parameter. - * - * @param params - params from which value will be fetch - * @return true if value was not provided on command line, false otherwise - */ - boolean getIsDefaultValue(Map params) { - Object o = params.get(getID()); - if (o != null) { - return false; // We have user provided value - } - - if (params.containsKey(getID())) { - return false; // explicit nulls are allowed for provided value - } - - return true; - } - - Function, T> getDefaultValueFunction() { - return defaultValueFunction; - } - - BiFunction, T> getStringConverter() { - return stringConverter; - } - - final T fetchFrom(Map params) { - return fetchFrom(params, true); - } - - @SuppressWarnings("unchecked") - final T fetchFrom(Map params, - boolean invokeDefault) { - Object o = params.get(getID()); - if (o instanceof String && getStringConverter() != null) { - return getStringConverter().apply((String) o, params); - } - - Class klass = getValueType(); - if (klass.isInstance(o)) { - return (T) o; - } - if (o != null) { - throw new IllegalArgumentException("Param " + getID() - + " should be of type " + getValueType() - + " but is a " + o.getClass()); - } - if (params.containsKey(getID())) { - // explicit nulls are allowed - return null; - } - - if (invokeDefault && (getDefaultValueFunction() != null)) { - T result = getDefaultValueFunction().apply(params); - if (result != null) { - params.put(getID(), result); - } - return result; - } - - // ultimate fallback - return null; - } - - Optional findIn(Map params) { - if (params.containsKey(getID())) { - return Optional.of(fetchFrom(params, true)); - } else { - return Optional.empty(); - } - } - - void copyInto(Map params, Consumer consumer) { - findIn(params).ifPresent(consumer); - } -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Bundlers.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Bundlers.java deleted file mode 100644 index 955952d563d..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Bundlers.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (c) 2014, 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. 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; - -import java.util.Collection; -import java.util.Iterator; -import java.util.ServiceLoader; - -/** - * Bundlers - * - * The interface implemented by BasicBundlers - */ -public interface Bundlers { - - /** - * This convenience method will call - * {@link #createBundlersInstance(ClassLoader)} - * with the classloader that this Bundlers is loaded from. - * - * @return an instance of Bundlers loaded and configured from - * the current ClassLoader. - */ - public static Bundlers createBundlersInstance() { - return createBundlersInstance(Bundlers.class.getClassLoader()); - } - - /** - * This convenience method will automatically load a Bundlers instance - * from either META-INF/services or the default - * {@link BasicBundlers} if none are found in - * the services meta-inf. - * - * After instantiating the bundlers instance it will load the default - * bundlers via {@link #loadDefaultBundlers()} as well as requesting - * the services loader to load any other bundelrs via - * {@link #loadBundlersFromServices(ClassLoader)}. - - * - * @param servicesClassLoader the classloader to search for - * META-INF/service registered bundlers - * @return an instance of Bundlers loaded and configured from - * the specified ClassLoader - */ - public static Bundlers createBundlersInstance( - ClassLoader servicesClassLoader) { - ServiceLoader bundlersLoader = - ServiceLoader.load(Bundlers.class, servicesClassLoader); - Bundlers bundlers = null; - Iterator iter = bundlersLoader.iterator(); - if (iter.hasNext()) { - bundlers = iter.next(); - } - if (bundlers == null) { - bundlers = new BasicBundlers(); - } - - bundlers.loadBundlersFromServices(servicesClassLoader); - return bundlers; - } - - /** - * Returns all of the preconfigured, requested, and manually - * configured bundlers loaded with this instance. - * - * @return a read-only collection of the requested bundlers - */ - Collection getBundlers(); - - /** - * Returns all of the preconfigured, requested, and manually - * configured bundlers loaded with this instance that are of - * a specific BundleType, such as disk images, installers, or - * remote installers. - * - * @return a read-only collection of the requested bundlers - */ - Collection getBundlers(String type); - - /** - * Loads bundlers from the META-INF/services directly. - * - * This method is called from the - * {@link #createBundlersInstance(ClassLoader)} - * and {@link #createBundlersInstance()} methods. - */ - void loadBundlersFromServices(ClassLoader cl); - -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/CLIHelp.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/CLIHelp.java deleted file mode 100644 index 7790ebb3ebb..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/CLIHelp.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * 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 - * 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; - -import jdk.internal.util.OperatingSystem; - -import java.util.ResourceBundle; -import java.io.File; -import java.text.MessageFormat; - - -/** - * CLIHelp - * - * Generate and show the command line interface help message(s). - */ -public class CLIHelp { - - private static final ResourceBundle I18N = ResourceBundle.getBundle( - "jdk.jpackage.internal.resources.HelpResources"); - - // generates --help for jpackage's CLI - public static void showHelp(boolean noArgs) { - - if (noArgs) { - Log.info(I18N.getString("MSG_Help_no_args")); - } else { - OperatingSystem platform = OperatingSystem.current(); - String types; - String pLaunchOptions; - String pInstallOptions; - String pInstallDir; - String pAppImageDescription; - String pSignSampleUsage; - String pAppContentNote; - switch (platform) { - case MACOS: - types = "{\"app-image\", \"dmg\", \"pkg\"}"; - pLaunchOptions = I18N.getString("MSG_Help_mac_launcher"); - pInstallOptions = I18N.getString("MSG_Help_mac_install"); - pInstallDir - = I18N.getString("MSG_Help_mac_linux_install_dir"); - pAppImageDescription - = I18N.getString("MSG_Help_mac_app_image"); - pSignSampleUsage - = I18N.getString("MSG_Help_mac_sign_sample_usage"); - pAppContentNote - = I18N.getString("MSG_Help_mac_app_content_note"); - break; - case LINUX: - types = "{\"app-image\", \"rpm\", \"deb\"}"; - pLaunchOptions = ""; - pInstallOptions = I18N.getString("MSG_Help_linux_install"); - pInstallDir - = I18N.getString("MSG_Help_mac_linux_install_dir"); - pAppImageDescription - = I18N.getString("MSG_Help_default_app_image"); - pSignSampleUsage = ""; - pAppContentNote = ""; - break; - case WINDOWS: - types = "{\"app-image\", \"exe\", \"msi\"}"; - pLaunchOptions = I18N.getString("MSG_Help_win_launcher"); - pInstallOptions = I18N.getString("MSG_Help_win_install"); - pInstallDir - = I18N.getString("MSG_Help_win_install_dir"); - pAppImageDescription - = I18N.getString("MSG_Help_default_app_image"); - pSignSampleUsage = ""; - pAppContentNote = ""; - break; - default: - types = "{\"app-image\", \"exe\", \"msi\", \"rpm\", \"deb\", \"pkg\", \"dmg\"}"; - pLaunchOptions = I18N.getString("MSG_Help_win_launcher") - + I18N.getString("MSG_Help_mac_launcher"); - pInstallOptions = I18N.getString("MSG_Help_win_install") - + I18N.getString("MSG_Help_linux_install") - + I18N.getString("MSG_Help_mac_install"); - pInstallDir - = I18N.getString("MSG_Help_default_install_dir"); - pAppImageDescription - = I18N.getString("MSG_Help_default_app_image"); - pSignSampleUsage = ""; - pAppContentNote = ""; - break; - } - Log.info(MessageFormat.format(I18N.getString("MSG_Help"), - File.pathSeparator, types, pLaunchOptions, - pInstallOptions, pInstallDir, pAppImageDescription, - pSignSampleUsage, pAppContentNote)); - } - } -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/CfgFile.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/CfgFile.java index 6b4ba3c4410..9cb9fb5cba0 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/CfgFile.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/CfgFile.java @@ -30,7 +30,7 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Optional; +import java.util.Objects; import java.util.stream.Stream; import jdk.jpackage.internal.model.Application; import jdk.jpackage.internal.model.ApplicationLayout; @@ -47,10 +47,14 @@ final class CfgFile { CfgFile(Application app, Launcher launcher) { startupInfo = launcher.startupInfo().orElseThrow(); outputFileName = launcher.executableName() + ".cfg"; - version = app.version(); + version = Objects.requireNonNull(app.version()); } void create(ApplicationLayout appLayout) throws IOException { + Objects.requireNonNull(appLayout); + + Objects.requireNonNull(startupInfo.qualifiedClassName()); + List> content = new ArrayList<>(); final var refs = new Referencies(appLayout); @@ -58,7 +62,7 @@ final class CfgFile { content.add(Map.entry("[Application]", SECTION_TAG)); if (startupInfo instanceof LauncherModularStartupInfo modularStartupInfo) { - content.add(Map.entry("app.mainmodule", modularStartupInfo.moduleName() + content.add(Map.entry("app.mainmodule", Objects.requireNonNull(modularStartupInfo.moduleName()) + "/" + startupInfo.qualifiedClassName())); } else if (startupInfo instanceof LauncherJarStartupInfo jarStartupInfo) { Path mainJarPath = refs.appDirectory().resolve(jarStartupInfo.jarPath()); @@ -67,16 +71,13 @@ final class CfgFile { content.add(Map.entry("app.mainjar", mainJarPath)); } else { content.add(Map.entry("app.classpath", mainJarPath)); - } - - if (!jarStartupInfo.isJarWithMainClass()) { content.add(Map.entry("app.mainclass", startupInfo.qualifiedClassName())); } } else { throw new UnsupportedOperationException(); } - for (var value : Optional.ofNullable(startupInfo.classPath()).orElseGet(List::of)) { + for (var value : startupInfo.classPath()) { content.add(Map.entry("app.classpath", refs.appDirectory().resolve(value).toString())); } @@ -88,7 +89,7 @@ final class CfgFile { "java-options", "-Djpackage.app-version=" + version)); // add user supplied java options if there are any - for (var value : Optional.ofNullable(startupInfo.javaOptions()).orElseGet(List::of)) { + for (var value : startupInfo.javaOptions()) { content.add(Map.entry("java-options", value)); } @@ -98,7 +99,7 @@ final class CfgFile { content.add(Map.entry("java-options", refs.appModsDirectory())); } - var arguments = Optional.ofNullable(startupInfo.defaultParameters()).orElseGet(List::of); + var arguments = startupInfo.defaultParameters(); if (!arguments.isEmpty()) { content.add(Map.entry("[ArgOptions]", SECTION_TAG)); for (var value : arguments) { diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/DefaultBundlingEnvironment.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/DefaultBundlingEnvironment.java new file mode 100644 index 00000000000..e4473b1e5ce --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/DefaultBundlingEnvironment.java @@ -0,0 +1,292 @@ +/* + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * 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; + +import static java.util.stream.Collectors.toMap; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; +import jdk.internal.util.OperatingSystem; +import jdk.jpackage.internal.cli.CliBundlingEnvironment; +import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.cli.StandardBundlingOperation; +import jdk.jpackage.internal.model.AppImagePackageType; +import jdk.jpackage.internal.model.Application; +import jdk.jpackage.internal.model.BundlingOperationDescriptor; +import jdk.jpackage.internal.model.JPackageException; +import jdk.jpackage.internal.model.Package; +import jdk.jpackage.internal.model.PackageType; +import jdk.jpackage.internal.model.StandardPackageType; +import jdk.jpackage.internal.util.Result; + +class DefaultBundlingEnvironment implements CliBundlingEnvironment { + + DefaultBundlingEnvironment(Builder builder) { + this(Optional.ofNullable(builder.defaultOperationSupplier), builder.bundlers); + } + + DefaultBundlingEnvironment(Optional>> defaultOperationSupplier, + Map>>> bundlers) { + + this.bundlers = bundlers.entrySet().stream().collect(toMap(Map.Entry::getKey, e -> { + return runOnce(e.getValue()); + })); + + this.defaultOperationSupplier = Objects.requireNonNull(defaultOperationSupplier).map(DefaultBundlingEnvironment::runOnce); + } + + + static final class Builder { + + Builder defaultOperation(Supplier> v) { + defaultOperationSupplier = v; + return this; + } + + Builder defaultOperation(StandardBundlingOperation v) { + return defaultOperation(() -> Optional.of(v.descriptor())); + } + + Builder bundler(StandardBundlingOperation op, Supplier>> bundlerSupplier) { + bundlers.put(Objects.requireNonNull(op.descriptor()), Objects.requireNonNull(bundlerSupplier)); + return this; + } + + Builder bundler(StandardBundlingOperation op, + Supplier> sysEnvResultSupplier, BiConsumer bundler) { + return bundler(op, createBundlerSupplier(sysEnvResultSupplier, bundler)); + } + + Builder bundler(StandardBundlingOperation op, Consumer bundler) { + Objects.requireNonNull(bundler); + return bundler(op, () -> Result.ofValue(bundler)); + } + + Builder mutate(Consumer mutator) { + mutator.accept(this); + return this; + } + + private Supplier> defaultOperationSupplier; + private final Map>>> bundlers = new HashMap<>(); + } + + + static Builder build() { + return new Builder(); + } + + static Supplier runOnce(Supplier supplier) { + return new CachingSupplier<>(supplier); + } + + static Supplier>> createBundlerSupplier( + Supplier> sysEnvResultSupplier, BiConsumer bundler) { + Objects.requireNonNull(sysEnvResultSupplier); + Objects.requireNonNull(bundler); + return () -> { + return sysEnvResultSupplier.get().map(sysEnv -> { + return options -> { + bundler.accept(options, sysEnv); + }; + }); + }; + } + + static void createApplicationImage(Options options, Application app, PackagingPipeline.Builder pipelineBuilder) { + Objects.requireNonNull(options); + Objects.requireNonNull(app); + Objects.requireNonNull(pipelineBuilder); + + final var outputDir = OptionUtils.outputDir(options).resolve(app.appImageDirName()); + + IOUtils.writableOutputDir(outputDir.getParent()); + + final var env = new BuildEnvFromOptions() + .predefinedAppImageLayout(app.asApplicationLayout().orElseThrow()) + .create(options, app); + + Log.verbose(I18N.format("message.creating-app-bundle", outputDir.getFileName(), outputDir.toAbsolutePath().getParent())); + + if (Files.exists(outputDir)) { + throw new JPackageException(I18N.format("error.root-exists", outputDir.toAbsolutePath())); + } + + pipelineBuilder.excludeDirFromCopying(outputDir.getParent()) + .create().execute(BuildEnv.withAppImageDir(env, outputDir), app); + } + + static void createNativePackage(Options options, + T pkg, + BiFunction createBuildEnv, + PackagingPipeline.Builder pipelineBuilder, + Packager.PipelineBuilderMutatorFactory pipelineBuilderMutatorFactory) { + + Objects.requireNonNull(pipelineBuilder); + createNativePackage(options, pkg, createBuildEnv, _ -> pipelineBuilder, pipelineBuilderMutatorFactory); + } + + static void createNativePackage(Options options, + T pkg, + BiFunction createBuildEnv, + Function createPipelineBuilder, + Packager.PipelineBuilderMutatorFactory pipelineBuilderMutatorFactory) { + + Objects.requireNonNull(options); + Objects.requireNonNull(pkg); + Objects.requireNonNull(createBuildEnv); + Objects.requireNonNull(createPipelineBuilder); + Objects.requireNonNull(pipelineBuilderMutatorFactory); + + Packager.build().pkg(pkg) + .outputDir(OptionUtils.outputDir(options)) + .env(Objects.requireNonNull(createBuildEnv.apply(options, pkg))) + .pipelineBuilderMutatorFactory(pipelineBuilderMutatorFactory) + .execute(Objects.requireNonNull(createPipelineBuilder.apply(pkg))); + } + + @Override + public Optional defaultOperation() { + return defaultOperationSupplier.flatMap(Supplier::get); + } + + @Override + public void createBundle(BundlingOperationDescriptor op, Options cmdline) { + final var bundler = getBundlerSupplier(op).get().orElseThrow(); + Optional permanentWorkDirectory = Optional.empty(); + try (var tempDir = new TempDirectory(cmdline)) { + if (!tempDir.deleteOnClose()) { + permanentWorkDirectory = Optional.of(tempDir.path()); + } + bundler.accept(tempDir.options()); + + var packageType = OptionUtils.bundlingOperation(cmdline).packageType(); + + Log.verbose(I18N.format("message.bundle-created", I18N.getString(bundleTypeDescription(packageType, op.os())))); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } finally { + permanentWorkDirectory.ifPresent(workDir -> { + Log.verbose(I18N.format("message.debug-working-directory", workDir.toAbsolutePath())); + }); + } + } + + @Override + public Collection configurationErrors(BundlingOperationDescriptor op) { + return getBundlerSupplier(op).get().errors(); + } + + private Supplier>> getBundlerSupplier(BundlingOperationDescriptor op) { + return Optional.ofNullable(bundlers.get(op)).orElseThrow(() -> { + throw new NoSuchElementException(String.format("Unsupported bundling operation: %s", op)); + }); + } + + private String bundleTypeDescription(PackageType type, OperatingSystem os) { + switch (type) { + case StandardPackageType stdType -> { + switch (stdType) { + case WIN_MSI -> { + return "bundle-type.win-msi"; + } + case WIN_EXE -> { + return "bundle-type.win-exe"; + } + case LINUX_DEB -> { + return "bundle-type.linux-deb"; + } + case LINUX_RPM -> { + return "bundle-type.linux-rpm"; + } + case MAC_DMG -> { + return "bundle-type.mac-dmg"; + } + case MAC_PKG -> { + return "bundle-type.mac-pkg"; + } + default -> { + throw new AssertionError(); + } + } + } + case AppImagePackageType appImageType -> { + switch (os) { + case WINDOWS -> { + return "bundle-type.win-app"; + } + case LINUX -> { + return "bundle-type.linux-app"; + } + case MACOS -> { + return "bundle-type.mac-app"; + } + default -> { + throw new AssertionError(); + } + } + } + default -> { + throw new AssertionError(); + } + } + } + + + private static final class CachingSupplier implements Supplier { + + CachingSupplier(Supplier getter) { + this.getter = Objects.requireNonNull(getter); + } + + @Override + public T get() { + return cachedValue.updateAndGet(v -> { + return Optional.ofNullable(v).orElseGet(getter); + }); + } + + private final Supplier getter; + private final AtomicReference cachedValue = new AtomicReference<>(); + } + + + private final Map>>> bundlers; + private final Optional>> defaultOperationSupplier; +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/DeployParams.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/DeployParams.java deleted file mode 100644 index d7b4052d34a..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/DeployParams.java +++ /dev/null @@ -1,362 +0,0 @@ -/* - * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * 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; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.InvalidPathException; -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; -import java.util.stream.Stream; -import jdk.jpackage.internal.model.PackagerException; - -/** - * DeployParams - * - * This class is generated and used in Arguments.processArguments() as - * intermediate step in generating the BundleParams and ultimately the Bundles - */ -public class DeployParams { - - String targetFormat = null; // means default type for this platform - - // raw arguments to the bundler - Map bundlerArguments = new LinkedHashMap<>(); - - static class Template { - Path in; - Path out; - - Template(Path in, Path out) { - this.in = in; - this.out = out; - } - } - - // we need to expand as in some cases - // (most notably jpackage) - // we may get "." as filename and assumption is we include - // everything in the given folder - // (IOUtils.copyfiles() have recursive behavior) - List expandFileset(Path root) throws IOException { - List files = new LinkedList<>(); - if (!Files.isSymbolicLink(root)) { - if (Files.isDirectory(root)) { - try (Stream stream = Files.list(root)) { - List children = stream.toList(); - if (children != null && children.size() > 0) { - children.forEach(f -> { - try { - files.addAll(expandFileset(f)); - } catch (IOException ex) { - throw new RuntimeException(ex); - } - }); - } else { - // Include empty folders - files.add(root); - } - } - } else { - files.add(root); - } - } - return files; - } - - static void validateName(String s, boolean forApp) - throws PackagerException { - - String exceptionKey = forApp ? - "ERR_InvalidAppName" : "ERR_InvalidSLName"; - - if (s == null) { - if (forApp) { - return; - } else { - throw new PackagerException(exceptionKey); - } - } - if (s.length() == 0 || s.charAt(s.length() - 1) == '\\') { - throw new PackagerException(exceptionKey, s); - } - try { - // name must be valid path element for this file system - Path p = Path.of(s); - // and it must be a single name element in a path - if (p.getNameCount() != 1) { - throw new PackagerException(exceptionKey, s); - } - } catch (InvalidPathException ipe) { - throw new PackagerException(ipe, exceptionKey, s); - } - - for (int i = 0; i < s.length(); i++) { - char a = s.charAt(i); - // We check for ASCII codes first which we accept. If check fails, - // check if it is acceptable extended ASCII or unicode character. - if (a < ' ' || a > '~') { - // Accept anything else including special chars like copyright - // symbols. Note: space will be included by ASCII check above, - // but other whitespace like tabs or new line will be rejected. - if (Character.isISOControl(a) || - Character.isWhitespace(a)) { - throw new PackagerException(exceptionKey, s); - } - } else if (a == '"' || a == '%') { - throw new PackagerException(exceptionKey, s); - } - } - } - - @SuppressWarnings("unchecked") - public void validate() throws PackagerException { - boolean hasModule = (bundlerArguments.get( - Arguments.CLIOptions.MODULE.getId()) != null); - boolean hasAppImage = (bundlerArguments.get( - Arguments.CLIOptions.PREDEFINED_APP_IMAGE.getId()) != null); - boolean hasMain = (bundlerArguments.get( - Arguments.CLIOptions.MAIN_JAR.getId()) != null); - boolean hasRuntimeImage = (bundlerArguments.get( - Arguments.CLIOptions.PREDEFINED_RUNTIME_IMAGE.getId()) != null); - boolean hasInput = (bundlerArguments.get( - Arguments.CLIOptions.INPUT.getId()) != null); - boolean hasModulePath = (bundlerArguments.get( - Arguments.CLIOptions.MODULE_PATH.getId()) != null); - boolean hasMacAppStore = (bundlerArguments.get( - Arguments.CLIOptions.MAC_APP_STORE.getId()) != null); - boolean runtimeInstaller = !isTargetAppImage() && - !hasAppImage && !hasModule && !hasMain && hasRuntimeImage; - - if (isTargetAppImage()) { - // Module application requires --runtime-image or --module-path - if (hasModule) { - if (!hasModulePath && !hasRuntimeImage && !hasAppImage) { - throw new PackagerException("ERR_MissingArgument", - "--runtime-image or --module-path"); - } - } else { - if (!hasInput && !hasAppImage) { - throw new PackagerException("error.no-input-parameter"); - } - } - } else { - if (!runtimeInstaller) { - if (hasModule) { - if (!hasModulePath && !hasRuntimeImage && !hasAppImage) { - throw new PackagerException("ERR_MissingArgument", - "--runtime-image, --module-path or --app-image"); - } - } else { - if (!hasInput && !hasAppImage) { - throw new PackagerException("ERR_MissingArgument", - "--input or --app-image"); - } - } - } - } - - // if bundling non-modular image, or installer without app-image - // then we need some resources and a main class - if (!hasModule && !hasAppImage && !runtimeInstaller && !hasMain) { - throw new PackagerException("ERR_MissingArgument", "--main-jar"); - } - - String name = (String)bundlerArguments.get( - Arguments.CLIOptions.NAME.getId()); - validateName(name, true); - - // Validate app image if set - String appImage = (String)bundlerArguments.get( - Arguments.CLIOptions.PREDEFINED_APP_IMAGE.getId()); - if (appImage != null) { - Path appImageDir = Path.of(appImage); - if (!Files.exists(appImageDir) - || appImageDir.toFile().list() == null - || appImageDir.toFile().list().length == 0) { - throw new PackagerException("ERR_AppImageNotExist", appImage); - } - } - - // Validate temp dir - String root = (String)bundlerArguments.get( - Arguments.CLIOptions.TEMP_ROOT.getId()); - if (root != null && Files.exists(Path.of(root))) { - try (Stream stream = Files.walk(Path.of(root), 1)) { - Path [] contents = stream.toArray(Path[]::new); - // contents.length > 1 because Files.walk(path) includes path - if (contents != null && contents.length > 1) { - throw new PackagerException( - "ERR_BuildRootInvalid", root); - } - } catch (IOException ioe) { - throw new PackagerException(ioe); - } - } - - // Validate resource dir - String resources = (String)bundlerArguments.get( - Arguments.CLIOptions.RESOURCE_DIR.getId()); - if (resources != null) { - if (!(Files.exists(Path.of(resources)))) { - throw new PackagerException( - "message.resource-dir-does-not-exist", - Arguments.CLIOptions.RESOURCE_DIR.getId(), resources); - } - } - - // Validate predefined runtime dir - String runtime = (String)bundlerArguments.get( - Arguments.CLIOptions.PREDEFINED_RUNTIME_IMAGE.getId()); - if (runtime != null) { - if (!(Files.exists(Path.of(runtime)))) { - throw new PackagerException( - "message.runtime-image-dir-does-not-exist", - Arguments.CLIOptions.PREDEFINED_RUNTIME_IMAGE.getId(), - runtime); - } - } - - - // Validate license file if set - String license = (String)bundlerArguments.get( - Arguments.CLIOptions.LICENSE_FILE.getId()); - if (license != null) { - if (!(Files.exists(Path.of(license)))) { - throw new PackagerException("ERR_LicenseFileNotExit"); - } - } - - // Validate icon file if set - String icon = (String)bundlerArguments.get( - Arguments.CLIOptions.ICON.getId()); - if (icon != null) { - if (!(Files.exists(Path.of(icon)))) { - throw new PackagerException("ERR_IconFileNotExit", - Path.of(icon).toAbsolutePath().toString()); - } - } - - - if (hasMacAppStore) { - // Validate jlink-options if mac-app-store is set - Object jlinkOptions = bundlerArguments.get( - Arguments.CLIOptions.JLINK_OPTIONS.getId()); - if (jlinkOptions instanceof List) { - List options = (List) jlinkOptions; - if (!options.contains("--strip-native-commands")) { - throw new PackagerException( - "ERR_MissingJLinkOptMacAppStore", - "--strip-native-commands"); - } - } - } - } - - void setTargetFormat(String t) { - targetFormat = t; - } - - String getTargetFormat() { - return targetFormat; - } - - boolean isTargetAppImage() { - return ("app-image".equals(targetFormat)); - } - - private static final Set multi_args = new TreeSet<>(Arrays.asList( - StandardBundlerParam.JAVA_OPTIONS.getID(), - StandardBundlerParam.ARGUMENTS.getID(), - StandardBundlerParam.MODULE_PATH.getID(), - StandardBundlerParam.ADD_MODULES.getID(), - StandardBundlerParam.LIMIT_MODULES.getID(), - StandardBundlerParam.FILE_ASSOCIATIONS.getID(), - StandardBundlerParam.DMG_CONTENT.getID(), - StandardBundlerParam.APP_CONTENT.getID(), - StandardBundlerParam.JLINK_OPTIONS.getID() - )); - - @SuppressWarnings("unchecked") - public void addBundleArgument(String key, Object value) { - // special hack for multi-line arguments - if (multi_args.contains(key)) { - Object existingValue = bundlerArguments.get(key); - if (existingValue instanceof String && value instanceof String) { - String delim = "\n\n"; - if (key.equals(StandardBundlerParam.MODULE_PATH.getID())) { - delim = File.pathSeparator; - } else if ( - key.equals(StandardBundlerParam.DMG_CONTENT.getID()) || - key.equals(StandardBundlerParam.APP_CONTENT.getID()) || - key.equals(StandardBundlerParam.ADD_MODULES.getID())) { - delim = ","; - } - bundlerArguments.put(key, existingValue + delim + value); - } else if (existingValue instanceof List && value instanceof List) { - ((List)existingValue).addAll((List)value); - } else if (existingValue instanceof Map && - value instanceof String && ((String)value).contains("=")) { - String[] mapValues = ((String)value).split("=", 2); - ((Map)existingValue).put(mapValues[0], mapValues[1]); - } else { - bundlerArguments.put(key, value); - } - } else { - bundlerArguments.put(key, value); - } - } - - BundleParams getBundleParams() { - BundleParams bundleParams = new BundleParams(); - - // check for collisions - TreeSet keys = new TreeSet<>(bundlerArguments.keySet()); - keys.retainAll(bundleParams.getBundleParamsAsMap().keySet()); - - if (!keys.isEmpty()) { - throw new RuntimeException("Deploy Params and Bundler Arguments " - + "overlap in the following values:" + keys.toString()); - } - - bundleParams.addAllBundleParams(bundlerArguments); - - return bundleParams; - } - - @Override - public String toString() { - return "DeployParams {" + "output: " + "}"; - } - -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Executor.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Executor.java index dd1cc4a24b4..ca7a630b6d1 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Executor.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Executor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * 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,53 +25,153 @@ package jdk.jpackage.internal; import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.file.Files; -import java.nio.file.Path; +import java.io.PrintStream; +import java.io.StringReader; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +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 java.util.concurrent.TimeUnit; -import java.util.function.Consumer; -import java.util.function.Supplier; +import java.util.function.UnaryOperator; +import java.util.spi.ToolProvider; import java.util.stream.Stream; +import jdk.jpackage.internal.util.CommandLineFormat; +import jdk.jpackage.internal.util.CommandOutputControl; +import jdk.jpackage.internal.util.CommandOutputControl.ProcessAttributes; +import jdk.jpackage.internal.util.CommandOutputControl.Result; +import jdk.jpackage.internal.util.RetryExecutor; +import jdk.jpackage.internal.util.function.ExceptionBox; -public final class Executor { +final class Executor { - Executor() { + static Executor of(String... cmdline) { + return of(List.of(cmdline)); } - Executor setOutputConsumer(Consumer> v) { - outputConsumer = v; - return this; + static Executor of(List cmdline) { + return of(new ProcessBuilder(cmdline)); + } + + static Executor of(ProcessBuilder pb) { + return Globals.instance().objectFactory().executor().processBuilder(pb); + } + + public Executor() { + commandOutputControl = new CommandOutputControl(); + args = new ArrayList<>(); + } + + private Executor(Executor other) { + commandOutputControl = other.commandOutputControl.copy(); + quietCommand = other.quietCommand; + args = new ArrayList<>(other.args); + processBuilder = other.processBuilder; + toolProvider = other.toolProvider; + timeout = other.timeout; + mapper = other.mapper; } Executor saveOutput(boolean v) { - saveOutput = v; + commandOutputControl.saveOutput(v); return this; } - Executor setWriteOutputToFile(boolean v) { - writeOutputToFile = v; + Executor saveOutput() { + return saveOutput(true); + } + + Executor saveFirstLineOfOutput() { + commandOutputControl.saveFirstLineOfOutput(); return this; } - Executor setTimeout(long v) { + Executor charset(Charset v) { + commandOutputControl.charset(v); + return this; + } + + Executor storeOutputInFiles(boolean v) { + commandOutputControl.storeOutputInFiles(v); + return this; + } + + Executor storeOutputInFiles() { + return storeOutputInFiles(true); + } + + Executor binaryOutput(boolean v) { + commandOutputControl.binaryOutput(v); + return this; + } + + Executor binaryOutput() { + return binaryOutput(true); + } + + Executor discardStdout(boolean v) { + commandOutputControl.discardStdout(v); + return this; + } + + Executor discardStdout() { + return discardStdout(true); + } + + Executor discardStderr(boolean v) { + commandOutputControl.discardStderr(v); + return this; + } + + Executor discardStderr() { + return discardStderr(true); + } + + Executor timeout(long v, TimeUnit unit) { + return timeout(Duration.of(v, unit.toChronoUnit())); + } + + Executor timeout(Duration v) { timeout = v; - if (timeout != INFINITE_TIMEOUT) { - // Redirect output to file if timeout is requested, otherwise we will - // reading until process ends and timeout will never be reached. - setWriteOutputToFile(true); - } return this; } - Executor setProcessBuilder(ProcessBuilder v) { - pb = v; + Executor toolProvider(ToolProvider v) { + toolProvider = Objects.requireNonNull(v); + processBuilder = null; return this; } - Executor setCommandLine(String... cmdline) { - return setProcessBuilder(new ProcessBuilder(cmdline)); + Optional toolProvider() { + return Optional.ofNullable(toolProvider); + } + + Executor processBuilder(ProcessBuilder v) { + processBuilder = Objects.requireNonNull(v); + toolProvider = null; + return this; + } + + Optional processBuilder() { + return Optional.ofNullable(processBuilder); + } + + Executor args(List v) { + args.addAll(v); + return this; + } + + Executor args(String... args) { + return args(List.of(args)); + } + + List args() { + return args; } Executor setQuiet(boolean v) { @@ -79,159 +179,207 @@ public final class Executor { return this; } - List getOutput() { - return output; - } - - Executor executeExpectSuccess() throws IOException { - int ret = execute(); - if (0 != ret) { - throw new IOException( - String.format("Command %s exited with %d code", - createLogMessage(pb, false), ret)); - } + Executor mapper(UnaryOperator v) { + mapper = v; return this; } - int execute() throws IOException { - output = null; + Optional> mapper() { + return Optional.ofNullable(mapper); + } - boolean needProcessOutput = outputConsumer != null || Log.isVerbose() || saveOutput; - Path outputFile = null; - if (needProcessOutput) { - pb.redirectErrorStream(true); - if (writeOutputToFile) { - outputFile = Files.createTempFile("jpackageOutputTempFile", ".tmp"); - pb.redirectOutput(outputFile.toFile()); + Executor copy() { + return new Executor(this); + } + + Result execute() throws IOException { + if (mapper != null) { + var mappedExecutor = Objects.requireNonNull(mapper.apply(this)); + if (mappedExecutor != this) { + return mappedExecutor.execute(); } + } + + var coc = commandOutputControl.copy(); + + final CommandOutputControl.Executable exec; + if (processBuilder != null) { + exec = coc.createExecutable(copyProcessBuilder()); + } else if (toolProvider != null) { + exec = coc.createExecutable(toolProvider, args.toArray(String[]::new)); } else { - // We are not going to read process output, so need to notify - // ProcessBuilder about this. Otherwise some processes might just - // hang up (`ldconfig -p`). - pb.redirectError(ProcessBuilder.Redirect.DISCARD); - pb.redirectOutput(ProcessBuilder.Redirect.DISCARD); + throw new IllegalStateException("No target to execute"); } - if (!quietCommand) { - Log.verbose(String.format("Running %s", createLogMessage(pb, true))); + PrintableOutputBuilder printableOutputBuilder; + if (dumpOutput()) { + printableOutputBuilder = new PrintableOutputBuilder(coc); + } else { + printableOutputBuilder = null; } - Process p = pb.start(); - - int code = 0; - if (writeOutputToFile) { - try { - code = waitForProcess(p); - } catch (InterruptedException ex) { - Log.verbose(ex); - throw new RuntimeException(ex); - } - } - - if (needProcessOutput) { - final List savedOutput; - Supplier> outputStream; - - if (writeOutputToFile) { - output = savedOutput = Files.readAllLines(outputFile); - Files.delete(outputFile); - outputStream = () -> { - if (savedOutput != null) { - return savedOutput.stream(); - } - return null; - }; - if (outputConsumer != null) { - outputConsumer.accept(outputStream.get()); - } - } else { - try (var br = new BufferedReader(new InputStreamReader( - p.getInputStream()))) { - - if ((outputConsumer != null || Log.isVerbose()) - || saveOutput) { - savedOutput = br.lines().toList(); - } else { - savedOutput = null; - } - output = savedOutput; - - outputStream = () -> { - if (savedOutput != null) { - return savedOutput.stream(); - } - return br.lines(); - }; - if (outputConsumer != null) { - outputConsumer.accept(outputStream.get()); - } - - if (savedOutput == null) { - // For some processes on Linux if the output stream - // of the process is opened but not consumed, the process - // would exit with code 141. - // It turned out that reading just a single line of process - // output fixes the problem, but let's process - // all of the output, just in case. - br.lines().forEach(x -> {}); - } - } - } + if (dumpOutput()) { + Log.verbose(String.format("Running %s", CommandLineFormat.DEFAULT.apply(List.of(commandLine().getFirst())))); } + Result result; try { - if (!writeOutputToFile) { - code = p.waitFor(); - } - if (!quietCommand) { - Log.verbose(pb.command(), getOutput(), code, IOUtils.getPID(p)); - } - return code; - } catch (InterruptedException ex) { - Log.verbose(ex); - throw new RuntimeException(ex); - } - } - - private int waitForProcess(Process p) throws InterruptedException { - if (timeout == INFINITE_TIMEOUT) { - return p.waitFor(); - } else { - if (p.waitFor(timeout, TimeUnit.SECONDS)) { - return p.exitValue(); + if (timeout == null) { + result = exec.execute(); } else { - Log.verbose(String.format("Command %s timeout after %d seconds", - createLogMessage(pb, false), timeout)); - p.destroy(); - return -1; + result = exec.execute(timeout.toMillis(), TimeUnit.MILLISECONDS); + } + } catch (InterruptedException ex) { + throw ExceptionBox.toUnchecked(ex); + } + + if (dumpOutput()) { + log(result, printableOutputBuilder.create()); + } + + return result; + } + + Result executeExpectSuccess() throws IOException { + return execute().expectExitCode(0); + } + + Result executeExpect(int mainExitCode, int... otherExitCodes) throws IOException { + return execute().expectExitCode(mainExitCode, otherExitCodes); + } + + RetryExecutor retry() { + return Globals.instance().objectFactory().retryExecutor(IOException.class) + .setExecutable(this::executeExpectSuccess); + } + + RetryExecutor retryOnKnownErrorMessage(String msg) { + Objects.requireNonNull(msg); + return saveOutput().retry().setExecutable(() -> { + // Execute it without exit code check. + var result = execute(); + if (result.stderr().stream().anyMatch(msg::equals)) { + throw result.unexpected(); + } + return result; + }); + } + + List commandLine() { + if (processBuilder != null) { + return Stream.of(processBuilder.command(), args).flatMap(Collection::stream).toList(); + } else if (toolProvider != null) { + return Stream.concat(Stream.of(toolProvider.name()), args.stream()).toList(); + } else { + throw new IllegalStateException("No target to execute"); + } + } + + private ProcessBuilder copyProcessBuilder() { + if (processBuilder == null) { + throw new IllegalStateException(); + } + + var copy = new ProcessBuilder(commandLine()); + copy.directory(processBuilder.directory()); + var env = copy.environment(); + env.clear(); + env.putAll(processBuilder.environment()); + + return copy; + } + + private boolean dumpOutput() { + return Log.isVerbose() && !quietCommand; + } + + private static void log(Result result, String printableOutput) throws IOException { + Objects.requireNonNull(result); + Objects.requireNonNull(printableOutput); + + Optional pid; + if (result.execAttrs() instanceof ProcessAttributes attrs) { + pid = attrs.pid(); + } else { + pid = Optional.empty(); + } + + var sb = new StringBuilder(); + sb.append("Command"); + pid.ifPresent(p -> { + sb.append(" [PID: ").append(p).append("]"); + }); + sb.append(":\n ").append(result.execAttrs()); + Log.verbose(sb.toString()); + + if (!printableOutput.isEmpty()) { + sb.delete(0, sb.length()); + sb.append("Output:"); + try (var lines = new BufferedReader(new StringReader(printableOutput)).lines()) { + lines.forEach(line -> { + sb.append("\n ").append(line); + }); + } + Log.verbose(sb.toString()); + } + + result.exitCode().ifPresentOrElse(exitCode -> { + Log.verbose("Returned: " + exitCode + "\n"); + }, () -> { + Log.verbose("Aborted: timed-out" + "\n"); + }); + } + + private static final class PrintableOutputBuilder { + + PrintableOutputBuilder(CommandOutputControl coc) { + coc.dumpOutput(true); + charset = coc.charset(); + if (coc.isBinaryOutput()) { + // Assume binary output goes into stdout and text error messages go into stderr, so keep them separated. + sinks = new ByteArrayOutputStream[2]; + sinks[0] = new ByteArrayOutputStream(); + sinks[1] = new ByteArrayOutputStream(); + coc.dumpStdout(new PrintStream(sinks[0], false, charset)) + .dumpStderr(new PrintStream(sinks[1], false, charset)); + } else { + sinks = new ByteArrayOutputStream[1]; + sinks[0] = new ByteArrayOutputStream(); + var ps = new PrintStream(sinks[0], false, charset); + // Redirect stderr in stdout. + coc.dumpStdout(ps).dumpStderr(ps); } } - } - static Executor of(String... cmdline) { - return new Executor().setCommandLine(cmdline); - } - - static Executor of(ProcessBuilder pb) { - return new Executor().setProcessBuilder(pb); - } - - private static String createLogMessage(ProcessBuilder pb, boolean quiet) { - StringBuilder sb = new StringBuilder(); - sb.append((quiet) ? pb.command().get(0) : pb.command()); - if (pb.directory() != null) { - sb.append(String.format(" in %s", pb.directory().getAbsolutePath())); + String create() { + if (isBinaryOutput()) { + // In case of binary output: + // - Convert binary stdout to text using ISO-8859-1 encoding and + // replace non-printable characters with the question mark symbol (?). + // - Convert binary stderr to text using designated encoding (assume stderr is always a character stream). + // - Merge text stdout and stderr into a single string; + // stderr first, stdout follows, with the aim to present user error messages first. + var sb = new StringBuilder(); + var stdout = sinks[0].toString(StandardCharsets.ISO_8859_1).replaceAll("[^\\p{Print}\\p{Space}]", "?"); + return sb.append(sinks[1].toString(charset)).append(stdout).toString(); + } else { + return sinks[0].toString(charset); + } } - return sb.toString(); + + private boolean isBinaryOutput() { + return sinks.length == 2; + } + + private final ByteArrayOutputStream sinks[]; + private final Charset charset; } - public static final int INFINITE_TIMEOUT = -1; - - private ProcessBuilder pb; - private boolean saveOutput; - private boolean writeOutputToFile; + private final CommandOutputControl commandOutputControl; private boolean quietCommand; - private long timeout = INFINITE_TIMEOUT; - private List output; - private Consumer> outputConsumer; + private final List args; + private ProcessBuilder processBuilder; + private ToolProvider toolProvider; + private Duration timeout; + private UnaryOperator mapper; } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ExecutorFactory.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ExecutorFactory.java new file mode 100644 index 00000000000..ce703358b82 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ExecutorFactory.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.jpackage.internal; + +@FunctionalInterface +interface ExecutorFactory { + + Executor executor(); + + static final ExecutorFactory DEFAULT = Executor::new; +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FileAssociationGroup.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FileAssociationGroup.java index 349a09f237c..ed89ffe1ef6 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FileAssociationGroup.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FileAssociationGroup.java @@ -111,6 +111,10 @@ final record FileAssociationGroup(List items) { return this; } + Optional description() { + return Optional.ofNullable(description); + } + Builder mimeTypes(Collection v) { mimeTypes = Set.copyOf(v); return this; diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromOptions.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromOptions.java new file mode 100644 index 00000000000..6b74cab4e65 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromOptions.java @@ -0,0 +1,237 @@ +/* + * 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; + +import static jdk.jpackage.internal.ApplicationBuilder.normalizeIcons; +import static jdk.jpackage.internal.JLinkRuntimeBuilder.ensureBaseModuleInModulePath; +import static jdk.jpackage.internal.OptionUtils.isRuntimeInstaller; +import static jdk.jpackage.internal.cli.StandardOption.ABOUT_URL; +import static jdk.jpackage.internal.cli.StandardOption.ADDITIONAL_LAUNCHERS; +import static jdk.jpackage.internal.cli.StandardOption.ADD_MODULES; +import static jdk.jpackage.internal.cli.StandardOption.APP_CONTENT; +import static jdk.jpackage.internal.cli.StandardOption.APP_VERSION; +import static jdk.jpackage.internal.cli.StandardOption.COPYRIGHT; +import static jdk.jpackage.internal.cli.StandardOption.DESCRIPTION; +import static jdk.jpackage.internal.cli.StandardOption.INPUT; +import static jdk.jpackage.internal.cli.StandardOption.INSTALL_DIR; +import static jdk.jpackage.internal.cli.StandardOption.JLINK_OPTIONS; +import static jdk.jpackage.internal.cli.StandardOption.LICENSE_FILE; +import static jdk.jpackage.internal.cli.StandardOption.MODULE_PATH; +import static jdk.jpackage.internal.cli.StandardOption.NAME; +import static jdk.jpackage.internal.cli.StandardOption.PREDEFINED_APP_IMAGE; +import static jdk.jpackage.internal.cli.StandardOption.PREDEFINED_RUNTIME_IMAGE; +import static jdk.jpackage.internal.cli.StandardOption.RESOURCE_DIR; +import static jdk.jpackage.internal.cli.StandardOption.VENDOR; + +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.Function; +import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.model.Application; +import jdk.jpackage.internal.model.ApplicationLaunchers; +import jdk.jpackage.internal.model.ApplicationLayout; +import jdk.jpackage.internal.model.Launcher; +import jdk.jpackage.internal.model.LauncherModularStartupInfo; +import jdk.jpackage.internal.model.PackageType; +import jdk.jpackage.internal.model.RuntimeLayout; + +final class FromOptions { + + static ApplicationBuilderBuilder buildApplicationBuilder() { + return new ApplicationBuilderBuilder(); + } + + static PackageBuilder createPackageBuilder(Options options, Application app, PackageType type) { + + final var builder = new PackageBuilder(app, type); + + NAME.ifPresentIn(options, builder::name); + DESCRIPTION.ifPresentIn(options, builder::description); + APP_VERSION.ifPresentIn(options, builder::version); + ABOUT_URL.ifPresentIn(options, builder::aboutURL); + LICENSE_FILE.ifPresentIn(options, builder::licenseFile); + PREDEFINED_APP_IMAGE.ifPresentIn(options, builder::predefinedAppImage); + PREDEFINED_RUNTIME_IMAGE.ifPresentIn(options, builder::predefinedAppImage); + INSTALL_DIR.ifPresentIn(options, builder::installDir); + + return builder; + } + + + static final class ApplicationBuilderBuilder { + + private ApplicationBuilderBuilder() { + } + + ApplicationBuilder create(Options options, + Function launcherCtor, + BiFunction launcherOverrideCtor, + ApplicationLayout appLayout) { + + final Optional thePredefinedRuntimeLayout; + if (PREDEFINED_RUNTIME_IMAGE.containsIn(options)) { + thePredefinedRuntimeLayout = Optional.ofNullable( + predefinedRuntimeLayout).or(() -> Optional.of(RuntimeLayout.DEFAULT)); + } else { + thePredefinedRuntimeLayout = Optional.empty(); + } + + final var transfomer = new OptionsTransformer(options, appLayout); + final var appBuilder = createApplicationBuilder( + transfomer.appOptions(), + launcherCtor, + launcherOverrideCtor, + appLayout, + Optional.ofNullable(runtimeLayout).orElse(RuntimeLayout.DEFAULT), + thePredefinedRuntimeLayout); + + transfomer.externalApp().ifPresent(appBuilder::externalApplication); + + return appBuilder; + } + + /** + * Sets the layout of the predefined runtime image. + * @param v the layout of the predefined runtime image. Null is permitted. + * @return this + */ + ApplicationBuilderBuilder predefinedRuntimeLayout(RuntimeLayout v) { + predefinedRuntimeLayout = v; + return this; + } + + /** + * Sets the layout of a runtime bundle. + * @param v the layout of a runtime bundle. Null is permitted. + * @return this + */ + ApplicationBuilderBuilder runtimeLayout(RuntimeLayout v) { + runtimeLayout = v; + return this; + } + + private RuntimeLayout runtimeLayout; + private RuntimeLayout predefinedRuntimeLayout; + } + + + private static ApplicationBuilder createApplicationBuilder(Options options, + Function launcherCtor, + BiFunction launcherOverrideCtor, + ApplicationLayout appLayout, RuntimeLayout runtimeLayout, + Optional predefinedRuntimeLayout) { + + final var appBuilder = new ApplicationBuilder(); + + final var isRuntimeInstaller = isRuntimeInstaller(options); + + final var predefinedRuntimeImage = PREDEFINED_RUNTIME_IMAGE.findIn(options); + + final var predefinedRuntimeDirectory = predefinedRuntimeLayout.flatMap(layout -> { + return predefinedRuntimeImage.map(layout::resolveAt); + }).map(RuntimeLayout::runtimeDirectory); + + NAME.findIn(options).or(() -> { + if (isRuntimeInstaller) { + return predefinedRuntimeImage.map(Path::getFileName).map(Path::toString); + } else { + return Optional.empty(); + } + }).ifPresent(appBuilder::name); + DESCRIPTION.ifPresentIn(options, appBuilder::description); + APP_VERSION.ifPresentIn(options, appBuilder::version); + VENDOR.ifPresentIn(options, appBuilder::vendor); + COPYRIGHT.ifPresentIn(options, appBuilder::copyright); + INPUT.ifPresentIn(options, appBuilder::srcDir); + APP_CONTENT.ifPresentIn(options, appBuilder::contentDirs); + + if (isRuntimeInstaller) { + appBuilder.appImageLayout(runtimeLayout); + } else { + appBuilder.appImageLayout(appLayout); + + final var launchers = createLaunchers(options, launcherCtor); + + if (PREDEFINED_APP_IMAGE.containsIn(options)) { + appBuilder.launchers(launchers); + } else { + appBuilder.launchers(normalizeIcons(launchers, RESOURCE_DIR.findIn(options), launcherOverrideCtor)); + + final var runtimeBuilderBuilder = new RuntimeBuilderBuilder(); + + runtimeBuilderBuilder.modulePath(ensureBaseModuleInModulePath(MODULE_PATH.findIn(options).orElseGet(List::of))); + + if (!APP_VERSION.containsIn(options)) { + // Version is not specified explicitly. Try to get it from the app's module. + launchers.mainLauncher().startupInfo().ifPresent(startupInfo -> { + if (startupInfo instanceof LauncherModularStartupInfo modularStartupInfo) { + modularStartupInfo.moduleVersion().ifPresent(moduleVersion -> { + appBuilder.version(moduleVersion); + Log.verbose(I18N.format("message.module-version", + moduleVersion, modularStartupInfo.moduleName())); + }); + } + }); + } + + predefinedRuntimeDirectory.ifPresentOrElse(runtimeBuilderBuilder::forRuntime, () -> { + final var startupInfos = launchers.asList().stream() + .map(Launcher::startupInfo) + .map(Optional::orElseThrow).toList(); + final var jlinkOptionsBuilder = runtimeBuilderBuilder.forNewRuntime(startupInfos); + ADD_MODULES.findIn(options).map(Set::copyOf).ifPresent(jlinkOptionsBuilder::addModules); + JLINK_OPTIONS.ifPresentIn(options, jlinkOptionsBuilder::options); + jlinkOptionsBuilder.apply(); + }); + + appBuilder.runtimeBuilder(runtimeBuilderBuilder.create()); + } + } + + return appBuilder; + } + + private static ApplicationLaunchers createLaunchers(Options options, Function launcherCtor) { + var launchers = ADDITIONAL_LAUNCHERS.getFrom(options); + + var mainLauncher = launcherCtor.apply(options); + + // + // Additional launcher should: + // - Use description from the main launcher by default. + // + var mainLauncherDefaults = Options.of(Map.of(DESCRIPTION, mainLauncher.description())); + + var additionalLaunchers = launchers.stream().map(launcherOptions -> { + return launcherOptions.copyWithParent(mainLauncherDefaults); + }).map(launcherCtor).toList(); + + return new ApplicationLaunchers(mainLauncher, additionalLaunchers); + } +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromParams.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromParams.java deleted file mode 100644 index cee7492dbc7..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromParams.java +++ /dev/null @@ -1,251 +0,0 @@ -/* - * 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; - -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; -import static jdk.jpackage.internal.StandardBundlerParam.APP_CONTENT; -import static jdk.jpackage.internal.StandardBundlerParam.APP_NAME; -import static jdk.jpackage.internal.StandardBundlerParam.COPYRIGHT; -import static jdk.jpackage.internal.StandardBundlerParam.DESCRIPTION; -import static jdk.jpackage.internal.StandardBundlerParam.FILE_ASSOCIATIONS; -import static jdk.jpackage.internal.StandardBundlerParam.ICON; -import static jdk.jpackage.internal.StandardBundlerParam.INSTALLER_NAME; -import static jdk.jpackage.internal.StandardBundlerParam.INSTALL_DIR; -import static jdk.jpackage.internal.StandardBundlerParam.JLINK_OPTIONS; -import static jdk.jpackage.internal.StandardBundlerParam.LAUNCHER_AS_SERVICE; -import static jdk.jpackage.internal.StandardBundlerParam.LICENSE_FILE; -import static jdk.jpackage.internal.StandardBundlerParam.LIMIT_MODULES; -import static jdk.jpackage.internal.StandardBundlerParam.MODULE_PATH; -import static jdk.jpackage.internal.StandardBundlerParam.NAME; -import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE; -import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE_FILE; -import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_RUNTIME_IMAGE; -import static jdk.jpackage.internal.StandardBundlerParam.RESOURCE_DIR; -import static jdk.jpackage.internal.StandardBundlerParam.SOURCE_DIR; -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; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.function.BiFunction; -import java.util.function.Function; -import jdk.jpackage.internal.model.Application; -import jdk.jpackage.internal.model.ApplicationLaunchers; -import jdk.jpackage.internal.model.ApplicationLayout; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.model.ExternalApplication; -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.ParseUtils; -import jdk.jpackage.internal.model.RuntimeLayout; -import jdk.jpackage.internal.util.function.ThrowingFunction; - -final class FromParams { - - static ApplicationBuilder createApplicationBuilder(Map params, - Function, Launcher> launcherMapper, - BiFunction launcherOverrideCtor, - ApplicationLayout appLayout) throws ConfigException, IOException { - return createApplicationBuilder(params, launcherMapper, launcherOverrideCtor, appLayout, RuntimeLayout.DEFAULT, Optional.of(RuntimeLayout.DEFAULT)); - } - - static ApplicationBuilder createApplicationBuilder(Map params, - Function, Launcher> launcherMapper, - BiFunction launcherOverrideCtor, - ApplicationLayout appLayout, RuntimeLayout runtimeLayout, - Optional predefinedRuntimeLayout) throws ConfigException, IOException { - - final var appBuilder = new ApplicationBuilder(); - - APP_NAME.copyInto(params, appBuilder::name); - DESCRIPTION.copyInto(params, appBuilder::description); - appBuilder.version(VERSION.fetchFrom(params)); - VENDOR.copyInto(params, appBuilder::vendor); - COPYRIGHT.copyInto(params, appBuilder::copyright); - SOURCE_DIR.copyInto(params, appBuilder::srcDir); - APP_CONTENT.copyInto(params, appBuilder::contentDirs); - - final var isRuntimeInstaller = isRuntimeInstaller(params); - - final var predefinedRuntimeImage = PREDEFINED_RUNTIME_IMAGE.findIn(params); - - final var predefinedRuntimeDirectory = predefinedRuntimeLayout.flatMap( - layout -> predefinedRuntimeImage.map(layout::resolveAt)).map(RuntimeLayout::runtimeDirectory); - - if (isRuntimeInstaller) { - appBuilder.appImageLayout(runtimeLayout); - } else { - appBuilder.appImageLayout(appLayout); - - if (hasPredefinedAppImage(params)) { - final var appImageFile = PREDEFINED_APP_IMAGE_FILE.fetchFrom(params); - appBuilder.initFromExternalApplication(appImageFile, launcherInfo -> { - var launcherParams = mapLauncherInfo(appImageFile, launcherInfo); - return launcherMapper.apply(mergeParams(params, launcherParams)); - }); - } else { - final var launchers = createLaunchers(params, launcherMapper); - - final var runtimeBuilderBuilder = new RuntimeBuilderBuilder(); - - runtimeBuilderBuilder.modulePath(MODULE_PATH.fetchFrom(params)); - - predefinedRuntimeDirectory.ifPresentOrElse(runtimeBuilderBuilder::forRuntime, () -> { - final var startupInfos = launchers.asList().stream() - .map(Launcher::startupInfo) - .map(Optional::orElseThrow).toList(); - final var jlinkOptionsBuilder = runtimeBuilderBuilder.forNewRuntime(startupInfos); - ADD_MODULES.copyInto(params, jlinkOptionsBuilder::addModules); - LIMIT_MODULES.copyInto(params, jlinkOptionsBuilder::limitModules); - JLINK_OPTIONS.copyInto(params, jlinkOptionsBuilder::options); - jlinkOptionsBuilder.apply(); - }); - - final var normalizedLaunchers = ApplicationBuilder.normalizeIcons(launchers, RESOURCE_DIR.findIn(params), launcherOverrideCtor); - - appBuilder.launchers(normalizedLaunchers).runtimeBuilder(runtimeBuilderBuilder.create()); - } - } - - return appBuilder; - } - - static PackageBuilder createPackageBuilder( - Map params, Application app, - PackageType type) throws ConfigException { - - final var builder = new PackageBuilder(app, type); - - builder.name(INSTALLER_NAME.fetchFrom(params)); - DESCRIPTION.copyInto(params, builder::description); - VERSION.copyInto(params, builder::version); - ABOUT_URL.copyInto(params, builder::aboutURL); - LICENSE_FILE.findIn(params).map(Path::of).ifPresent(builder::licenseFile); - PREDEFINED_APP_IMAGE.findIn(params).ifPresent(builder::predefinedAppImage); - PREDEFINED_RUNTIME_IMAGE.findIn(params).ifPresent(builder::predefinedAppImage); - INSTALL_DIR.findIn(params).map(Path::of).ifPresent(builder::installDir); - - return builder; - } - - static BundlerParamInfo createApplicationBundlerParam( - ThrowingFunction, T> ctor) { - return BundlerParamInfo.createBundlerParam(Application.class, ctor); - } - - static BundlerParamInfo createPackageBundlerParam( - ThrowingFunction, T> ctor) { - return BundlerParamInfo.createBundlerParam(jdk.jpackage.internal.model.Package.class, ctor); - } - - static Optional getCurrentPackage(Map params) { - return Optional.ofNullable((jdk.jpackage.internal.model.Package)params.get( - 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(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( - Map params, - Function, Launcher> launcherMapper) { - var launchers = ADD_LAUNCHERS.findIn(params).orElseGet(List::of); - - var mainLauncher = launcherMapper.apply(params); - var additionalLaunchers = launchers.stream().map(launcherParams -> { - return launcherMapper.apply(mergeParams(params, launcherParams)); - }).toList(); - - return new ApplicationLaunchers(mainLauncher, additionalLaunchers); - } - - private static Map mapLauncherInfo(ExternalApplication appImageFile, LauncherInfo launcherInfo) { - Map launcherParams = new HashMap<>(); - launcherParams.put(NAME.getID(), launcherInfo.name()); - if (!appImageFile.getLauncherName().equals(launcherInfo.name())) { - // This is not the main launcher, accept the value - // of "launcher-as-service" from the app image file (.jpackage.xml). - launcherParams.put(LAUNCHER_AS_SERVICE.getID(), Boolean.toString(launcherInfo.service())); - } - launcherParams.putAll(launcherInfo.extra()); - return launcherParams; - } - - private static Map mergeParams(Map mainParams, - Map launcherParams) { - if (!launcherParams.containsKey(DESCRIPTION.getID())) { - launcherParams = new HashMap<>(launcherParams); -// FIXME: this is a good improvement but it fails existing tests -// launcherParams.put(DESCRIPTION.getID(), String.format("%s (%s)", DESCRIPTION.fetchFrom( -// 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(), 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/Globals.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Globals.java new file mode 100644 index 00000000000..c1b56b24e0a --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Globals.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.jpackage.internal; + +import java.util.Optional; +import java.util.function.Supplier; + +public final class Globals { + + private Globals() { + } + + Globals objectFactory(ObjectFactory v) { + checkMutable(); + objectFactory = Optional.ofNullable(v).orElse(ObjectFactory.DEFAULT); + return this; + } + + ObjectFactory objectFactory() { + return objectFactory; + } + + Globals executorFactory(ExecutorFactory v) { + return objectFactory(ObjectFactory.build(objectFactory).executorFactory(v).create()); + } + + public static int main(Supplier mainBody) { + if (INSTANCE.isBound()) { + return mainBody.get(); + } else { + return ScopedValue.where(INSTANCE, new Globals()).call(mainBody::get); + } + } + + public static Globals instance() { + return INSTANCE.orElse(DEFAULT); + } + + private void checkMutable() { + if (this == DEFAULT) { + throw new UnsupportedOperationException("Can't modify immutable instance"); + } + } + + private ObjectFactory objectFactory = ObjectFactory.DEFAULT; + + private static final ScopedValue INSTANCE = ScopedValue.newInstance(); + private static final Globals DEFAULT = new Globals(); +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/IOUtils.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/IOUtils.java index 427051719bb..08cf0c10982 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/IOUtils.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/IOUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,13 +26,10 @@ package jdk.jpackage.internal; import java.io.IOException; -import java.io.PrintStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; -import java.util.ArrayList; -import java.util.List; -import jdk.jpackage.internal.model.PackagerException; +import jdk.jpackage.internal.model.JPackageException; /** * IOUtils @@ -50,70 +47,17 @@ final class IOUtils { StandardCopyOption.COPY_ATTRIBUTES); } - public static void exec(ProcessBuilder pb) - throws IOException { - exec(pb, false, null, false, Executor.INFINITE_TIMEOUT); - } - - // timeout in seconds. -1 will be return if process timeouts. - public static void exec(ProcessBuilder pb, long timeout) - throws IOException { - exec(pb, false, null, false, timeout); - } - - static void exec(ProcessBuilder pb, boolean testForPresenceOnly, - PrintStream consumer, boolean writeOutputToFile, long timeout) - throws IOException { - exec(pb, testForPresenceOnly, consumer, writeOutputToFile, - timeout, false); - } - - static void exec(ProcessBuilder pb, boolean testForPresenceOnly, - PrintStream consumer, boolean writeOutputToFile, - long timeout, boolean quiet) throws IOException { - List output = new ArrayList<>(); - Executor exec = Executor.of(pb) - .setWriteOutputToFile(writeOutputToFile) - .setTimeout(timeout) - .setQuiet(quiet) - .setOutputConsumer(lines -> { - lines.forEach(output::add); - if (consumer != null) { - output.forEach(consumer::println); - } - }); - - if (testForPresenceOnly) { - exec.execute(); - } else { - exec.executeExpectSuccess(); - } - } - - static void writableOutputDir(Path outdir) throws PackagerException { + static void writableOutputDir(Path outdir) { if (!Files.isDirectory(outdir)) { try { Files.createDirectories(outdir); } catch (IOException ex) { - throw new PackagerException("error.cannot-create-output-dir", - outdir.toAbsolutePath().toString()); + throw new JPackageException(I18N.format("error.cannot-create-output-dir", outdir.toAbsolutePath())); } } if (!Files.isWritable(outdir)) { - throw new PackagerException("error.cannot-write-to-output-dir", - outdir.toAbsolutePath().toString()); - } - } - - public static long getPID(Process p) { - try { - return p.pid(); - } catch (UnsupportedOperationException ex) { - Log.verbose(ex); // Just log exception and ignore it. This method - // is used for verbose output, so not a problem - // if unsupported. - return -1; + throw new JPackageException(I18N.format("error.cannot-write-to-output-dir", outdir.toAbsolutePath())); } } } 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 6ac9758e179..f960f8dc2bf 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JLinkRuntimeBuilder.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JLinkRuntimeBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,13 +22,14 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ + package jdk.jpackage.internal; + import static jdk.jpackage.internal.model.RuntimeBuilder.getDefaultModulePath; +import static jdk.jpackage.internal.util.function.ThrowingRunnable.toRunnable; import java.io.File; import java.io.IOException; -import java.io.PrintWriter; -import java.io.StringWriter; import java.lang.module.Configuration; import java.lang.module.ModuleDescriptor; import java.lang.module.ModuleFinder; @@ -36,7 +37,6 @@ import java.lang.module.ModuleReference; import java.lang.module.ResolvedModule; import java.nio.file.Files; import java.nio.file.Path; -import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -51,36 +51,22 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import jdk.internal.module.ModulePath; import jdk.jpackage.internal.model.AppImageLayout; -import jdk.jpackage.internal.model.ConfigException; import jdk.jpackage.internal.model.LauncherModularStartupInfo; import jdk.jpackage.internal.model.LauncherStartupInfo; -import jdk.jpackage.internal.model.PackagerException; import jdk.jpackage.internal.model.RuntimeBuilder; final class JLinkRuntimeBuilder implements RuntimeBuilder { private JLinkRuntimeBuilder(List jlinkCmdLine) { - this.jlinkCmdLine = jlinkCmdLine; + this.jlinkCmdLine = Objects.requireNonNull(jlinkCmdLine); } @Override - public void create(AppImageLayout appImageLayout) throws PackagerException { - var args = new ArrayList(); - args.add("--output"); - args.add(appImageLayout.runtimeDirectory().toString()); - args.addAll(jlinkCmdLine); - - StringWriter writer = new StringWriter(); - PrintWriter pw = new PrintWriter(writer); - - int retVal = LazyLoad.JLINK_TOOL.run(pw, pw, args.toArray(String[]::new)); - String jlinkOut = writer.toString(); - - args.add(0, "jlink"); - Log.verbose(args, List.of(jlinkOut), retVal, -1); - if (retVal != 0) { - throw new PackagerException("error.jlink.failed", jlinkOut); - } + public void create(AppImageLayout appImageLayout) { + toRunnable(Executor.of() + .toolProvider(LazyLoad.JLINK_TOOL) + .args("--output", appImageLayout.runtimeDirectory().toString()) + .args(jlinkCmdLine)::executeExpectSuccess).run(); } @Override @@ -96,7 +82,7 @@ final class JLinkRuntimeBuilder implements RuntimeBuilder { } static RuntimeBuilder createJLinkRuntimeBuilder(List modulePath, Set addModules, - Set limitModules, List options, List startupInfos) throws ConfigException { + Set limitModules, List options, List startupInfos) { return new JLinkRuntimeBuilder(createJLinkCmdline(modulePath, addModules, limitModules, options, startupInfos)); } @@ -148,7 +134,7 @@ final class JLinkRuntimeBuilder implements RuntimeBuilder { } private static List createJLinkCmdline(List modulePath, Set addModules, - Set limitModules, List options, List startupInfos) throws ConfigException { + Set limitModules, List options, List startupInfos) { List launcherModules = startupInfos.stream().map(si -> { if (si instanceof LauncherModularStartupInfo siModular) { return siModular.moduleName(); @@ -182,8 +168,7 @@ final class JLinkRuntimeBuilder implements RuntimeBuilder { for (String option : options) { switch (option) { case "--output", "--add-modules", "--module-path" -> { - throw new ConfigException(MessageFormat.format(I18N.getString( - "error.blocked.option"), option), null); + throw I18N.buildConfigException("error.blocked.option", option).create(); } default -> { args.add(option); diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JPackageToolProvider.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JPackageToolProvider.java deleted file mode 100644 index 079d03a076c..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JPackageToolProvider.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved. - * 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; - -import java.io.PrintWriter; -import java.util.Optional; -import java.util.spi.ToolProvider; - -/** - * JPackageToolProvider - * - * This is the ToolProvider implementation exported - * to java.util.spi.ToolProvider and ultimately javax.tools.ToolProvider - */ -public class JPackageToolProvider implements ToolProvider { - - public String name() { - return "jpackage"; - } - - public Optional description() { - return Optional.of(jdk.jpackage.main.Main.I18N.getString("jpackage.description")); - } - - public synchronized int run( - PrintWriter out, PrintWriter err, String... args) { - try { - return new jdk.jpackage.main.Main().execute(out, err, args); - } catch (RuntimeException re) { - Log.fatalError(re.getMessage()); - Log.verbose(re); - return 1; - } - } -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherBuilder.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherBuilder.java index 0f6b5d6ac8d..5658760b4d6 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherBuilder.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherBuilder.java @@ -25,7 +25,6 @@ package jdk.jpackage.internal; import static jdk.jpackage.internal.I18N.buildConfigException; -import static jdk.jpackage.internal.util.function.ThrowingConsumer.toConsumer; import java.nio.file.Path; import java.util.List; @@ -34,33 +33,33 @@ import java.util.Objects; import java.util.Optional; import java.util.function.Function; import jdk.internal.util.OperatingSystem; -import jdk.jpackage.internal.model.ConfigException; import jdk.jpackage.internal.model.CustomLauncherIcon; -import jdk.jpackage.internal.model.DefaultLauncherIcon; import jdk.jpackage.internal.model.FileAssociation; import jdk.jpackage.internal.model.Launcher; import jdk.jpackage.internal.model.Launcher.Stub; -import jdk.jpackage.internal.util.PathUtils; import jdk.jpackage.internal.model.LauncherIcon; import jdk.jpackage.internal.model.LauncherStartupInfo; -import jdk.jpackage.internal.model.ResourceDirLauncherIcon; +import jdk.jpackage.internal.util.PathUtils; final class LauncherBuilder { - Launcher create() throws ConfigException { + Launcher create() { CustomLauncherIcon.fromLauncherIcon(icon) .map(CustomLauncherIcon::path) - .ifPresent(toConsumer(LauncherBuilder::validateIcon)); + .ifPresent(LauncherBuilder::validateIcon); final var fa = createFileAssociations(faSources, Optional.ofNullable(faTraits).orElse(DEFAULT_FA_TRAITS)); - Objects.requireNonNull(defaultIconResourceName); - final var nonNullName = deriveNonNullName(); - return new Stub(nonNullName, Optional.ofNullable(startupInfo), fa, - isService, Optional.ofNullable(description).orElse(nonNullName), - Optional.ofNullable(icon), defaultIconResourceName, + return new Stub( + nonNullName, + Optional.ofNullable(startupInfo), + fa, + isService, + Optional.ofNullable(description).orElse(nonNullName), + Optional.ofNullable(icon), + Optional.ofNullable(defaultIconResourceName).orElseGet(LauncherBuilder::defaultIconResourceName), Optional.ofNullable(extraAppImageFileData).orElseGet(Map::of)); } @@ -123,7 +122,7 @@ final class LauncherBuilder { .setCategory("icon"); } - static void validateIcon(Path icon) throws ConfigException { + static Path validateIcon(Path icon) { switch (OperatingSystem.current()) { case WINDOWS -> { if (!icon.getFileName().toString().toLowerCase().endsWith(".ico")) { @@ -144,13 +143,15 @@ final class LauncherBuilder { throw new UnsupportedOperationException(); } } + + return icon; } record FileAssociationTraits() { } private static List createFileAssociations( - List groups, FileAssociationTraits faTraits) throws ConfigException { + List groups, FileAssociationTraits faTraits) { Objects.requireNonNull(groups); Objects.requireNonNull(faTraits); @@ -172,6 +173,23 @@ final class LauncherBuilder { return FileAssociationGroup.flatMap(groups.stream()).toList(); } + private static String defaultIconResourceName() { + switch (OperatingSystem.current()) { + case WINDOWS -> { + return "JavaApp.ico"; + } + case LINUX -> { + return "JavaApp.png"; + } + case MACOS -> { + return "JavaApp.icns"; + } + default -> { + throw new UnsupportedOperationException(); + } + } + } + private String name; private LauncherStartupInfo startupInfo; private List faSources = List.of(); diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherData.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherData.java deleted file mode 100644 index 488e9106479..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherData.java +++ /dev/null @@ -1,307 +0,0 @@ -/* - * 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 - * 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; - -import jdk.jpackage.internal.model.ConfigException; -import java.io.File; -import java.io.IOException; -import java.lang.module.ModuleReference; -import java.nio.file.Files; -import java.nio.file.InvalidPathException; -import java.nio.file.Path; -import java.text.MessageFormat; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.function.Supplier; -import java.util.jar.Attributes; -import java.util.jar.JarFile; -import java.util.jar.Manifest; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_RUNTIME_IMAGE; - -/** - * Extracts data needed to run application from parameters. - */ -final class LauncherData { - boolean isModular() { - return moduleInfo != null; - } - - String qualifiedClassName() { - return qualifiedClassName; - } - - boolean isClassNameFromMainJar() { - return jarMainClass != null; - } - - String packageName() { - int sepIdx = qualifiedClassName.lastIndexOf('.'); - if (sepIdx < 0) { - return ""; - } - return qualifiedClassName.substring(sepIdx + 1); - } - - String moduleName() { - verifyIsModular(true); - return moduleInfo.name(); - } - - List modulePath() { - verifyIsModular(true); - return modulePath; - } - - Path mainJarName() { - verifyIsModular(false); - return mainJarName; - } - - List classPath() { - return classPath; - } - - String getAppVersion() { - if (isModular()) { - return moduleInfo.version().orElse(null); - } - - return null; - } - - private LauncherData() { - } - - private void verifyIsModular(boolean isModular) { - if ((moduleInfo == null) == isModular) { - throw new IllegalStateException(); - } - } - - static LauncherData create(Map params) throws - ConfigException, IOException { - - final String mainModule = getMainModule(params); - final LauncherData result; - if (mainModule == null) { - result = createNonModular(params); - } else { - result = createModular(mainModule, params); - } - result.initClasspath(params); - return result; - } - - private static LauncherData createModular(String mainModule, - Map params) throws ConfigException, - IOException { - - LauncherData launcherData = new LauncherData(); - - final int sepIdx = mainModule.indexOf("/"); - final String moduleName; - if (sepIdx > 0) { - launcherData.qualifiedClassName = mainModule.substring(sepIdx + 1); - moduleName = mainModule.substring(0, sepIdx); - } else { - moduleName = mainModule; - } - launcherData.modulePath = getModulePath(params); - - // Try to find module in the specified module path list. - ModuleReference moduleRef = JLinkRuntimeBuilder.createModuleFinder( - launcherData.modulePath).find(moduleName).orElse(null); - - if (moduleRef != null) { - launcherData.moduleInfo = ModuleInfo.fromModuleReference(moduleRef); - } else if (params.containsKey(PREDEFINED_RUNTIME_IMAGE.getID())) { - // Failed to find module in the specified module path list and - // there is external runtime given to jpackage. - // Lookup module in this runtime. - Path cookedRuntime = PREDEFINED_RUNTIME_IMAGE.fetchFrom(params); - launcherData.moduleInfo = ModuleInfo.fromCookedRuntime(moduleName, - cookedRuntime).orElse(null); - } - - if (launcherData.moduleInfo == null) { - throw new ConfigException(MessageFormat.format(I18N.getString( - "error.no-module-in-path"), moduleName), null); - } - - if (launcherData.qualifiedClassName == null) { - launcherData.qualifiedClassName = launcherData.moduleInfo.mainClass().orElse(null); - if (launcherData.qualifiedClassName == null) { - throw new ConfigException(I18N.getString("ERR_NoMainClass"), null); - } - } - - return launcherData; - } - - private static LauncherData createNonModular( - Map params) throws ConfigException, IOException { - LauncherData launcherData = new LauncherData(); - - launcherData.qualifiedClassName = getMainClass(params); - - launcherData.mainJarName = getMainJarName(params); - - Path mainJarDir = StandardBundlerParam.SOURCE_DIR.fetchFrom(params); - - final Path mainJarPath; - if (launcherData.mainJarName != null && mainJarDir != null) { - mainJarPath = mainJarDir.resolve(launcherData.mainJarName); - if (!Files.exists(mainJarPath)) { - throw new ConfigException(MessageFormat.format(I18N.getString( - "error.main-jar-does-not-exist"), - launcherData.mainJarName), I18N.getString( - "error.main-jar-does-not-exist.advice")); - } - } else { - mainJarPath = null; - } - - if (launcherData.qualifiedClassName == null) { - if (mainJarPath == null) { - throw new ConfigException(I18N.getString("error.no-main-class"), - I18N.getString("error.no-main-class.advice")); - } - - try (JarFile jf = new JarFile(mainJarPath.toFile())) { - Manifest m = jf.getManifest(); - Attributes attrs = (m != null) ? m.getMainAttributes() : null; - if (attrs != null) { - launcherData.qualifiedClassName = attrs.getValue( - Attributes.Name.MAIN_CLASS); - launcherData.jarMainClass = launcherData.qualifiedClassName; - } - } - } - - if (launcherData.qualifiedClassName == null) { - throw new ConfigException(MessageFormat.format(I18N.getString( - "error.no-main-class-with-main-jar"), - launcherData.mainJarName), MessageFormat.format( - I18N.getString( - "error.no-main-class-with-main-jar.advice"), - launcherData.mainJarName)); - } - - return launcherData; - } - - private void initClasspath(Map params) - throws IOException { - Path inputDir = StandardBundlerParam.SOURCE_DIR.fetchFrom(params); - if (inputDir == null) { - classPath = Collections.emptyList(); - } else { - try (Stream walk = Files.walk(inputDir, Integer.MAX_VALUE)) { - Set jars = walk.filter(Files::isRegularFile) - .filter(file -> file.toString().endsWith(".jar")) - .map(p -> inputDir.toAbsolutePath() - .relativize(p.toAbsolutePath())) - .collect(Collectors.toSet()); - jars.remove(mainJarName); - classPath = jars.stream().sorted().toList(); - } - } - } - - private static String getMainClass(Map params) { - return getStringParam(params, Arguments.CLIOptions.APPCLASS.getId()); - } - - private static Path getMainJarName(Map params) - throws ConfigException { - return getPathParam(params, Arguments.CLIOptions.MAIN_JAR.getId()); - } - - private static String getMainModule(Map params) { - return getStringParam(params, Arguments.CLIOptions.MODULE.getId()); - } - - private static String getStringParam(Map params, - String paramName) { - Optional value = Optional.ofNullable(params.get(paramName)); - return value.map(Object::toString).orElse(null); - } - - private static T getPathParam(String paramName, Supplier func) throws ConfigException { - try { - return func.get(); - } catch (InvalidPathException ex) { - throw new ConfigException(MessageFormat.format(I18N.getString( - "error.not-path-parameter"), paramName, - ex.getLocalizedMessage()), null, ex); - } - } - - private static Path getPathParam(Map params, - String paramName) throws ConfigException { - return getPathParam(paramName, () -> { - String value = getStringParam(params, paramName); - Path result = null; - if (value != null) { - result = Path.of(value); - } - return result; - }); - } - - private static List getModulePath(Map params) - throws ConfigException { - List modulePath = getPathListParameter(Arguments.CLIOptions.MODULE_PATH.getId(), params); - - if (params.containsKey(PREDEFINED_RUNTIME_IMAGE.getID())) { - Path runtimePath = PREDEFINED_RUNTIME_IMAGE.fetchFrom(params); - runtimePath = runtimePath.resolve("lib"); - modulePath = Stream.of(modulePath, List.of(runtimePath)) - .flatMap(List::stream) - .toList(); - } - - return modulePath; - } - - private static List getPathListParameter(String paramName, - Map params) throws ConfigException { - return getPathParam(paramName, () -> - params.get(paramName) instanceof String value ? - Stream.of(value.split(File.pathSeparator)).map(Path::of).toList() : List.of()); - } - - private String qualifiedClassName; - private String jarMainClass; - private Path mainJarName; - private List classPath; - private List modulePath; - private ModuleInfo moduleInfo; -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherFromOptions.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherFromOptions.java new file mode 100644 index 00000000000..ed030a4a726 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherFromOptions.java @@ -0,0 +1,171 @@ +/* + * 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; + +import static jdk.jpackage.internal.cli.StandardOption.APPCLASS; +import static jdk.jpackage.internal.cli.StandardOption.ARGUMENTS; +import static jdk.jpackage.internal.cli.StandardOption.DESCRIPTION; +import static jdk.jpackage.internal.cli.StandardOption.FILE_ASSOCIATIONS; +import static jdk.jpackage.internal.cli.StandardOption.ICON; +import static jdk.jpackage.internal.cli.StandardOption.INPUT; +import static jdk.jpackage.internal.cli.StandardOption.JAVA_OPTIONS; +import static jdk.jpackage.internal.cli.StandardOption.LAUNCHER_AS_SERVICE; +import static jdk.jpackage.internal.cli.StandardOption.MAIN_JAR; +import static jdk.jpackage.internal.cli.StandardOption.MODULE; +import static jdk.jpackage.internal.cli.StandardOption.MODULE_PATH; +import static jdk.jpackage.internal.cli.StandardOption.NAME; +import static jdk.jpackage.internal.cli.StandardOption.PREDEFINED_APP_IMAGE; +import static jdk.jpackage.internal.cli.StandardOption.PREDEFINED_RUNTIME_IMAGE; + +import java.nio.file.Path; +import java.util.List; +import java.util.Optional; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.stream.IntStream; +import jdk.jpackage.internal.FileAssociationGroup.FileAssociationException; +import jdk.jpackage.internal.FileAssociationGroup.FileAssociationNoExtensionsException; +import jdk.jpackage.internal.FileAssociationGroup.FileAssociationNoMimesException; +import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.cli.StandardFaOption; +import jdk.jpackage.internal.model.CustomLauncherIcon; +import jdk.jpackage.internal.model.DefaultLauncherIcon; +import jdk.jpackage.internal.model.FileAssociation; +import jdk.jpackage.internal.model.Launcher; +import jdk.jpackage.internal.model.LauncherIcon; + +final class LauncherFromOptions { + + LauncherFromOptions() { + } + + LauncherFromOptions faGroupBuilderMutator(BiConsumer v) { + faGroupBuilderMutator = v; + return this; + } + + LauncherFromOptions faMapper(BiFunction v) { + faMapper = v; + return this; + } + + LauncherFromOptions faWithDefaultDescription() { + return faGroupBuilderMutator((faGroupBuilder, launcherBuilder) -> { + if (faGroupBuilder.description().isEmpty()) { + var description = String.format("%s association", launcherBuilder.create().name()); + faGroupBuilder.description(description); + } + }); + } + + Launcher create(Options options) { + final var builder = new LauncherBuilder(); + + DESCRIPTION.ifPresentIn(options, builder::description); + builder.icon(toLauncherIcon(ICON.findIn(options).orElse(null))); + LAUNCHER_AS_SERVICE.ifPresentIn(options, builder::isService); + NAME.ifPresentIn(options, builder::name); + + if (PREDEFINED_APP_IMAGE.findIn(options).isEmpty()) { + final var startupInfoBuilder = new LauncherStartupInfoBuilder(); + + INPUT.ifPresentIn(options, startupInfoBuilder::inputDir); + ARGUMENTS.ifPresentIn(options, startupInfoBuilder::defaultParameters); + JAVA_OPTIONS.ifPresentIn(options, startupInfoBuilder::javaOptions); + MAIN_JAR.ifPresentIn(options, startupInfoBuilder::mainJar); + APPCLASS.ifPresentIn(options, startupInfoBuilder::mainClassName); + MODULE.ifPresentIn(options, startupInfoBuilder::moduleName); + MODULE_PATH.ifPresentIn(options, startupInfoBuilder::modulePath); + PREDEFINED_RUNTIME_IMAGE.ifPresentIn(options, startupInfoBuilder::predefinedRuntimeImage); + + builder.startupInfo(startupInfoBuilder.create()); + } + + final var faOptionsList = FILE_ASSOCIATIONS.findIn(options).orElseGet(List::of); + + final var faGroups = IntStream.range(0, faOptionsList.size()).mapToObj(idx -> { + final var faOptions = faOptionsList.get(idx); + + final var faGroupBuilder = FileAssociationGroup.build(); + + StandardFaOption.DESCRIPTION.ifPresentIn(faOptions, faGroupBuilder::description); + StandardFaOption.ICON.ifPresentIn(faOptions, faGroupBuilder::icon); + StandardFaOption.EXTENSIONS.ifPresentIn(faOptions, faGroupBuilder::extensions); + StandardFaOption.CONTENT_TYPE.ifPresentIn(faOptions, faGroupBuilder::mimeTypes); + + faGroupBuilderMutator().ifPresent(mutator -> { + mutator.accept(faGroupBuilder, builder); + }); + + final var faID = idx + 1; + + final FileAssociationGroup faGroup; + try { + faGroup = faGroupBuilder.create(); + } catch (FileAssociationNoMimesException ex) { + throw I18N.buildConfigException() + .message("error.no-content-types-for-file-association", faID) + .advice("error.no-content-types-for-file-association.advice", faID) + .create(); + } catch (FileAssociationNoExtensionsException ex) { + // TODO: Must do something about this condition! + throw new AssertionError(); + } catch (FileAssociationException ex) { + // Should never happen + throw new UnsupportedOperationException(ex); + } + + return faMapper().map(mapper -> { + return new FileAssociationGroup(faGroup.items().stream().map(fa -> { + return mapper.apply(faOptions, fa); + }).toList()); + }).orElse(faGroup); + + }).toList(); + + return builder.faGroups(faGroups).create(); + } + + private Optional> faGroupBuilderMutator() { + return Optional.ofNullable(faGroupBuilderMutator); + } + + private Optional> faMapper() { + return Optional.ofNullable(faMapper); + } + + private static LauncherIcon toLauncherIcon(Path launcherIconPath) { + if (launcherIconPath == null) { + return DefaultLauncherIcon.INSTANCE; + } else if (launcherIconPath.toString().isEmpty()) { + return null; + } else { + return CustomLauncherIcon.create(launcherIconPath); + } + } + + private BiConsumer faGroupBuilderMutator; + private BiFunction faMapper; +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherFromParams.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherFromParams.java deleted file mode 100644 index b4ce1cdb200..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherFromParams.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * 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; - -import static jdk.jpackage.internal.I18N.buildConfigException; -import static jdk.jpackage.internal.StandardBundlerParam.ARGUMENTS; -import static jdk.jpackage.internal.StandardBundlerParam.DESCRIPTION; -import static jdk.jpackage.internal.StandardBundlerParam.FA_CONTENT_TYPE; -import static jdk.jpackage.internal.StandardBundlerParam.FA_DESCRIPTION; -import static jdk.jpackage.internal.StandardBundlerParam.FA_EXTENSIONS; -import static jdk.jpackage.internal.StandardBundlerParam.FA_ICON; -import static jdk.jpackage.internal.StandardBundlerParam.FILE_ASSOCIATIONS; -import static jdk.jpackage.internal.StandardBundlerParam.ICON; -import static jdk.jpackage.internal.StandardBundlerParam.JAVA_OPTIONS; -import static jdk.jpackage.internal.StandardBundlerParam.LAUNCHER_AS_SERVICE; -import static jdk.jpackage.internal.StandardBundlerParam.LAUNCHER_DATA; -import static jdk.jpackage.internal.StandardBundlerParam.NAME; -import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE; -import static jdk.jpackage.internal.util.function.ThrowingSupplier.toSupplier; - -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.BiFunction; -import java.util.stream.IntStream; -import jdk.internal.util.OperatingSystem; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.model.CustomLauncherIcon; -import jdk.jpackage.internal.model.DefaultLauncherIcon; -import jdk.jpackage.internal.model.FileAssociation; -import jdk.jpackage.internal.model.Launcher; -import jdk.jpackage.internal.model.LauncherIcon; - -record LauncherFromParams(Optional, FileAssociation>> faExtension) { - - LauncherFromParams { - Objects.requireNonNull(faExtension); - } - - LauncherFromParams() { - this(Optional.empty()); - } - - Launcher create(Map params) throws ConfigException { - final var builder = new LauncherBuilder().defaultIconResourceName(defaultIconResourceName()); - - DESCRIPTION.copyInto(params, builder::description); - builder.icon(toLauncherIcon(ICON.findIn(params).orElse(null))); - LAUNCHER_AS_SERVICE.copyInto(params, builder::isService); - NAME.copyInto(params, builder::name); - - if (PREDEFINED_APP_IMAGE.findIn(params).isEmpty()) { - final var startupInfoBuilder = new LauncherStartupInfoBuilder(); - - startupInfoBuilder.launcherData(LAUNCHER_DATA.fetchFrom(params)); - ARGUMENTS.copyInto(params, startupInfoBuilder::defaultParameters); - JAVA_OPTIONS.copyInto(params, startupInfoBuilder::javaOptions); - - builder.startupInfo(startupInfoBuilder.create()); - } - - final var faParamsList = FILE_ASSOCIATIONS.findIn(params).orElseGet(List::of); - - final var faGroups = IntStream.range(0, faParamsList.size()).mapToObj(idx -> { - final var faParams = faParamsList.get(idx); - return toSupplier(() -> { - final var faGroupBuilder = FileAssociationGroup.build(); - - if (OperatingSystem.current() == OperatingSystem.MACOS) { - FA_DESCRIPTION.copyInto(faParams, faGroupBuilder::description); - } else { - faGroupBuilder.description(FA_DESCRIPTION.findIn(faParams).orElseGet(() -> { - return String.format("%s association", toSupplier(builder::create).get().name()); - })); - } - - FA_ICON.copyInto(faParams, faGroupBuilder::icon); - FA_EXTENSIONS.copyInto(faParams, faGroupBuilder::extensions); - FA_CONTENT_TYPE.copyInto(faParams, faGroupBuilder::mimeTypes); - - final var faID = idx + 1; - - final FileAssociationGroup faGroup; - try { - faGroup = faGroupBuilder.create(); - } catch (FileAssociationGroup.FileAssociationNoMimesException ex) { - throw buildConfigException() - .message("error.no-content-types-for-file-association", faID) - .advice("error.no-content-types-for-file-association.advice", faID) - .create(); - } - - if (faExtension.isPresent()) { - return new FileAssociationGroup(faGroup.items().stream().map(fa -> { - return faExtension.get().apply(fa, faParams); - }).toList()); - } else { - return faGroup; - } - }).get(); - }).toList(); - - return builder.faGroups(faGroups).create(); - } - - private static LauncherIcon toLauncherIcon(Path launcherIconPath) { - if (launcherIconPath == null) { - return DefaultLauncherIcon.INSTANCE; - } else if (launcherIconPath.toString().isEmpty()) { - return null; - } else { - return CustomLauncherIcon.create(launcherIconPath); - } - } - - private static String defaultIconResourceName() { - switch (OperatingSystem.current()) { - case WINDOWS -> { - return "JavaApp.ico"; - } - case LINUX -> { - return "JavaApp.png"; - } - case MACOS -> { - return "JavaApp.icns"; - } - default -> { - throw new UnsupportedOperationException(); - } - } - } -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherStartupInfoBuilder.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherStartupInfoBuilder.java index 5273f2d251c..b2fc48af9e4 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherStartupInfoBuilder.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherStartupInfoBuilder.java @@ -24,69 +24,216 @@ */ package jdk.jpackage.internal; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; import java.nio.file.Path; import java.util.List; -import java.util.function.UnaryOperator; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.Predicate; +import java.util.jar.Attributes; +import java.util.jar.JarFile; +import java.util.jar.Manifest; +import java.util.stream.Stream; +import jdk.jpackage.internal.model.JPackageException; import jdk.jpackage.internal.model.LauncherJarStartupInfo; import jdk.jpackage.internal.model.LauncherJarStartupInfoMixin; import jdk.jpackage.internal.model.LauncherModularStartupInfo; import jdk.jpackage.internal.model.LauncherModularStartupInfoMixin; -import jdk.jpackage.internal.model.LauncherStartupInfo.Stub; import jdk.jpackage.internal.model.LauncherStartupInfo; final class LauncherStartupInfoBuilder { LauncherStartupInfo create() { - return decorator.apply(new Stub(qualifiedClassName, javaOptions, - defaultParameters, classPath)); + if (moduleName != null) { + return createModular(); + } else if (mainJar != null) { + return createNonModular(); + } else { + throw new JPackageException(I18N.format("ERR_NoEntryPoint")); + } } - LauncherStartupInfoBuilder launcherData(LauncherData launcherData) { - if (launcherData.isModular()) { - decorator = new ModuleStartupInfo(launcherData.moduleName()); - } else { - decorator = new JarStartupInfo(launcherData.mainJarName(), - launcherData.isClassNameFromMainJar()); - } - classPath = launcherData.classPath(); - qualifiedClassName = launcherData.qualifiedClassName(); + LauncherStartupInfoBuilder inputDir(Path v) { + inputDir = v; return this; } LauncherStartupInfoBuilder javaOptions(List v) { + if (v != null) { + v.forEach(Objects::requireNonNull); + } javaOptions = v; return this; } LauncherStartupInfoBuilder defaultParameters(List v) { + if (v != null) { + v.forEach(Objects::requireNonNull); + } defaultParameters = v; return this; } - private static record ModuleStartupInfo(String moduleName) implements UnaryOperator { + LauncherStartupInfoBuilder mainJar(Path v) { + mainJar = v; + return this; + } - @Override - public LauncherStartupInfo apply(LauncherStartupInfo base) { - return LauncherModularStartupInfo.create(base, - new LauncherModularStartupInfoMixin.Stub(moduleName)); + LauncherStartupInfoBuilder mainClassName(String v) { + mainClassName = v; + return this; + } + + LauncherStartupInfoBuilder predefinedRuntimeImage(Path v) { + cookedRuntimePath = v; + return this; + } + + LauncherStartupInfoBuilder moduleName(String v) { + if (v == null) { + moduleName = null; + } else { + var slashIdx = v.indexOf('/'); + if (slashIdx < 0) { + moduleName = v; + } else { + moduleName = v.substring(0, slashIdx); + if (slashIdx < v.length() - 1) { + mainClassName(v.substring(slashIdx + 1)); + } + } + } + return this; + } + + LauncherStartupInfoBuilder modulePath(List v) { + modulePath = v; + return this; + } + + private Optional inputDir() { + return Optional.ofNullable(inputDir); + } + + private Optional mainClassName() { + return Optional.ofNullable(mainClassName); + } + + private Optional cookedRuntimePath() { + return Optional.ofNullable(cookedRuntimePath); + } + + private LauncherStartupInfo createLauncherStartupInfo(String mainClassName, List classpath) { + Objects.requireNonNull(mainClassName); + classpath.forEach(Objects::requireNonNull); + return new LauncherStartupInfo.Stub(mainClassName, + Optional.ofNullable(javaOptions).orElseGet(List::of), + Optional.ofNullable(defaultParameters).orElseGet(List::of), + classpath); + } + + private static List createClasspath(Path inputDir, Set excludes) { + excludes.forEach(Objects::requireNonNull); + try (final var walk = Files.walk(inputDir)) { + return walk.filter(Files::isRegularFile) + .filter(file -> file.getFileName().toString().endsWith(".jar")) + .map(inputDir::relativize) + .filter(Predicate.not(excludes::contains)) + .distinct() + .toList(); + } catch (IOException ex) { + throw new UncheckedIOException(ex); } } - private static record JarStartupInfo(Path jarPath, - boolean isClassNameFromMainJar) implements - UnaryOperator { + private LauncherModularStartupInfo createModular() { + final var fullModulePath = getFullModulePath(); - @Override - public LauncherStartupInfo apply(LauncherStartupInfo base) { - return LauncherJarStartupInfo.create(base, - new LauncherJarStartupInfoMixin.Stub(jarPath, - isClassNameFromMainJar)); - } + // Try to find the module in the specified module path list. + final var moduleInfo = JLinkRuntimeBuilder.createModuleFinder(fullModulePath).find(moduleName) + .map(ModuleInfo::fromModuleReference).or(() -> { + // Failed to find the module in the specified module path list. + return cookedRuntimePath().flatMap(cookedRuntime -> { + // Lookup the module in the external runtime. + return ModuleInfo.fromCookedRuntime(moduleName, cookedRuntime); + }); + }).orElseThrow(() -> { + return I18N.buildConfigException("error.no-module-in-path", moduleName).create(); + }); + + final var effectiveMainClassName = mainClassName().or(moduleInfo::mainClass).orElseThrow(() -> { + return I18N.buildConfigException("ERR_NoMainClass").create(); + }); + + // If module is located in the file system, exclude it from the classpath. + final var classpath = inputDir().map(theInputDir -> { + var classpathExcludes = moduleInfo.fileLocation().filter(moduleFile -> { + return moduleFile.startsWith(theInputDir); + }).map(theInputDir::relativize).map(Set::of).orElseGet(Set::of); + return createClasspath(theInputDir, classpathExcludes); + }).orElseGet(List::of); + + return LauncherModularStartupInfo.create( + createLauncherStartupInfo(effectiveMainClassName, classpath), + new LauncherModularStartupInfoMixin.Stub(moduleInfo.name(), moduleInfo.version())); } - private String qualifiedClassName; + private List getFullModulePath() { + return cookedRuntimePath().map(runtimeImage -> { + return Stream.of(modulePath(), List.of(runtimeImage.resolve("lib"))).flatMap(List::stream).toList(); + }).orElse(modulePath()); + } + + private List modulePath() { + return Optional.ofNullable(modulePath).orElseGet(List::of); + } + + private LauncherJarStartupInfo createNonModular() { + final var theInputDir = inputDir().orElseThrow(); + + final var mainJarPath = theInputDir.resolve(mainJar); + + if (!Files.exists(mainJarPath)) { + throw I18N.buildConfigException() + .message("error.main-jar-does-not-exist", mainJar) + .advice("error.main-jar-does-not-exist.advice") + .create(); + } + + final var effectiveMainClassName = mainClassName().or(() -> { + try (final var jf = new JarFile(mainJarPath.toFile())) { + return Optional.ofNullable(jf.getManifest()).map(Manifest::getMainAttributes).map(attrs -> { + return attrs.getValue(Attributes.Name.MAIN_CLASS); + }); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + }).orElseThrow(() -> { + return I18N.buildConfigException() + .message("error.no-main-class-with-main-jar", mainJar) + .advice("error.no-main-class-with-main-jar.advice", mainJar) + .create(); + }); + + return LauncherJarStartupInfo.create( + createLauncherStartupInfo(effectiveMainClassName, createClasspath(theInputDir, Set.of(mainJar))), + new LauncherJarStartupInfoMixin.Stub(mainJar, mainClassName().isEmpty())); + } + + // Modular options + private String moduleName; + private List modulePath; + + // Non-modular options + private Path mainJar; + + // Common options + private Path inputDir; + private String mainClassName; private List javaOptions; private List defaultParameters; - private List classPath; - private UnaryOperator decorator; + private Path cookedRuntimePath; } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Log.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Log.java index b14b4ab22ca..0f51fa166f9 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Log.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Log.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,7 +28,6 @@ package jdk.jpackage.internal; import java.io.PrintWriter; import java.text.SimpleDateFormat; import java.util.Date; -import java.util.List; /** * Log @@ -105,29 +104,6 @@ public class Log { } } - public void verbose(List strings, - List output, int returnCode, long pid) { - if (verbose) { - StringBuilder sb = new StringBuilder(); - sb.append("Command [PID: "); - sb.append(pid); - sb.append("]:\n "); - - for (String s : strings) { - sb.append(" " + s); - } - verbose(sb.toString()); - if (output != null && !output.isEmpty()) { - sb = new StringBuilder("Output:"); - for (String s : output) { - sb.append("\n " + s); - } - verbose(sb.toString()); - } - verbose("Returned: " + returnCode + "\n"); - } - } - private String addTimestamp(String msg) { SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS"); Date time = new Date(System.currentTimeMillis()); @@ -177,9 +153,4 @@ public class Log { public static void verbose(Throwable t) { instance.get().verbose(t); } - - public static void verbose(List strings, List out, - int ret, long pid) { - instance.get().verbose(strings, out, ret, pid); - } } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ObjectFactory.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ObjectFactory.java new file mode 100644 index 00000000000..f1a83eb9eab --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ObjectFactory.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.jpackage.internal; + +import java.util.Objects; +import java.util.Optional; +import jdk.jpackage.internal.util.CompositeProxy; + +interface ObjectFactory extends ExecutorFactory, RetryExecutorFactory { + + static ObjectFactory.Builder build() { + return new Builder(); + } + + static ObjectFactory.Builder build(ObjectFactory from) { + return build().initFrom(from); + } + + static final class Builder { + private Builder() { + } + + ObjectFactory create() { + return CompositeProxy.build().invokeTunnel(CompositeProxyTunnel.INSTANCE).create( + ObjectFactory.class, + Optional.ofNullable(executorFactory).orElse(ExecutorFactory.DEFAULT), + Optional.ofNullable(retryExecutorFactory).orElse(RetryExecutorFactory.DEFAULT)); + } + + Builder initFrom(ObjectFactory of) { + Objects.requireNonNull(of); + return executorFactory(of).retryExecutorFactory(of); + } + + Builder executorFactory(ExecutorFactory v) { + executorFactory = v; + return this; + } + + Builder retryExecutorFactory(RetryExecutorFactory v) { + retryExecutorFactory = v; + return this; + } + + private ExecutorFactory executorFactory; + private RetryExecutorFactory retryExecutorFactory; + } + + static final ObjectFactory DEFAULT = build().create(); +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/OptionUtils.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/OptionUtils.java new file mode 100644 index 00000000000..97e1274f078 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/OptionUtils.java @@ -0,0 +1,54 @@ +/* + * 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; + +import static jdk.jpackage.internal.cli.StandardOption.BUNDLING_OPERATION_DESCRIPTOR; +import static jdk.jpackage.internal.cli.StandardOption.DEST; +import static jdk.jpackage.internal.cli.StandardOption.MAIN_JAR; +import static jdk.jpackage.internal.cli.StandardOption.MODULE; +import static jdk.jpackage.internal.cli.StandardOption.PREDEFINED_APP_IMAGE; +import static jdk.jpackage.internal.cli.StandardOption.PREDEFINED_RUNTIME_IMAGE; + +import java.nio.file.Path; +import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.cli.StandardBundlingOperation; + +final class OptionUtils { + + static boolean isRuntimeInstaller(Options options) { + return PREDEFINED_RUNTIME_IMAGE.containsIn(options) + && !PREDEFINED_APP_IMAGE.containsIn(options) + && !MAIN_JAR.containsIn(options) + && !MODULE.containsIn(options); + } + + static Path outputDir(Options options) { + return DEST.getFrom(options); + } + + static StandardBundlingOperation bundlingOperation(Options options) { + return StandardBundlingOperation.valueOf(BUNDLING_OPERATION_DESCRIPTOR.getFrom(options)).orElseThrow(); + } +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/OptionsTransformer.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/OptionsTransformer.java new file mode 100644 index 00000000000..a01be089a49 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/OptionsTransformer.java @@ -0,0 +1,75 @@ +/* + * 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; + +import static jdk.jpackage.internal.cli.StandardOption.ADDITIONAL_LAUNCHERS; +import static jdk.jpackage.internal.cli.StandardOption.APP_VERSION; +import static jdk.jpackage.internal.cli.StandardOption.ICON; +import static jdk.jpackage.internal.cli.StandardOption.NAME; +import static jdk.jpackage.internal.cli.StandardOption.PREDEFINED_APP_IMAGE; + +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.cli.WithOptionIdentifier; +import jdk.jpackage.internal.model.ApplicationLayout; +import jdk.jpackage.internal.model.ExternalApplication; + +record OptionsTransformer(Options mainOptions, Optional externalApp) { + + OptionsTransformer { + Objects.requireNonNull(mainOptions); + Objects.requireNonNull(externalApp); + } + + OptionsTransformer(Options mainOptions, ApplicationLayout appLayout) { + this(mainOptions, PREDEFINED_APP_IMAGE.findIn(mainOptions).map(appLayout::resolveAt).map(AppImageFile::load)); + } + + Options appOptions() { + return externalApp.map(ea -> { + var overrideOptions = Map.of( + NAME, ea.appName(), + APP_VERSION, ea.appVersion(), + ADDITIONAL_LAUNCHERS, ea.addLaunchers().stream().map(li -> { + return Options.concat(li.extra(), Options.of(Map.of( + NAME, li.name(), + // This should prevent the code building the Launcher instance + // from the Options object from trying to create a startup info object. + PREDEFINED_APP_IMAGE, PREDEFINED_APP_IMAGE.getFrom(mainOptions) + ))); + }).toList() + ); + return Options.concat( + Options.of(overrideOptions), + ea.extra(), + // Remove icon if any from the application/launcher options. + // If the icon is specified in the main options, it for the installer. + mainOptions.copyWithout(ICON.id()) + ); + }).orElse(mainOptions); + } +} 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 f93cd0eab14..f537a1ea0a2 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PackageBuilder.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PackageBuilder.java @@ -31,7 +31,6 @@ 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.model.Package.Stub; import jdk.jpackage.internal.model.PackageType; @@ -44,7 +43,7 @@ final class PackageBuilder { this.type = Objects.requireNonNull(type); } - Package create() throws ConfigException { + Package create() { final var validatedName = validatedName(); Path relativeInstallDir; @@ -208,8 +207,7 @@ final class PackageBuilder { }); } - private static Path mapInstallDir(Path installDir, PackageType pkgType) - throws ConfigException { + private static Path mapInstallDir(Path installDir, PackageType pkgType) { var ex = buildConfigException("error.invalid-install-dir", installDir).create(); if (installDir.getNameCount() == 0) { diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Packager.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Packager.java index 501fd64bdca..8e47b046eb1 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Packager.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Packager.java @@ -29,7 +29,6 @@ import java.util.Objects; import java.util.Optional; import java.util.function.Consumer; import jdk.jpackage.internal.model.Package; -import jdk.jpackage.internal.model.PackagerException; final class Packager { @@ -69,7 +68,7 @@ final class Packager { return Objects.requireNonNull(env); } - Path execute(PackagingPipeline.Builder pipelineBuilder) throws PackagerException { + Path execute(PackagingPipeline.Builder pipelineBuilder) { Objects.requireNonNull(pkg); Objects.requireNonNull(env); Objects.requireNonNull(outputDir); 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 6f4e0d0d2d8..c94dada9262 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PackagingPipeline.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PackagingPipeline.java @@ -46,7 +46,6 @@ import jdk.jpackage.internal.model.AppImageLayout; import jdk.jpackage.internal.model.Application; import jdk.jpackage.internal.model.ApplicationLayout; import jdk.jpackage.internal.model.Package; -import jdk.jpackage.internal.model.PackagerException; import jdk.jpackage.internal.pipeline.DirectedEdge; import jdk.jpackage.internal.pipeline.FixedDAG; import jdk.jpackage.internal.pipeline.TaskPipelineBuilder; @@ -62,7 +61,7 @@ final class PackagingPipeline { * @param env the build environment * @param app the application */ - void execute(BuildEnv env, Application app) throws PackagerException { + void execute(BuildEnv env, Application app) { execute(contextMapper.apply(createTaskContext(env, app))); } @@ -81,7 +80,7 @@ final class PackagingPipeline { * @param pkg the package * @param outputDir the output directory for the package file */ - void execute(BuildEnv env, Package pkg, Path outputDir) throws PackagerException { + void execute(BuildEnv env, Package pkg, Path outputDir) { execute((StartupParameters)createPackagingTaskContext(env, pkg, outputDir, taskConfig)); } @@ -91,7 +90,7 @@ final class PackagingPipeline { * * @param startupParameters the pipeline startup parameters */ - void execute(StartupParameters startupParameters) throws PackagerException { + void execute(StartupParameters startupParameters) { execute(contextMapper.apply(createTaskContext((PackagingTaskContext)startupParameters))); } @@ -132,7 +131,7 @@ final class PackagingPipeline { } interface TaskContext extends Predicate { - void execute(TaskAction taskAction) throws IOException, PackagerException; + void execute(TaskAction taskAction) throws IOException; } record AppImageBuildEnv(BuildEnv env, T app) { @@ -161,27 +160,27 @@ final class PackagingPipeline { @FunctionalInterface interface ApplicationImageTaskAction extends TaskAction { - void execute(AppImageBuildEnv env) throws IOException, PackagerException; + void execute(AppImageBuildEnv env) throws IOException; } @FunctionalInterface interface AppImageTaskAction extends TaskAction { - void execute(AppImageBuildEnv env) throws IOException, PackagerException; + void execute(AppImageBuildEnv env) throws IOException; } @FunctionalInterface interface CopyAppImageTaskAction extends TaskAction { - void execute(T pkg, AppImageLayout srcAppImage, AppImageLayout dstAppImage) throws IOException, PackagerException; + void execute(T pkg, AppImageLayout srcAppImage, AppImageLayout dstAppImage) throws IOException; } @FunctionalInterface interface PackageTaskAction extends TaskAction { - void execute(PackageBuildEnv env) throws IOException, PackagerException; + void execute(PackageBuildEnv env) throws IOException; } @FunctionalInterface interface NoArgTaskAction extends TaskAction { - void execute() throws IOException, PackagerException; + void execute() throws IOException; } record TaskConfig(Optional action) { @@ -493,7 +492,7 @@ final class PackagingPipeline { return new PackagingTaskContext(BuildEnv.withAppImageLayout(env, dstLayout), pkg, outputDir, srcLayout); } - private void execute(TaskContext context) throws PackagerException { + private void execute(TaskContext context) { final Map> tasks = taskConfig.entrySet().stream().collect(toMap(Map.Entry::getKey, task -> { return createTask(context, task.getKey(), task.getValue()); })); @@ -508,14 +507,8 @@ 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) { - throw new PackagerException(ex); + throw ExceptionBox.toUnchecked(ex); } } @@ -546,7 +539,7 @@ final class PackagingPipeline { @SuppressWarnings("unchecked") @Override - public void execute(TaskAction taskAction) throws IOException, PackagerException { + public void execute(TaskAction taskAction) throws IOException { if (taskAction instanceof PackageTaskAction) { ((PackageTaskAction)taskAction).execute(pkgBuildEnv()); } else if (taskAction instanceof CopyAppImageTaskAction) { @@ -600,7 +593,7 @@ final class PackagingPipeline { @SuppressWarnings("unchecked") @Override - public void execute(TaskAction taskAction) throws IOException, PackagerException { + public void execute(TaskAction taskAction) throws IOException { if (taskAction instanceof AppImageTaskAction) { final var taskEnv = pkg.map(PackagingTaskContext::appImageBuildEnv).orElseGet(this::appBuildEnv); ((AppImageTaskAction)taskAction).execute(taskEnv); @@ -639,7 +632,7 @@ final class PackagingPipeline { final var accepted = withAction && context.test(id); if (TRACE_TASK_ACTION) { - var sb = new StringBuffer(); + var sb = new StringBuilder(); sb.append("Execute task=[").append(id).append("]: "); if (!withAction) { sb.append("no action"); diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/RetryExecutor.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/RetryExecutor.java deleted file mode 100644 index f806217e8f9..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/RetryExecutor.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. - * 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; - -import java.io.IOException; -import java.util.List; -import java.util.function.Consumer; -import java.util.function.Supplier; - -public final class RetryExecutor { - public RetryExecutor() { - setMaxAttemptsCount(5); - setAttemptTimeoutMillis(2 * 1000); - setWriteOutputToFile(false); - } - - public RetryExecutor setMaxAttemptsCount(int v) { - attempts = v; - return this; - } - - public RetryExecutor setAttemptTimeoutMillis(int v) { - timeoutMillis = v; - return this; - } - - public RetryExecutor saveOutput(boolean v) { - saveOutput = v; - return this; - } - - public List getOutput() { - return output; - } - - public RetryExecutor setWriteOutputToFile(boolean v) { - writeOutputToFile = v; - return this; - } - - public RetryExecutor setExecutorInitializer(Consumer v) { - executorInitializer = v; - return this; - } - - public void abort() { - aborted = true; - } - - public boolean isAborted() { - return aborted; - } - - static RetryExecutor retryOnKnownErrorMessage(String v) { - RetryExecutor result = new RetryExecutor(); - return result.setExecutorInitializer(exec -> { - exec.setOutputConsumer(output -> { - if (!output.anyMatch(v::equals)) { - result.abort(); - } - }); - }); - } - - public void execute(String cmdline[]) throws IOException { - executeLoop(() -> - Executor.of(cmdline).setWriteOutputToFile(writeOutputToFile)); - } - - public void execute(ProcessBuilder pb) throws IOException { - executeLoop(() -> - Executor.of(pb).setWriteOutputToFile(writeOutputToFile)); - } - - private void executeLoop(Supplier execSupplier) throws IOException { - aborted = false; - for (;;) { - if (aborted) { - break; - } - - try { - Executor exec = execSupplier.get().saveOutput(saveOutput); - if (executorInitializer != null) { - executorInitializer.accept(exec); - } - exec.executeExpectSuccess(); - if (saveOutput) { - output = exec.getOutput(); - } - break; - } catch (IOException ex) { - if (aborted || (--attempts) <= 0) { - throw ex; - } - } - - try { - Thread.sleep(timeoutMillis); - } catch (InterruptedException ex) { - Log.verbose(ex); - throw new RuntimeException(ex); - } - } - } - - private Consumer executorInitializer; - private boolean aborted; - private int attempts; - private int timeoutMillis; - private boolean saveOutput; - private List output; - private boolean writeOutputToFile; -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/RetryExecutorFactory.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/RetryExecutorFactory.java new file mode 100644 index 00000000000..3efb522abd4 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/RetryExecutorFactory.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.jpackage.internal; + +import jdk.jpackage.internal.util.RetryExecutor; + +@FunctionalInterface +interface RetryExecutorFactory { + + RetryExecutor retryExecutor(Class exceptionType); + + static final RetryExecutorFactory DEFAULT = RetryExecutor::new; +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/RuntimeBuilderBuilder.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/RuntimeBuilderBuilder.java index dcaf097b1a3..a7d9bad39b1 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/RuntimeBuilderBuilder.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/RuntimeBuilderBuilder.java @@ -25,7 +25,6 @@ package jdk.jpackage.internal; import static jdk.jpackage.internal.model.RuntimeBuilder.getDefaultModulePath; -import static jdk.jpackage.internal.util.function.ThrowingSupplier.toSupplier; import java.io.IOException; import java.io.UncheckedIOException; @@ -38,7 +37,6 @@ import java.util.Optional; import java.util.Set; import java.util.function.Supplier; import jdk.jpackage.internal.model.ApplicationLayout; -import jdk.jpackage.internal.model.ConfigException; import jdk.jpackage.internal.model.LauncherStartupInfo; import jdk.jpackage.internal.model.RuntimeBuilder; import jdk.jpackage.internal.util.FileUtils; @@ -112,8 +110,7 @@ final class RuntimeBuilderBuilder { ); } - private static RuntimeBuilder createCopyingRuntimeBuilder(Path runtimeDir, - Path... modulePath) throws ConfigException { + private static RuntimeBuilder createCopyingRuntimeBuilder(Path runtimeDir, Path... modulePath) { return appImageLayout -> { try { // copy whole runtime, skipping jmods and src.zip @@ -147,10 +144,9 @@ final class RuntimeBuilderBuilder { @Override public RuntimeBuilder get() { - return toSupplier(() -> createCopyingRuntimeBuilder( + return createCopyingRuntimeBuilder( predefinedRuntimeImage, - Optional.ofNullable(thiz.modulePath).orElseGet(List::of).toArray(Path[]::new)) - ).get(); + Optional.ofNullable(thiz.modulePath).orElseGet(List::of).toArray(Path[]::new)); } } @@ -160,13 +156,12 @@ final class RuntimeBuilderBuilder { @Override public RuntimeBuilder get() { - return toSupplier(() -> JLinkRuntimeBuilder.createJLinkRuntimeBuilder( + return JLinkRuntimeBuilder.createJLinkRuntimeBuilder( Optional.ofNullable(thiz.modulePath).orElseGet(List::of), Optional.ofNullable(addModules).orElseGet(Set::of), Optional.ofNullable(limitModules).orElseGet(Set::of), Optional.ofNullable(options).orElseGet(List::of), - startupInfos) - ).get(); + startupInfos); } } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/StandardBundlerParam.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/StandardBundlerParam.java deleted file mode 100644 index 2b35a6830f8..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/StandardBundlerParam.java +++ /dev/null @@ -1,513 +0,0 @@ -/* - * 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 - * 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; - - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Date; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Stream; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.model.ExternalApplication; -import static jdk.jpackage.internal.ApplicationLayoutUtils.PLATFORM_APPLICATION_LAYOUT; - -/** - * Standard bundler parameters. - * - * Contains static definitions of all of the common bundler parameters. - * (additional platform specific and mode specific bundler parameters - * are defined in each of the specific bundlers) - * - * Also contains static methods that operate on maps of parameters. - */ -final class StandardBundlerParam { - - private static final String DEFAULT_VERSION = "1.0"; - private static final String DEFAULT_RELEASE = "1"; - private static final String[] DEFAULT_JLINK_OPTIONS = { - "--strip-native-commands", - "--strip-debug", - "--no-man-pages", - "--no-header-files"}; - - static final BundlerParamInfo LAUNCHER_DATA = BundlerParamInfo.createBundlerParam( - LauncherData.class, LauncherData::create); - - static final BundlerParamInfo SOURCE_DIR = - new BundlerParamInfo<>( - Arguments.CLIOptions.INPUT.getId(), - Path.class, - p -> null, - (s, p) -> Path.of(s) - ); - - static final BundlerParamInfo OUTPUT_DIR = - new BundlerParamInfo<>( - Arguments.CLIOptions.OUTPUT.getId(), - Path.class, - p -> Path.of("").toAbsolutePath(), - (s, p) -> Path.of(s) - ); - - // note that each bundler is likely to replace this one with - // their own converter - static final BundlerParamInfo MAIN_JAR = - new BundlerParamInfo<>( - Arguments.CLIOptions.MAIN_JAR.getId(), - Path.class, - params -> LAUNCHER_DATA.fetchFrom(params).mainJarName(), - null - ); - - static final BundlerParamInfo PREDEFINED_APP_IMAGE = - new BundlerParamInfo<>( - Arguments.CLIOptions.PREDEFINED_APP_IMAGE.getId(), - Path.class, - params -> null, - (s, p) -> Path.of(s)); - - static final BundlerParamInfo PREDEFINED_APP_IMAGE_FILE = BundlerParamInfo.createBundlerParam( - ExternalApplication.class, params -> { - if (hasPredefinedAppImage(params)) { - var appImage = PREDEFINED_APP_IMAGE.fetchFrom(params); - return AppImageFile.load(appImage, PLATFORM_APPLICATION_LAYOUT); - } else { - return null; - } - }); - - static final BundlerParamInfo MAIN_CLASS = - new BundlerParamInfo<>( - Arguments.CLIOptions.APPCLASS.getId(), - String.class, - params -> { - if (isRuntimeInstaller(params)) { - return null; - } else if (hasPredefinedAppImage(params)) { - PREDEFINED_APP_IMAGE_FILE.fetchFrom(params).getMainClass(); - } - return LAUNCHER_DATA.fetchFrom(params).qualifiedClassName(); - }, - (s, p) -> s - ); - - static final BundlerParamInfo PREDEFINED_RUNTIME_IMAGE = - new BundlerParamInfo<>( - Arguments.CLIOptions.PREDEFINED_RUNTIME_IMAGE.getId(), - Path.class, - params -> null, - (s, p) -> Path.of(s) - ); - - // this is the raw --app-name arg - used in APP_NAME and INSTALLER_NAME - static final BundlerParamInfo NAME = - new BundlerParamInfo<>( - Arguments.CLIOptions.NAME.getId(), - String.class, - params -> null, - (s, p) -> s - ); - - // this is the application name, either from the app-image (if given), - // the name (if given) derived from the main-class, or the runtime image - static final BundlerParamInfo APP_NAME = - new BundlerParamInfo<>( - "application-name", - String.class, - params -> { - String appName = NAME.fetchFrom(params); - if (hasPredefinedAppImage(params)) { - appName = PREDEFINED_APP_IMAGE_FILE.fetchFrom(params).getLauncherName(); - } else if (appName == null) { - String s = MAIN_CLASS.fetchFrom(params); - if (s != null) { - int idx = s.lastIndexOf("."); - appName = (idx < 0) ? s : s.substring(idx+1); - } else if (isRuntimeInstaller(params)) { - Path f = PREDEFINED_RUNTIME_IMAGE.fetchFrom(params); - if (f != null) { - appName = f.getFileName().toString(); - } - } - } - return appName; - }, - (s, p) -> s - ); - - static final BundlerParamInfo INSTALLER_NAME = - new BundlerParamInfo<>( - "installer-name", - String.class, - params -> { - String installerName = NAME.fetchFrom(params); - return (installerName != null) ? installerName : - APP_NAME.fetchFrom(params); - }, - (s, p) -> s - ); - - static final BundlerParamInfo ICON = - new BundlerParamInfo<>( - Arguments.CLIOptions.ICON.getId(), - Path.class, - params -> null, - (s, p) -> Path.of(s) - ); - - static final BundlerParamInfo ABOUT_URL = - new BundlerParamInfo<>( - Arguments.CLIOptions.ABOUT_URL.getId(), - String.class, - params -> null, - (s, p) -> s - ); - - static final BundlerParamInfo VENDOR = - new BundlerParamInfo<>( - Arguments.CLIOptions.VENDOR.getId(), - String.class, - params -> I18N.getString("param.vendor.default"), - (s, p) -> s - ); - - static final BundlerParamInfo DESCRIPTION = - new BundlerParamInfo<>( - Arguments.CLIOptions.DESCRIPTION.getId(), - String.class, - params -> params.containsKey(APP_NAME.getID()) - ? APP_NAME.fetchFrom(params) - : I18N.getString("param.description.default"), - (s, p) -> s - ); - - static final BundlerParamInfo COPYRIGHT = - new BundlerParamInfo<>( - Arguments.CLIOptions.COPYRIGHT.getId(), - String.class, - params -> MessageFormat.format(I18N.getString( - "param.copyright.default"), new Date()), - (s, p) -> s - ); - - @SuppressWarnings("unchecked") - static final BundlerParamInfo> ARGUMENTS = - new BundlerParamInfo<>( - Arguments.CLIOptions.ARGUMENTS.getId(), - (Class>) (Object) List.class, - params -> Collections.emptyList(), - (s, p) -> null - ); - - @SuppressWarnings("unchecked") - static final BundlerParamInfo> JAVA_OPTIONS = - new BundlerParamInfo<>( - Arguments.CLIOptions.JAVA_OPTIONS.getId(), - (Class>) (Object) List.class, - params -> Collections.emptyList(), - (s, p) -> Arrays.asList(s.split("\n\n")) - ); - - static final BundlerParamInfo VERSION = - new BundlerParamInfo<>( - Arguments.CLIOptions.VERSION.getId(), - String.class, - StandardBundlerParam::getDefaultAppVersion, - (s, p) -> s - ); - - static final BundlerParamInfo RELEASE = - new BundlerParamInfo<>( - Arguments.CLIOptions.RELEASE.getId(), - String.class, - params -> DEFAULT_RELEASE, - (s, p) -> s - ); - - public static final BundlerParamInfo LICENSE_FILE = - new BundlerParamInfo<>( - Arguments.CLIOptions.LICENSE_FILE.getId(), - String.class, - params -> null, - (s, p) -> s - ); - - static final BundlerParamInfo TEMP_ROOT = - new BundlerParamInfo<>( - Arguments.CLIOptions.TEMP_ROOT.getId(), - Path.class, - params -> { - try { - return Files.createTempDirectory("jdk.jpackage"); - } catch (IOException ioe) { - return null; - } - }, - (s, p) -> Path.of(s) - ); - - public static final BundlerParamInfo CONFIG_ROOT = - new BundlerParamInfo<>( - "configRoot", - Path.class, - params -> { - Path root = TEMP_ROOT.fetchFrom(params).resolve("config"); - try { - Files.createDirectories(root); - } catch (IOException ioe) { - return null; - } - return root; - }, - (s, p) -> null - ); - - static final BundlerParamInfo VERBOSE = - new BundlerParamInfo<>( - Arguments.CLIOptions.VERBOSE.getId(), - Boolean.class, - params -> false, - // valueOf(null) is false, and we actually do want null - (s, p) -> (s == null || "null".equalsIgnoreCase(s)) ? - true : Boolean.valueOf(s) - ); - - static final BundlerParamInfo RESOURCE_DIR = - new BundlerParamInfo<>( - Arguments.CLIOptions.RESOURCE_DIR.getId(), - Path.class, - params -> null, - (s, p) -> Path.of(s) - ); - - static final BundlerParamInfo INSTALL_DIR = - new BundlerParamInfo<>( - Arguments.CLIOptions.INSTALL_DIR.getId(), - String.class, - params -> null, - (s, p) -> s - ); - - static final BundlerParamInfo LAUNCHER_AS_SERVICE = - new BundlerParamInfo<>( - Arguments.CLIOptions.LAUNCHER_AS_SERVICE.getId(), - Boolean.class, - params -> false, - // valueOf(null) is false, and we actually do want null - (s, p) -> (s == null || "null".equalsIgnoreCase(s)) ? - true : Boolean.valueOf(s) - ); - - - @SuppressWarnings("unchecked") - static final BundlerParamInfo>> ADD_LAUNCHERS = - new BundlerParamInfo<>( - Arguments.CLIOptions.ADD_LAUNCHER.getId(), - (Class>>) (Object) - List.class, - params -> new ArrayList<>(1), - // valueOf(null) is false, and we actually do want null - (s, p) -> null - ); - - @SuppressWarnings("unchecked") - static final BundlerParamInfo - >> FILE_ASSOCIATIONS = - new BundlerParamInfo<>( - Arguments.CLIOptions.FILE_ASSOCIATIONS.getId(), - (Class>>) (Object) - List.class, - params -> new ArrayList<>(1), - // valueOf(null) is false, and we actually do want null - (s, p) -> null - ); - - @SuppressWarnings("unchecked") - static final BundlerParamInfo> FA_EXTENSIONS = - new BundlerParamInfo<>( - "fileAssociation.extension", - (Class>) (Object) List.class, - params -> null, // null means not matched to an extension - (s, p) -> Arrays.asList(s.split("(,|\\s)+")) - ); - - @SuppressWarnings("unchecked") - static final BundlerParamInfo> FA_CONTENT_TYPE = - new BundlerParamInfo<>( - "fileAssociation.contentType", - (Class>) (Object) List.class, - params -> null, - // null means not matched to a content/mime type - (s, p) -> Arrays.asList(s.split("(,|\\s)+")) - ); - - static final BundlerParamInfo FA_DESCRIPTION = - new BundlerParamInfo<>( - "fileAssociation.description", - String.class, - p -> null, - (s, p) -> s - ); - - static final BundlerParamInfo FA_ICON = - new BundlerParamInfo<>( - "fileAssociation.icon", - Path.class, - ICON::fetchFrom, - (s, p) -> Path.of(s) - ); - - @SuppressWarnings("unchecked") - static final BundlerParamInfo> DMG_CONTENT = - new BundlerParamInfo<>( - Arguments.CLIOptions.DMG_CONTENT.getId(), - (Class>) (Object)List.class, - p -> Collections.emptyList(), - (s, p) -> Stream.of(s.split(",")).map(Path::of).toList() - ); - - @SuppressWarnings("unchecked") - static final BundlerParamInfo> APP_CONTENT = - new BundlerParamInfo<>( - Arguments.CLIOptions.APP_CONTENT.getId(), - (Class>) (Object)List.class, - p->Collections.emptyList(), - (s, p) -> Stream.of(s.split(",")).map(Path::of).toList() - ); - - @SuppressWarnings("unchecked") - static final BundlerParamInfo> MODULE_PATH = - new BundlerParamInfo<>( - Arguments.CLIOptions.MODULE_PATH.getId(), - (Class>) (Object)List.class, - p -> JLinkRuntimeBuilder.ensureBaseModuleInModulePath(List.of()), - (s, p) -> { - List modulePath = Stream.of(s.split(File.pathSeparator)) - .map(Path::of) - .toList(); - return JLinkRuntimeBuilder.ensureBaseModuleInModulePath(modulePath); - }); - - static final BundlerParamInfo MODULE = - new BundlerParamInfo<>( - Arguments.CLIOptions.MODULE.getId(), - String.class, - p -> null, - (s, p) -> { - return String.valueOf(s); - }); - - @SuppressWarnings("unchecked") - static final BundlerParamInfo> ADD_MODULES = - new BundlerParamInfo<>( - Arguments.CLIOptions.ADD_MODULES.getId(), - (Class>) (Object) Set.class, - p -> new LinkedHashSet(), - (s, p) -> new LinkedHashSet<>(Arrays.asList(s.split(","))) - ); - - @SuppressWarnings("unchecked") - static final BundlerParamInfo> JLINK_OPTIONS = - new BundlerParamInfo<>( - Arguments.CLIOptions.JLINK_OPTIONS.getId(), - (Class>) (Object) List.class, - p -> Arrays.asList(DEFAULT_JLINK_OPTIONS), - (s, p) -> null); - - @SuppressWarnings("unchecked") - static final BundlerParamInfo> LIMIT_MODULES = - new BundlerParamInfo<>( - "limit-modules", - (Class>) (Object) Set.class, - p -> new LinkedHashSet(), - (s, p) -> new LinkedHashSet<>(Arrays.asList(s.split(","))) - ); - - static final BundlerParamInfo SIGN_BUNDLE = - new BundlerParamInfo<>( - Arguments.CLIOptions.MAC_SIGN.getId(), - Boolean.class, - params -> false, - (s, p) -> (s == null || "null".equalsIgnoreCase(s)) ? - null : Boolean.valueOf(s) - ); - - static boolean isRuntimeInstaller(Map params) { - if (params.containsKey(MODULE.getID()) || - params.containsKey(MAIN_JAR.getID()) || - params.containsKey(PREDEFINED_APP_IMAGE.getID())) { - return false; // we are building or are given an application - } - // runtime installer requires --runtime-image, if this is false - // here then we should have thrown error validating args. - return params.containsKey(PREDEFINED_RUNTIME_IMAGE.getID()); - } - - static boolean hasPredefinedAppImage(Map params) { - return params.containsKey(PREDEFINED_APP_IMAGE.getID()); - } - - private static String getDefaultAppVersion(Map params) { - String appVersion = DEFAULT_VERSION; - - if (isRuntimeInstaller(params)) { - return appVersion; - } - - LauncherData launcherData = null; - try { - launcherData = LAUNCHER_DATA.fetchFrom(params); - } catch (RuntimeException ex) { - if (ex.getCause() instanceof ConfigException) { - return appVersion; - } - throw ex; - } - - if (launcherData.isModular()) { - String moduleVersion = launcherData.getAppVersion(); - if (moduleVersion != null) { - Log.verbose(MessageFormat.format(I18N.getString( - "message.module-version"), - moduleVersion, - launcherData.moduleName())); - appVersion = moduleVersion; - } - } - - return appVersion; - } -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/SystemEnvironment.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/SystemEnvironment.java index de98e97c922..5d9a1d6d147 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/SystemEnvironment.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/SystemEnvironment.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,5 +24,5 @@ */ package jdk.jpackage.internal; -public interface SystemEnvironment { +interface SystemEnvironment { } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/TempDirectory.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/TempDirectory.java new file mode 100644 index 00000000000..50d1701bf0d --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/TempDirectory.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. 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; + +import java.io.Closeable; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.cli.StandardOption; +import jdk.jpackage.internal.util.FileUtils; + +final class TempDirectory implements Closeable { + + TempDirectory(Options options) throws IOException { + final var tempDir = StandardOption.TEMP_ROOT.findIn(options); + if (tempDir.isPresent()) { + this.path = tempDir.orElseThrow(); + this.options = options; + } else { + this.path = Files.createTempDirectory("jdk.jpackage"); + this.options = options.copyWithDefaultValue(StandardOption.TEMP_ROOT, path); + } + + deleteOnClose = tempDir.isEmpty(); + } + + Options options() { + return options; + } + + Path path() { + return path; + } + + boolean deleteOnClose() { + return deleteOnClose; + } + + @Override + public void close() throws IOException { + if (deleteOnClose) { + FileUtils.deleteRecursive(path); + } + } + + private final Path path; + private final Options options; + private final boolean deleteOnClose; +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ToolValidator.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ToolValidator.java index 13e87d5cfa6..9440aff3a53 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ToolValidator.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ToolValidator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -121,32 +121,32 @@ final class ToolValidator { cmdline.addAll(args); } - boolean canUseTool[] = new boolean[1]; + boolean canUseTool = false; if (minimalVersion == null) { // No version check. - canUseTool[0] = true; + canUseTool = true; } - String[] version = new String[1]; + String version = null; try { - Executor.of(cmdline.toArray(String[]::new)).setQuiet(true).setOutputConsumer(lines -> { - if (versionParser != null && minimalVersion != null) { - version[0] = versionParser.apply(lines); - if (version[0] != null && minimalVersion.compareTo(version[0]) <= 0) { - canUseTool[0] = true; - } + var result = Executor.of(cmdline).setQuiet(true).saveOutput().execute(); + var lines = result.content(); + if (versionParser != null && minimalVersion != null) { + version = versionParser.apply(lines.stream()); + if (version != null && minimalVersion.compareTo(version) <= 0) { + canUseTool = true; } - }).execute(); + } } catch (IOException e) { return new ConfigException(I18N.format("error.tool-error", toolPath, e.getMessage()), null, e); } - if (canUseTool[0]) { + if (canUseTool) { // All good. Tool can be used. return null; } else if (toolOldVersionErrorHandler != null) { - return toolOldVersionErrorHandler.apply(toolPath, version[0]); + return toolOldVersionErrorHandler.apply(toolPath, version); } else { return new ConfigException( I18N.format("error.tool-old-version", toolPath, minimalVersion), diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ValidOptions.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ValidOptions.java deleted file mode 100644 index 89a2e4b56fe..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ValidOptions.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. - * 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; - -import java.util.EnumSet; -import java.util.HashMap; - -import jdk.internal.util.OperatingSystem; -import jdk.jpackage.internal.Arguments.CLIOptions; - -/** - * ValidOptions - * - * Two basic methods for validating command line options. - * - * initArgs() - * Computes the Map of valid options for each mode on this Platform. - * - * checkIfSupported(CLIOptions arg) - * Determine if the given arg is valid on this platform. - * - * checkIfImageSupported(CLIOptions arg) - * Determine if the given arg is valid for creating app image. - * - * checkIfInstallerSupported(CLIOptions arg) - * Determine if the given arg is valid for creating installer. - * - * checkIfSigningSupported(CLIOptions arg) - * Determine if the given arg is valid for signing app image. - * - */ -class ValidOptions { - - enum USE { - ALL, // valid in all cases - LAUNCHER, // valid when creating a launcher - INSTALL, // valid when creating an installer - SIGN, // valid when signing is requested - } - - private static final HashMap> options = new HashMap<>(); - - // initializing list of mandatory arguments - static { - put(CLIOptions.NAME.getId(), USE.ALL); - put(CLIOptions.VERSION.getId(), USE.ALL); - put(CLIOptions.OUTPUT.getId(), USE.ALL); - put(CLIOptions.TEMP_ROOT.getId(), USE.ALL); - put(CLIOptions.VERBOSE.getId(), - EnumSet.of(USE.ALL, USE.SIGN)); - put(CLIOptions.PREDEFINED_RUNTIME_IMAGE.getId(), USE.ALL); - put(CLIOptions.RESOURCE_DIR.getId(), USE.ALL); - put(CLIOptions.DESCRIPTION.getId(), USE.ALL); - put(CLIOptions.VENDOR.getId(), USE.ALL); - put(CLIOptions.COPYRIGHT.getId(), USE.ALL); - put(CLIOptions.PACKAGE_TYPE.getId(), - EnumSet.of(USE.ALL, USE.SIGN)); - put(CLIOptions.ICON.getId(), USE.ALL); - - put(CLIOptions.INPUT.getId(), USE.LAUNCHER); - put(CLIOptions.MODULE.getId(), USE.LAUNCHER); - put(CLIOptions.MODULE_PATH.getId(), USE.LAUNCHER); - put(CLIOptions.ADD_MODULES.getId(), USE.LAUNCHER); - put(CLIOptions.MAIN_JAR.getId(), USE.LAUNCHER); - put(CLIOptions.APPCLASS.getId(), USE.LAUNCHER); - put(CLIOptions.ARGUMENTS.getId(), USE.LAUNCHER); - put(CLIOptions.JAVA_OPTIONS.getId(), USE.LAUNCHER); - put(CLIOptions.ADD_LAUNCHER.getId(), USE.LAUNCHER); - put(CLIOptions.JLINK_OPTIONS.getId(), USE.LAUNCHER); - put(CLIOptions.APP_CONTENT.getId(), USE.LAUNCHER); - - put(CLIOptions.LICENSE_FILE.getId(), USE.INSTALL); - put(CLIOptions.INSTALL_DIR.getId(), USE.INSTALL); - put(CLIOptions.PREDEFINED_APP_IMAGE.getId(), - (OperatingSystem.isMacOS()) ? - EnumSet.of(USE.INSTALL, USE.SIGN) : - EnumSet.of(USE.INSTALL)); - put(CLIOptions.LAUNCHER_AS_SERVICE.getId(), USE.INSTALL); - - put(CLIOptions.ABOUT_URL.getId(), USE.INSTALL); - - put(CLIOptions.FILE_ASSOCIATIONS.getId(), - (OperatingSystem.isMacOS()) ? USE.ALL : USE.INSTALL); - - if (OperatingSystem.isWindows()) { - put(CLIOptions.WIN_CONSOLE_HINT.getId(), USE.LAUNCHER); - - put(CLIOptions.WIN_HELP_URL.getId(), USE.INSTALL); - put(CLIOptions.WIN_UPDATE_URL.getId(), USE.INSTALL); - - put(CLIOptions.WIN_MENU_HINT.getId(), USE.INSTALL); - put(CLIOptions.WIN_MENU_GROUP.getId(), USE.INSTALL); - put(CLIOptions.WIN_SHORTCUT_HINT.getId(), USE.INSTALL); - put(CLIOptions.WIN_SHORTCUT_PROMPT.getId(), USE.INSTALL); - put(CLIOptions.WIN_DIR_CHOOSER.getId(), USE.INSTALL); - put(CLIOptions.WIN_UPGRADE_UUID.getId(), USE.INSTALL); - put(CLIOptions.WIN_PER_USER_INSTALLATION.getId(), - USE.INSTALL); - } - - if (OperatingSystem.isMacOS()) { - put(CLIOptions.MAC_SIGN.getId(), - EnumSet.of(USE.ALL, USE.SIGN)); - put(CLIOptions.MAC_BUNDLE_NAME.getId(), USE.ALL); - put(CLIOptions.MAC_BUNDLE_IDENTIFIER.getId(), USE.ALL); - put(CLIOptions.MAC_BUNDLE_SIGNING_PREFIX.getId(), - EnumSet.of(USE.ALL, USE.SIGN)); - put(CLIOptions.MAC_SIGNING_KEY_NAME.getId(), - EnumSet.of(USE.ALL, USE.SIGN)); - put(CLIOptions.MAC_APP_IMAGE_SIGN_IDENTITY.getId(), - EnumSet.of(USE.ALL, USE.SIGN)); - put(CLIOptions.MAC_INSTALLER_SIGN_IDENTITY.getId(), - EnumSet.of(USE.INSTALL, USE.SIGN)); - put(CLIOptions.MAC_SIGNING_KEYCHAIN.getId(), - EnumSet.of(USE.ALL, USE.SIGN)); - put(CLIOptions.MAC_APP_STORE.getId(), USE.ALL); - put(CLIOptions.MAC_CATEGORY.getId(), USE.ALL); - put(CLIOptions.MAC_ENTITLEMENTS.getId(), - EnumSet.of(USE.ALL, USE.SIGN)); - put(CLIOptions.DMG_CONTENT.getId(), USE.INSTALL); - } - - if (OperatingSystem.isLinux()) { - put(CLIOptions.LINUX_BUNDLE_NAME.getId(), USE.INSTALL); - put(CLIOptions.LINUX_DEB_MAINTAINER.getId(), USE.INSTALL); - put(CLIOptions.LINUX_CATEGORY.getId(), USE.INSTALL); - put(CLIOptions.LINUX_RPM_LICENSE_TYPE.getId(), USE.INSTALL); - put(CLIOptions.LINUX_PACKAGE_DEPENDENCIES.getId(), - USE.INSTALL); - put(CLIOptions.LINUX_MENU_GROUP.getId(), USE.INSTALL); - put(CLIOptions.RELEASE.getId(), USE.INSTALL); - put(CLIOptions.LINUX_SHORTCUT_HINT.getId(), USE.INSTALL); - } - } - - static boolean checkIfSupported(CLIOptions arg) { - return options.containsKey(arg.getId()); - } - - static boolean checkIfImageSupported(CLIOptions arg) { - EnumSet value = options.get(arg.getId()); - return value.contains(USE.ALL) || - value.contains(USE.LAUNCHER) || - value.contains(USE.SIGN); - } - - static boolean checkIfInstallerSupported(CLIOptions arg) { - EnumSet value = options.get(arg.getId()); - return value.contains(USE.ALL) || value.contains(USE.INSTALL); - } - - static boolean checkIfSigningSupported(CLIOptions arg) { - EnumSet value = options.get(arg.getId()); - return value.contains(USE.SIGN); - } - - private static EnumSet put(String key, USE value) { - return options.put(key, EnumSet.of(value)); - } - - private static EnumSet put(String key, EnumSet value) { - return options.put(key, value); - } -} diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBuildEnvFromParams.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/AdditionalLauncher.java similarity index 76% rename from src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBuildEnvFromParams.java rename to src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/AdditionalLauncher.java index 31759c8c529..34019ff9e81 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBuildEnvFromParams.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/AdditionalLauncher.java @@ -22,13 +22,11 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package jdk.jpackage.internal; -import jdk.jpackage.internal.model.MacPackage; +package jdk.jpackage.internal.cli; -final class MacBuildEnvFromParams { +import java.nio.file.Path; - static final BundlerParamInfo BUILD_ENV = BundlerParamInfo.createBundlerParam(BuildEnv.class, params -> { - return BuildEnvFromParams.create(params, MacPackagingPipeline.APPLICATION_LAYOUT::resolveAt, MacPackage::guessRuntimeLayout); - }); + +record AdditionalLauncher(String name, Path propertyFile) { } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/BundlingOperationModifier.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/BundlingOperationModifier.java new file mode 100644 index 00000000000..33525f3e54d --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/BundlingOperationModifier.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. 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.cli; + +/** + * Modifiers for jpackage operations. + */ +enum BundlingOperationModifier implements OptionScope { + /** + * Create runtime native bundle. + */ + BUNDLE_RUNTIME, + + /** + * Create native bundle from the predefined app image. + */ + BUNDLE_PREDEFINED_APP_IMAGE, + + ; +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/BundlingOperationOptionScope.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/BundlingOperationOptionScope.java new file mode 100644 index 00000000000..04af5865baa --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/BundlingOperationOptionScope.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. 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.cli; + + +import jdk.jpackage.internal.model.BundlingOperationDescriptor; + +/** + * Bundling operation scope. + *

    + * The scope of bundling operations. E.g., app image or native package bundling. + */ +interface BundlingOperationOptionScope extends OptionScope { + BundlingOperationDescriptor descriptor(); +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/CliBundlingEnvironment.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/CliBundlingEnvironment.java new file mode 100644 index 00000000000..09ff0997c14 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/CliBundlingEnvironment.java @@ -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. 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.cli; + +import java.util.NoSuchElementException; +import jdk.jpackage.internal.model.BundlingEnvironment; +import jdk.jpackage.internal.model.BundlingOperationDescriptor; + +/** + * CLI bundling environment. + */ +public interface CliBundlingEnvironment extends BundlingEnvironment { + + /** + * Requests to run a bundling operation denoted with the given descriptor with + * the given values of command line options. + * + * @param op the descriptor of the requested bundling operation + * @param cmdline the validated values of the command line options + * @throws NoSuchElementException if the specified descriptor is not one of the + * items in the list returned by + * {@link #supportedOperations()} method + */ + void createBundle(BundlingOperationDescriptor op, Options cmdline); +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/DefaultOptions.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/DefaultOptions.java new file mode 100644 index 00000000000..91342500277 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/DefaultOptions.java @@ -0,0 +1,191 @@ +/* + * 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.cli; + +import static java.util.stream.Collectors.toUnmodifiableMap; +import static java.util.stream.Collectors.toUnmodifiableSet; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + + +final class DefaultOptions implements Options { + + DefaultOptions(Map values) { + this(values, Optional.empty()); + } + + DefaultOptions( + Map values, + Predicate optionNamesFilter) { + + this(values, Optional.of(optionNamesFilter)); + } + + DefaultOptions( + Map values, + Optional> optionNamesFilter) { + + map = values.entrySet().stream().collect(toUnmodifiableMap(e -> { + return e.getKey().id(); + }, e -> { + return new OptionIdentifierWithValue(e.getKey(), e.getValue()); + })); + + var optionNamesStream = optionNames(values.keySet().stream()); + optionNames = optionNamesFilter.map(optionNamesStream::filter).orElse(optionNamesStream) + .collect(toUnmodifiableSet()); + } + + private DefaultOptions(Snapshot snapshot) { + map = snapshot.map(); + optionNames = snapshot.optionNames(); + } + + static DefaultOptions create(Snapshot snapshot) { + var options = new DefaultOptions(snapshot); + + var mapOptionNames = optionNames( + options.map.values().stream().map(OptionIdentifierWithValue::withId) + ).collect(toUnmodifiableSet()); + + for (var e : options.map.entrySet()) { + if (e.getKey() != e.getValue().withId().id()) { + throw new IllegalArgumentException("Corrupted options map"); + } + } + + if (!mapOptionNames.containsAll(snapshot.optionNames())) { + throw new IllegalArgumentException("Unexpected option names"); + } + return options; + } + + @Override + public Optional find(OptionIdentifier id) { + return Optional.ofNullable(map.get(Objects.requireNonNull(id))).map(OptionIdentifierWithValue::value); + } + + @Override + public boolean contains(OptionName optionName) { + return optionNames.contains(Objects.requireNonNull(optionName)); + } + + @Override + public Set ids() { + return Collections.unmodifiableSet(map.keySet()); + } + + @Override + public DefaultOptions copyWithout(Iterable ids) { + return copy(StreamSupport.stream(ids.spliterator(), false), false); + } + + @Override + public DefaultOptions copyWith(Iterable ids) { + return copy(StreamSupport.stream(ids.spliterator(), false), true); + } + + DefaultOptions add(DefaultOptions other) { + return new DefaultOptions(new Snapshot(Stream.of(this, other).flatMap(v -> { + return v.map.values().stream(); + }).collect(toUnmodifiableMap(OptionIdentifierWithValue::id, x -> x, (first, _) -> { + return first; + })), Stream.of(this, other) + .map(DefaultOptions::optionNames) + .flatMap(Collection::stream) + .collect(toUnmodifiableSet()))); + } + + Set optionNames() { + return optionNames; + } + + Set withOptionIdentifierSet() { + return map.values().stream() + .map(OptionIdentifierWithValue::withId) + .collect(toUnmodifiableSet()); + } + + record Snapshot(Map map, Set optionNames) { + Snapshot { + Objects.requireNonNull(map); + Objects.requireNonNull(optionNames); + } + } + + record OptionIdentifierWithValue(WithOptionIdentifier withId, Object value) { + OptionIdentifierWithValue { + Objects.requireNonNull(withId); + Objects.requireNonNull(value); + } + + OptionIdentifier id() { + return withId.id(); + } + + OptionIdentifierWithValue copyWithValue(Object value) { + return new OptionIdentifierWithValue(withId, value); + } + } + + private DefaultOptions copy(Stream ids, boolean includes) { + var includeIds = ids.collect(toUnmodifiableSet()); + return new DefaultOptions(map.values().stream().filter(v -> { + return includeIds.contains(v.id()) == includes; + }).collect(toUnmodifiableMap(OptionIdentifierWithValue::withId, OptionIdentifierWithValue::value))); + } + + private static Stream optionNames(Stream options) { + return options.map(v -> { + Optional> spec; + switch (v) { + case Option option -> { + spec = Optional.of(option.spec()); + } + case OptionValue optionValue -> { + spec = optionValue.asOption().map(Option::spec); + } + default -> { + spec = Optional.empty(); + } + } + return spec; + }).filter(Optional::isPresent).map(Optional::get).map(OptionSpec::names).flatMap(Collection::stream); + } + + static final DefaultOptions EMPTY = new DefaultOptions(Map.of()); + + private final Map map; + private final Set optionNames; +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/HelpFormatter.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/HelpFormatter.java new file mode 100644 index 00000000000..1d8a5eefc79 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/HelpFormatter.java @@ -0,0 +1,178 @@ +/* + * 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.cli; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; + + +/** + * Generic help formatter. + */ +final class HelpFormatter { + + private HelpFormatter(List optionGroups, OptionGroupFormatter formatter) { + this.optionGroups = Objects.requireNonNull(optionGroups); + this.formatter = Objects.requireNonNull(formatter); + } + + void format(Consumer sink) { + for (var group : optionGroups) { + formatter.format(group, sink); + } + } + + static Builder build() { + return new Builder(); + } + + + static final class Builder { + + private Builder() { + } + + HelpFormatter create() { + return new HelpFormatter(groups, validatedGroupFormatter()); + } + + Builder groups(Collection v) { + groups.addAll(v); + return this; + } + + Builder groups(OptionGroup... v) { + return groups(List.of(v)); + } + + Builder groupFormatter(OptionGroupFormatter v) { + groupFormatter = v; + return this; + } + + private OptionGroupFormatter validatedGroupFormatter() { + return Optional.ofNullable(groupFormatter).orElseGet(Builder::createConsoleFormatter); + } + + private static OptionGroupFormatter createConsoleFormatter() { + return new ConsoleOptionGroupFormatter(new ConsoleOptionFormatter(2, 10)); + } + + private final List groups = new ArrayList<>(); + private OptionGroupFormatter groupFormatter; + } + + + interface OptionFormatter { + + public default void format(OptionSpec optionSpec, Consumer sink) { + format(optionSpec.names().stream().map(OptionName::formatForCommandLine).collect(Collectors.joining(" ")), + optionSpec.valuePattern(), + optionSpec.description(), sink); + } + + void format(String optionNames, Optional valuePattern, String description, Consumer sink); + } + + interface OptionGroupFormatter { + + default void format(OptionGroup group, Consumer sink) { + formatHeader(group.name(), sink); + formatBody(group.options(), sink); + } + + void formatHeader(String gropName, Consumer sink); + + void formatBody(Iterable> optionSpecs, Consumer sink); + } + + + record ConsoleOptionFormatter(int nameOffset, int descriptionOffset) implements OptionFormatter { + + @Override + public void format(String optionNames, Optional valuePattern, String description, Consumer sink) { + sink.accept(" ".repeat(nameOffset)); + sink.accept(optionNames); + valuePattern.map(v -> " " + v).ifPresent(sink); + eol(sink); + final var descriptionOffsetStr = " ".repeat(descriptionOffset); + Stream.of(description.split("\\R")).map(line -> { + return descriptionOffsetStr + line; + }).forEach(line -> { + sink.accept(line); + eol(sink); + }); + } + } + + + record ConsoleOptionGroupFormatter(OptionFormatter optionFormatter) implements OptionGroupFormatter { + + ConsoleOptionGroupFormatter { + Objects.requireNonNull(optionFormatter); + } + + @Override + public void formatHeader(String groupName, Consumer sink) { + Objects.requireNonNull(groupName); + eol(sink); + sink.accept(groupName + ":"); + eol(sink); + } + + @Override + public void formatBody(Iterable> optionSpecs, Consumer sink) { + optionSpecs.forEach(optionSpec -> { + optionFormatter.format(optionSpec, sink); + }); + } + } + + + record OptionGroup(String name, List> options) { + + OptionGroup { + Objects.requireNonNull(name); + Objects.requireNonNull(options); + } + } + + + static Consumer eol(Consumer sink) { + sink.accept(System.lineSeparator()); + return sink; + } + + + private final List optionGroups; + private final OptionGroupFormatter formatter; +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AbstractBundler.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/I18N.java similarity index 55% rename from src/jdk.jpackage/share/classes/jdk/jpackage/internal/AbstractBundler.java rename to src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/I18N.java index 762b65b530e..63c2b88039f 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AbstractBundler.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/I18N.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,36 +22,34 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ +package jdk.jpackage.internal.cli; -package jdk.jpackage.internal; - -import java.io.IOException; -import java.nio.file.Path; +import java.util.List; import java.util.Map; -import jdk.jpackage.internal.util.FileUtils; +import jdk.internal.util.OperatingSystem; +import jdk.jpackage.internal.util.MultiResourceBundle; +import jdk.jpackage.internal.util.StringBundle; +final class I18N { -/** - * AbstractBundler - * - * This is the base class all bundlers extend from. - * It contains methods and parameters common to all bundlers. - * The concrete implementations are in the platform specific bundlers. - */ -abstract class AbstractBundler implements Bundler { - - @Override - public String toString() { - return getName(); + private I18N() { } - @Override - public void cleanup(Map params) { - try { - FileUtils.deleteRecursive( - StandardBundlerParam.TEMP_ROOT.fetchFrom(params)); - } catch (IOException e) { - Log.verbose(e.getMessage()); - } + static String format(String key, Object ... args) { + return BUNDLE.format(key, args); + } + + private static final StringBundle BUNDLE; + + static { + var prefix = "jdk.jpackage.internal.resources."; + BUNDLE = StringBundle.fromResourceBundle(MultiResourceBundle.create( + prefix + "MainResources", + Map.of( + OperatingSystem.LINUX, List.of(prefix + "LinuxResources"), + OperatingSystem.MACOS, List.of(prefix + "MacResources"), + OperatingSystem.WINDOWS, List.of(prefix + "WinResources") + ) + )); } } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/JOptSimpleOptionsBuilder.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/JOptSimpleOptionsBuilder.java new file mode 100644 index 00000000000..57b92471e4a --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/JOptSimpleOptionsBuilder.java @@ -0,0 +1,817 @@ +/* + * 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.cli; + +import static java.util.stream.Collectors.toUnmodifiableMap; +import static java.util.stream.Collectors.toUnmodifiableSet; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +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.UnaryOperator; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import jdk.internal.joptsimple.ArgumentAcceptingOptionSpec; +import jdk.internal.joptsimple.OptionParser; +import jdk.internal.joptsimple.OptionSet; +import jdk.jpackage.internal.cli.DefaultOptions.OptionIdentifierWithValue; +import jdk.jpackage.internal.cli.DefaultOptions.Snapshot; +import jdk.jpackage.internal.cli.OptionSpec.MergePolicy; +import jdk.jpackage.internal.util.Result; + + +/** + * Builds an instance of {@link Options} interface backed with joptsimple command + * line parser. + * + * Two types of command line argument processing are supported: + *
      + *
    1. Parse command line. Parsed data is stored as a map of strings. + *
    2. Convert strings to objects. Parsed data is stored as a map of objects. + *
    + */ +final class JOptSimpleOptionsBuilder { + + Function> create() { + return createJOptSimpleParser()::parse; + } + + JOptSimpleOptionsBuilder options(Collection v) { + v.stream().map(u -> { + switch (u) { + case Option o -> { + return o; + } + case OptionValue ov -> { + return ov.getOption(); + } + default -> { + throw new IllegalArgumentException(); + } + } + }).forEach(options::add); + return this; + } + + JOptSimpleOptionsBuilder options(WithOptionIdentifier... v) { + return options(List.of(v)); + } + + JOptSimpleOptionsBuilder optionSpecMapper(UnaryOperator> v) { + optionSpecMapper = v; + return this; + } + + JOptSimpleOptionsBuilder jOptSimpleParserErrorHandler(Function v) { + jOptSimpleParserErrorHandler = v; + return this; + } + + private JOptSimpleParser createJOptSimpleParser() { + return JOptSimpleParser.create(options, Optional.ofNullable(optionSpecMapper), + Optional.ofNullable(jOptSimpleParserErrorHandler)); + } + + + static final class ConvertedOptionsBuilder { + + private ConvertedOptionsBuilder(TypedOptions options) { + impl = Objects.requireNonNull(options); + } + + Options create() { + return impl; + } + + ConvertedOptionsBuilder copyWithExcludes(Collection v) { + return new ConvertedOptionsBuilder(impl.copyWithout(v)); + } + + List nonOptionArguments() { + return impl.nonOptionArguments(); + } + + List detectedOptions() { + return impl.detectedOptions(); + } + + private final TypedOptions impl; + } + + + static final class OptionsBuilder { + + private OptionsBuilder(UntypedOptions options) { + impl = Objects.requireNonNull(options); + } + + Result convertedOptions() { + return impl.toTypedOptions().map(ConvertedOptionsBuilder::new); + } + + Options create() { + return impl; + } + + OptionsBuilder copyWithExcludes(Collection v) { + return new OptionsBuilder(impl.copyWithout(v)); + } + + List nonOptionArguments() { + return impl.nonOptionArguments(); + } + + List detectedOptions() { + return impl.detectedOptions(); + } + + private final UntypedOptions impl; + } + + + enum JOptSimpleErrorType { + + // jdk.internal.joptsimple.UnrecognizedOptionException + UNRECOGNIZED_OPTION(() -> { + new OptionParser(false).parse("--foo"); + }), + + // jdk.internal.joptsimple.OptionMissingRequiredArgumentException + OPTION_MISSING_REQUIRED_ARGUMENT(() -> { + var parser = new OptionParser(false); + parser.accepts("foo").withRequiredArg(); + parser.parse("--foo"); + }), + ; + + JOptSimpleErrorType(Runnable initializer) { + try { + initializer.run(); + // Should never get to this point as the above line is expected to throw + // an exception of type `jdk.internal.joptsimple.OptionException`. + throw new AssertionError(); + } catch (jdk.internal.joptsimple.OptionException ex) { + type = ex.getClass(); + } + } + + private final Class type; + } + + + record JOptSimpleError(JOptSimpleErrorType type, OptionName optionName) { + + JOptSimpleError { + Objects.requireNonNull(type); + Objects.requireNonNull(optionName); + } + + static JOptSimpleError create(jdk.internal.joptsimple.OptionException ex) { + var optionName = OptionName.of(ex.options().getFirst()); + return Stream.of(JOptSimpleErrorType.values()).filter(v -> { + return v.type.isInstance(ex); + }).findFirst().map(v -> { + return new JOptSimpleError(v, optionName); + }).orElseThrow(); + } + } + + + private record JOptSimpleParser( + OptionParser parser, + Map> optionMap, + Optional> jOptSimpleParserErrorHandler) { + + private JOptSimpleParser { + Objects.requireNonNull(parser); + Objects.requireNonNull(optionMap); + Objects.requireNonNull(jOptSimpleParserErrorHandler); + } + + Result parse(String... args) { + return applyParser(parser, args).map(optionSet -> { + final OptionSet mergerOptionSet; + if (optionMap.values().stream().allMatch(spec -> spec.names().size() == 1)) { + // No specs with multiple names, merger not needed. + mergerOptionSet = optionSet; + } else { + final var parser2 = createOptionParser(); + final var optionSpecApplier = new OptionSpecApplier(); + for (final var spec : optionMap.values()) { + optionSpecApplier.applyToParser(parser2, spec); + } + + mergerOptionSet = parser2.parse(args); + } + return new OptionsBuilder(new UntypedOptions(optionSet, mergerOptionSet, optionMap)); + }); + } + + static JOptSimpleParser create(Iterable