From 6644dd33f6f4b440105d84ef187a0ff6b1d60827 Mon Sep 17 00:00:00 2001 From: Hamlin Li Date: Thu, 22 Aug 2024 07:22:35 +0000 Subject: [PATCH 01/72] 8338760: Adjust the comment after UseObjectMonitorTable Reviewed-by: coleenp, dcubed --- src/hotspot/share/oops/markWord.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/hotspot/share/oops/markWord.hpp b/src/hotspot/share/oops/markWord.hpp index 92577a8b40b01..bbd80d02cbda8 100644 --- a/src/hotspot/share/oops/markWord.hpp +++ b/src/hotspot/share/oops/markWord.hpp @@ -53,7 +53,8 @@ // [ptr | 00] locked ptr points to real header on stack (stack-locking in use) // [header | 00] locked locked regular object header (fast-locking in use) // [header | 01] unlocked regular object header -// [ptr | 10] monitor inflated lock (header is swapped out) +// [ptr | 10] monitor inflated lock (header is swapped out, UseObjectMonitorTable == false) +// [header | 10] monitor inflated lock (UseObjectMonitorTable == true) // [ptr | 11] marked used to mark an object // [0 ............ 0| 00] inflating inflation in progress (stack-locking in use) // From 129f527f4f6de04897440a11f0be024f1a378433 Mon Sep 17 00:00:00 2001 From: Magnus Ihse Bursie Date: Thu, 22 Aug 2024 10:31:34 +0000 Subject: [PATCH 02/72] 8338290: Xcode project generator for hotspot Co-authored-by: Gerard Ziemski Co-authored-by: Magnus Ihse Bursie Reviewed-by: azafari, erikj --- make/Main.gmk | 12 + make/common/FileUtils.gmk | 5 + make/common/NativeCompilation.gmk | 2 +- make/common/native/Link.gmk | 12 + .../visualstudio/hotspot/CreateVSProject.gmk | 4 +- make/ide/xcode/hotspot/CreateXcodeProject.gmk | 112 +++ .../data/Breakpoints_v2.xcbkptlist.template | 41 + .../xcode/hotspot/data/jvm.xcscheme.template | 112 +++ .../hotspot/data/project.pbxproj.template | 207 +++++ .../hotspot/data/runJ2Demo.xcscheme.template | 112 +++ make/ide/xcode/hotspot/data/script_after.sh | 34 + make/ide/xcode/hotspot/data/script_before.sh | 67 ++ .../xcode/hotspot/src/classes/DiskFile.java | 291 +++++++ .../src/classes/XcodeProjectMaker.java | 754 ++++++++++++++++++ make/modules/java.base/Copy.gmk | 24 +- 15 files changed, 1773 insertions(+), 16 deletions(-) create mode 100644 make/ide/xcode/hotspot/CreateXcodeProject.gmk create mode 100644 make/ide/xcode/hotspot/data/Breakpoints_v2.xcbkptlist.template create mode 100644 make/ide/xcode/hotspot/data/jvm.xcscheme.template create mode 100644 make/ide/xcode/hotspot/data/project.pbxproj.template create mode 100644 make/ide/xcode/hotspot/data/runJ2Demo.xcscheme.template create mode 100644 make/ide/xcode/hotspot/data/script_after.sh create mode 100644 make/ide/xcode/hotspot/data/script_before.sh create mode 100644 make/ide/xcode/hotspot/src/classes/DiskFile.java create mode 100644 make/ide/xcode/hotspot/src/classes/XcodeProjectMaker.java diff --git a/make/Main.gmk b/make/Main.gmk index fbd394c147282..cdff43b82f3a7 100644 --- a/make/Main.gmk +++ b/make/Main.gmk @@ -278,6 +278,18 @@ $(eval $(call SetupTarget, eclipse-mixed-env, \ ARGS := --always-make, \ )) +$(eval $(call SetupTarget, hotspot-xcode-project, \ + MAKEFILE := ide/xcode/hotspot/CreateXcodeProject, \ + TARGET := build, \ + DEPS := hotspot compile-commands-hotspot jdk-image, \ +)) + +$(eval $(call SetupTarget, open-hotspot-xcode-project, \ + MAKEFILE := ide/xcode/hotspot/CreateXcodeProject, \ + TARGET := open, \ + DEPS := hotspot-xcode-project, \ +)) + ALL_TARGETS += $(HOTSPOT_VARIANT_TARGETS) $(HOTSPOT_VARIANT_GENSRC_TARGETS) \ $(HOTSPOT_VARIANT_LIBS_TARGETS) $(HOTSPOT_VARIANT_STATIC_LIBS_TARGETS) diff --git a/make/common/FileUtils.gmk b/make/common/FileUtils.gmk index cda5932395fca..ea1d0b6d4af05 100644 --- a/make/common/FileUtils.gmk +++ b/make/common/FileUtils.gmk @@ -189,6 +189,11 @@ else endef endif +define copy-and-chmod-executable + $(install-file) + $(CHMOD) a+rx $@ +endef + ################################################################################ # Recursive wildcard function. Walks down directories recursively and matches diff --git a/make/common/NativeCompilation.gmk b/make/common/NativeCompilation.gmk index abd4f65f7536e..5f1c6539cdbd4 100644 --- a/make/common/NativeCompilation.gmk +++ b/make/common/NativeCompilation.gmk @@ -222,7 +222,7 @@ define SetupNativeCompilationBody ifeq ($(GENERATE_COMPILE_COMMANDS_ONLY), true) # Override all targets (this is a hack) - $1 := $$($1_ALL_OBJS_JSON) + $1 := $$($1_ALL_OBJS_JSON) $$($1_LDFLAGS_FILE) endif endef diff --git a/make/common/native/Link.gmk b/make/common/native/Link.gmk index ca03c6ee6b12f..d64df1bebb996 100644 --- a/make/common/native/Link.gmk +++ b/make/common/native/Link.gmk @@ -198,4 +198,16 @@ define CreateDynamicLibraryOrExecutable $(CODESIGN) -f -s $$($1_CODESIGN_OPTS) --entitlements \ $$(call GetEntitlementsFile, $$@) $$@) endif + + # This is for IDE integration purposes only, and is not normally generated + $1_LDFLAGS_FILE := $$(MAKESUPPORT_OUTPUTDIR)/compile-commands/$$($1_NAME)-ldflags.txt + + $1_ALL_LD_ARGS := $$($1_LDFLAGS) $$($1_EXTRA_LDFLAGS) $$($1_SYSROOT_LDFLAGS) \ + $$($1_LIBS) $$($1_EXTRA_LIBS) + + $$($1_LDFLAGS_FILE): $$($1_VARDEPS_FILE) + $$(call LogInfo, Creating compile commands linker flags output for $$($1_BASENAME)) + $$(call MakeDir, $$(dir $$@)) + $$(ECHO) $$($1_ALL_LD_ARGS) > $$@ + endef diff --git a/make/ide/visualstudio/hotspot/CreateVSProject.gmk b/make/ide/visualstudio/hotspot/CreateVSProject.gmk index cd093d3c6de85..11efad2b4f23d 100644 --- a/make/ide/visualstudio/hotspot/CreateVSProject.gmk +++ b/make/ide/visualstudio/hotspot/CreateVSProject.gmk @@ -1,5 +1,5 @@ # -# Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -83,7 +83,7 @@ ifeq ($(call isTargetOs, windows), true) ################################################################################ # Build the ProjectCreator java tool. - TOOLS_OUTPUTDIR := $(HOTSPOT_OUTPUTDIR)/support/ide_classes + TOOLS_OUTPUTDIR := $(MAKESUPPORT_OUTPUTDIR)/ide/visualstudio $(eval $(call SetupJavaCompilation, BUILD_PROJECT_CREATOR, \ TARGET_RELEASE := $(TARGET_RELEASE_BOOTJDK), \ diff --git a/make/ide/xcode/hotspot/CreateXcodeProject.gmk b/make/ide/xcode/hotspot/CreateXcodeProject.gmk new file mode 100644 index 0000000000000..db8f7f401eff4 --- /dev/null +++ b/make/ide/xcode/hotspot/CreateXcodeProject.gmk @@ -0,0 +1,112 @@ +# +# Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. 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. +# + +# This must be the first rule +default: all + +include $(SPEC) +include MakeBase.gmk +include CopyFiles.gmk +include Execute.gmk +include JavaCompilation.gmk + +ifeq ($(call isTargetOs, macosx), true) + ############################################################################## + # Build the XcodeProjectMaker java tool. + + PROJECT_MAKER_DIR := $(TOPDIR)/make/ide/xcode/hotspot + TOOLS_OUTPUTDIR := $(MAKESUPPORT_OUTPUTDIR)/ide/xcode + IDE_OUTPUTDIR := $(OUTPUTDIR)/xcode + PROJECT_FILE_NAME := hotspot.xcodeproj + + COMPILE_COMMAND_FILE := $(OUTPUTDIR)/compile_commands.json + LINKER_FLAGS_FILE := $(MAKESUPPORT_OUTPUTDIR)/compile-commands/jvm-ldflags.txt + + $(eval $(call SetupJavaCompilation, BUILD_PROJECT_CREATOR, \ + TARGET_RELEASE := $(TARGET_RELEASE_BOOTJDK), \ + SRC := $(PROJECT_MAKER_DIR)/src/classes, \ + BIN := $(TOOLS_OUTPUTDIR), \ + DISABLED_WARNINGS := rawtypes unchecked serial, \ + )) + + TARGETS += $(BUILD_PROJECT_CREATOR) + + # Run the XcodeProjectMaker tool + PROJECT_CREATOR_TOOL := $(JAVA_SMALL) -cp $(TOOLS_OUTPUTDIR) XcodeProjectMaker + + ifneq ($(findstring $(LOG_LEVEL), debug trace), ) + XCODE_PROJ_DEBUG_OPTION := -d + endif + + XCODE_PROJ_VARDEPS := $(WORKSPACE_ROOT) $(IDE_OUTPUTDIR) \ + $(PROJECT_MAKER_DIR)/data $(COMPILE_COMMAND_FILE) $(LINKER_FLAGS_FILE) + XCODE_PROJ_VARDEPS_FILE := $(call DependOnVariable, XCODE_PROJ_VARDEPS, \ + $(TOOLS_OUTPUTDIR)/xcodeproj.vardeps) + + $(eval $(call SetupExecute, build_xcode_project, \ + WARN := Generating Xcode project file, \ + DEPS := $(BUILD_PROJECT_CREATOR) $(COMPILE_COMMAND_FILE) \ + $(LINKER_FLAGS_FILE) $(XCODE_PROJ_VARDEPS_FILE), \ + OUTPUT_DIR := $(TOOLS_OUTPUTDIR), \ + COMMAND := $(PROJECT_CREATOR_TOOL) $(WORKSPACE_ROOT) $(IDE_OUTPUTDIR) \ + $(PROJECT_MAKER_DIR)/data $(COMPILE_COMMAND_FILE) \ + $(LINKER_FLAGS_FILE) $(XCODE_PROJ_DEBUG_OPTION), \ + )) + + TARGETS += $(build_xcode_project) + + $(eval $(call SetupCopyFiles, copy_xcode_project, \ + DEST := $(IDE_OUTPUTDIR), \ + FILES := $(PROJECT_MAKER_DIR)/data/script_before.sh $(PROJECT_MAKER_DIR)/data/script_after.sh , \ + MACRO := copy-and-chmod-executable, \ + )) + + TARGETS += $(copy_xcode_project) + + $(eval $(call SetupExecute, open_xcode_project, \ + INFO := Opening Xcode project file, \ + DEPS := $(build_xcodeproject_TARGET) FORCE, \ + OUTPUT_DIR := $(TOOLS_OUTPUTDIR), \ + COMMAND := open $(IDE_OUTPUTDIR)/$(PROJECT_FILE_NAME), \ + )) + + TARGETS += $(open_xcode_project) + + # Always call open without considering dependencies being up to date + FORCE: + + build: $(build_xcode_project) $(copy_xcode_project) + + open: $(open_xcode_project) + + all: $(TARGETS) +else + build: + open: + all: + $(info Xcode projects are only supported on macOS) +endif + +.PHONY: default all build open diff --git a/make/ide/xcode/hotspot/data/Breakpoints_v2.xcbkptlist.template b/make/ide/xcode/hotspot/data/Breakpoints_v2.xcbkptlist.template new file mode 100644 index 0000000000000..42f6c9a689aaa --- /dev/null +++ b/make/ide/xcode/hotspot/data/Breakpoints_v2.xcbkptlist.template @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + diff --git a/make/ide/xcode/hotspot/data/jvm.xcscheme.template b/make/ide/xcode/hotspot/data/jvm.xcscheme.template new file mode 100644 index 0000000000000..894d4ffc46fd9 --- /dev/null +++ b/make/ide/xcode/hotspot/data/jvm.xcscheme.template @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/make/ide/xcode/hotspot/data/project.pbxproj.template b/make/ide/xcode/hotspot/data/project.pbxproj.template new file mode 100644 index 0000000000000..2af90477210c5 --- /dev/null +++ b/make/ide/xcode/hotspot/data/project.pbxproj.template @@ -0,0 +1,207 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 48; + objects = { + +/* Begin PBXBuildFile section */ +TEMPLATE_PBXBUILDFILE +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + D60000000000000000000003 /* script_before.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = script_before.sh; sourceTree = ""; }; + D60000000000000000000002 /* script_after.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = script_after.sh; sourceTree = ""; }; + D60000000000000000000006 /* libjvm.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libjvm.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; +TEMPLATE_PBXFILEREFERENCE +/* End PBXFileReference section */ + +/* Begin PBXGroup section */ + D60000000000000000000004 /* scripts */ = { + isa = PBXGroup; + children = ( + D60000000000000000000003 /* script_before.sh */, + D60000000000000000000002 /* script_after.sh */, + ); + name = scripts; + sourceTree = ""; + }; + D60000000000000000000005 /* Products */ = { + isa = PBXGroup; + children = ( + D60000000000000000000006 /* libjvm.dylib */, + ); + name = Products; + sourceTree = ""; + }; + D60000000000000000000001 = { + isa = PBXGroup; + children = ( + D60000000000000000000004 /* scripts */, +TEMPLATE_GROUP_GENSRC /* gensrc */, +TEMPLATE_GROUP_SRC /* src */, +TEMPLATE_GROUP_TEST /* test */, + D60000000000000000000005 /* Products */, + ); + sourceTree = ""; + }; +TEMPLATE_GROUPS +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + D60000000000000000000000 /* jvm */ = { + isa = PBXNativeTarget; + buildConfigurationList = D6000000000000000000000F /* Build configuration list for PBXNativeTarget "jvm" */; + buildPhases = ( + D60000000000000000000007 /* Run script_before */, + D60000000000000000000008 /* Sources */, + D6000000000000000000000A /* Run script_after */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = jvm; + productName = jvm; + productReference = D60000000000000000000006 /* libjvm.dylib */; + productType = "com.apple.product-type.library.dynamic"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + D60000000000000000000010 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0900; + ORGANIZATIONNAME = Oracle; + TargetAttributes = { + D60000000000000000000000 = { + CreatedOnToolsVersion = 9.0; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = D6000000000000000000000E /* Build configuration list for PBXProject "jvm" */; + compatibilityVersion = "Xcode 8.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = D60000000000000000000001; + productRefGroup = D60000000000000000000005 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + D60000000000000000000000 /* jvm */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXShellScriptBuildPhase section */ + D60000000000000000000007 /* Run script_before */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run script_before"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "cd $PROJECT_DIR;\ntime ./script_before.sh;\n"; + }; + D6000000000000000000000A /* Run script_after */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run script_after"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "cd $PROJECT_DIR;\ntime ./script_after.sh;\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + D60000000000000000000008 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( +TEMPLATE_PBXSOURCESSBUILDPHASE + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + D6000000000000000000000B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CODE_SIGN_IDENTITY = "-"; + CONFIGURATION_BUILD_DIR = build/jdk/lib/server; + CONFIGURATION_TEMP_DIR = build; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + MTL_ENABLE_DEBUG_INFO = NO; + OBJROOT = build; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + }; + name = Release; + }; + D6000000000000000000000D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)"; + EXECUTABLE_PREFIX = lib; + FRAMEWORK_SEARCH_PATHS = ( +TEMPLATE_FRAMEWORK_SEARCH_PATHS + ); + OTHER_CFLAGS = ( +TEMPLATE_OTHER_CFLAGS + ); + OTHER_LDFLAGS = ( +TEMPLATE_OTHER_LDFLAGS + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SYMROOT = build/jdk/lib/server; + USER_HEADER_SEARCH_PATHS = ( +TEMPLATE_USER_HEADER_SEARCH_PATHS + ); + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + D6000000000000000000000E /* Build configuration list for PBXProject "jvm" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D6000000000000000000000B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D6000000000000000000000F /* Build configuration list for PBXNativeTarget "jvm" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D6000000000000000000000D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = D60000000000000000000010 /* Project object */; +} diff --git a/make/ide/xcode/hotspot/data/runJ2Demo.xcscheme.template b/make/ide/xcode/hotspot/data/runJ2Demo.xcscheme.template new file mode 100644 index 0000000000000..fc38775300496 --- /dev/null +++ b/make/ide/xcode/hotspot/data/runJ2Demo.xcscheme.template @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/make/ide/xcode/hotspot/data/script_after.sh b/make/ide/xcode/hotspot/data/script_after.sh new file mode 100644 index 0000000000000..8ce4507faf1ae --- /dev/null +++ b/make/ide/xcode/hotspot/data/script_after.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# +# Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. +# + +echo "running script_after.sh" + +readonly JDK_LIB_PATH="build/jdk/lib/server/libjvm.dylib"; + +if [ ! -f ${JDK_LIB_PATH} ] ; then +{ + echo ">>>>>>> Cannot find ${JDK_LIB_PATH}, the build failed!?"; + exit 1; +} +fi diff --git a/make/ide/xcode/hotspot/data/script_before.sh b/make/ide/xcode/hotspot/data/script_before.sh new file mode 100644 index 0000000000000..5ba7e62e428be --- /dev/null +++ b/make/ide/xcode/hotspot/data/script_before.sh @@ -0,0 +1,67 @@ +#!/bin/bash +# +# Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. +# + +echo "running script_before.sh" + +readonly JDK_BUILD_PATH=".."; +readonly JAVAC_LOCATE_PATTERN="images/jdk/bin/javac"; +readonly HOTSPOT_TOUCH_FILE="../../../src/hotspot/os/posix/jvm_posix.cpp"; + +echo ">>>>>>> Making a copy of JDK ..."; + +javac_file_array=( $(find ${JDK_BUILD_PATH} | grep ${JAVAC_LOCATE_PATTERN}) ); +javac_file=${javac_file_array[0]}; +if [ -z ${javac_file} ] ; then +{ + echo ">>>>>>> ERROR: could not locate ${JAVAC_LOCATE_PATTERN} (did you remember to do \"make images\"?)"; + exit 1; +} +fi + +jdk_build_path=$(dirname $(dirname ${javac_file})); +if [ ! -f "build/${JAVAC_LOCATE_PATTERN}" ] ; then +{ + echo ">>>>>>> Copying jdk over..."; + rsync -a "${jdk_build_path}" "build/"; +} +fi + +# the following files will be supplied by the Xcode build +rm -rf "build/jdk/lib/server/libjvm.dylib"; +rm -rf "build/jdk/lib/server/libjvm.dylib.dSYM"; + +echo ">>>>>>> DONE"; + +echo ">>>>>>> Touching ${HOTSPOT_TOUCH_FILE} to force HotspotVM rebuilt"; +if [ ! -f ${HOTSPOT_TOUCH_FILE} ] ; then +{ + echo ">>>>>>> Cannot find ${HOTSPOT_TOUCH_FILE}"; + exit 1; +} +fi +touch ${HOTSPOT_TOUCH_FILE}; + +echo ">>>>>>> DONE"; + +echo ">>>>>>> Xcode should be building the HotspotVM now..."; diff --git a/make/ide/xcode/hotspot/src/classes/DiskFile.java b/make/ide/xcode/hotspot/src/classes/DiskFile.java new file mode 100644 index 0000000000000..bef90b427ae43 --- /dev/null +++ b/make/ide/xcode/hotspot/src/classes/DiskFile.java @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +import java.io.File; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +public class DiskFile extends LinkedHashMap implements Comparable { + // xcode id ex: D50000000000000000000000 + private static long xcodeIdCount = 0xF0000001; + private final Path path; + private final boolean directory; + private final String xcodeId; + private final String xcodeId2; + private Iterable compilerFlags; + + public DiskFile(String path, boolean directory) { + this(stringToPath(path), directory); + } + + private DiskFile(Path path, boolean directory) { + this.path = path; + this.directory = directory; + this.compilerFlags = null; + this.xcodeId = getNextXcodeId(); + this.xcodeId2 = getNextXcodeId(); + } + + private static Path stringToPath(String string) { + if (string != null) { + return new File(string).toPath(); + } else { + return null; + } + } + + private static Path clipPath(Path path, String clip) { + return clipPath(path.toString(), clip); + } + + private static Path clipPath(String path, String clip) { + String subpath = path; + if (path.contains(clip)) { + subpath = clip; + } + int index = path.indexOf(subpath); + return stringToPath(path.substring(index)); + } + + private String getNextXcodeId() { + String id = "D5FFFFFF" + Long.toHexString(xcodeIdCount).toUpperCase(Locale.ROOT); + xcodeIdCount++; + + return id; + } + + private String getPath() { + return this.path.toString(); + } + + public boolean isDirectory() { + return this.directory; + } + + public void markAsCompiled(List compilerFlags) { + this.compilerFlags = compilerFlags; + } + + private boolean isCompiled() { + return (this.compilerFlags != null); + } + + public String getXcodeId() { + return this.xcodeId; + } + + public String generatePbxSourcesBuildPhase() { + String string = ""; + if (isCompiled()) { + String fileName = getFileName(); + string += String.format(" %s /* %s in Sources */,\n", this.xcodeId2, fileName); + } else if (isDirectory()) { + for (Map.Entry entry : entrySet()) { + DiskFile file = entry.getValue(); + string += file.generatePbxSourcesBuildPhase(); + } + } + return string; + } + + // D5FFFFFFFFFFFFFFF0006506 /* vm_version.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D5FFFFFFFFFFFFFFF0006505 /* vm_version.cpp */; settings = {COMPILER_FLAGS = HEREHERE; }; }; + public String generatePbxBuildFile() { + String string = ""; + if (isCompiled()) { + String flagsString = ""; + for (String flag : this.compilerFlags) { + flagsString += flag.replace("\"", "\\\\\"") + " "; + } + String fileName = getFileName(); + string += String.format(" %s /* %s in Sources */ = {isa = PBXBuildFile; fileRef = %s /* %s */; settings = {COMPILER_FLAGS = \"%s\"; }; };\n", this.xcodeId2, fileName, this.xcodeId, fileName, flagsString); + } else if (isDirectory()) { + for (Map.Entry entry : entrySet()) { + DiskFile file = entry.getValue(); + string += file.generatePbxBuildFile(); + } + } + return string; + } + + public String generatePbxFileReference(String relativePathToRoot) { + String string = ""; + if (!isDirectory()) { + String fileName = getFileName(); + String suffix = getFileNameSuffix(); + string += String.format(" %s /* %s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = %s%s; name = %s; path = \"%s%s\"; sourceTree = \"\"; };\n", this.xcodeId, fileName, fileName, suffix, fileName, relativePathToRoot, getPath()); + } else if (isDirectory()) { + for (Map.Entry entry : entrySet()) { + DiskFile file = entry.getValue(); + string += file.generatePbxFileReference(relativePathToRoot); + } + } + return string; + } + + public String generatePbxGroup() { + String string = String.format(" %s /* %s */ = {\n isa = PBXGroup;\n children = (\n", this.xcodeId, getFileName()); + + Set sortedSet = new TreeSet<>(values()); + + for (DiskFile file : sortedSet) { + string += String.format(" %s /* %s */,\n", file.getXcodeId(), file.getFileName()); + } + string += String.format(" );\n name = %s;\n sourceTree = \"\";\n };\n", getFileName()); + + for (DiskFile file : sortedSet) { + if (file.isDirectory()) { + string += file.generatePbxGroup(); + } + } + + return string; + } + + private ArrayList getFiles(ArrayList array) { + for (Map.Entry entry : entrySet()) { + DiskFile file = entry.getValue(); + if (file.isDirectory()) { + array.add(file); + array = file.getFiles(array); + } else { + array.add(file); + } + } + return array; + } + + public ArrayList getFiles() { + return getFiles(new ArrayList<>()); + } + + public String getFilePath() { + return this.path.toString(); + } + + private String getFileName() { + Path fileName = this.path.getFileName(); + if (fileName != null) { + return fileName.toString(); + } else { + return this.path.toString(); + } + } + + private String getFileNameNoSuffix() { + String string; + Path fileName = this.path.getFileName(); + if (fileName != null) { + string = fileName.toString(); + int index = string.indexOf('.'); + if (index >= 0) { + string = string.substring(0, index); + } + } else { + string = this.path.toString(); + } + return string; + } + + private String getFileNameSuffix() { + String fileName = getFileName(); + int index = fileName.indexOf('.'); + if (index >= 0) { + return fileName.substring(index); + } else { + return ""; + } + } + + public DiskFile getChild(String fileName) { + DiskFile child = null; + for (Map.Entry entry : entrySet()) { + DiskFile file = entry.getValue(); + if (file.getFileName().equals(fileName)) { + child = entry.getValue(); + break; + } else if (file.isDirectory()) { + child = file.getChild(fileName); + if (child != null) { + break; + } + } + } + return child; + } + + private DiskFile getParent(Path path) { + Path pathParent = path.getParent(); + DiskFile parent = get(pathParent); + if (parent == null) { + if (this.path.equals(pathParent)) { + parent = this; + } else { + parent = getParent(pathParent).get(pathParent); + } + parent.putIfAbsent(path, new DiskFile(path, true)); + } + return parent; + } + + public void addFile(Path path, String clip) { + path = clipPath(path, clip); + DiskFile parent = getParent(path); + parent.put(path, new DiskFile(path, false)); + } + + public void addDirectory(Path path, String clip) { + path = clipPath(path, clip); + DiskFile parent = getParent(path); + parent.putIfAbsent(path, new DiskFile(path, true)); + } + + @Override + public int compareTo(DiskFile file) { + // ".hpp", then ".inline.hpp", then ".cpp" + int equal = getFileNameNoSuffix().compareTo(file.getFileNameNoSuffix()); + if (equal == 0) { + String suffix1 = getFileNameSuffix(); + String suffix2 = file.getFileNameSuffix(); + if (!suffix1.equals(".inline.hpp") && !suffix2.equals(".inline.hpp")) { + // .hpp before .cpp + equal = -(getFileNameSuffix().compareTo(file.getFileNameSuffix())); + } else if (suffix1.equals(".inline.hpp") && suffix2.equals(".hpp")) { + return 1; + } else if (suffix1.equals(".inline.hpp") && suffix2.equals(".cpp")) { + return -1; + } else if (suffix1.equals(".hpp") && suffix2.equals(".inline.hpp")) { + return -1; + } else if (suffix1.equals(".cpp") && suffix2.equals(".inline.hpp")) { + return 1; + } + } + return equal; + } +} diff --git a/make/ide/xcode/hotspot/src/classes/XcodeProjectMaker.java b/make/ide/xcode/hotspot/src/classes/XcodeProjectMaker.java new file mode 100644 index 0000000000000..9e262a8277811 --- /dev/null +++ b/make/ide/xcode/hotspot/src/classes/XcodeProjectMaker.java @@ -0,0 +1,754 @@ +/* + * Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +import java.io.File; +import java.io.IOException; +import java.nio.file.FileSystemLoopException; +import java.nio.file.FileVisitResult; +import java.nio.file.FileVisitor; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.PosixFilePermission; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +public final class XcodeProjectMaker { + private static final String JDK_SCRIPT_TOKEN_1 = "configure"; + private static final String JDK_SCRIPT_TOKEN_2 = ".jcheck"; + private static final String COMPILER_LINE_HEADER = "-I"; + private static final String COMPILER_IFRAMEWORK = "-iframework"; + private static final String COMPILER_FFRAMEWORK = "-F"; + private static final String SRC_HOTSPOT_PATH = "/src/hotspot"; + private static final String TEST_HOTSPOT_PATH = "/test/hotspot/gtest"; + private static final String ALIAS_JAVA_OLD = "java_old.sh"; + private static final String ALIAS_JAVA_NEW = "java_new.sh"; + private static final String JDK_BIN_JAVA = "/jdk/bin/java"; + private static final String FILE_TOKEN = "\"file\": "; + private static final String COMMAND_TOKEN = "\"command\": "; + private static final String QUOTE_START_TOKEN = "'\\\""; + private static final String QUOTE_END_TOKEN = "\\\"'"; + private static final String VERSION = "2.0.0"; + private static final String EXCLUDE_PARSE_TOKEN_1 = "gtest"; + private static final String TEMPLATE_FRAMEWORK_SEARCH_PATHS = "TEMPLATE_FRAMEWORK_SEARCH_PATHS"; + private static final String TEMPLATE_OTHER_CFLAGS = "TEMPLATE_OTHER_CFLAGS"; + private static final String TEMPLATE_OTHER_LDFLAGS = "TEMPLATE_OTHER_LDFLAGS"; + private static final String TEMPLATE_USER_HEADER_SEARCH_PATHS = "TEMPLATE_USER_HEADER_SEARCH_PATHS"; + private static final String TEMPLATE_GROUP_GENSRC = "TEMPLATE_GROUP_GENSRC"; + private static final String TEMPLATE_GROUP_SRC = "TEMPLATE_GROUP_SRC"; + private static final String TEMPLATE_GROUP_TEST = "TEMPLATE_GROUP_TEST"; + private static final String TEMPLATE_GROUPS = "TEMPLATE_GROUPS"; + private static final String TEMPLATE_PBXBUILDFILE = "TEMPLATE_PBXBUILDFILE"; + private static final String TEMPLATE_PBXFILEREFERENCE = "TEMPLATE_PBXFILEREFERENCE"; + private static final String TEMPLATE_PBXSOURCESSBUILDPHASE = "TEMPLATE_PBXSOURCESSBUILDPHASE"; + private static final String TEMPLATE_JDK_PATH = "TEMPLATE_JDK_PATH"; + private static final String HOTSPOT_PBXPROJ = "hotspot.xcodeproj"; + private static final String PBXPROJ = "project.pbxproj"; + private static final String XCSAHAREDDATA = "xcshareddata"; + private static final String XCSCHEMES = "xcschemes"; + private static final String JVM_XCSCHEME = "jvm.xcscheme"; + private static final String J2D_XCSCHEME = "runJ2Demo.xcscheme"; + private static final String XCDEBUGGER = "xcdebugger"; + private static final String XCBKPTLIST = "Breakpoints_v2.xcbkptlist"; + private static final String TEMPLATE_PBXPROJ = PBXPROJ + ".template"; + private static final String TEMPLATE_JVM_XCSCHEME = JVM_XCSCHEME + ".template"; + private static final String TEMPLATE_J2D_XCSCHEME = J2D_XCSCHEME + ".template"; + private static final String TEMPLATE_XCBKPTLIST = XCBKPTLIST + ".template"; + private static final String[] EXCLUDE_FILES_PREFIX = {"."}; + private static final String[] EXCLUDE_FILES_POSTFIX = {".log", ".cmdline"}; + private static final String[] COMPILER_FLAGS_INCLUDE = {"-m", "-f", "-D", "-W"}; + private static final String[] COMPILER_FLAGS_IS = {"-g", "-Os", "-0"}; + private static final String[] COMPILER_FLAGS_EXCLUDE = {"-DTHIS_FILE", "-DGTEST_OS_MAC", "-mmacosx-version-min", "-Werror"}; // "-Werror" causes Xcode to stop compiling + private static final int EXIT4 = -4; + private static final int EXIT5 = -5; + private static final int EXIT6 = -6; + private static final int EXIT7 = -7; + + private final HashMap> compiledFiles = new HashMap<>(); + private final TreeSet compilerFlags = new TreeSet<>(); + private List linkerFlags = List.of(); + private final TreeSet headerPaths = new TreeSet<>(); + private final boolean debugLog; + private String projectMakerDataPath = null; + private String generatedHotspotPath = null; + private String iframework = null; + private String fframework = null; + private DiskFile rootGensrc = new DiskFile("/", true); + private DiskFile rootSrc = new DiskFile("/", true); + private DiskFile rootTest = new DiskFile("/", true); + + public XcodeProjectMaker(boolean debugLog) { + this.debugLog = debugLog; + } + + public static void main(String[] args) { + String workspaceRoot = args[0]; + String outputDir = args[1]; + String pathToProjectMakerData = args[2]; + String pathToCompileCommands = args[3]; + String pathToLinkerOptionsFile = args[4]; + String linkerOptionsString = readFile(pathToLinkerOptionsFile); + boolean debugLog = args.length > 5 && args[5].equals("-d"); + + File xcodeFolder = new File(outputDir); + xcodeFolder.mkdirs(); + String workspaceRootPathFromOutputDir = findRelativePathToWorkspaceRoot(outputDir); + + if (debugLog) { + System.out.println(); + System.out.println("Version " + VERSION); + System.out.println(); + System.out.println(" Path to workspace root is \"" + workspaceRoot + "\""); + System.out.println("Path to compile commands file is \"" + pathToCompileCommands + "\""); + System.out.println(" Xcode project will be placed in \"" + outputDir + "\""); + System.out.println(); + } + + XcodeProjectMaker maker = new XcodeProjectMaker(debugLog); + maker.parseHotspotCompileCommands(pathToCompileCommands); + maker.linkerFlags = List.of(linkerOptionsString.split(" ")); + maker.projectMakerDataPath = pathToProjectMakerData; + + maker.printLogDetails(); + + maker.prepareFiles(workspaceRoot); + maker.makeXcodeProj(outputDir, workspaceRootPathFromOutputDir); + + String pathToBuild = getFileParent(outputDir); + maker.makeAliases(outputDir, pathToBuild); + + System.out.println(); + System.out.println("The Xcode project for hotspot was succesfully created"); + System.out.println("It can be found in '" + outputDir + "/" + HOTSPOT_PBXPROJ + "'"); + System.out.println(); + } + + // find a path to what looks like jdk + private static String findRelativePathToWorkspaceRoot(String root) { + String pathToWorkspaceRoot = null; + String path = root; + boolean found1 = false; + boolean found2 = false; + + while (!found1 && !found2) { + File folder = new File(path); + File[] files = folder.listFiles(); + for (File file : files) { + String fileName = file.toPath().getFileName().toString(); + if (fileName.equals(JDK_SCRIPT_TOKEN_1)) { + found1 = true; + } + if (fileName.equals(JDK_SCRIPT_TOKEN_2)) { + found2 = true; + } + if (found1 && found2) { + break; + } + } + + if (!found1 && !found2) { + path = Paths.get(path).getParent().toString(); + if (pathToWorkspaceRoot == null) { + pathToWorkspaceRoot = ".."; + } else { + pathToWorkspaceRoot += "/.."; + } + } + } + return pathToWorkspaceRoot; + } + + private static String readFile(File file) { + return readFile(file.toPath()); + } + + private static String readFile(String path) { + return readFile(Paths.get(path)); + } + + private static String readFile(Path path) { + try { + return Files.readString(path); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + private static void writeFile(File file, String string) { + writeFile(file.toPath(), string); + } + + private static void writeFile(Path path, String string) { + try { + Files.writeString(path, string); + } catch (IOException e) { + e.printStackTrace(); + System.exit(EXIT4); + } + } + + private static boolean excludeFile(Path path) { + return excludeFile(path.toString()); + } + + private static boolean excludeFile(String string) { + return excludeFile(string, null); + } + + private static boolean excludeFile(String string, String exclude) { + if (exclude != null) { + if (contains(string, exclude)) { + return true; + } + } + for (String excludeFilesPrefix : EXCLUDE_FILES_PREFIX) { + if (string.startsWith(excludeFilesPrefix)) { + return true; + } + } + for (String excludeFilesPostfix : EXCLUDE_FILES_POSTFIX) { + if (string.endsWith(excludeFilesPostfix)) { + return true; + } + } + return false; + } + + private static boolean isExcludeCompilerFlag(String string) { + boolean flag = false; + for (String exclude : COMPILER_FLAGS_EXCLUDE) { + if (string.contains(exclude)) { + flag = true; + break; + } + } + return flag; + } + + private static boolean isCompilerFlag(String string) { + boolean flag = false; + for (String include : COMPILER_FLAGS_INCLUDE) { + if (string.startsWith(include)) { + flag = true; + break; + } + } + for (String is : COMPILER_FLAGS_IS) { + if (string.equals(is)) { + flag = true; + break; + } + } + if (isExcludeCompilerFlag(string)) { + flag = false; + } + return flag; + } + + private static String strip(String string) { + return string.substring(2, string.length() - 1); + } + + private static String strip(String string, String token) { + int start = string.indexOf(token); + int end = start + token.length(); + return strip(string.substring(end)); + } + + private static boolean contains(String string, String token) { + return ((string.length() >= token.length()) && (string.contains(token))); + } + + private static String getFileParent(String path) { + return Paths.get(path).getParent().toString(); + } + + private static String extractPath(String string, String from, String to) { + String result = null; + String[] tokens = string.split("/"); + int i = 0; + for (; i < tokens.length; i++) { + if (tokens[i].equals(from)) { + result = ""; + break; + } + } + for (; i < tokens.length; i++) { + result += "/" + tokens[i]; + if (tokens[i].equals(to)) { + break; + } + } + return result; + } + + private void extractCommonCompilerFlags() { + // heuristic, find average count of number of flags used by each compiled file + int countFiles = 0; + int countFlags = 0; + + for (Map.Entry> entry : this.compiledFiles.entrySet()) { + countFiles++; + List flags = entry.getValue(); + countFlags += flags.size(); + } + + // when finding common flags, only consider files with this many flags + int flagCutoff = (countFlags / countFiles) / 2; + + // collect all flags + for (Map.Entry> entry : this.compiledFiles.entrySet()) { + List flags = entry.getValue(); + if (flags.size() > flagCutoff) { + this.compilerFlags.addAll(flags); + } + } + + // find flags to remove + Set removeFlags = new TreeSet<>(); + for (Map.Entry> entry : this.compiledFiles.entrySet()) { + List flags = entry.getValue(); + if (flags.size() > flagCutoff) { + for (String common : this.compilerFlags) { + if (!flags.contains(common)) { + removeFlags.add(common); + } + } + } + } + + // leave only common flags + for (String flag : removeFlags) { + this.compilerFlags.remove(flag); + } + + // remove common flags from each compiler file, leaving only the unique ones + for (Map.Entry> entry : this.compiledFiles.entrySet()) { + List flags = entry.getValue(); + if (flags.size() > flagCutoff) { + for (String common : this.compilerFlags) { + flags.remove(common); + } + } + } + } + + private void extractCompilerFlags(String line) { + boolean verboseCompilerTokens = false; + String file = null; + ArrayList flags = null; + + String[] commands = line.split(","); + for (String command : commands) { + if (contains(command, FILE_TOKEN)) { + file = strip(command, FILE_TOKEN); + //verbose_compiler_tokens = Contains(file, "vm_version.cpp"); + } else if (contains(command, COMMAND_TOKEN)) { + String tokens = strip(command, COMMAND_TOKEN); + String[] arguments = tokens.split(" "); + if (arguments.length >= 3) { + flags = new ArrayList<>(); + for (int a = 2; a < arguments.length; a++) { + String argument = arguments[a]; + if (isCompilerFlag(argument)) { + // catch argument like -DVMTYPE=\"Minimal\" + if (contains(argument, "\\\\\\\"") && argument.endsWith("\\\\\\\"")) { + // TODO: more robust fix needed here + argument = argument.replace("\\", ""); + argument = argument.replaceFirst("\"", "~.~"); // temp token ~.~ + argument = argument.replace("\"", "\\\"'"); + argument = argument.replace("~.~", "'\\\""); + } + + // argument like -DHOTSPOT_VM_DISTRO='\"Java HotSpot(TM)\"' + // gets split up, so reconstruct as single string + if (contains(argument, QUOTE_START_TOKEN) && !argument.endsWith(QUOTE_END_TOKEN)) { + String fullArgument = argument; + do { + ++a; + argument = arguments[a]; + fullArgument = fullArgument + " " + argument; + } while (!argument.endsWith(QUOTE_END_TOKEN)); + argument = fullArgument; + } + flags.add(argument); + if (verboseCompilerTokens) { + System.out.println(" FOUND COMPILER FLAG: " + argument); + } + } else if (argument.startsWith(COMPILER_LINE_HEADER)) { + this.headerPaths.add(argument.substring(2)); + } else if (argument.equals(COMPILER_IFRAMEWORK)) { + if (iframework == null) { + ++a; + this.iframework = arguments[a]; // gets the value, so skip it for the next loop + } + } else if (argument.equals(COMPILER_FFRAMEWORK)) { + if (fframework == null) { + ++a; + this.fframework = arguments[a]; // gets the value, so skip it for the next loop + } + } + } + } + } + } + + if ((file != null) && (flags != null)) { + this.compiledFiles.put(file, flags); + } else { + System.err.println(" WARNING: extractCompilerFlags returns file:" + file + ", flags:" + flags); + } + + if (verboseCompilerTokens) { + System.exit(0); + } + } + + public void parseHotspotCompileCommands(String path) { + String content = readFile(path); + String[] parts = content.split("\\{"); // } + + int found = 0; + for (String line : parts) { + if (!contains(line, EXCLUDE_PARSE_TOKEN_1) && !line.startsWith("[")) { + extractCompilerFlags(line); + found++; + } + } + if (debugLog) { + System.out.println("Found total of " + found + " files that make up the libjvm.dylib"); + } + extractCommonCompilerFlags(); + + // figure out "gensrc" folder + // from: "/Users/gerard/Desktop/jdk_test/jdk10/build/macosx-x86_64-normal-server-fastdebug/hotspot/variant-server/gensrc/adfiles/ad_x86_clone.cpp" + // to: "/build/macosx-x86_64-normal-server-fastdebug/hotspot/variant-server/gensrc" + for (Map.Entry> entry : this.compiledFiles.entrySet()) { + String file = entry.getKey(); + if (file.contains("gensrc")) { + this.generatedHotspotPath = extractPath(file, "build", "gensrc"); + //generatedHotspotPath = "/build/macosx-x64/hotspot/variant-server/gensrc"; + //generatedHotspotPath = "/build/macosx-x86_64-normal-server-fastdebug/hotspot/variant-server/gensrc"; + } + } + } + + // https://docs.oracle.com/javase/tutorial/displayCode.html?code=https://docs.oracle.com/javase/tutorial/essential/io/examples/Copy.java + private DiskFile getHotspotFiles(DiskFile root, String workspaceRoot, String hotspotPath) { + File file = new File(workspaceRoot + "/" + hotspotPath); + if (!file.exists()) { + return null; + } + + try { + final Path rootDir = Paths.get(workspaceRoot + hotspotPath); + Files.walkFileTree(rootDir, new HotspotFileVisitor(root, hotspotPath)); + } catch (IOException ex) { + System.err.println("ex: " + ex); + } + + return root; + } + + public void prepareFiles(String workspaceRoot) { + this.rootGensrc = getHotspotFiles(this.rootGensrc, workspaceRoot, this.generatedHotspotPath); + this.rootSrc = getHotspotFiles(this.rootSrc, workspaceRoot, SRC_HOTSPOT_PATH); + this.rootTest = getHotspotFiles(this.rootTest, workspaceRoot, TEST_HOTSPOT_PATH); + + // make a copy of files from the log + Set logFiles = new TreeSet<>(this.compiledFiles.keySet()); + + int totalMarkedFiles = 0; + DiskFile[] roots = { this.rootGensrc, this.rootSrc }; + for (DiskFile root : roots) { + List diskFiles = root.getFiles(); + for (DiskFile diskFile : diskFiles) { + if (!diskFile.isDirectory()) { + String logFileProcessed = null; + String diskFilePath = diskFile.getFilePath(); + for (String logFilePath : logFiles) { + if (contains(logFilePath, diskFilePath)) { + totalMarkedFiles++; + + logFileProcessed = logFilePath; + + // mark the file as needing compilation + diskFile.markAsCompiled(this.compiledFiles.get(logFilePath)); + + // break early if found + break; + } + } + if (logFileProcessed != null) { + // remove the file, so we don't have to search through it again + logFiles.remove(logFileProcessed); + } + } + } + } + + if (this.compiledFiles.size() != totalMarkedFiles) { + System.err.println("\nError: was expecting to compile " + this.compiledFiles.size() + " files, but marked " + totalMarkedFiles); + for (String file : logFiles) { + System.err.println("file: " + file); + } + System.exit(EXIT5); + } + + if (!logFiles.isEmpty()) { + System.err.println("\nError: unprocessed files left over:"); + for (String logFile : logFiles) { + System.err.println(" " + logFile); + } + System.exit(EXIT6); + } + } + + public void printLogDetails() { + if (!debugLog) return; + + System.out.println("\nFound " + this.compilerFlags.size() + " common compiler flags:"); + for (String flag : this.compilerFlags) { + System.out.println(" " + flag); + } + + System.out.println("\nList of compiled files (each one uses common compiler flags plus extra ones as specified):"); + int count = 1; + for (Map.Entry> entry : this.compiledFiles.entrySet()) { + String file = entry.getKey(); + System.out.format("%4d: %s\n", (count), file); + count++; + List flags = entry.getValue(); + for (String flag : flags) { + System.out.println(" " + flag); + } + } + + System.out.println("\nFound " + this.linkerFlags.size() + " linker flags:"); + for (String flag : this.linkerFlags) { + System.out.println(" " + flag); + } + + System.out.println("\nFound " + this.headerPaths.size() + " header paths:"); + for (String header : this.headerPaths) { + System.out.println(" " + header); + } + + System.out.println("\nFrameworks:"); + System.out.println(" -iframework " + iframework); + System.out.println(" -f " + fframework); + } + + private String makeProjectPbxproj(String workspaceRootPathFromOutputDir, String string) { + String cFlags = ""; + for (String flag : this.compilerFlags) { + cFlags += " \"" + flag.replace("\"", "\\\\\"") + "\",\n"; + } + cFlags = cFlags.substring(0, cFlags.length() - 2); + string = string.replaceFirst(TEMPLATE_OTHER_CFLAGS, cFlags); + + String ldFlags = ""; + for (String flag : this.linkerFlags) { + ldFlags += " \"" + flag + "\",\n"; + } + ldFlags = ldFlags.substring(0, ldFlags.length() - 2); + string = string.replaceFirst(TEMPLATE_OTHER_LDFLAGS, ldFlags); + + String headerPaths = ""; + for (String header : this.headerPaths) { + headerPaths += " \"" + header + "\",\n"; + } + headerPaths = headerPaths.substring(0, headerPaths.length() - 2); + string = string.replaceFirst(TEMPLATE_USER_HEADER_SEARCH_PATHS, headerPaths); + + String frameworkPaths = ""; + if (fframework != null) { + frameworkPaths += " \"" + fframework + "\"\n"; + } + string = string.replaceFirst(TEMPLATE_FRAMEWORK_SEARCH_PATHS, frameworkPaths); + + DiskFile gensrcFile = this.rootGensrc.getChild("gensrc"); + string = string.replaceFirst(TEMPLATE_GROUP_GENSRC, " " + gensrcFile.getXcodeId()); + + DiskFile srcFile = this.rootSrc.getChild("src"); + string = string.replaceFirst(TEMPLATE_GROUP_SRC, " " + srcFile.getXcodeId()); + + DiskFile testFile = this.rootTest.getChild("test"); + string = string.replaceFirst(TEMPLATE_GROUP_TEST, " " + testFile.getXcodeId()); + + String gensrcGroups = gensrcFile.generatePbxGroup(); + String srcGroups = srcFile.generatePbxGroup(); + String testGroups = testFile.generatePbxGroup(); + string = string.replaceFirst(TEMPLATE_GROUPS, gensrcGroups + srcGroups + testGroups); + + String gensrcFiles = gensrcFile.generatePbxFileReference(workspaceRootPathFromOutputDir); + String srcFiles = srcFile.generatePbxFileReference(workspaceRootPathFromOutputDir); + String testFiles = testFile.generatePbxFileReference(workspaceRootPathFromOutputDir); + string = string.replaceFirst(TEMPLATE_PBXFILEREFERENCE, gensrcFiles + srcFiles + testFiles); + + String gensrcCompiled = gensrcFile.generatePbxBuildFile(); + String compiled = srcFile.generatePbxBuildFile(); + string = string.replaceFirst(TEMPLATE_PBXBUILDFILE, gensrcCompiled + compiled); + + String gensrcBuilt = gensrcFile.generatePbxSourcesBuildPhase(); + String built = srcFile.generatePbxSourcesBuildPhase(); + string = string.replaceFirst(TEMPLATE_PBXSOURCESSBUILDPHASE, gensrcBuilt + built); + + return string; + } + + private String makeTemplateXcscheme(String outputDir, String string) { + string = string.replaceAll(TEMPLATE_JDK_PATH, outputDir); + + return string; + } + + public void makeXcodeProj(String outputDir, String workspaceRootPathFromOutputDir) { + /* + jvm.xcodeproj <-- folder + project.pbxproj <-- file + xcshareddata <-- folder + xcschemes <-- folder + jvm.xcscheme <-- file + xcdebugger <-- folder + Breakpoints_v2.xcbkptlist <-- file + */ + File xcodeDir = new File(outputDir); + File jvmXcodeprojDir = new File(xcodeDir, HOTSPOT_PBXPROJ); + File projectPbxprojFile = new File(jvmXcodeprojDir, PBXPROJ); + File xcshareddataDir = new File(jvmXcodeprojDir, XCSAHAREDDATA); + File xcschemesDir = new File(xcshareddataDir, XCSCHEMES); + File jvmXcschemeFile = new File(xcschemesDir, JVM_XCSCHEME); + File j2DemoXcschemeFile = new File(xcschemesDir, J2D_XCSCHEME); + File xcdebuggerDir = new File(xcshareddataDir, XCDEBUGGER); + File jBreakpointsV2XcbkptlistFile = new File(xcdebuggerDir, XCBKPTLIST); + + if (xcodeDir.exists()) { + xcodeDir.delete(); + } + + jvmXcodeprojDir.mkdirs(); + xcshareddataDir.mkdirs(); + xcschemesDir.mkdirs(); + xcdebuggerDir.mkdirs(); + + File dataDir = new File(projectMakerDataPath); + File templateProjectPbxprojFile = new File(dataDir, TEMPLATE_PBXPROJ); + File templateJvmXcschemeFile = new File(dataDir, TEMPLATE_JVM_XCSCHEME); + File templateJ2DemoXcschemeFile = new File(dataDir, TEMPLATE_J2D_XCSCHEME); + File templateJBreakpointsV2XcbkptlistFile = new File(dataDir, TEMPLATE_XCBKPTLIST); + + String projectPbxprojString = readFile(templateProjectPbxprojFile); + String jvmXcschemeString = readFile(templateJvmXcschemeFile); + String j2DemoXcschemeString = readFile(templateJ2DemoXcschemeFile); + String jBreakpointsV2XcbkptlistString = readFile(templateJBreakpointsV2XcbkptlistFile); + + writeFile(projectPbxprojFile, makeProjectPbxproj(workspaceRootPathFromOutputDir, projectPbxprojString)); + writeFile(jvmXcschemeFile, makeTemplateXcscheme(outputDir, jvmXcschemeString)); + writeFile(j2DemoXcschemeFile, makeTemplateXcscheme(outputDir, j2DemoXcschemeString)); + writeFile(jBreakpointsV2XcbkptlistFile, jBreakpointsV2XcbkptlistString); + } + + public void makeAliases(String outputDir, String pathToBuild) { + File xcodeDir = new File(outputDir); + File jdkOldSh = new File(xcodeDir, ALIAS_JAVA_OLD); + File jdkNewSh = new File(xcodeDir, ALIAS_JAVA_NEW); + + writeFile(jdkOldSh, "#!/bin/bash\n" + pathToBuild + JDK_BIN_JAVA + " $@"); + writeFile(jdkNewSh, "#!/bin/bash\n" + outputDir + "/build" + JDK_BIN_JAVA + " $@"); + + try { + Set permissions = new HashSet<>(); + permissions.add(PosixFilePermission.OWNER_READ); + permissions.add(PosixFilePermission.OWNER_WRITE); + permissions.add(PosixFilePermission.OWNER_EXECUTE); + permissions.add(PosixFilePermission.GROUP_READ); + permissions.add(PosixFilePermission.OTHERS_READ); + Files.setPosixFilePermissions(jdkOldSh.toPath(), permissions); + Files.setPosixFilePermissions(jdkNewSh.toPath(), permissions); + } catch (IOException ex) { + System.err.println("Warning: unable to change file permissions"); + System.err.println(ex); + } + } + + private static class HotspotFileVisitor implements FileVisitor { + private final DiskFile root; + private final String hotspotPath; + + public HotspotFileVisitor(DiskFile root, String hotspotPath) { + this.root = root; + this.hotspotPath = hotspotPath; + } + + @Override + public FileVisitResult preVisitDirectory(Path path, BasicFileAttributes attrs) { + if (excludeFile(path)) { + return FileVisitResult.SKIP_SUBTREE; + } else { + // consider folders based on their names + Path file = path.getFileName(); + if (!excludeFile(file)) { + root.addDirectory(path, hotspotPath); + return FileVisitResult.CONTINUE; + } else { + // skip folders with names beginning with ".", etc + return FileVisitResult.SKIP_SUBTREE; + } + } + } + + @Override + public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) { + Path file = path.getFileName(); + if (!excludeFile(file)) { + //System.err.println(path.toString()); + root.addFile(path, hotspotPath); + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path path, IOException exc) { + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed(Path path, IOException exc) { + if (exc instanceof FileSystemLoopException) { + System.err.println("cycle detected: " + path); + } else { + System.err.format("Unable to process: %s: %s\n", path, exc); + } + return FileVisitResult.CONTINUE; + } + } +} diff --git a/make/modules/java.base/Copy.gmk b/make/modules/java.base/Copy.gmk index 911fff60ea898..5a4a5d400f240 100644 --- a/make/modules/java.base/Copy.gmk +++ b/make/modules/java.base/Copy.gmk @@ -1,5 +1,5 @@ # -# Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -51,27 +51,25 @@ endif ifeq ($(call isTargetOs, windows)+$(CREATING_BUILDJDK), true+false) # Chmod to avoid permission issues if bundles are unpacked on unix platforms. - define copy-and-chmod - $(install-file) - $(CHMOD) a+rx $@ - endef - # Use separate macro calls in case the source files are not in the same # directory. - $(eval $(call SetupCopyFiles,COPY_MSVCR, \ + $(eval $(call SetupCopyFiles, COPY_MSVCR, \ DEST := $(LIB_DST_DIR), \ FILES := $(MSVCR_DLL), \ - MACRO := copy-and-chmod)) + MACRO := copy-and-chmod-executable, \ + )) - $(eval $(call SetupCopyFiles,COPY_VCRUNTIME_1, \ + $(eval $(call SetupCopyFiles, COPY_VCRUNTIME_1, \ DEST := $(LIB_DST_DIR), \ FILES := $(VCRUNTIME_1_DLL), \ - MACRO := copy-and-chmod)) + MACRO := copy-and-chmod-executable, \ + )) - $(eval $(call SetupCopyFiles,COPY_MSVCP, \ + $(eval $(call SetupCopyFiles, COPY_MSVCP, \ DEST := $(LIB_DST_DIR), \ FILES := $(MSVCP_DLL), \ - MACRO := copy-and-chmod)) + MACRO := copy-and-chmod-executable, \ + )) TARGETS += $(COPY_MSVCR) $(COPY_VCRUNTIME_1) $(COPY_MSVCP) @@ -80,7 +78,7 @@ ifeq ($(call isTargetOs, windows)+$(CREATING_BUILDJDK), true+false) DEST := $(LIB_DST_DIR), \ SRC := $(UCRT_DLL_DIR), \ FILES := $(wildcard $(UCRT_DLL_DIR)/*.dll), \ - MACRO := copy-and-chmod, \ + MACRO := copy-and-chmod-executable, \ )) TARGETS += $(COPY_UCRT_DLLS) From 6cf7f9c4a76b99ed7aa4dc7ee54692331fc73408 Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Thu, 22 Aug 2024 11:39:47 +0000 Subject: [PATCH 03/72] 8338662: Shenandoah: Remove excessive ShenandoahVerifier::verify_during_evacuation Reviewed-by: wkemper, ysr --- .../gc/shenandoah/shenandoahConcurrentGC.cpp | 6 ------ .../gc/shenandoah/shenandoahDegeneratedGC.cpp | 8 ++++---- .../gc/shenandoah/shenandoahVerifier.cpp | 20 ------------------- .../gc/shenandoah/shenandoahVerifier.hpp | 6 +----- 4 files changed, 5 insertions(+), 35 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp index 0e18b59103746..ed40fecfc1db8 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp @@ -613,12 +613,6 @@ void ShenandoahConcurrentGC::op_final_mark() { // From here on, we need to update references. heap->set_has_forwarded_objects(true); - // Verify before arming for concurrent processing. - // Otherwise, verification can trigger stack processing. - if (ShenandoahVerify) { - heap->verifier()->verify_during_evacuation(); - } - // Arm nmethods/stack for concurrent processing ShenandoahCodeRoots::arm_nmethods_for_evac(); ShenandoahStackWatermark::change_epoch_id(); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.cpp index 6b597d9b2d7b3..81d07a414cd95 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.cpp @@ -276,12 +276,12 @@ void ShenandoahDegenGC::op_prepare_evacuation() { } if (!heap->collection_set()->is_empty()) { + if (ShenandoahVerify) { + heap->verifier()->verify_before_evacuation(); + } + heap->set_evacuation_in_progress(true); heap->set_has_forwarded_objects(true); - - if(ShenandoahVerify) { - heap->verifier()->verify_during_evacuation(); - } } else { if (ShenandoahVerify) { heap->verifier()->verify_after_concmark(); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahVerifier.cpp b/src/hotspot/share/gc/shenandoah/shenandoahVerifier.cpp index e5db7a9b392e9..518787728895b 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahVerifier.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahVerifier.cpp @@ -661,14 +661,6 @@ void ShenandoahVerifier::verify_at_safepoint(const char *label, enabled = true; expected = ShenandoahHeap::HAS_FORWARDED; break; - case _verify_gcstate_evacuation: - enabled = true; - expected = ShenandoahHeap::HAS_FORWARDED | ShenandoahHeap::EVACUATION; - if (!_heap->is_stw_gc_in_progress()) { - // Only concurrent GC sets this. - expected |= ShenandoahHeap::WEAK_ROOTS; - } - break; case _verify_gcstate_stable: enabled = true; expected = ShenandoahHeap::STABLE; @@ -851,18 +843,6 @@ void ShenandoahVerifier::verify_before_evacuation() { ); } -void ShenandoahVerifier::verify_during_evacuation() { - verify_at_safepoint( - "During Evacuation", - _verify_forwarded_allow, // some forwarded references are allowed - _verify_marked_disable, // walk only roots - _verify_cset_disable, // some cset references are not forwarded yet - _verify_liveness_disable, // liveness data might be already stale after pre-evacs - _verify_regions_disable, // trash regions not yet recycled - _verify_gcstate_evacuation // evacuation is in progress - ); -} - void ShenandoahVerifier::verify_before_updaterefs() { verify_at_safepoint( "Before Updating References", diff --git a/src/hotspot/share/gc/shenandoah/shenandoahVerifier.hpp b/src/hotspot/share/gc/shenandoah/shenandoahVerifier.hpp index dd4eb901a3348..1617678d92826 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahVerifier.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahVerifier.hpp @@ -133,10 +133,7 @@ class ShenandoahVerifier : public CHeapObj { _verify_gcstate_stable_weakroots, // Nothing is in progress, some objects are forwarded - _verify_gcstate_forwarded, - - // Evacuation is in progress, some objects are forwarded - _verify_gcstate_evacuation + _verify_gcstate_forwarded } VerifyGCState; struct VerifyOptions { @@ -175,7 +172,6 @@ class ShenandoahVerifier : public CHeapObj { void verify_before_concmark(); void verify_after_concmark(); void verify_before_evacuation(); - void verify_during_evacuation(); void verify_before_updaterefs(); void verify_after_updaterefs(); void verify_before_fullgc(); From 6041c936d6dd39c5b3a89ed2823b25a8aef42b9f Mon Sep 17 00:00:00 2001 From: Matias Saavedra Silva Date: Thu, 22 Aug 2024 15:55:02 +0000 Subject: [PATCH 04/72] 8335664: Parsing jsr broken: assert(bci>= 0 && bci < c->method()->code_size()) failed: index out of bounds Co-authored-by: Emanuel Peter Reviewed-by: dlong, thartmann --- src/hotspot/share/oops/generateOopMap.cpp | 18 ++++----- .../jtreg/runtime/interpreter/LastJsr.jasm | 35 ++++++++++++++++ .../runtime/interpreter/LastJsrReachable.jasm | 37 +++++++++++++++++ .../runtime/interpreter/LastJsrTest.java | 40 +++++++++++++++++++ 4 files changed, 121 insertions(+), 9 deletions(-) create mode 100644 test/hotspot/jtreg/runtime/interpreter/LastJsr.jasm create mode 100644 test/hotspot/jtreg/runtime/interpreter/LastJsrReachable.jasm create mode 100644 test/hotspot/jtreg/runtime/interpreter/LastJsrTest.java diff --git a/src/hotspot/share/oops/generateOopMap.cpp b/src/hotspot/share/oops/generateOopMap.cpp index eb078daee6bb4..918e6969713c9 100644 --- a/src/hotspot/share/oops/generateOopMap.cpp +++ b/src/hotspot/share/oops/generateOopMap.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -436,12 +436,12 @@ void GenerateOopMap::mark_bbheaders_and_count_gc_points() { /* We will also mark successors of jsr's as basic block headers. */ switch (bytecode) { case Bytecodes::_jsr: - assert(!fellThrough, "should not happen"); - bb_mark_fct(this, bci + Bytecodes::length_for(bytecode), nullptr); - break; case Bytecodes::_jsr_w: assert(!fellThrough, "should not happen"); - bb_mark_fct(this, bci + Bytecodes::length_for(bytecode), nullptr); + // If this is the last bytecode, there is no successor to mark + if (bci + Bytecodes::length_for(bytecode) < method()->code_size()) { + bb_mark_fct(this, bci + Bytecodes::length_for(bytecode), nullptr); + } break; default: break; @@ -502,7 +502,10 @@ void GenerateOopMap::mark_reachable_code() { case Bytecodes::_jsr: case Bytecodes::_jsr_w: assert(!fell_through, "should not happen"); - reachable_basicblock(this, bci + Bytecodes::length_for(bytecode), &change); + // If this is the last bytecode, there is no successor to mark + if (bci + Bytecodes::length_for(bytecode) < method()->code_size()) { + reachable_basicblock(this, bci + Bytecodes::length_for(bytecode), &change); + } break; default: break; @@ -586,9 +589,6 @@ bool GenerateOopMap::jump_targets_do(BytecodeStream *bcs, jmpFct_t jmpFct, int * case Bytecodes::_jsr: assert(bcs->is_wide()==false, "sanity check"); (*jmpFct)(this, bcs->dest(), data); - - - break; case Bytecodes::_jsr_w: (*jmpFct)(this, bcs->dest_w(), data); diff --git a/test/hotspot/jtreg/runtime/interpreter/LastJsr.jasm b/test/hotspot/jtreg/runtime/interpreter/LastJsr.jasm new file mode 100644 index 0000000000000..4f53cbc18ad7b --- /dev/null +++ b/test/hotspot/jtreg/runtime/interpreter/LastJsr.jasm @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +super public class LastJsr +{ + public static Method test:"()V" + stack 100 locals 100 + { + return; + LABEL: + nop; + jsr LABEL; // bci=2. Compute bci + length(jsr) -> bci = 5 accessed, out of bounds. + } +} diff --git a/test/hotspot/jtreg/runtime/interpreter/LastJsrReachable.jasm b/test/hotspot/jtreg/runtime/interpreter/LastJsrReachable.jasm new file mode 100644 index 0000000000000..5bcc3ffd382a8 --- /dev/null +++ b/test/hotspot/jtreg/runtime/interpreter/LastJsrReachable.jasm @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +super public class LastJsrReachable +{ + public static Method test:"()V" + stack 100 locals 100 + { + goto LB2; + LABEL: + return; + LB2: + nop; + jsr LABEL; + } +} diff --git a/test/hotspot/jtreg/runtime/interpreter/LastJsrTest.java b/test/hotspot/jtreg/runtime/interpreter/LastJsrTest.java new file mode 100644 index 0000000000000..913a304ae38a3 --- /dev/null +++ b/test/hotspot/jtreg/runtime/interpreter/LastJsrTest.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8335664 + * @summary Ensure a program that ends with a JSR does not crash + * @library /test/lib + * @compile LastJsr.jasm + * @compile LastJsrReachable.jasm + * @run main/othervm LastJsrTest + */ + +public class LastJsrTest { + public static void main(String[] args) { + LastJsr.test(); + LastJsrReachable.test(); + System.out.println("PASSED"); + } +} From 0b5c8870e5aa4fd0889d60faa9b1f65a9c338fff Mon Sep 17 00:00:00 2001 From: Matthew Donovan Date: Thu, 22 Aug 2024 17:58:08 +0000 Subject: [PATCH 05/72] 8338380: Update TLSCommon/interop/AbstractServer to specify an interface to listen for connections Reviewed-by: rhalade --- .../net/ssl/TLSCommon/interop/AbstractServer.java | 14 +++++++++++++- .../javax/net/ssl/TLSCommon/interop/JdkServer.java | 6 ++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/test/jdk/javax/net/ssl/TLSCommon/interop/AbstractServer.java b/test/jdk/javax/net/ssl/TLSCommon/interop/AbstractServer.java index a5f61ce869a0d..03b3aadb0074f 100644 --- a/test/jdk/javax/net/ssl/TLSCommon/interop/AbstractServer.java +++ b/test/jdk/javax/net/ssl/TLSCommon/interop/AbstractServer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,6 +22,8 @@ */ import java.io.IOException; +import java.net.InetAddress; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -52,11 +54,21 @@ public void signalStop() throws Exception { } public static abstract class Builder extends AbstractPeer.Builder { + private InetAddress listenInterface = InetAddress.getLoopbackAddress(); private int port; // Indicates if requires client authentication. private boolean clientAuth = true; + public InetAddress getListenInterface() { + return listenInterface; + } + + public Builder setListenInterface(InetAddress listenInterface) { + this.listenInterface = listenInterface; + return this; + } + public int getPort() { return port; } diff --git a/test/jdk/javax/net/ssl/TLSCommon/interop/JdkServer.java b/test/jdk/javax/net/ssl/TLSCommon/interop/JdkServer.java index 6519e89febb9e..1521325b65ac8 100644 --- a/test/jdk/javax/net/ssl/TLSCommon/interop/JdkServer.java +++ b/test/jdk/javax/net/ssl/TLSCommon/interop/JdkServer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,6 +22,7 @@ */ import java.io.IOException; +import java.net.InetAddress; import java.net.SocketException; import java.util.ArrayList; import java.util.List; @@ -53,7 +54,8 @@ public JdkServer(Builder builder) throws Exception { context = Utilities.createSSLContext(builder.getCertTuple()); SSLServerSocketFactory serverFactory = context.getServerSocketFactory(); serverSocket - = (SSLServerSocket) serverFactory.createServerSocket(builder.getPort()); + = (SSLServerSocket) serverFactory.createServerSocket(builder.getPort(), + 0, builder.getListenInterface()); configServerSocket(builder); } From 813546f9236d278c380888f1f90cd49b23792d92 Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Thu, 22 Aug 2024 19:48:25 +0000 Subject: [PATCH 06/72] 8338856: [BACKOUT] JDK-8337828: CDS: Trim down minimum GC region alignment Reviewed-by: dcubed --- src/hotspot/share/cds/archiveHeapWriter.hpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/hotspot/share/cds/archiveHeapWriter.hpp b/src/hotspot/share/cds/archiveHeapWriter.hpp index b337b75402fd6..352aeb9a08f7c 100644 --- a/src/hotspot/share/cds/archiveHeapWriter.hpp +++ b/src/hotspot/share/cds/archiveHeapWriter.hpp @@ -112,10 +112,11 @@ class ArchiveHeapWriter : AllStatic { public: static const intptr_t NOCOOPS_REQUESTED_BASE = 0x10000000; - // The minimum region size of all collectors that are supported by CDS. - // G1 heap region size can never be smaller than 1M. - // Shenandoah heap region size can never be smaller than 256K. - static constexpr int MIN_GC_REGION_ALIGNMENT = 256 * K; + // The minimum region size of all collectors that are supported by CDS in + // ArchiveHeapLoader::can_map() mode. Currently only G1 is supported. G1's region size + // depends on -Xmx, but can never be smaller than 1 * M. + // (TODO: Perhaps change to 256K to be compatible with Shenandoah) + static constexpr int MIN_GC_REGION_ALIGNMENT = 1 * M; private: class EmbeddedOopRelocator; From c89a1c35bda9002ee687b3fa267f3ef9cba78b00 Mon Sep 17 00:00:00 2001 From: Brian Burkhalter Date: Thu, 22 Aug 2024 21:41:25 +0000 Subject: [PATCH 07/72] 8338696: (fs) BasicFileAttributes.creationTime() falls back to epoch if birth time is unavailable (Linux) Reviewed-by: sgehwolf, alanb --- .../native/libnio/fs/UnixNativeDispatcher.c | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/java.base/unix/native/libnio/fs/UnixNativeDispatcher.c b/src/java.base/unix/native/libnio/fs/UnixNativeDispatcher.c index 13fe39c6551b9..b91ab6f0cab92 100644 --- a/src/java.base/unix/native/libnio/fs/UnixNativeDispatcher.c +++ b/src/java.base/unix/native/libnio/fs/UnixNativeDispatcher.c @@ -133,9 +133,10 @@ struct my_statx #define STATX_BTIME 0x00000800U #endif -#ifndef STATX_ALL -#define STATX_ALL (STATX_BTIME | STATX_BASIC_STATS) -#endif +// +// STATX_ALL is deprecated; use a different name to avoid confusion. +// +#define LOCAL_STATX_ALL (STATX_BASIC_STATS | STATX_BTIME) #ifndef AT_FDCWD #define AT_FDCWD -100 @@ -619,8 +620,19 @@ static void copy_statx_attributes(JNIEnv* env, struct my_statx* buf, jobject att (*env)->SetLongField(env, attrs, attrs_st_atime_sec, (jlong)buf->stx_atime.tv_sec); (*env)->SetLongField(env, attrs, attrs_st_mtime_sec, (jlong)buf->stx_mtime.tv_sec); (*env)->SetLongField(env, attrs, attrs_st_ctime_sec, (jlong)buf->stx_ctime.tv_sec); - (*env)->SetLongField(env, attrs, attrs_st_birthtime_sec, (jlong)buf->stx_btime.tv_sec); - (*env)->SetLongField(env, attrs, attrs_st_birthtime_nsec, (jlong)buf->stx_btime.tv_nsec); + if ((buf->stx_mask & STATX_BTIME) != 0) { + // Birth time was filled in so use it + (*env)->SetLongField(env, attrs, attrs_st_birthtime_sec, + (jlong)buf->stx_btime.tv_sec); + (*env)->SetLongField(env, attrs, attrs_st_birthtime_nsec, + (jlong)buf->stx_btime.tv_nsec); + } else { + // Birth time was not filled in: fall back to last modification time + (*env)->SetLongField(env, attrs, attrs_st_birthtime_sec, + (jlong)buf->stx_mtime.tv_sec); + (*env)->SetLongField(env, attrs, attrs_st_birthtime_nsec, + (jlong)buf->stx_mtime.tv_nsec); + } (*env)->SetLongField(env, attrs, attrs_st_atime_nsec, (jlong)buf->stx_atime.tv_nsec); (*env)->SetLongField(env, attrs, attrs_st_mtime_nsec, (jlong)buf->stx_mtime.tv_nsec); (*env)->SetLongField(env, attrs, attrs_st_ctime_nsec, (jlong)buf->stx_ctime.tv_nsec); @@ -674,7 +686,7 @@ Java_sun_nio_fs_UnixNativeDispatcher_stat0(JNIEnv* env, jclass this, #if defined(__linux__) struct my_statx statx_buf; int flags = AT_STATX_SYNC_AS_STAT; - unsigned int mask = STATX_ALL; + unsigned int mask = LOCAL_STATX_ALL; if (my_statx_func != NULL) { // Prefer statx over stat on Linux if it's available @@ -706,7 +718,7 @@ Java_sun_nio_fs_UnixNativeDispatcher_lstat0(JNIEnv* env, jclass this, #if defined(__linux__) struct my_statx statx_buf; int flags = AT_STATX_SYNC_AS_STAT | AT_SYMLINK_NOFOLLOW; - unsigned int mask = STATX_ALL; + unsigned int mask = LOCAL_STATX_ALL; if (my_statx_func != NULL) { // Prefer statx over stat on Linux if it's available @@ -737,7 +749,7 @@ Java_sun_nio_fs_UnixNativeDispatcher_fstat0(JNIEnv* env, jclass this, jint fd, #if defined(__linux__) struct my_statx statx_buf; int flags = AT_EMPTY_PATH | AT_STATX_SYNC_AS_STAT; - unsigned int mask = STATX_ALL; + unsigned int mask = LOCAL_STATX_ALL; if (my_statx_func != NULL) { // statx supports FD use via dirfd iff pathname is an empty string and the @@ -770,7 +782,7 @@ Java_sun_nio_fs_UnixNativeDispatcher_fstatat0(JNIEnv* env, jclass this, jint dfd #if defined(__linux__) struct my_statx statx_buf; int flags = AT_STATX_SYNC_AS_STAT; - unsigned int mask = STATX_ALL; + unsigned int mask = LOCAL_STATX_ALL; if (my_statx_func != NULL) { // Prefer statx over stat on Linux if it's available From ea3370982bfd3da4b200b738dd3b8c16cebb3a34 Mon Sep 17 00:00:00 2001 From: David Holmes Date: Fri, 23 Aug 2024 02:35:48 +0000 Subject: [PATCH 08/72] 8328880: Events::log_exception should limit the size of the logging message Reviewed-by: shade, kvn --- src/hotspot/share/utilities/events.cpp | 11 ++++++++--- src/hotspot/share/utilities/events.hpp | 12 ++++++++---- src/hotspot/share/utilities/exceptions.cpp | 2 +- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/hotspot/share/utilities/events.cpp b/src/hotspot/share/utilities/events.cpp index b4d46d79ffa69..1185e349b53a5 100644 --- a/src/hotspot/share/utilities/events.cpp +++ b/src/hotspot/share/utilities/events.cpp @@ -151,7 +151,9 @@ void UnloadingEventLog::log(Thread* thread, InstanceKlass* ik) { ik->name()->print_value_on(&st); } -void ExceptionsEventLog::log(Thread* thread, Handle h_exception, const char* message, const char* file, int line) { +void ExceptionsEventLog::log(Thread* thread, Handle h_exception, + const char* message, const char* file, int line, + int message_length_limit) { if (!should_log()) return; double timestamp = fetch_timestamp(); @@ -163,8 +165,11 @@ void ExceptionsEventLog::log(Thread* thread, Handle h_exception, const char* mes _records[index].data.size()); st.print("Exception <"); h_exception->print_value_on(&st); - st.print("%s%s> (" PTR_FORMAT ") \n" + if (message != nullptr) { + int len = message_length_limit > 0 ? message_length_limit : (int)strlen(message); + st.print(": %.*s", len, message); + } + st.print("> (" PTR_FORMAT ") \n" "thrown [%s, line %d]", - message ? ": " : "", message ? message : "", p2i(h_exception()), file, line); } diff --git a/src/hotspot/share/utilities/events.hpp b/src/hotspot/share/utilities/events.hpp index 4470002a1e367..cbbed7232fb1e 100644 --- a/src/hotspot/share/utilities/events.hpp +++ b/src/hotspot/share/utilities/events.hpp @@ -207,7 +207,9 @@ class ExceptionsEventLog : public ExtendedStringEventLog { ExceptionsEventLog(const char* name, const char* short_name, int count = LogEventsBufferEntries) : ExtendedStringEventLog(name, short_name, count) {} - void log(Thread* thread, Handle h_exception, const char* message, const char* file, int line); + // Message length limit of zero means no limit. + void log(Thread* thread, Handle h_exception, const char* message, + const char* file, int line, int message_length_limit = 0); }; @@ -275,7 +277,7 @@ class Events : AllStatic { // Log exception related message static void log_exception(Thread* thread, const char* format, ...) ATTRIBUTE_PRINTF(2, 3); - static void log_exception(Thread* thread, Handle h_exception, const char* message, const char* file, int line); + static void log_exception(Thread* thread, Handle h_exception, const char* message, const char* file, int line, int message_length_limit = 0); static void log_redefinition(Thread* thread, const char* format, ...) ATTRIBUTE_PRINTF(2, 3); @@ -345,9 +347,11 @@ inline void Events::log_exception(Thread* thread, const char* format, ...) { } } -inline void Events::log_exception(Thread* thread, Handle h_exception, const char* message, const char* file, int line) { +inline void Events::log_exception(Thread* thread, Handle h_exception, + const char* message, const char* file, + int line, int message_length_limit) { if (LogEvents && _exceptions != nullptr) { - _exceptions->log(thread, h_exception, message, file, line); + _exceptions->log(thread, h_exception, message, file, line, message_length_limit); } } diff --git a/src/hotspot/share/utilities/exceptions.cpp b/src/hotspot/share/utilities/exceptions.cpp index 034444839ab50..f730b37b8fffa 100644 --- a/src/hotspot/share/utilities/exceptions.cpp +++ b/src/hotspot/share/utilities/exceptions.cpp @@ -183,7 +183,7 @@ void Exceptions::_throw(JavaThread* thread, const char* file, int line, Handle h thread->set_pending_exception(h_exception(), file, line); // vm log - Events::log_exception(thread, h_exception, message, file, line); + Events::log_exception(thread, h_exception, message, file, line, MAX_LEN); } From e06652ad3c02dfe54104eaa04eaa3d117699b27f Mon Sep 17 00:00:00 2001 From: Axel Boldt-Christmas Date: Fri, 23 Aug 2024 05:47:29 +0000 Subject: [PATCH 09/72] 8338810: PPC, s390x: LightweightSynchronizer::exit asserts, missing lock Reviewed-by: mdoerr, amitkumar --- src/hotspot/cpu/ppc/macroAssembler_ppc.cpp | 6 +++++- src/hotspot/cpu/s390/macroAssembler_s390.cpp | 8 +++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp b/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp index 3b48b4020ccb3..9fb4a43097dd5 100644 --- a/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp +++ b/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp @@ -2902,7 +2902,11 @@ void MacroAssembler::compiler_fast_unlock_lightweight_object(ConditionRegister f // Check for monitor (0b10). ld(mark, oopDesc::mark_offset_in_bytes(), obj); andi_(t, mark, markWord::monitor_value); - bne(CCR0, inflated); + if (!UseObjectMonitorTable) { + bne(CCR0, inflated); + } else { + bne(CCR0, push_and_slow); + } #ifdef ASSERT // Check header not unlocked (0b01). diff --git a/src/hotspot/cpu/s390/macroAssembler_s390.cpp b/src/hotspot/cpu/s390/macroAssembler_s390.cpp index b31d08f9fde10..d527b4d2aea11 100644 --- a/src/hotspot/cpu/s390/macroAssembler_s390.cpp +++ b/src/hotspot/cpu/s390/macroAssembler_s390.cpp @@ -6286,6 +6286,7 @@ void MacroAssembler::compiler_fast_unlock_lightweight_object(Register obj, Regis BLOCK_COMMENT("compiler_fast_lightweight_unlock {"); { // Lightweight Unlock + NearLabel push_and_slow_path; // Check if obj is top of lock-stack. z_lgf(top, Address(Z_thread, ls_top_offset)); @@ -6315,7 +6316,11 @@ void MacroAssembler::compiler_fast_unlock_lightweight_object(Register obj, Regis // Check for monitor (0b10). z_lg(mark, Address(obj, mark_offset)); z_tmll(mark, markWord::monitor_value); - z_brnaz(inflated); + if (!UseObjectMonitorTable) { + z_brnaz(inflated); + } else { + z_brnaz(push_and_slow_path); + } #ifdef ASSERT // Check header not unlocked (0b01). @@ -6334,6 +6339,7 @@ void MacroAssembler::compiler_fast_unlock_lightweight_object(Register obj, Regis branch_optimized(Assembler::bcondEqual, unlocked); } + bind(push_and_slow_path); // Restore lock-stack and handle the unlock in runtime. z_lgf(top, Address(Z_thread, ls_top_offset)); DEBUG_ONLY(z_stg(obj, Address(Z_thread, top));) From 8e0d0190ed19bc1a9d4ec0c6ee3aa6454542989f Mon Sep 17 00:00:00 2001 From: SendaoYan Date: Fri, 23 Aug 2024 06:26:24 +0000 Subject: [PATCH 10/72] 8338630: Test java/nio/channels/DatagramChannel/SendReceiveMaxSize.java timeout Reviewed-by: dfuchs, jpai, djelinski --- test/lib/jdk/test/lib/NetworkConfiguration.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/test/lib/jdk/test/lib/NetworkConfiguration.java b/test/lib/jdk/test/lib/NetworkConfiguration.java index 8ea10ede6a4a2..3532bb1a3ee20 100644 --- a/test/lib/jdk/test/lib/NetworkConfiguration.java +++ b/test/lib/jdk/test/lib/NetworkConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -127,8 +127,8 @@ public static boolean isSameInterface(NetworkInterface ni1, NetworkInterface ni2 public static boolean isTestable(NetworkInterface nif) { if (Platform.isOSX()) { - if (nif.getName().contains("awdl")) { - return false; // exclude awdl + if (nif.getName().contains("awdl") || nif.getName().contains("docker")) { + return false; // exclude awdl or docker } // filter out interfaces that only have link-local IPv6 addresses // on macOS interfaces like 'en6' fall in this category and @@ -145,6 +145,13 @@ public static boolean isTestable(NetworkInterface nif) { return false; } } + + if (Platform.isLinux()) { + String dName = nif.getDisplayName(); + if (dName != null && dName.contains("docker")) { + return false; + } + } return true; } From 9cbf685b0b1ade5e6ddebfeec225b2efb5cf4cfc Mon Sep 17 00:00:00 2001 From: Stefan Karlsson Date: Fri, 23 Aug 2024 07:09:40 +0000 Subject: [PATCH 11/72] 8337658: ZGC: Move soft reference handling out of the driver loop function Reviewed-by: gli, aboldtch, eosterlund --- src/hotspot/share/gc/z/zDriver.cpp | 43 +++++++++++-------- src/hotspot/share/gc/z/zDriver.hpp | 2 +- src/hotspot/share/gc/z/zGeneration.cpp | 4 ++ src/hotspot/share/gc/z/zGeneration.hpp | 1 + src/hotspot/share/gc/z/zHeap.inline.hpp | 4 +- src/hotspot/share/gc/z/zPageAllocator.cpp | 4 +- .../share/gc/z/zReferenceProcessor.cpp | 14 +++--- .../share/gc/z/zReferenceProcessor.hpp | 6 ++- 8 files changed, 47 insertions(+), 31 deletions(-) diff --git a/src/hotspot/share/gc/z/zDriver.cpp b/src/hotspot/share/gc/z/zDriver.cpp index 3836f330142f1..107be979c2584 100644 --- a/src/hotspot/share/gc/z/zDriver.cpp +++ b/src/hotspot/share/gc/z/zDriver.cpp @@ -230,8 +230,8 @@ void ZDriverMinor::terminate() { _port.send_async(request); } -static bool should_clear_soft_references(GCCause::Cause cause) { - // Clear soft references if implied by the GC cause +static bool should_clear_all_soft_references(GCCause::Cause cause) { + // Clear all soft references if implied by the GC cause switch (cause) { case GCCause::_wb_full_gc: case GCCause::_metadata_GC_clear_soft_refs: @@ -259,12 +259,12 @@ static bool should_clear_soft_references(GCCause::Cause cause) { break; } - // Clear soft references if threads are stalled waiting for an old collection + // Clear all soft references if threads are stalled waiting for an old collection if (ZHeap::heap()->is_alloc_stalling_for_old()) { return true; } - // Don't clear + // Don't clear all soft references return false; } @@ -302,13 +302,17 @@ static bool should_preclean_young(GCCause::Cause cause) { return true; } - // It is important that when soft references are cleared, we also pre-clean the young - // generation, as we might otherwise throw premature OOM. Therefore, all causes that - // trigger soft ref cleaning must also trigger pre-cleaning of young gen. If allocations - // stalled when checking for soft ref cleaning, then since we hold the driver locker all - // the way until we check for young gen pre-cleaning, we can be certain that we should + // We clear all soft references as a last-ditch effort to collect memory + // before throwing an OOM. Therefore it is important that when the GC policy + // is to clear all soft references, that we also pre-clean the young + // generation, as we might otherwise throw premature OOM. + // + // Therefore, all causes that trigger all soft ref clearing must also trigger + // pre-cleaning of young gen. If allocations stalled when checking for all + // soft ref clearing, then since we hold the driver locker all the way until + // we check for young gen pre-cleaning, we can be certain that we should // catch that above and perform young gen pre-cleaning. - assert(!should_clear_soft_references(cause), "Clearing soft references without pre-cleaning young gen"); + assert(!should_clear_all_soft_references(cause), "Clearing all soft references without pre-cleaning young gen"); return false; } @@ -395,6 +399,10 @@ class ZDriverScopeMajor : public StackObj { // Select number of worker threads to use ZGeneration::young()->set_active_workers(request.young_nworkers()); ZGeneration::old()->set_active_workers(request.old_nworkers()); + + // Set up soft reference policy + const bool clear_all = should_clear_all_soft_references(request.cause()); + ZGeneration::old()->set_soft_reference_policy(clear_all); } ~ZDriverScopeMajor() { @@ -444,12 +452,13 @@ void ZDriverMajor::gc(const ZDriverRequest& request) { collect_old(); } -static void handle_alloc_stalling_for_old(bool cleared_soft_refs) { - ZHeap::heap()->handle_alloc_stalling_for_old(cleared_soft_refs); +static void handle_alloc_stalling_for_old() { + const bool cleared_all = ZGeneration::old()->uses_clear_all_soft_reference_policy(); + ZHeap::heap()->handle_alloc_stalling_for_old(cleared_all); } -void ZDriverMajor::handle_alloc_stalls(bool cleared_soft_refs) const { - handle_alloc_stalling_for_old(cleared_soft_refs); +void ZDriverMajor::handle_alloc_stalls() const { + handle_alloc_stalling_for_old(); } void ZDriverMajor::run_thread() { @@ -464,10 +473,6 @@ void ZDriverMajor::run_thread() { abortpoint(); - // Set up soft reference policy - const bool clear_soft_refs = should_clear_soft_references(request.cause()); - ZGeneration::old()->set_soft_reference_policy(clear_soft_refs); - // Run GC gc(request); @@ -477,7 +482,7 @@ void ZDriverMajor::run_thread() { _port.ack(); // Handle allocation stalls - handle_alloc_stalls(clear_soft_refs); + handle_alloc_stalls(); ZBreakpoint::at_after_gc(); } diff --git a/src/hotspot/share/gc/z/zDriver.hpp b/src/hotspot/share/gc/z/zDriver.hpp index 6291a8f359ce2..5f1fe08a0b6ae 100644 --- a/src/hotspot/share/gc/z/zDriver.hpp +++ b/src/hotspot/share/gc/z/zDriver.hpp @@ -112,7 +112,7 @@ class ZDriverMajor : public ZDriver { void collect_old(); void gc(const ZDriverRequest& request); - void handle_alloc_stalls(bool cleared_soft_refs) const; + void handle_alloc_stalls() const; protected: virtual void run_thread(); diff --git a/src/hotspot/share/gc/z/zGeneration.cpp b/src/hotspot/share/gc/z/zGeneration.cpp index 8a7d38e299166..655f47ba49cd8 100644 --- a/src/hotspot/share/gc/z/zGeneration.cpp +++ b/src/hotspot/share/gc/z/zGeneration.cpp @@ -1286,6 +1286,10 @@ void ZGenerationOld::set_soft_reference_policy(bool clear) { _reference_processor.set_soft_reference_policy(clear); } +bool ZGenerationOld::uses_clear_all_soft_reference_policy() const { + return _reference_processor.uses_clear_all_soft_reference_policy(); +} + class ZRendezvousHandshakeClosure : public HandshakeClosure { public: ZRendezvousHandshakeClosure() diff --git a/src/hotspot/share/gc/z/zGeneration.hpp b/src/hotspot/share/gc/z/zGeneration.hpp index aec48a0a07236..3e18919eee43a 100644 --- a/src/hotspot/share/gc/z/zGeneration.hpp +++ b/src/hotspot/share/gc/z/zGeneration.hpp @@ -309,6 +309,7 @@ class ZGenerationOld : public ZGeneration { // Reference processing ReferenceDiscoverer* reference_discoverer(); void set_soft_reference_policy(bool clear); + bool uses_clear_all_soft_reference_policy() const; uint total_collections_at_start() const; diff --git a/src/hotspot/share/gc/z/zHeap.inline.hpp b/src/hotspot/share/gc/z/zHeap.inline.hpp index d983c002afaa8..127212aef0b3a 100644 --- a/src/hotspot/share/gc/z/zHeap.inline.hpp +++ b/src/hotspot/share/gc/z/zHeap.inline.hpp @@ -86,8 +86,8 @@ inline void ZHeap::handle_alloc_stalling_for_young() { _page_allocator.handle_alloc_stalling_for_young(); } -inline void ZHeap::handle_alloc_stalling_for_old(bool cleared_soft_refs) { - _page_allocator.handle_alloc_stalling_for_old(cleared_soft_refs); +inline void ZHeap::handle_alloc_stalling_for_old(bool cleared_all_soft_refs) { + _page_allocator.handle_alloc_stalling_for_old(cleared_all_soft_refs); } inline bool ZHeap::is_oop(uintptr_t addr) const { diff --git a/src/hotspot/share/gc/z/zPageAllocator.cpp b/src/hotspot/share/gc/z/zPageAllocator.cpp index 519c36e556d8f..b1fb1e4837391 100644 --- a/src/hotspot/share/gc/z/zPageAllocator.cpp +++ b/src/hotspot/share/gc/z/zPageAllocator.cpp @@ -985,9 +985,9 @@ void ZPageAllocator::handle_alloc_stalling_for_young() { restart_gc(); } -void ZPageAllocator::handle_alloc_stalling_for_old(bool cleared_soft_refs) { +void ZPageAllocator::handle_alloc_stalling_for_old(bool cleared_all_soft_refs) { ZLocker locker(&_lock); - if (cleared_soft_refs) { + if (cleared_all_soft_refs) { notify_out_of_memory(); } restart_gc(); diff --git a/src/hotspot/share/gc/z/zReferenceProcessor.cpp b/src/hotspot/share/gc/z/zReferenceProcessor.cpp index 1252de2ac2723..797bd96353687 100644 --- a/src/hotspot/share/gc/z/zReferenceProcessor.cpp +++ b/src/hotspot/share/gc/z/zReferenceProcessor.cpp @@ -113,7 +113,7 @@ static void list_append(zaddress& head, zaddress& tail, zaddress reference) { ZReferenceProcessor::ZReferenceProcessor(ZWorkers* workers) : _workers(workers), _soft_reference_policy(nullptr), - _clear_all_soft_refs(false), + _uses_clear_all_soft_reference_policy(false), _encountered_count(), _discovered_count(), _enqueued_count(), @@ -121,13 +121,13 @@ ZReferenceProcessor::ZReferenceProcessor(ZWorkers* workers) _pending_list(zaddress::null), _pending_list_tail(zaddress::null) {} -void ZReferenceProcessor::set_soft_reference_policy(bool clear) { +void ZReferenceProcessor::set_soft_reference_policy(bool clear_all_soft_references) { static AlwaysClearPolicy always_clear_policy; static LRUMaxHeapPolicy lru_max_heap_policy; - _clear_all_soft_refs = clear; + _uses_clear_all_soft_reference_policy = clear_all_soft_references; - if (clear) { + if (clear_all_soft_references) { _soft_reference_policy = &always_clear_policy; } else { _soft_reference_policy = &lru_max_heap_policy; @@ -136,6 +136,10 @@ void ZReferenceProcessor::set_soft_reference_policy(bool clear) { _soft_reference_policy->setup(); } +bool ZReferenceProcessor::uses_clear_all_soft_reference_policy() const { + return _uses_clear_all_soft_reference_policy; +} + bool ZReferenceProcessor::is_inactive(zaddress reference, oop referent, ReferenceType type) const { if (type == REF_FINAL) { // A FinalReference is inactive if its next field is non-null. An application can't @@ -440,7 +444,7 @@ class ZReferenceProcessorTask : public ZTask { void ZReferenceProcessor::process_references() { ZStatTimerOld timer(ZSubPhaseConcurrentReferencesProcess); - if (_clear_all_soft_refs) { + if (_uses_clear_all_soft_reference_policy) { log_info(gc, ref)("Clearing All SoftReferences"); } diff --git a/src/hotspot/share/gc/z/zReferenceProcessor.hpp b/src/hotspot/share/gc/z/zReferenceProcessor.hpp index 7a8900827da83..f8e924ed99fc1 100644 --- a/src/hotspot/share/gc/z/zReferenceProcessor.hpp +++ b/src/hotspot/share/gc/z/zReferenceProcessor.hpp @@ -41,7 +41,7 @@ class ZReferenceProcessor : public ReferenceDiscoverer { ZWorkers* const _workers; ReferencePolicy* _soft_reference_policy; - bool _clear_all_soft_refs; + bool _uses_clear_all_soft_reference_policy; ZPerWorker _encountered_count; ZPerWorker _discovered_count; ZPerWorker _enqueued_count; @@ -69,7 +69,9 @@ class ZReferenceProcessor : public ReferenceDiscoverer { public: ZReferenceProcessor(ZWorkers* workers); - void set_soft_reference_policy(bool clear); + void set_soft_reference_policy(bool clear_all_soft_references); + bool uses_clear_all_soft_reference_policy() const; + void reset_statistics(); virtual bool discover_reference(oop reference, ReferenceType type); From a5e28005fa95426f811e1ed98a7d726cbdbe196d Mon Sep 17 00:00:00 2001 From: Pavel Rappo Date: Fri, 23 Aug 2024 08:05:16 +0000 Subject: [PATCH 12/72] 8338834: Remove unused import declarations in java.compiler Reviewed-by: darcy --- .../share/classes/javax/annotation/processing/Filer.java | 4 +--- .../share/classes/javax/lang/model/SourceVersion.java | 4 ---- .../share/classes/javax/lang/model/element/Element.java | 4 +--- .../share/classes/javax/lang/model/element/TypeElement.java | 4 +--- .../classes/javax/lang/model/element/VariableElement.java | 4 +--- .../share/classes/javax/lang/model/type/TypeVariable.java | 4 +--- .../share/classes/javax/lang/model/type/TypeVisitor.java | 3 +-- .../model/util/AbstractAnnotationValueVisitorPreview.java | 1 - .../javax/lang/model/util/AbstractElementVisitorPreview.java | 3 --- .../classes/javax/lang/model/util/AbstractTypeVisitor9.java | 1 - .../javax/lang/model/util/AbstractTypeVisitorPreview.java | 1 - .../classes/javax/lang/model/util/ElementKindVisitor6.java | 3 +-- .../classes/javax/lang/model/util/ElementKindVisitor9.java | 1 - .../javax/lang/model/util/ElementKindVisitorPreview.java | 1 - .../share/classes/javax/lang/model/util/ElementScanner14.java | 1 - .../share/classes/javax/lang/model/util/ElementScanner6.java | 3 +-- .../share/classes/javax/lang/model/util/ElementScanner7.java | 1 - .../share/classes/javax/lang/model/util/ElementScanner9.java | 1 - .../classes/javax/lang/model/util/ElementScannerPreview.java | 4 ---- .../lang/model/util/SimpleAnnotationValueVisitorPreview.java | 1 - .../classes/javax/lang/model/util/SimpleElementVisitor6.java | 3 +-- .../classes/javax/lang/model/util/SimpleElementVisitor7.java | 1 - .../javax/lang/model/util/SimpleElementVisitorPreview.java | 3 --- .../classes/javax/lang/model/util/SimpleTypeVisitor6.java | 3 +-- .../classes/javax/lang/model/util/SimpleTypeVisitor7.java | 1 - .../classes/javax/lang/model/util/SimpleTypeVisitor9.java | 1 - .../javax/lang/model/util/SimpleTypeVisitorPreview.java | 1 - .../classes/javax/lang/model/util/TypeKindVisitorPreview.java | 1 - .../share/classes/javax/lang/model/util/Types.java | 3 --- 29 files changed, 10 insertions(+), 56 deletions(-) diff --git a/src/java.compiler/share/classes/javax/annotation/processing/Filer.java b/src/java.compiler/share/classes/javax/annotation/processing/Filer.java index 61af2aae0c7a5..ba9b7ed60ecab 100644 --- a/src/java.compiler/share/classes/javax/annotation/processing/Filer.java +++ b/src/java.compiler/share/classes/javax/annotation/processing/Filer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,10 +25,8 @@ package javax.annotation.processing; -import javax.tools.JavaFileManager; import javax.tools.*; import javax.lang.model.element.Element; -import javax.lang.model.element.TypeElement; import javax.lang.model.util.Elements; import java.io.IOException; diff --git a/src/java.compiler/share/classes/javax/lang/model/SourceVersion.java b/src/java.compiler/share/classes/javax/lang/model/SourceVersion.java index fd3cb1cb0f8ea..4e32f0cdcd88e 100644 --- a/src/java.compiler/share/classes/javax/lang/model/SourceVersion.java +++ b/src/java.compiler/share/classes/javax/lang/model/SourceVersion.java @@ -25,10 +25,6 @@ package javax.lang.model; -import java.util.Collections; -import java.util.Set; -import java.util.HashSet; - /** * Source versions of the Java programming language. * diff --git a/src/java.compiler/share/classes/javax/lang/model/element/Element.java b/src/java.compiler/share/classes/javax/lang/model/element/Element.java index 59866114325ae..129dad29020ed 100644 --- a/src/java.compiler/share/classes/javax/lang/model/element/Element.java +++ b/src/java.compiler/share/classes/javax/lang/model/element/Element.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,8 +27,6 @@ import java.lang.annotation.Annotation; -import java.lang.annotation.AnnotationTypeMismatchException; -import java.lang.annotation.IncompleteAnnotationException; import java.util.List; import java.util.Set; diff --git a/src/java.compiler/share/classes/javax/lang/model/element/TypeElement.java b/src/java.compiler/share/classes/javax/lang/model/element/TypeElement.java index a8c3b91f8b504..3f34b1799929d 100644 --- a/src/java.compiler/share/classes/javax/lang/model/element/TypeElement.java +++ b/src/java.compiler/share/classes/javax/lang/model/element/TypeElement.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,8 +25,6 @@ package javax.lang.model.element; -import jdk.internal.javac.PreviewFeature; - import java.util.List; import javax.lang.model.type.*; import javax.lang.model.util.*; diff --git a/src/java.compiler/share/classes/javax/lang/model/element/VariableElement.java b/src/java.compiler/share/classes/javax/lang/model/element/VariableElement.java index 07e8c25b8f77f..4d2b2582901f8 100644 --- a/src/java.compiler/share/classes/javax/lang/model/element/VariableElement.java +++ b/src/java.compiler/share/classes/javax/lang/model/element/VariableElement.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,8 +25,6 @@ package javax.lang.model.element; -import jdk.internal.javac.PreviewFeature; - import javax.lang.model.util.Elements; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeKind; diff --git a/src/java.compiler/share/classes/javax/lang/model/type/TypeVariable.java b/src/java.compiler/share/classes/javax/lang/model/type/TypeVariable.java index e5ab0458f693a..abea88fb6f263 100644 --- a/src/java.compiler/share/classes/javax/lang/model/type/TypeVariable.java +++ b/src/java.compiler/share/classes/javax/lang/model/type/TypeVariable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,8 +28,6 @@ import javax.lang.model.element.Element; import javax.lang.model.element.TypeParameterElement; -import javax.lang.model.util.Types; - /** * Represents a type variable. diff --git a/src/java.compiler/share/classes/javax/lang/model/type/TypeVisitor.java b/src/java.compiler/share/classes/javax/lang/model/type/TypeVisitor.java index 31d75fe792940..f3f76803e8f43 100644 --- a/src/java.compiler/share/classes/javax/lang/model/type/TypeVisitor.java +++ b/src/java.compiler/share/classes/javax/lang/model/type/TypeVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,7 +25,6 @@ package javax.lang.model.type; -import javax.lang.model.element.*; import javax.lang.model.util.*; /** diff --git a/src/java.compiler/share/classes/javax/lang/model/util/AbstractAnnotationValueVisitorPreview.java b/src/java.compiler/share/classes/javax/lang/model/util/AbstractAnnotationValueVisitorPreview.java index fed666eb32f44..724ebe593790c 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/AbstractAnnotationValueVisitorPreview.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/AbstractAnnotationValueVisitorPreview.java @@ -28,7 +28,6 @@ import jdk.internal.javac.PreviewFeature; import static javax.lang.model.SourceVersion.*; -import javax.lang.model.SourceVersion; import javax.annotation.processing.SupportedSourceVersion; import javax.annotation.processing.ProcessingEnvironment; diff --git a/src/java.compiler/share/classes/javax/lang/model/util/AbstractElementVisitorPreview.java b/src/java.compiler/share/classes/javax/lang/model/util/AbstractElementVisitorPreview.java index 66a62b7fcb69c..7297e7c0c7b26 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/AbstractElementVisitorPreview.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/AbstractElementVisitorPreview.java @@ -29,9 +29,6 @@ import javax.annotation.processing.SupportedSourceVersion; import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.SourceVersion; -import javax.lang.model.element.ElementVisitor; -import javax.lang.model.element.RecordComponentElement; import static javax.lang.model.SourceVersion.*; /** diff --git a/src/java.compiler/share/classes/javax/lang/model/util/AbstractTypeVisitor9.java b/src/java.compiler/share/classes/javax/lang/model/util/AbstractTypeVisitor9.java index b5b9cfb23b0fc..f802c3bf1b99f 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/AbstractTypeVisitor9.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/AbstractTypeVisitor9.java @@ -26,7 +26,6 @@ package javax.lang.model.util; import javax.annotation.processing.SupportedSourceVersion; -import javax.lang.model.type.*; import javax.lang.model.SourceVersion; import static javax.lang.model.SourceVersion.*; diff --git a/src/java.compiler/share/classes/javax/lang/model/util/AbstractTypeVisitorPreview.java b/src/java.compiler/share/classes/javax/lang/model/util/AbstractTypeVisitorPreview.java index c066f1c2f6f4f..823bad0748acd 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/AbstractTypeVisitorPreview.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/AbstractTypeVisitorPreview.java @@ -29,7 +29,6 @@ import javax.annotation.processing.SupportedSourceVersion; import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.SourceVersion; import static javax.lang.model.SourceVersion.*; /** diff --git a/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitor6.java b/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitor6.java index 41ad8ffe218fb..764f4e327372c 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitor6.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitor6.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,7 +28,6 @@ import javax.lang.model.element.*; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; -import javax.lang.model.element.ElementVisitor; import static javax.lang.model.element.ElementKind.*; import static javax.lang.model.SourceVersion.*; diff --git a/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitor9.java b/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitor9.java index 4197f53940839..89dcb1f51f33b 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitor9.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitor9.java @@ -28,7 +28,6 @@ import javax.lang.model.element.*; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; -import javax.lang.model.element.ElementVisitor; import static javax.lang.model.SourceVersion.*; /** diff --git a/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitorPreview.java b/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitorPreview.java index 7c3a1166e317b..71d5f15fc85b9 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitorPreview.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitorPreview.java @@ -30,7 +30,6 @@ import javax.annotation.processing.SupportedSourceVersion; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.*; -import javax.lang.model.SourceVersion; import static javax.lang.model.SourceVersion.*; /** diff --git a/src/java.compiler/share/classes/javax/lang/model/util/ElementScanner14.java b/src/java.compiler/share/classes/javax/lang/model/util/ElementScanner14.java index 03a69516b514f..2f6fb0e03a0d7 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/ElementScanner14.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/ElementScanner14.java @@ -30,7 +30,6 @@ import javax.lang.model.element.*; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; -import javax.lang.model.element.ElementVisitor; import static javax.lang.model.SourceVersion.*; /** diff --git a/src/java.compiler/share/classes/javax/lang/model/util/ElementScanner6.java b/src/java.compiler/share/classes/javax/lang/model/util/ElementScanner6.java index c5bde23b47e1f..bff95313508dd 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/ElementScanner6.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/ElementScanner6.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,7 +27,6 @@ import javax.lang.model.element.*; import javax.annotation.processing.SupportedSourceVersion; -import javax.lang.model.element.ElementVisitor; import javax.lang.model.SourceVersion; import static javax.lang.model.SourceVersion.*; diff --git a/src/java.compiler/share/classes/javax/lang/model/util/ElementScanner7.java b/src/java.compiler/share/classes/javax/lang/model/util/ElementScanner7.java index 51c78c60067b5..be7fa9908b6bb 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/ElementScanner7.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/ElementScanner7.java @@ -28,7 +28,6 @@ import javax.lang.model.element.*; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; -import javax.lang.model.element.ElementVisitor; import static javax.lang.model.SourceVersion.*; diff --git a/src/java.compiler/share/classes/javax/lang/model/util/ElementScanner9.java b/src/java.compiler/share/classes/javax/lang/model/util/ElementScanner9.java index a9a66d6c18566..d572181b32c73 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/ElementScanner9.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/ElementScanner9.java @@ -28,7 +28,6 @@ import javax.lang.model.element.*; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; -import javax.lang.model.element.ElementVisitor; import static javax.lang.model.SourceVersion.*; diff --git a/src/java.compiler/share/classes/javax/lang/model/util/ElementScannerPreview.java b/src/java.compiler/share/classes/javax/lang/model/util/ElementScannerPreview.java index 6e21554132a7b..85c30afcf2bc2 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/ElementScannerPreview.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/ElementScannerPreview.java @@ -27,13 +27,9 @@ import jdk.internal.javac.PreviewFeature; -import java.util.List; -import java.util.ArrayList; import javax.annotation.processing.SupportedSourceVersion; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.*; -import javax.lang.model.SourceVersion; -import javax.lang.model.element.ElementVisitor; import static javax.lang.model.SourceVersion.*; /** diff --git a/src/java.compiler/share/classes/javax/lang/model/util/SimpleAnnotationValueVisitorPreview.java b/src/java.compiler/share/classes/javax/lang/model/util/SimpleAnnotationValueVisitorPreview.java index 48182072416c6..c0444c91060a2 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/SimpleAnnotationValueVisitorPreview.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/SimpleAnnotationValueVisitorPreview.java @@ -29,7 +29,6 @@ import javax.annotation.processing.SupportedSourceVersion; import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.SourceVersion; import static javax.lang.model.SourceVersion.*; /** diff --git a/src/java.compiler/share/classes/javax/lang/model/util/SimpleElementVisitor6.java b/src/java.compiler/share/classes/javax/lang/model/util/SimpleElementVisitor6.java index 2e8e27eeff00a..c28b13d4992cb 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/SimpleElementVisitor6.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/SimpleElementVisitor6.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,7 +28,6 @@ import javax.lang.model.element.*; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; -import javax.lang.model.element.ElementVisitor; import static javax.lang.model.SourceVersion.*; diff --git a/src/java.compiler/share/classes/javax/lang/model/util/SimpleElementVisitor7.java b/src/java.compiler/share/classes/javax/lang/model/util/SimpleElementVisitor7.java index fcd26dda60d00..3c71f97b67543 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/SimpleElementVisitor7.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/SimpleElementVisitor7.java @@ -28,7 +28,6 @@ import javax.lang.model.element.*; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; -import javax.lang.model.element.ElementVisitor; import static javax.lang.model.SourceVersion.*; /** diff --git a/src/java.compiler/share/classes/javax/lang/model/util/SimpleElementVisitorPreview.java b/src/java.compiler/share/classes/javax/lang/model/util/SimpleElementVisitorPreview.java index 6acc8f2da48a3..79da686ee0869 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/SimpleElementVisitorPreview.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/SimpleElementVisitorPreview.java @@ -29,9 +29,6 @@ import javax.annotation.processing.SupportedSourceVersion; import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.SourceVersion; -import javax.lang.model.element.ElementVisitor; -import javax.lang.model.element.RecordComponentElement; import static javax.lang.model.SourceVersion.*; /** diff --git a/src/java.compiler/share/classes/javax/lang/model/util/SimpleTypeVisitor6.java b/src/java.compiler/share/classes/javax/lang/model/util/SimpleTypeVisitor6.java index ce6321d9fd3fa..9386c02731600 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/SimpleTypeVisitor6.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/SimpleTypeVisitor6.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,7 +29,6 @@ import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; import static javax.lang.model.SourceVersion.*; -import javax.lang.model.type.TypeVisitor; /** diff --git a/src/java.compiler/share/classes/javax/lang/model/util/SimpleTypeVisitor7.java b/src/java.compiler/share/classes/javax/lang/model/util/SimpleTypeVisitor7.java index f47314aeeb24c..c550a76e4b6cd 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/SimpleTypeVisitor7.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/SimpleTypeVisitor7.java @@ -29,7 +29,6 @@ import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; import static javax.lang.model.SourceVersion.*; -import javax.lang.model.type.TypeVisitor; /** * A simple visitor of types with default behavior appropriate for the diff --git a/src/java.compiler/share/classes/javax/lang/model/util/SimpleTypeVisitor9.java b/src/java.compiler/share/classes/javax/lang/model/util/SimpleTypeVisitor9.java index 8f97b34d547bf..32174be9949bd 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/SimpleTypeVisitor9.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/SimpleTypeVisitor9.java @@ -27,7 +27,6 @@ import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; -import javax.lang.model.type.IntersectionType; import static javax.lang.model.SourceVersion.*; /** diff --git a/src/java.compiler/share/classes/javax/lang/model/util/SimpleTypeVisitorPreview.java b/src/java.compiler/share/classes/javax/lang/model/util/SimpleTypeVisitorPreview.java index a058f42937a65..1efbe7108370c 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/SimpleTypeVisitorPreview.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/SimpleTypeVisitorPreview.java @@ -29,7 +29,6 @@ import javax.annotation.processing.SupportedSourceVersion; import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.SourceVersion; import static javax.lang.model.SourceVersion.*; /** diff --git a/src/java.compiler/share/classes/javax/lang/model/util/TypeKindVisitorPreview.java b/src/java.compiler/share/classes/javax/lang/model/util/TypeKindVisitorPreview.java index 703ef744edc20..70e0498f5658b 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/TypeKindVisitorPreview.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/TypeKindVisitorPreview.java @@ -29,7 +29,6 @@ import javax.annotation.processing.SupportedSourceVersion; import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.SourceVersion; import javax.lang.model.type.*; import static javax.lang.model.SourceVersion.*; diff --git a/src/java.compiler/share/classes/javax/lang/model/util/Types.java b/src/java.compiler/share/classes/javax/lang/model/util/Types.java index f6296050ca9f5..0cea2734e213c 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/Types.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/Types.java @@ -25,9 +25,6 @@ package javax.lang.model.util; -import java.lang.annotation.Annotation; -import java.lang.annotation.AnnotationTypeMismatchException; -import java.lang.annotation.IncompleteAnnotationException; import java.util.List; import javax.lang.model.element.*; import javax.lang.model.type.*; From fead3cf54130e3ab10f94a94dfbd382e4cb1e597 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Gr=C3=B6nlund?= Date: Fri, 23 Aug 2024 09:26:00 +0000 Subject: [PATCH 13/72] 8338745: Intrinsify Continuation.pin() and Continuation.unpin() Reviewed-by: kvn --- src/hotspot/share/classfile/vmIntrinsics.cpp | 2 + src/hotspot/share/classfile/vmIntrinsics.hpp | 2 + src/hotspot/share/classfile/vmSymbols.hpp | 4 +- src/hotspot/share/jvmci/vmStructs_jvmci.cpp | 3 + src/hotspot/share/opto/c2compiler.cpp | 2 + src/hotspot/share/opto/library_call.cpp | 90 +++++++++++++++++++ src/hotspot/share/opto/library_call.hpp | 1 + .../share/runtime/continuationEntry.hpp | 5 +- .../classes/jdk/internal/vm/Continuation.java | 4 +- 9 files changed, 109 insertions(+), 4 deletions(-) diff --git a/src/hotspot/share/classfile/vmIntrinsics.cpp b/src/hotspot/share/classfile/vmIntrinsics.cpp index e60495d1f47fd..b470eb9b8380d 100644 --- a/src/hotspot/share/classfile/vmIntrinsics.cpp +++ b/src/hotspot/share/classfile/vmIntrinsics.cpp @@ -242,6 +242,8 @@ bool vmIntrinsics::disabled_by_jvm_flags(vmIntrinsics::ID id) { case vmIntrinsics::_Reference_get: case vmIntrinsics::_Continuation_doYield: case vmIntrinsics::_Continuation_enterSpecial: + case vmIntrinsics::_Continuation_pin: + case vmIntrinsics::_Continuation_unpin: break; default: return true; diff --git a/src/hotspot/share/classfile/vmIntrinsics.hpp b/src/hotspot/share/classfile/vmIntrinsics.hpp index 7a5c2ee47bb52..4b772c171d5a6 100644 --- a/src/hotspot/share/classfile/vmIntrinsics.hpp +++ b/src/hotspot/share/classfile/vmIntrinsics.hpp @@ -600,6 +600,8 @@ class methodHandle; do_alias(continuationOnPinned_signature, int_void_signature) \ do_intrinsic(_Continuation_doYield, jdk_internal_vm_Continuation, doYield_name, continuationDoYield_signature, F_SN) \ do_alias( continuationDoYield_signature, void_int_signature) \ + do_intrinsic(_Continuation_pin, jdk_internal_vm_Continuation, pin_name, void_method_signature, F_SN) \ + do_intrinsic(_Continuation_unpin, jdk_internal_vm_Continuation, unpin_name, void_method_signature, F_SN) \ \ /* java/lang/VirtualThread */ \ do_intrinsic(_notifyJvmtiVThreadStart, java_lang_VirtualThread, notifyJvmtiStart_name, void_method_signature, F_RN) \ diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp index 8d1ae20eac07c..c1ba050b454c3 100644 --- a/src/hotspot/share/classfile/vmSymbols.hpp +++ b/src/hotspot/share/classfile/vmSymbols.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -406,6 +406,8 @@ class SerializeClosure; template(onContinue_name, "onContinue0") \ template(scope_name, "scope") \ template(yieldInfo_name, "yieldInfo") \ + template(pin_name, "pin") \ + template(unpin_name, "unpin") \ template(tail_name, "tail") \ template(size_name, "size") \ template(bottom_name, "bottom") \ diff --git a/src/hotspot/share/jvmci/vmStructs_jvmci.cpp b/src/hotspot/share/jvmci/vmStructs_jvmci.cpp index 688691fb9765c..1526f81295b2a 100644 --- a/src/hotspot/share/jvmci/vmStructs_jvmci.cpp +++ b/src/hotspot/share/jvmci/vmStructs_jvmci.cpp @@ -35,6 +35,7 @@ #include "oops/methodCounters.hpp" #include "oops/objArrayKlass.hpp" #include "prims/jvmtiThreadState.hpp" +#include "runtime/continuationEntry.hpp" #include "runtime/deoptimization.hpp" #include "runtime/flags/jvmFlag.hpp" #include "runtime/osThread.hpp" @@ -244,10 +245,12 @@ nonstatic_field(JavaThread, _held_monitor_count, intx) \ nonstatic_field(JavaThread, _lock_stack, LockStack) \ nonstatic_field(JavaThread, _om_cache, OMCache) \ + nonstatic_field(JavaThread, _cont_entry, ContinuationEntry*) \ JVMTI_ONLY(nonstatic_field(JavaThread, _is_in_VTMS_transition, bool)) \ JVMTI_ONLY(nonstatic_field(JavaThread, _is_in_tmp_VTMS_transition, bool)) \ JVMTI_ONLY(nonstatic_field(JavaThread, _is_disable_suspend, bool)) \ \ + nonstatic_field(ContinuationEntry, _pin_count, uint32_t) \ nonstatic_field(LockStack, _top, uint32_t) \ \ JVMTI_ONLY(static_field(JvmtiVTMSTransitionDisabler, _VTMS_notify_jvmti_events, bool)) \ diff --git a/src/hotspot/share/opto/c2compiler.cpp b/src/hotspot/share/opto/c2compiler.cpp index c5e174784773f..2f087858efd48 100644 --- a/src/hotspot/share/opto/c2compiler.cpp +++ b/src/hotspot/share/opto/c2compiler.cpp @@ -731,6 +731,8 @@ bool C2Compiler::is_intrinsic_supported(vmIntrinsics::ID id) { case vmIntrinsics::_setCurrentThread: case vmIntrinsics::_scopedValueCache: case vmIntrinsics::_setScopedValueCache: + case vmIntrinsics::_Continuation_pin: + case vmIntrinsics::_Continuation_unpin: #ifdef JFR_HAVE_INTRINSICS case vmIntrinsics::_counterTime: case vmIntrinsics::_getEventWriter: diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp index eced285f8cbdf..12cd005f93e50 100644 --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -482,6 +482,9 @@ bool LibraryCallKit::try_to_inline(int predicate) { case vmIntrinsics::_scopedValueCache: return inline_native_scopedValueCache(); case vmIntrinsics::_setScopedValueCache: return inline_native_setScopedValueCache(); + case vmIntrinsics::_Continuation_pin: return inline_native_Continuation_pinning(false); + case vmIntrinsics::_Continuation_unpin: return inline_native_Continuation_pinning(true); + #if INCLUDE_JVMTI case vmIntrinsics::_notifyJvmtiVThreadStart: return inline_native_notify_jvmti_funcs(CAST_FROM_FN_PTR(address, OptoRuntime::notify_jvmti_vthread_start()), "notifyJvmtiStart", true, false); @@ -3715,6 +3718,93 @@ bool LibraryCallKit::inline_native_setScopedValueCache() { return true; } +//------------------------inline_native_Continuation_pin and unpin----------- + +// Shared implementation routine for both pin and unpin. +bool LibraryCallKit::inline_native_Continuation_pinning(bool unpin) { + enum { _true_path = 1, _false_path = 2, PATH_LIMIT }; + + // Save input memory. + Node* input_memory_state = reset_memory(); + set_all_memory(input_memory_state); + + // TLS + Node* tls_ptr = _gvn.transform(new ThreadLocalNode()); + Node* last_continuation_offset = basic_plus_adr(top(), tls_ptr, in_bytes(JavaThread::cont_entry_offset())); + Node* last_continuation = make_load(control(), last_continuation_offset, last_continuation_offset->get_ptr_type(), T_ADDRESS, MemNode::unordered); + + // Null check the last continuation object. + Node* continuation_cmp_null = _gvn.transform(new CmpPNode(last_continuation, null())); + Node* test_continuation_not_equal_null = _gvn.transform(new BoolNode(continuation_cmp_null, BoolTest::ne)); + IfNode* iff_continuation_not_equal_null = create_and_map_if(control(), test_continuation_not_equal_null, PROB_MAX, COUNT_UNKNOWN); + + // False path, last continuation is null. + Node* continuation_is_null = _gvn.transform(new IfFalseNode(iff_continuation_not_equal_null)); + + // True path, last continuation is not null. + Node* continuation_is_not_null = _gvn.transform(new IfTrueNode(iff_continuation_not_equal_null)); + + set_control(continuation_is_not_null); + + // Load the pin count from the last continuation. + Node* pin_count_offset = basic_plus_adr(top(), last_continuation, in_bytes(ContinuationEntry::pin_count_offset())); + Node* pin_count = make_load(control(), pin_count_offset, TypeInt::INT, T_INT, MemNode::unordered); + + // The loaded pin count is compared against a context specific rhs for over/underflow detection. + Node* pin_count_rhs; + if (unpin) { + pin_count_rhs = _gvn.intcon(0); + } else { + pin_count_rhs = _gvn.intcon(UINT32_MAX); + } + Node* pin_count_cmp = _gvn.transform(new CmpUNode(_gvn.transform(pin_count), pin_count_rhs)); + Node* test_pin_count_over_underflow = _gvn.transform(new BoolNode(pin_count_cmp, BoolTest::eq)); + IfNode* iff_pin_count_over_underflow = create_and_map_if(control(), test_pin_count_over_underflow, PROB_MIN, COUNT_UNKNOWN); + + // False branch, no pin count over/underflow. Increment or decrement pin count and store back. + Node* valid_pin_count = _gvn.transform(new IfFalseNode(iff_pin_count_over_underflow)); + set_control(valid_pin_count); + + Node* next_pin_count; + if (unpin) { + next_pin_count = _gvn.transform(new SubINode(pin_count, _gvn.intcon(1))); + } else { + next_pin_count = _gvn.transform(new AddINode(pin_count, _gvn.intcon(1))); + } + + Node* updated_pin_count_memory = store_to_memory(control(), pin_count_offset, next_pin_count, T_INT, Compile::AliasIdxRaw, MemNode::unordered); + + // True branch, pin count over/underflow. + Node* pin_count_over_underflow = _gvn.transform(new IfTrueNode(iff_pin_count_over_underflow)); + { + // Trap (but not deoptimize (Action_none)) and continue in the interpreter + // which will throw IllegalStateException for pin count over/underflow. + PreserveJVMState pjvms(this); + set_control(pin_count_over_underflow); + set_all_memory(input_memory_state); + uncommon_trap_exact(Deoptimization::Reason_intrinsic, + Deoptimization::Action_none); + assert(stopped(), "invariant"); + } + + // Result of top level CFG and Memory. + RegionNode* result_rgn = new RegionNode(PATH_LIMIT); + record_for_igvn(result_rgn); + PhiNode* result_mem = new PhiNode(result_rgn, Type::MEMORY, TypePtr::BOTTOM); + record_for_igvn(result_mem); + + result_rgn->init_req(_true_path, _gvn.transform(valid_pin_count)); + result_rgn->init_req(_false_path, _gvn.transform(continuation_is_null)); + result_mem->init_req(_true_path, _gvn.transform(updated_pin_count_memory)); + result_mem->init_req(_false_path, _gvn.transform(input_memory_state)); + + // Set output state. + set_control(_gvn.transform(result_rgn)); + set_all_memory(_gvn.transform(result_mem)); + + return true; +} + //---------------------------load_mirror_from_klass---------------------------- // Given a klass oop, load its java mirror (a java.lang.Class oop). Node* LibraryCallKit::load_mirror_from_klass(Node* klass) { diff --git a/src/hotspot/share/opto/library_call.hpp b/src/hotspot/share/opto/library_call.hpp index 313e8c39544c6..4ae9d0216e9fd 100644 --- a/src/hotspot/share/opto/library_call.hpp +++ b/src/hotspot/share/opto/library_call.hpp @@ -241,6 +241,7 @@ class LibraryCallKit : public GraphKit { const Type* scopedValueCache_type(); Node* scopedValueCache_helper(); bool inline_native_setScopedValueCache(); + bool inline_native_Continuation_pinning(bool unpin); bool inline_native_time_funcs(address method, const char* funcName); #if INCLUDE_JVMTI diff --git a/src/hotspot/share/runtime/continuationEntry.hpp b/src/hotspot/share/runtime/continuationEntry.hpp index 5930b008d2771..ac76cd6f0882d 100644 --- a/src/hotspot/share/runtime/continuationEntry.hpp +++ b/src/hotspot/share/runtime/continuationEntry.hpp @@ -39,6 +39,7 @@ class RegisterMap; // Metadata stored in the continuation entry frame class ContinuationEntry { + friend class JVMCIVMStructs; ContinuationEntryPD _pd; #ifdef ASSERT private: @@ -78,7 +79,7 @@ class ContinuationEntry { #else int32_t _parent_held_monitor_count; #endif - uint _pin_count; + uint32_t _pin_count; public: static ByteSize parent_offset() { return byte_offset_of(ContinuationEntry, _parent); } @@ -108,7 +109,7 @@ class ContinuationEntry { bool is_pinned() { return _pin_count > 0; } bool pin() { - if (_pin_count == UINT_MAX) return false; + if (_pin_count == UINT32_MAX) return false; _pin_count++; return true; } diff --git a/src/java.base/share/classes/jdk/internal/vm/Continuation.java b/src/java.base/share/classes/jdk/internal/vm/Continuation.java index 5c1d41c36d97d..99d0c62aaec8d 100644 --- a/src/java.base/share/classes/jdk/internal/vm/Continuation.java +++ b/src/java.base/share/classes/jdk/internal/vm/Continuation.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -427,6 +427,7 @@ public boolean isPreempted() { * Pins the current continuation (enters a critical section). * This increments an internal semaphore that, when greater than 0, pins the continuation. */ + @IntrinsicCandidate public static native void pin(); /** @@ -434,6 +435,7 @@ public boolean isPreempted() { * This decrements an internal semaphore that, when equal 0, unpins the current continuation * if pinned with {@link #pin()}. */ + @IntrinsicCandidate public static native void unpin(); /** From 69bd227e6c497eb82c46ab85125610c0b44dc04e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Gr=C3=B6nlund?= Date: Fri, 23 Aug 2024 09:29:23 +0000 Subject: [PATCH 14/72] 8338417: Explicitly pin a virtual thread before acquiring the JFR string pool monitor Reviewed-by: alanb, egahlin, dholmes --- .../share/jfr/writers/jfrJavaEventWriter.cpp | 24 +++- src/hotspot/share/opto/library_call.cpp | 20 +++- src/java.base/share/classes/module-info.java | 3 +- .../classes/jdk/jfr/internal/StringPool.java | 88 +++++++++----- .../jdk/jfr/internal/event/EventWriter.java | 8 +- .../TestStringPoolVirtualThreadPinning.java | 110 ++++++++++++++++++ 6 files changed, 216 insertions(+), 37 deletions(-) create mode 100644 test/jdk/jdk/jfr/threading/TestStringPoolVirtualThreadPinning.java diff --git a/src/hotspot/share/jfr/writers/jfrJavaEventWriter.cpp b/src/hotspot/share/jfr/writers/jfrJavaEventWriter.cpp index 2bd66ce7c1cc4..d9610bcc97005 100644 --- a/src/hotspot/share/jfr/writers/jfrJavaEventWriter.cpp +++ b/src/hotspot/share/jfr/writers/jfrJavaEventWriter.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -36,6 +36,7 @@ #include "oops/instanceKlass.hpp" #include "oops/oop.inline.hpp" #include "runtime/fieldDescriptor.inline.hpp" +#include "runtime/globals.hpp" #include "runtime/handles.inline.hpp" #include "runtime/interfaceSupport.inline.hpp" #include "runtime/jniHandles.inline.hpp" @@ -49,6 +50,7 @@ static int max_pos_offset = invalid_offset; static int excluded_offset = invalid_offset; static int thread_id_offset = invalid_offset; static int valid_offset = invalid_offset; +static int pin_offset = invalid_offset; static bool setup_event_writer_offsets(TRAPS) { const char class_name[] = "jdk/jfr/internal/event/EventWriter"; @@ -98,6 +100,13 @@ static bool setup_event_writer_offsets(TRAPS) { assert(invalid_offset == valid_offset, "invariant"); JfrJavaSupport::compute_field_offset(valid_offset, klass, valid_sym, vmSymbols::bool_signature()); assert(valid_offset != invalid_offset, "invariant"); + + const char pin_name[] = "pinVirtualThread"; + Symbol* const pin_sym = SymbolTable::new_symbol(valid_name); + assert(pin_sym != nullptr, "invariant"); + assert(invalid_offset == pin_offset, "invariant"); + JfrJavaSupport::compute_field_offset(pin_offset, klass, pin_sym, vmSymbols::bool_signature()); + assert(pin_offset != invalid_offset, "invariant"); return true; } @@ -219,13 +228,18 @@ void JfrJavaEventWriter::notify(JavaThread* jt) { } } +static inline bool pin_virtual(const JavaThread* jt) { + assert(jt != nullptr, "invariant"); + return JfrThreadLocal::is_vthread(jt) && VMContinuations; +} + static jobject create_new_event_writer(JfrBuffer* buffer, JfrThreadLocal* tl, TRAPS) { assert(buffer != nullptr, "invariant"); DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(THREAD)); HandleMark hm(THREAD); static const char klass[] = "jdk/jfr/internal/event/EventWriter"; static const char method[] = ""; - static const char signature[] = "(JJJZZ)V"; + static const char signature[] = "(JJJZZZ)V"; JavaValue result(T_OBJECT); JfrJavaArguments args(&result, klass, method, signature, CHECK_NULL); @@ -234,6 +248,7 @@ static jobject create_new_event_writer(JfrBuffer* buffer, JfrThreadLocal* tl, TR args.push_long((jlong)buffer->end()); args.push_long((jlong)JfrThreadLocal::thread_id(THREAD)); args.push_int((jint)JNI_TRUE); // valid + args.push_int(pin_virtual(THREAD) ? (jint)JNI_TRUE : (jint)JNI_FALSE); args.push_int(tl->is_excluded() ? (jint)JNI_TRUE : (jint)JNI_FALSE); // excluded JfrJavaSupport::new_object_global_ref(&args, CHECK_NULL); return result.get_jobject(); @@ -249,9 +264,12 @@ jobject JfrJavaEventWriter::event_writer(JavaThread* jt) { const jlong event_writer_tid = writer->long_field(thread_id_offset); const jlong current_tid = static_cast(JfrThreadLocal::thread_id(jt)); if (event_writer_tid != current_tid) { + writer->long_field_put(thread_id_offset, current_tid); const bool excluded = tl->is_excluded(); writer->bool_field_put(excluded_offset, excluded); - writer->long_field_put(thread_id_offset, current_tid); + if (!excluded) { + writer->bool_field_put(pin_offset, pin_virtual(jt)); + } } } return h_writer; diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp index 12cd005f93e50..24901855b91a1 100644 --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -3233,10 +3233,12 @@ bool LibraryCallKit::inline_native_jvm_commit() { * oop threadObj = Thread::threadObj(); * oop vthread = java_lang_Thread::vthread(threadObj); * traceid tid; + * bool pinVirtualThread; * bool excluded; * if (vthread != threadObj) { // i.e. current thread is virtual * tid = java_lang_Thread::tid(vthread); * u2 vthread_epoch_raw = java_lang_Thread::jfr_epoch(vthread); + * pinVirtualThread = VMContinuations; * excluded = vthread_epoch_raw & excluded_mask; * if (!excluded) { * traceid current_epoch = JfrTraceIdEpoch::current_generation(); @@ -3248,13 +3250,15 @@ bool LibraryCallKit::inline_native_jvm_commit() { * } else { * tid = java_lang_Thread::tid(threadObj); * u2 thread_epoch_raw = java_lang_Thread::jfr_epoch(threadObj); + * pinVirtualThread = false; * excluded = thread_epoch_raw & excluded_mask; * } * oop event_writer = JNIHandles::resolve_non_null(h_event_writer); * traceid tid_in_event_writer = getField(event_writer, "threadID"); * if (tid_in_event_writer != tid) { - * setField(event_writer, "threadID", tid); + * setField(event_writer, "pinVirtualThread", pinVirtualThread); * setField(event_writer, "excluded", excluded); + * setField(event_writer, "threadID", tid); * } * return event_writer */ @@ -3325,6 +3329,10 @@ bool LibraryCallKit::inline_native_getEventWriter() { // Load the tid field from the vthread object. Node* vthread_tid = load_field_from_object(vthread, "tid", "J"); + // Continuation support determines if a virtual thread should be pinned. + Node* global_addr = makecon(TypeRawPtr::make((address)&VMContinuations)); + Node* continuation_support = make_load(control(), global_addr, TypeInt::BOOL, T_BOOLEAN, MemNode::unordered); + // Load the raw epoch value from the vthread. Node* vthread_epoch_offset = basic_plus_adr(vthread, java_lang_Thread::jfr_epoch_offset()); Node* vthread_epoch_raw = access_load_at(vthread, vthread_epoch_offset, TypeRawPtr::BOTTOM, TypeInt::CHAR, T_CHAR, @@ -3415,6 +3423,8 @@ bool LibraryCallKit::inline_native_getEventWriter() { record_for_igvn(tid); PhiNode* exclusion = new PhiNode(vthread_compare_rgn, TypeInt::BOOL); record_for_igvn(exclusion); + PhiNode* pinVirtualThread = new PhiNode(vthread_compare_rgn, TypeInt::BOOL); + record_for_igvn(pinVirtualThread); // Update control and phi nodes. vthread_compare_rgn->init_req(_true_path, _gvn.transform(exclude_compare_rgn)); @@ -3427,6 +3437,8 @@ bool LibraryCallKit::inline_native_getEventWriter() { tid->init_req(_false_path, _gvn.transform(thread_obj_tid)); exclusion->init_req(_true_path, _gvn.transform(vthread_is_excluded)); exclusion->init_req(_false_path, _gvn.transform(threadObj_is_excluded)); + pinVirtualThread->init_req(_true_path, _gvn.transform(continuation_support)); + pinVirtualThread->init_req(_false_path, _gvn.intcon(0)); // Update branch state. set_control(_gvn.transform(vthread_compare_rgn)); @@ -3450,6 +3462,9 @@ bool LibraryCallKit::inline_native_getEventWriter() { // Get the field offset to, conditionally, store an updated exclusion value later. Node* const event_writer_excluded_field = field_address_from_object(event_writer, "excluded", "Z", false); const TypePtr* event_writer_excluded_field_type = _gvn.type(event_writer_excluded_field)->isa_ptr(); + // Get the field offset to, conditionally, store an updated pinVirtualThread value later. + Node* const event_writer_pin_field = field_address_from_object(event_writer, "pinVirtualThread", "Z", false); + const TypePtr* event_writer_pin_field_type = _gvn.type(event_writer_pin_field)->isa_ptr(); RegionNode* event_writer_tid_compare_rgn = new RegionNode(PATH_LIMIT); record_for_igvn(event_writer_tid_compare_rgn); @@ -3470,6 +3485,9 @@ bool LibraryCallKit::inline_native_getEventWriter() { Node* tid_is_not_equal = _gvn.transform(new IfTrueNode(iff_tid_not_equal)); record_for_igvn(tid_is_not_equal); + // Store the pin state to the event writer. + store_to_memory(tid_is_not_equal, event_writer_pin_field, _gvn.transform(pinVirtualThread), T_BOOLEAN, event_writer_pin_field_type, MemNode::unordered); + // Store the exclusion state to the event writer. store_to_memory(tid_is_not_equal, event_writer_excluded_field, _gvn.transform(exclusion), T_BOOLEAN, event_writer_excluded_field_type, MemNode::unordered); diff --git a/src/java.base/share/classes/module-info.java b/src/java.base/share/classes/module-info.java index 52c1029dd3d52..74a7451582cf7 100644 --- a/src/java.base/share/classes/module-info.java +++ b/src/java.base/share/classes/module-info.java @@ -256,7 +256,8 @@ jdk.internal.jvmstat, jdk.management, jdk.management.agent, - jdk.internal.vm.ci; + jdk.internal.vm.ci, + jdk.jfr; exports jdk.internal.vm.annotation to java.instrument, jdk.internal.vm.ci, diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/StringPool.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/StringPool.java index 6513895069e17..d16bd9ab2a9cd 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/StringPool.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/StringPool.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,6 +26,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; +import jdk.internal.vm.Continuation; public final class StringPool { public static final int MIN_LIMIT = 16; @@ -71,33 +72,49 @@ private static long externalSid(long internalSid) { return internalSid >> 16; } - /* synchronized because of writing the string to the JVM. */ - private static synchronized long storeString(String s) { - Long lsid = cache.get(s); - long internalSid; - if (lsid != null) { - internalSid = lsid.longValue(); - if (isCurrentGeneration(internalSid)) { - // Someone already updated the cache. - return externalSid(internalSid); + /* Explicitly pin a virtual thread before acquiring the string pool monitor + * because migrating the EventWriter onto another carrier thread is impossible. + */ + private static long storeString(String s, boolean pinVirtualThread) { + if (pinVirtualThread) { + assert(Thread.currentThread().isVirtual()); + Continuation.pin(); + } + try { + /* synchronized because of writing the string to the JVM. */ + synchronized (StringPool.class) { + Long lsid = cache.get(s); + long internalSid; + if (lsid != null) { + internalSid = lsid.longValue(); + if (isCurrentGeneration(internalSid)) { + // Someone already updated the cache. + return externalSid(internalSid); + } + internalSid = updateInternalSid(internalSid); + } else { + // Not yet added or the cache was cleared. + internalSid = nextInternalSid(); + currentSizeUTF16 += s.length(); + } + long extSid = externalSid(internalSid); + // Write the string to the JVM before publishing to the cache. + JVM.addStringConstant(extSid, s); + cache.put(s, internalSid); + return extSid; + } + } finally { + if (pinVirtualThread) { + assert(Thread.currentThread().isVirtual()); + Continuation.unpin(); } - internalSid = updateInternalSid(internalSid); - } else { - // Not yet added or the cache was cleared. - internalSid = nextInternalSid(); - currentSizeUTF16 += s.length(); } - long extSid = externalSid(internalSid); - // Write the string to the JVM before publishing to the cache. - JVM.addStringConstant(extSid, s); - cache.put(s, internalSid); - return extSid; } /* a string fetched from the string pool must be of the current generation */ - private static long ensureCurrentGeneration(String s, Long lsid) { + private static long ensureCurrentGeneration(String s, Long lsid, boolean pinVirtualThread) { long internalSid = lsid.longValue(); - return isCurrentGeneration(internalSid) ? externalSid(internalSid) : storeString(s); + return isCurrentGeneration(internalSid) ? externalSid(internalSid) : storeString(s, pinVirtualThread); } /* @@ -109,10 +126,10 @@ private static long ensureCurrentGeneration(String s, Long lsid) { * effectively invalidating the fetched string id. The event restart mechanism * of the EventWriter ensures that committed strings are in the correct generation. */ - public static long addString(String s) { + public static long addString(String s, boolean pinVirtualThread) { Long lsid = cache.get(s); if (lsid != null) { - return ensureCurrentGeneration(s, lsid); + return ensureCurrentGeneration(s, lsid, pinVirtualThread); } if (!preCache(s)) { /* we should not pool this string */ @@ -120,9 +137,9 @@ public static long addString(String s) { } if (cache.size() > MAX_SIZE || currentSizeUTF16 > MAX_SIZE_UTF16) { /* pool was full */ - reset(); + reset(pinVirtualThread); } - return storeString(s); + return storeString(s, pinVirtualThread); } private static boolean preCache(String s) { @@ -143,8 +160,21 @@ private static boolean preCache(String s) { return false; } - private static synchronized void reset() { - cache.clear(); - currentSizeUTF16 = 0; + private static void reset(boolean pinVirtualThread) { + if (pinVirtualThread) { + assert(Thread.currentThread().isVirtual()); + Continuation.pin(); + } + try { + synchronized (StringPool.class) { + cache.clear(); + currentSizeUTF16 = 0; + } + } finally { + if (pinVirtualThread) { + assert(Thread.currentThread().isVirtual()); + Continuation.unpin(); + } + } } } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/event/EventWriter.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/event/EventWriter.java index 833fb087e2420..970365ef73dfe 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/event/EventWriter.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/event/EventWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -65,6 +65,7 @@ public final class EventWriter { private long currentPosition; private long maxPosition; private boolean valid; + private boolean pinVirtualThread; boolean excluded; private PlatformEventType eventType; @@ -144,7 +145,7 @@ public void putString(String s) { return; } if (length > StringPool.MIN_LIMIT && length < StringPool.MAX_LIMIT) { - long l = StringPool.addString(s); + long l = StringPool.addString(s, pinVirtualThread); if (l > 0) { putByte(StringParser.Encoding.CONSTANT_POOL.byteValue()); putLong(l); @@ -296,11 +297,12 @@ public boolean endEvent() { return false; } - private EventWriter(long startPos, long maxPos, long threadID, boolean valid, boolean excluded) { + private EventWriter(long startPos, long maxPos, long threadID, boolean valid, boolean pinVirtualThread, boolean excluded) { startPosition = currentPosition = startPos; maxPosition = maxPos; this.threadID = threadID; this.valid = valid; + this.pinVirtualThread = pinVirtualThread; this.excluded = excluded; } diff --git a/test/jdk/jdk/jfr/threading/TestStringPoolVirtualThreadPinning.java b/test/jdk/jdk/jfr/threading/TestStringPoolVirtualThreadPinning.java new file mode 100644 index 0000000000000..0dc1ef8566139 --- /dev/null +++ b/test/jdk/jdk/jfr/threading/TestStringPoolVirtualThreadPinning.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.jfr.threading; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ThreadFactory; + +import jdk.jfr.Event; +import jdk.jfr.Name; +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedThread; +import jdk.jfr.consumer.RecordingFile; +import jdk.test.lib.Asserts; +import jdk.test.lib.Utils; + +/** + * @test + * @bug 8338417 + * @summary Tests pinning of virtual threads when the JFR string pool monitor is contended. + * @key jfr + * @requires vm.hasJFR & vm.continuations + * @library /test/lib /test/jdk + * @run main/othervm jdk.jfr.threading.TestStringPoolVirtualThreadPinning + */ +public class TestStringPoolVirtualThreadPinning { + + private static final int VIRTUAL_THREAD_COUNT = 100_000; + private static final int STARTER_THREADS = 10; + + @Name("test.Tester") + private static class TestEvent extends Event { + private String eventString = Thread.currentThread().getName(); + } + + /* + * During event commit, the thread is in a critical section because it has loaded a carrier thread local event writer object. + * For virtual threads, a contended monitor, such as a synchronized block, is a point where a thread could become unmounted. + * A monitor guards the JFR string pool, but because of the event writer, remounting a virtual thread onto another carrier is impossible. + * + * The test provokes JFR string pool monitor contention to exercise explicit pin constructs to ensure the pinning of virtual threads. + */ + public static void main(String... args) throws Exception { + try (Recording r = new Recording()) { + r.start(); + + ThreadFactory factory = Thread.ofVirtual().factory(); + CompletableFuture[] c = new CompletableFuture[STARTER_THREADS]; + for (int j = 0; j < STARTER_THREADS; j++) { + c[j] = CompletableFuture.runAsync(() -> { + for (int i = 0; i < VIRTUAL_THREAD_COUNT / STARTER_THREADS; i++) { + try { + Thread vt = factory.newThread(TestStringPoolVirtualThreadPinning::emitEvent); + // For an event field string to be placed in the JFR string pool, it must exceed 16 characters. + // We use the virtual thread name as the event field string so we can verify the result as a 1-1 mapping. + vt.setName("VirtualTestThread-" + i); + vt.start(); + vt.join(); + } catch (InterruptedException ie) { + ie.printStackTrace(); + } + } + }); + } + for (int j = 0; j < STARTER_THREADS; j++) { + c[j].get(); + } + + r.stop(); + Path p = Utils.createTempFile("test", ".jfr"); + r.dump(p); + List events = RecordingFile.readAllEvents(p); + Asserts.assertEquals(events.size(), VIRTUAL_THREAD_COUNT, "Expected " + VIRTUAL_THREAD_COUNT + " events"); + for (RecordedEvent e : events) { + RecordedThread t = e.getThread(); + Asserts.assertNotNull(t); + Asserts.assertTrue(t.isVirtual()); + Asserts.assertEquals(e.getString("eventString"), t.getJavaName()); + } + } + } + + private static void emitEvent() { + TestEvent t = new TestEvent(); + t.commit(); + } +} From 965dd1acd0ce5b225d85e2c55cc097856e0e9f3c Mon Sep 17 00:00:00 2001 From: Qizheng Xing Date: Fri, 23 Aug 2024 09:30:47 +0000 Subject: [PATCH 15/72] 8333334: C2: Make result of `Node::dominates` more precise to enhance scalar replacement Reviewed-by: chagedorn, kvn, thartmann --- src/hotspot/share/opto/memnode.cpp | 116 +++++++++++------ src/hotspot/share/opto/memnode.hpp | 9 +- src/hotspot/share/opto/node.cpp | 17 +-- src/hotspot/share/opto/node.hpp | 9 +- .../ScalarReplacementWithGCBarrierTests.java | 118 ++++++++++++++++++ .../bench/java/util/concurrent/Maps.java | 17 +++ 6 files changed, 236 insertions(+), 50 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/c2/irTests/scalarReplacement/ScalarReplacementWithGCBarrierTests.java diff --git a/src/hotspot/share/opto/memnode.cpp b/src/hotspot/share/opto/memnode.cpp index c7f0fb9fc3202..2d79857b62a78 100644 --- a/src/hotspot/share/opto/memnode.cpp +++ b/src/hotspot/share/opto/memnode.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 1997, 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. * * This code is free software; you can redistribute it and/or modify it @@ -427,23 +428,31 @@ Node *MemNode::Ideal_common(PhaseGVN *phase, bool can_reshape) { // Used by MemNode::find_previous_store to prove that the // control input of a memory operation predates (dominates) // an allocation it wants to look past. -bool MemNode::all_controls_dominate(Node* dom, Node* sub) { - if (dom == nullptr || dom->is_top() || sub == nullptr || sub->is_top()) - return false; // Conservative answer for dead code +// Returns 'DomResult::Dominate' if all control inputs of 'dom' +// dominate 'sub', 'DomResult::NotDominate' if not, +// and 'DomResult::EncounteredDeadCode' if we can't decide due to +// dead code, but at the end of IGVN, we know the definite result +// once the dead code is cleaned up. +Node::DomResult MemNode::maybe_all_controls_dominate(Node* dom, Node* sub) { + if (dom == nullptr || dom->is_top() || sub == nullptr || sub->is_top()) { + return DomResult::EncounteredDeadCode; // Conservative answer for dead code + } // Check 'dom'. Skip Proj and CatchProj nodes. dom = dom->find_exact_control(dom); - if (dom == nullptr || dom->is_top()) - return false; // Conservative answer for dead code + if (dom == nullptr || dom->is_top()) { + return DomResult::EncounteredDeadCode; // Conservative answer for dead code + } if (dom == sub) { // For the case when, for example, 'sub' is Initialize and the original // 'dom' is Proj node of the 'sub'. - return false; + return DomResult::NotDominate; } - if (dom->is_Con() || dom->is_Start() || dom->is_Root() || dom == sub) - return true; + if (dom->is_Con() || dom->is_Start() || dom->is_Root() || dom == sub) { + return DomResult::Dominate; + } // 'dom' dominates 'sub' if its control edge and control edges // of all its inputs dominate or equal to sub's control edge. @@ -457,16 +466,19 @@ bool MemNode::all_controls_dominate(Node* dom, Node* sub) { // Get control edge of 'sub'. Node* orig_sub = sub; sub = sub->find_exact_control(sub->in(0)); - if (sub == nullptr || sub->is_top()) - return false; // Conservative answer for dead code + if (sub == nullptr || sub->is_top()) { + return DomResult::EncounteredDeadCode; // Conservative answer for dead code + } assert(sub->is_CFG(), "expecting control"); - if (sub == dom) - return true; + if (sub == dom) { + return DomResult::Dominate; + } - if (sub->is_Start() || sub->is_Root()) - return false; + if (sub->is_Start() || sub->is_Root()) { + return DomResult::NotDominate; + } { // Check all control edges of 'dom'. @@ -480,41 +492,47 @@ bool MemNode::all_controls_dominate(Node* dom, Node* sub) { for (uint next = 0; next < dom_list.size(); next++) { Node* n = dom_list.at(next); - if (n == orig_sub) - return false; // One of dom's inputs dominated by sub. + if (n == orig_sub) { + return DomResult::NotDominate; // One of dom's inputs dominated by sub. + } if (!n->is_CFG() && n->pinned()) { // Check only own control edge for pinned non-control nodes. n = n->find_exact_control(n->in(0)); - if (n == nullptr || n->is_top()) - return false; // Conservative answer for dead code + if (n == nullptr || n->is_top()) { + return DomResult::EncounteredDeadCode; // Conservative answer for dead code + } assert(n->is_CFG(), "expecting control"); dom_list.push(n); } else if (n->is_Con() || n->is_Start() || n->is_Root()) { only_dominating_controls = true; } else if (n->is_CFG()) { - if (n->dominates(sub, nlist)) + DomResult dom_result = n->dominates(sub, nlist); + if (dom_result == DomResult::Dominate) { only_dominating_controls = true; - else - return false; + } else { + return dom_result; + } } else { // First, own control edge. Node* m = n->find_exact_control(n->in(0)); if (m != nullptr) { - if (m->is_top()) - return false; // Conservative answer for dead code + if (m->is_top()) { + return DomResult::EncounteredDeadCode; // Conservative answer for dead code + } dom_list.push(m); } // Now, the rest of edges. uint cnt = n->req(); for (uint i = 1; i < cnt; i++) { m = n->find_exact_control(n->in(i)); - if (m == nullptr || m->is_top()) + if (m == nullptr || m->is_top()) { continue; + } dom_list.push(m); } } } - return only_dominating_controls; + return only_dominating_controls ? DomResult::Dominate : DomResult::NotDominate; } } @@ -726,16 +744,18 @@ Node* MemNode::find_previous_store(PhaseValues* phase) { } else if (mem->is_Proj() && mem->in(0)->is_Initialize()) { InitializeNode* st_init = mem->in(0)->as_Initialize(); AllocateNode* st_alloc = st_init->allocation(); - if (st_alloc == nullptr) + if (st_alloc == nullptr) { break; // something degenerated + } bool known_identical = false; bool known_independent = false; - if (alloc == st_alloc) + if (alloc == st_alloc) { known_identical = true; - else if (alloc != nullptr) + } else if (alloc != nullptr) { known_independent = true; - else if (all_controls_dominate(this, st_alloc)) + } else if (all_controls_dominate(this, st_alloc)) { known_independent = true; + } if (known_independent) { // The bases are provably independent: Either they are @@ -1566,8 +1586,9 @@ bool LoadNode::can_split_through_phi_base(PhaseGVN* phase) { } if (!mem->is_Phi()) { - if (!MemNode::all_controls_dominate(mem, base->in(0))) + if (!MemNode::all_controls_dominate(mem, base->in(0))) { return false; + } } else if (base->in(0) != mem->in(0)) { if (!MemNode::all_controls_dominate(mem, base->in(0))) { return false; @@ -1658,35 +1679,49 @@ Node* LoadNode::split_through_phi(PhaseGVN* phase, bool ignore_missing_instance_ // Select Region to split through. Node* region; + DomResult dom_result = DomResult::Dominate; if (!base_is_phi) { assert(mem->is_Phi(), "sanity"); region = mem->in(0); // Skip if the region dominates some control edge of the address. - if (!MemNode::all_controls_dominate(address, region)) - return nullptr; + // We will check `dom_result` later. + dom_result = MemNode::maybe_all_controls_dominate(address, region); } else if (!mem->is_Phi()) { assert(base_is_phi, "sanity"); region = base->in(0); // Skip if the region dominates some control edge of the memory. - if (!MemNode::all_controls_dominate(mem, region)) - return nullptr; + // We will check `dom_result` later. + dom_result = MemNode::maybe_all_controls_dominate(mem, region); } else if (base->in(0) != mem->in(0)) { assert(base_is_phi && mem->is_Phi(), "sanity"); - if (MemNode::all_controls_dominate(mem, base->in(0))) { + dom_result = MemNode::maybe_all_controls_dominate(mem, base->in(0)); + if (dom_result == DomResult::Dominate) { region = base->in(0); - } else if (MemNode::all_controls_dominate(address, mem->in(0))) { - region = mem->in(0); } else { - return nullptr; // complex graph + dom_result = MemNode::maybe_all_controls_dominate(address, mem->in(0)); + if (dom_result == DomResult::Dominate) { + region = mem->in(0); + } + // Otherwise we encountered a complex graph. } } else { assert(base->in(0) == mem->in(0), "sanity"); region = mem->in(0); } + PhaseIterGVN* igvn = phase->is_IterGVN(); + if (dom_result != DomResult::Dominate) { + if (dom_result == DomResult::EncounteredDeadCode) { + // There is some dead code which eventually will be removed in IGVN. + // Once this is the case, we get an unambiguous dominance result. + // Push the node to the worklist again until the dead code is removed. + igvn->_worklist.push(this); + } + return nullptr; + } + Node* phi = nullptr; const Type* this_type = this->bottom_type(); - PhaseIterGVN* igvn = phase->is_IterGVN(); if (t_oop != nullptr && (t_oop->is_known_instance_field() || load_boxed_values)) { int this_index = C->get_alias_index(t_oop); int this_offset = t_oop->offset(); @@ -4571,8 +4606,9 @@ bool InitializeNode::detect_init_independence(Node* value, PhaseGVN* phase) { // must have preceded the init, or else be equal to the init. // Even after loop optimizations (which might change control edges) // a store is never pinned *before* the availability of its inputs. - if (!MemNode::all_controls_dominate(n, this)) + if (!MemNode::all_controls_dominate(n, this)) { return false; // failed to prove a good control + } } // Check data edges for possible dependencies on 'this'. diff --git a/src/hotspot/share/opto/memnode.hpp b/src/hotspot/share/opto/memnode.hpp index 1b65585f1a006..85d206749f6be 100644 --- a/src/hotspot/share/opto/memnode.hpp +++ b/src/hotspot/share/opto/memnode.hpp @@ -1,5 +1,6 @@ /* * Copyright (c) 1997, 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. * * This code is free software; you can redistribute it and/or modify it @@ -105,8 +106,12 @@ class MemNode : public Node { static Node *optimize_simple_memory_chain(Node *mchain, const TypeOopPtr *t_oop, Node *load, PhaseGVN *phase); static Node *optimize_memory_chain(Node *mchain, const TypePtr *t_adr, Node *load, PhaseGVN *phase); - // This one should probably be a phase-specific function: - static bool all_controls_dominate(Node* dom, Node* sub); + // The following two should probably be phase-specific functions: + static DomResult maybe_all_controls_dominate(Node* dom, Node* sub); + static bool all_controls_dominate(Node* dom, Node* sub) { + DomResult dom_result = maybe_all_controls_dominate(dom, sub); + return dom_result == DomResult::Dominate; + } virtual const class TypePtr *adr_type() const; // returns bottom_type of address diff --git a/src/hotspot/share/opto/node.cpp b/src/hotspot/share/opto/node.cpp index f36770e67ea6c..2e586de33a3d8 100644 --- a/src/hotspot/share/opto/node.cpp +++ b/src/hotspot/share/opto/node.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 1997, 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. * * This code is free software; you can redistribute it and/or modify it @@ -1249,7 +1250,7 @@ Node* Node::find_exact_control(Node* ctrl) { // We already know that if any path back to Root or Start reaches 'this', // then all paths so, so this is a simple search for one example, // not an exhaustive search for a counterexample. -bool Node::dominates(Node* sub, Node_List &nlist) { +Node::DomResult Node::dominates(Node* sub, Node_List &nlist) { assert(this->is_CFG(), "expecting control"); assert(sub != nullptr && sub->is_CFG(), "expecting control"); @@ -1269,12 +1270,15 @@ bool Node::dominates(Node* sub, Node_List &nlist) { // will either exit through the loop head, or give up. // (If we get confused, break out and return a conservative 'false'.) while (sub != nullptr) { - if (sub->is_top()) break; // Conservative answer for dead code. + if (sub->is_top()) { + // Conservative answer for dead code. + return DomResult::EncounteredDeadCode; + } if (sub == dom) { if (nlist.size() == 0) { // No Region nodes except loops were visited before and the EntryControl // path was taken for loops: it did not walk in a cycle. - return true; + return DomResult::Dominate; } else if (met_dom) { break; // already met before: walk in a cycle } else { @@ -1288,7 +1292,7 @@ bool Node::dominates(Node* sub, Node_List &nlist) { // Success if we met 'dom' along a path to Start or Root. // We assume there are no alternative paths that avoid 'dom'. // (This assumption is up to the caller to ensure!) - return met_dom; + return met_dom ? DomResult::Dominate : DomResult::NotDominate; } Node* up = sub->in(0); // Normalize simple pass-through regions and projections: @@ -1319,7 +1323,7 @@ bool Node::dominates(Node* sub, Node_List &nlist) { if (visited == sub) { if (visited_twice_already) { // Visited 2 paths, but still stuck in loop body. Give up. - return false; + return DomResult::NotDominate; } // The Region node was visited before only once. // (We will repush with the low bit set, below.) @@ -1362,8 +1366,7 @@ bool Node::dominates(Node* sub, Node_List &nlist) { } // Did not meet Root or Start node in pred. chain. - // Conservative answer for dead code. - return false; + return DomResult::NotDominate; } //------------------------------remove_dead_region----------------------------- diff --git a/src/hotspot/share/opto/node.hpp b/src/hotspot/share/opto/node.hpp index 3e39e1ed2fbfb..bc5842866a186 100644 --- a/src/hotspot/share/opto/node.hpp +++ b/src/hotspot/share/opto/node.hpp @@ -1,5 +1,6 @@ /* * Copyright (c) 1997, 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. * * This code is free software; you can redistribute it and/or modify it @@ -1107,8 +1108,14 @@ class Node { // Skip Proj and CatchProj nodes chains. Check for Null and Top. Node* find_exact_control(Node* ctrl); + // Results of the dominance analysis. + enum class DomResult { + NotDominate, // 'this' node does not dominate 'sub'. + Dominate, // 'this' node dominates or is equal to 'sub'. + EncounteredDeadCode // Result is undefined due to encountering dead code. + }; // Check if 'this' node dominates or equal to 'sub'. - bool dominates(Node* sub, Node_List &nlist); + DomResult dominates(Node* sub, Node_List &nlist); protected: bool remove_dead_region(PhaseGVN *phase, bool can_reshape); diff --git a/test/hotspot/jtreg/compiler/c2/irTests/scalarReplacement/ScalarReplacementWithGCBarrierTests.java b/test/hotspot/jtreg/compiler/c2/irTests/scalarReplacement/ScalarReplacementWithGCBarrierTests.java new file mode 100644 index 0000000000000..fbf5cdd61cc03 --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/irTests/scalarReplacement/ScalarReplacementWithGCBarrierTests.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2024 Alibaba Group Holding Limited. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.c2.irTests.scalarReplacement; + +import compiler.lib.ir_framework.*; + +/* + * @test + * @bug 8333334 + * @summary Tests that dead barrier control flows do not affect the scalar replacement. + * @library /test/lib / + * @requires vm.compiler2.enabled + * @requires vm.gc.G1 + * @run driver compiler.c2.irTests.scalarReplacement.ScalarReplacementWithGCBarrierTests + */ +public class ScalarReplacementWithGCBarrierTests { + static class List { + public Node head; + + public void push(int value) { + Node n = new Node(); + n.value = value; + n.next = head; + head = n; + } + + @ForceInline + public Iter iter() { + Iter iter = new Iter(); + iter.list = this; + iter.n = head; + iter.sum = 0; + return iter; + } + } + + static class Node { + public int value; + public Node next; + } + + static class Iter { + public List list; + public Node n; + public Integer sum; + + @ForceInline + public boolean next() { + int lastSum = sum; + while (sum - lastSum < 1000) { + while (n != null && n.value < 30) n = n.next; + if (n == null) return false; + sum += n.value; + n = n.next; + } + return true; + } + } + + private static final int SIZE = 1000; + + public static void main(String[] args) { + // Must use G1 GC to ensure there is a pre-barrier + // before the first field write. + TestFramework.runWithFlags("-XX:+UseG1GC"); + } + + @Run(test = "testScalarReplacementWithGCBarrier") + private void runner() { + List list = new List(); + for (int i = 0; i < SIZE; i++) { + list.push(i); + } + testScalarReplacementWithGCBarrier(list); + } + + // Allocation of `Iter iter` should be eliminated by scalar replacement, and + // the allocation of `Integer sum` can not be eliminated, so there should be + // 1 allocation after allocations and locks elimination. + // + // Before the patch of JDK-8333334, both allocations of `Iter` and `Integer` + // could not be eliminated. + @Test + @IR(phase = { CompilePhase.AFTER_PARSING }, counts = { IRNode.ALLOC, "1" }) + @IR(phase = { CompilePhase.INCREMENTAL_BOXING_INLINE }, counts = { IRNode.ALLOC, "2" }) + @IR(phase = { CompilePhase.ITER_GVN_AFTER_ELIMINATION }, counts = { IRNode.ALLOC, "1" }) + private int testScalarReplacementWithGCBarrier(List list) { + Iter iter = list.iter(); + while (true) { + while (iter.next()) {} + if (list.head == null) break; + list.head = list.head.next; + iter.n = list.head; + } + return iter.sum; + } +} diff --git a/test/micro/org/openjdk/bench/java/util/concurrent/Maps.java b/test/micro/org/openjdk/bench/java/util/concurrent/Maps.java index bd68e582e6a2f..62883efb8bb89 100644 --- a/test/micro/org/openjdk/bench/java/util/concurrent/Maps.java +++ b/test/micro/org/openjdk/bench/java/util/concurrent/Maps.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2014, 2022, 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. * * This code is free software; you can redistribute it and/or modify it @@ -35,6 +36,7 @@ import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; +import java.util.Enumeration; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; @@ -127,6 +129,21 @@ public ConcurrentHashMap testConcurrentHashMapPutAll() { return map; } + @Benchmark + public int testConcurrentHashMapIterators() { + ConcurrentHashMap map = (ConcurrentHashMap) staticMap; + int sum = 0; + Enumeration it = map.elements(); + while (it.hasMoreElements()) { + sum += (int) it.nextElement(); + } + it = map.keys(); + while (it.hasMoreElements()) { + sum += (int) it.nextElement(); + } + return sum; + } + private static class SimpleRandom { private final static long multiplier = 0x5DEECE66DL; private final static long addend = 0xBL; From 21d1e4d8039ecccbf60138ede574e0177ee5550f Mon Sep 17 00:00:00 2001 From: Erik Gahlin Date: Fri, 23 Aug 2024 09:59:15 +0000 Subject: [PATCH 16/72] 8338819: JFR: Internal events causes crash when no other events are in use Reviewed-by: mgronlun, sjohanss --- src/hotspot/share/jfr/jni/jfrUpcalls.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/hotspot/share/jfr/jni/jfrUpcalls.cpp b/src/hotspot/share/jfr/jni/jfrUpcalls.cpp index bb316700ac011..2f1cb7e2d540d 100644 --- a/src/hotspot/share/jfr/jni/jfrUpcalls.cpp +++ b/src/hotspot/share/jfr/jni/jfrUpcalls.cpp @@ -191,6 +191,10 @@ void JfrUpcalls::new_bytes_eager_instrumentation(jlong trace_id, bool JfrUpcalls::unhide_internal_types(TRAPS) { DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(THREAD)); + if (!initialize(THREAD)) { + log_error(jfr, system)("JfrUpcall could not be initialized."); + return false; + } JavaValue result(T_VOID); const Klass* klass = SystemDictionary::resolve_or_fail(jvm_upcalls_class_sym, true, CHECK_false); assert(klass != nullptr, "invariant"); From 916f1aa04f6fcc6da9bf9d725e3639cf4c0755a1 Mon Sep 17 00:00:00 2001 From: Tejesh R Date: Fri, 23 Aug 2024 10:51:12 +0000 Subject: [PATCH 17/72] 8329756: [macos] "javax/swing/JTable/KeyBoardNavigation.java" fail because most combinations of navigational keys with the Ctrl key do not work Reviewed-by: abhiscxk, dnguyen --- .../com/apple/laf/AquaKeyBindings.java | 4 +++- .../swing/JTable/KeyBoardNavigation.java | 21 +++++++------------ 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/java.desktop/macosx/classes/com/apple/laf/AquaKeyBindings.java b/src/java.desktop/macosx/classes/com/apple/laf/AquaKeyBindings.java index d64ac90e9ac7f..60bb3da9f87db 100644 --- a/src/java.desktop/macosx/classes/com/apple/laf/AquaKeyBindings.java +++ b/src/java.desktop/macosx/classes/com/apple/laf/AquaKeyBindings.java @@ -388,7 +388,9 @@ LateBoundInputMap getTableInputMap() { "alt shift TAB", "focusHeader", "F8", "focusHeader", "ctrl shift UP", "selectFirstRowExtendSelection", - "ctrl shift DOWN", "selectLastRowExtendSelection" + "ctrl shift DOWN", "selectLastRowExtendSelection", + "ctrl shift RIGHT", "selectLastColumnExtendSelection", + "ctrl shift LEFT", "selectFirstColumnExtendSelection" })); } diff --git a/test/jdk/javax/swing/JTable/KeyBoardNavigation.java b/test/jdk/javax/swing/JTable/KeyBoardNavigation.java index 595547421ad16..728361a3e9022 100644 --- a/test/jdk/javax/swing/JTable/KeyBoardNavigation.java +++ b/test/jdk/javax/swing/JTable/KeyBoardNavigation.java @@ -39,7 +39,7 @@ /* * @test * @key headful - * @bug 4112270 8264102 + * @bug 4112270 8264102 8329756 * @library /java/awt/regtesthelpers * @build PassFailJFrame * @summary Test Keyboard Navigation in JTable. @@ -178,7 +178,7 @@ public static void main(String[] args) throws Exception { 1. Refer the below keyboard navigation specs (referenced from bug report 4112270). 2. Check all combinations of navigational keys mentioned below - and verifying each key combinations against the spec defined. + and verify each key combination against the spec defined. If it does, press "pass", otherwise press "fail". """; @@ -270,24 +270,19 @@ public static String getOSSpecificInstructions() { up/down Left/Right Arrow - Deselect current selection; move focus one cell left/right - FN+Up Arrow/FN+Down Arrow - Deselect current selection; + fn+Up/Down Arrow - Deselect current selection; scroll up/down one JViewport view; first visible cell in current column gets focus - Control-FN+Up Arrow/FN+Down Arrow - Deselect current selection; - move focus and view to - first/last cell in current row - F2 - Allows editing in a cell containing information without + fn - Allows editing in a cell containing information without overwriting the information Esc - Resets the cell content back to the state it was in before editing started - Ctrl+A, Ctrl+/ - Select All - Ctrl+\\ - Deselect all + Cmd+A - Select All Shift-Up/Down Arrow - Extend selection up/down one row Shift-Left/Right Arrow - Extend selection left/right one column - FN-Shift Up/Down Arrow - Extend selection to top/bottom of column - Shift-PageUp/PageDown - Extend selection up/down one view and scroll - table - """; + Ctrl-Shift Up/Down Arrow - Extend selection to top/bottom of row + Ctrl-Shift Left/Right Arrow - Extend selection to first/last of column + """; String osName = System.getProperty("os.name").toLowerCase(); if (osName.startsWith("mac")) { return MAC_SPECIFIC; From a461369f16a2d92ab428d14c36dd69fa5942bbc5 Mon Sep 17 00:00:00 2001 From: Chen Liang Date: Fri, 23 Aug 2024 15:16:44 +0000 Subject: [PATCH 18/72] 8338700: AttributeMapper type parameter should be bounded by Attribute Reviewed-by: asotona --- .../share/classes/java/lang/classfile/AttributeMapper.java | 4 ++-- .../share/classes/java/lang/classfile/package-info.java | 2 +- .../classes/jdk/internal/classfile/impl/AttributeHolder.java | 2 +- .../classes/jdk/internal/classfile/impl/BoundAttribute.java | 2 +- .../share/classes/jdk/internal/classfile/impl/Util.java | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/java.base/share/classes/java/lang/classfile/AttributeMapper.java b/src/java.base/share/classes/java/lang/classfile/AttributeMapper.java index 0e7d625290e5c..0b46055423ae3 100644 --- a/src/java.base/share/classes/java/lang/classfile/AttributeMapper.java +++ b/src/java.base/share/classes/java/lang/classfile/AttributeMapper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -40,7 +40,7 @@ * @since 22 */ @PreviewFeature(feature = PreviewFeature.Feature.CLASSFILE_API) -public interface AttributeMapper { +public interface AttributeMapper> { /** * Attribute stability indicator 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 921cf9e1a3443..6e9ecfe4819e6 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 @@ -147,7 +147,7 @@ * ClassReader, int)} method for mapping from the classfile format * to an attribute instance, and the * {@link java.lang.classfile.AttributeMapper#writeAttribute(java.lang.classfile.BufWriter, - * java.lang.Object)} method for mapping back to the classfile format. It also + * java.lang.classfile.Attribute)} method for mapping back to the classfile format. It also * contains metadata including the attribute name, the set of classfile entities * where the attribute is applicable, and whether multiple attributes of the * same kind are allowed on a single entity. diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/AttributeHolder.java b/src/java.base/share/classes/jdk/internal/classfile/impl/AttributeHolder.java index 31e1a7f2533fc..fb9ecc98902d7 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/AttributeHolder.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/AttributeHolder.java @@ -54,7 +54,7 @@ public void writeTo(BufWriterImpl buf) { } @SuppressWarnings("unchecked") - A get(AttributeMapper am) { + > A get(AttributeMapper am) { for (Attribute a : attributes) if (a.attributeMapper() == am) return (A)a; diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/BoundAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/impl/BoundAttribute.java index d5ed0c14bcef4..36ef2fa55ebef 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/BoundAttribute.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/BoundAttribute.java @@ -148,7 +148,7 @@ public static List> readAttributes(AttributedElement enclosing, Cla mapper = customAttributes.apply(name); } if (mapper != null) { - filled.add((Attribute) Objects.requireNonNull(mapper.readAttribute(enclosing, reader, p))); + filled.add(Objects.requireNonNull(mapper.readAttribute(enclosing, reader, p))); } else { AttributeMapper fakeMapper = new AttributeMapper<>() { @Override diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/Util.java b/src/java.base/share/classes/jdk/internal/classfile/impl/Util.java index 1ff80d766766e..a064c40be30a9 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/Util.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/Util.java @@ -224,7 +224,7 @@ public static MethodTypeDesc methodTypeSymbol(NameAndTypeEntry nat) { } @SuppressWarnings("unchecked") - private static void writeAttribute(BufWriterImpl writer, Attribute attr) { + private static > void writeAttribute(BufWriterImpl writer, Attribute attr) { if (attr instanceof CustomAttribute ca) { var mapper = (AttributeMapper) ca.attributeMapper(); mapper.writeAttribute(writer, (T) ca); From 23dc3b02468836f4c9b4303f2c7c0a7305461ce1 Mon Sep 17 00:00:00 2001 From: Brian Burkhalter Date: Fri, 23 Aug 2024 16:32:14 +0000 Subject: [PATCH 19/72] 8324048: (fc) Make FileKey fields final Reviewed-by: djelinski, alanb, jpai --- .../unix/classes/sun/nio/ch/FileKey.java | 23 ++++++++------- src/java.base/unix/native/libnio/ch/FileKey.c | 24 ++++++---------- .../windows/classes/sun/nio/ch/FileKey.java | 24 +++++++++------- .../windows/native/libnio/ch/FileKey.c | 28 ++++++------------- 4 files changed, 43 insertions(+), 56 deletions(-) diff --git a/src/java.base/unix/classes/sun/nio/ch/FileKey.java b/src/java.base/unix/classes/sun/nio/ch/FileKey.java index e60e63f073d22..119abe4a3b57a 100644 --- a/src/java.base/unix/classes/sun/nio/ch/FileKey.java +++ b/src/java.base/unix/classes/sun/nio/ch/FileKey.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -33,15 +33,18 @@ */ public class FileKey { - private long st_dev; // ID of device - private long st_ino; // Inode number + private final long st_dev; // ID of device + private final long st_ino; // Inode number - private FileKey() { } + private FileKey(long st_dev, long st_ino) { + this.st_dev = st_dev; + this.st_ino = st_ino; + } public static FileKey create(FileDescriptor fd) throws IOException { - FileKey fk = new FileKey(); - fk.init(fd); - return fk; + long finfo[] = new long[2]; + init(fd, finfo); + return new FileKey(finfo[0], finfo[1]); } @Override @@ -59,10 +62,10 @@ public boolean equals(Object obj) { && (this.st_ino == other.st_ino); } - private native void init(FileDescriptor fd) throws IOException; - private static native void initIDs(); + private static native void init(FileDescriptor fd, long[] finfo) + throws IOException; static { - initIDs(); + IOUtil.load(); } } diff --git a/src/java.base/unix/native/libnio/ch/FileKey.c b/src/java.base/unix/native/libnio/ch/FileKey.c index c3817003b5b16..59a866bcaff3a 100644 --- a/src/java.base/unix/native/libnio/ch/FileKey.c +++ b/src/java.base/unix/native/libnio/ch/FileKey.c @@ -30,29 +30,21 @@ #include "nio_util.h" #include "sun_nio_ch_FileKey.h" -static jfieldID key_st_dev; /* id for FileKey.st_dev */ -static jfieldID key_st_ino; /* id for FileKey.st_ino */ - - -JNIEXPORT void JNICALL -Java_sun_nio_ch_FileKey_initIDs(JNIEnv *env, jclass clazz) -{ - CHECK_NULL(key_st_dev = (*env)->GetFieldID(env, clazz, "st_dev", "J")); - CHECK_NULL(key_st_ino = (*env)->GetFieldID(env, clazz, "st_ino", "J")); -} - - JNIEXPORT void JNICALL -Java_sun_nio_ch_FileKey_init(JNIEnv *env, jobject this, jobject fdo) +Java_sun_nio_ch_FileKey_init(JNIEnv* env, jclass clazz, jobject fdo, + jlongArray finfo) { struct stat fbuf; int res; + jlong deviceAndInode[2]; - RESTARTABLE(fstat(fdval(env, fdo), &fbuf), res); + int fd = fdval(env, fdo); + RESTARTABLE(fstat(fd, &fbuf), res); if (res < 0) { JNU_ThrowIOExceptionWithLastError(env, "fstat failed"); } else { - (*env)->SetLongField(env, this, key_st_dev, (jlong)fbuf.st_dev); - (*env)->SetLongField(env, this, key_st_ino, (jlong)fbuf.st_ino); + deviceAndInode[0] = (jlong)fbuf.st_dev; + deviceAndInode[1] = (jlong)fbuf.st_ino; + (*env)->SetLongArrayRegion(env, finfo, 0, 2, deviceAndInode); } } diff --git a/src/java.base/windows/classes/sun/nio/ch/FileKey.java b/src/java.base/windows/classes/sun/nio/ch/FileKey.java index 8669517f0c581..a900c382e6895 100644 --- a/src/java.base/windows/classes/sun/nio/ch/FileKey.java +++ b/src/java.base/windows/classes/sun/nio/ch/FileKey.java @@ -33,16 +33,21 @@ */ public class FileKey { - private int dwVolumeSerialNumber; - private int nFileIndexHigh; - private int nFileIndexLow; + private final int dwVolumeSerialNumber; + private final int nFileIndexHigh; + private final int nFileIndexLow; - private FileKey() { } + private FileKey(int dwVolumeSerialNumber, int nFileIndexHigh, + int nFileIndexLow) { + this.dwVolumeSerialNumber = dwVolumeSerialNumber; + this.nFileIndexHigh = nFileIndexHigh; + this.nFileIndexLow = nFileIndexLow; + } public static FileKey create(FileDescriptor fd) throws IOException { - FileKey fk = new FileKey(); - fk.init(fd); - return fk; + int finfo[] = new int[3]; + init(fd, finfo); + return new FileKey(finfo[0], finfo[1], finfo[2]); } @Override @@ -60,11 +65,10 @@ public boolean equals(Object obj) { && this.nFileIndexLow == other.nFileIndexLow; } - private native void init(FileDescriptor fd) throws IOException; - private static native void initIDs(); + private static native void init(FileDescriptor fd, int[] finfo) + throws IOException; static { IOUtil.load(); - initIDs(); } } diff --git a/src/java.base/windows/native/libnio/ch/FileKey.c b/src/java.base/windows/native/libnio/ch/FileKey.c index 9cd3459aef861..e552ed6b7ae2a 100644 --- a/src/java.base/windows/native/libnio/ch/FileKey.c +++ b/src/java.base/windows/native/libnio/ch/FileKey.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -30,32 +30,20 @@ #include "nio_util.h" #include "sun_nio_ch_FileKey.h" -static jfieldID key_volumeSN; /* id for FileKey.dwVolumeSerialNumber */ -static jfieldID key_indexHigh; /* id for FileKey.nFileIndexHigh */ -static jfieldID key_indexLow; /* id for FileKey.nFileIndexLow */ - - -JNIEXPORT void JNICALL -Java_sun_nio_ch_FileKey_initIDs(JNIEnv *env, jclass clazz) -{ - CHECK_NULL(key_volumeSN = (*env)->GetFieldID(env, clazz, "dwVolumeSerialNumber", "I")); - CHECK_NULL(key_indexHigh = (*env)->GetFieldID(env, clazz, "nFileIndexHigh", "I")); - CHECK_NULL(key_indexLow = (*env)->GetFieldID(env, clazz, "nFileIndexLow", "I")); -} - - JNIEXPORT void JNICALL -Java_sun_nio_ch_FileKey_init(JNIEnv *env, jobject this, jobject fdo) +Java_sun_nio_ch_FileKey_init(JNIEnv *env, jclass clazz, jobject fdo, jintArray finfo) { - HANDLE fileHandle = (HANDLE)(handleval(env, fdo)); + HANDLE fileHandle = (HANDLE)handleval(env, fdo); BOOL result; BY_HANDLE_FILE_INFORMATION fileInfo; + jint info[3]; result = GetFileInformationByHandle(fileHandle, &fileInfo); if (result) { - (*env)->SetIntField(env, this, key_volumeSN, fileInfo.dwVolumeSerialNumber); - (*env)->SetIntField(env, this, key_indexHigh, fileInfo.nFileIndexHigh); - (*env)->SetIntField(env, this, key_indexLow, fileInfo.nFileIndexLow); + info[0] = (jint)fileInfo.dwVolumeSerialNumber; + info[1] = (jint)fileInfo.nFileIndexHigh; + info[2] = (jint)fileInfo.nFileIndexLow; + (*env)->SetIntArrayRegion(env, finfo, 0, 3, info); } else { JNU_ThrowIOExceptionWithLastError(env, "GetFileInformationByHandle failed"); } From 5d12ac3fcb076bf701d7a572942f57f4de7a9ca0 Mon Sep 17 00:00:00 2001 From: Joe Darcy Date: Fri, 23 Aug 2024 20:01:16 +0000 Subject: [PATCH 20/72] 8337715: Update --release 23 symbol information for JDK 23 build 37 Reviewed-by: iris, liach --- .../share/data/symbols/java.base-N.sym.txt | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/jdk.compiler/share/data/symbols/java.base-N.sym.txt b/src/jdk.compiler/share/data/symbols/java.base-N.sym.txt index fd1bd11f6b9c3..f14c1fb1a4ca5 100644 --- a/src/jdk.compiler/share/data/symbols/java.base-N.sym.txt +++ b/src/jdk.compiler/share/data/symbols/java.base-N.sym.txt @@ -462,18 +462,6 @@ class name java/net/Socket method name descriptor (Ljava/lang/String;IZ)V thrownTypes java/io/IOException flags 1 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(forRemoval=Ztrue,since="1.1") method name descriptor (Ljava/net/InetAddress;IZ)V thrownTypes java/io/IOException flags 1 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(forRemoval=Ztrue,since="1.1") -class name java/nio/HeapByteBuffer -method name hashCode descriptor ()I flags 1 - -class name java/nio/HeapByteBufferR -method name hashCode descriptor ()I flags 1 - -class name java/nio/HeapCharBuffer -method name hashCode descriptor ()I flags 1 - -class name java/nio/HeapCharBufferR -method name hashCode descriptor ()I flags 1 - class name java/security/Provider header extends java/util/Properties nestMembers java/security/Provider$Service flags 421 innerclass innerClass java/util/Map$Entry outerClass java/util/Map innerClassName Entry flags 609 From 32b3d707c1b3a9a0d127684e245e5c975ac5566a Mon Sep 17 00:00:00 2001 From: "Daniel D. Daugherty" Date: Fri, 23 Aug 2024 22:04:43 +0000 Subject: [PATCH 21/72] 8338925: ProblemList runtime/interpreter/LastJsrTest.java on linux-all Reviewed-by: matsaave --- test/hotspot/jtreg/ProblemList.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/test/hotspot/jtreg/ProblemList.txt b/test/hotspot/jtreg/ProblemList.txt index 2414f3d090a18..a991817f95184 100644 --- a/test/hotspot/jtreg/ProblemList.txt +++ b/test/hotspot/jtreg/ProblemList.txt @@ -119,6 +119,7 @@ runtime/ErrorHandling/TestDwarf.java#checkDecoder 8305489 linux-all runtime/ErrorHandling/MachCodeFramesInErrorFile.java 8313315 linux-ppc64le runtime/Thread/TestAlwaysPreTouchStacks.java 8335167 macosx-aarch64 runtime/cds/appcds/customLoader/HelloCustom_JFR.java 8241075 linux-all,windows-x64 +runtime/interpreter/LastJsrTest.java 8338924 linux-all applications/jcstress/copy.java 8229852 linux-all From 5671f836039ef1683e3e9ce5b7cf0fa2f1860e2d Mon Sep 17 00:00:00 2001 From: Sergey Bylokhov Date: Sat, 24 Aug 2024 00:05:30 +0000 Subject: [PATCH 22/72] 8338785: The java.awt.datatransfer.SystemFlavorMap#FLAVOR_MAP_KEY field is not used Reviewed-by: honkar, dnguyen, prr --- .../share/classes/java/awt/datatransfer/SystemFlavorMap.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/java.datatransfer/share/classes/java/awt/datatransfer/SystemFlavorMap.java b/src/java.datatransfer/share/classes/java/awt/datatransfer/SystemFlavorMap.java index d481465138b47..6b2dc14c05fc1 100644 --- a/src/java.datatransfer/share/classes/java/awt/datatransfer/SystemFlavorMap.java +++ b/src/java.datatransfer/share/classes/java/awt/datatransfer/SystemFlavorMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -61,8 +61,6 @@ public final class SystemFlavorMap implements FlavorMap, FlavorTable { */ private static String JavaMIME = "JAVA_DATAFLAVOR:"; - private static final Object FLAVOR_MAP_KEY = new Object(); - /** * The list of valid, decoded text flavor representation classes, in order * from best to worst. From 0c14579fef902f0501d0510bdc32e8cece34834a Mon Sep 17 00:00:00 2001 From: Roland Westrelin Date: Mon, 26 Aug 2024 07:31:04 +0000 Subject: [PATCH 23/72] 8336830: C2: assert(get_loop(lca)->_nest < n_loop->_nest || lca->in(0)->is_NeverBranch()) failed: must not be moved into inner loop Co-authored-by: Emanuel Peter Reviewed-by: thartmann, chagedorn, epeter --- src/hotspot/share/opto/loopopts.cpp | 2 +- .../loopopts/TestSunkNodeInInfiniteLoop.java | 57 +++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 test/hotspot/jtreg/compiler/loopopts/TestSunkNodeInInfiniteLoop.java diff --git a/src/hotspot/share/opto/loopopts.cpp b/src/hotspot/share/opto/loopopts.cpp index fc6c1a34823e5..0e4fa00f1a273 100644 --- a/src/hotspot/share/opto/loopopts.cpp +++ b/src/hotspot/share/opto/loopopts.cpp @@ -1069,7 +1069,7 @@ void PhaseIdealLoop::try_move_store_after_loop(Node* n) { #endif lca = place_outside_loop(lca, n_loop); assert(!n_loop->is_member(get_loop(lca)), "control must not be back in the loop"); - assert(get_loop(lca)->_nest < n_loop->_nest || lca->in(0)->is_NeverBranch(), "must not be moved into inner loop"); + assert(get_loop(lca)->_nest < n_loop->_nest || get_loop(lca)->_head->as_Loop()->is_in_infinite_subgraph(), "must not be moved into inner loop"); // Move store out of the loop _igvn.replace_node(hook, n->in(MemNode::Memory)); diff --git a/test/hotspot/jtreg/compiler/loopopts/TestSunkNodeInInfiniteLoop.java b/test/hotspot/jtreg/compiler/loopopts/TestSunkNodeInInfiniteLoop.java new file mode 100644 index 0000000000000..7ab05cca242d6 --- /dev/null +++ b/test/hotspot/jtreg/compiler/loopopts/TestSunkNodeInInfiniteLoop.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8336830 + * @summary C2: assert(get_loop(lca)->_nest < n_loop->_nest || lca->in(0)->is_NeverBranch()) failed: must not be moved into inner loop + * @library /test/lib + * @run main/othervm -XX:CompileCommand=compileonly,TestSunkNodeInInfiniteLoop::* -Xcomp TestSunkNodeInInfiniteLoop + * + */ + +import jdk.test.lib.Utils; + +public class TestSunkNodeInInfiniteLoop { + public static void main(String[] args) throws InterruptedException { + byte[] a = new byte[1]; + Thread thread = new Thread(() -> test(a)); + thread.setDaemon(true); + thread.start(); + Thread.sleep(Utils.adjustTimeout(4000)); + } + + static void test(byte[] a) { + // L0: + while(true) { + int i1 = a.length; + // L3: + while(true) { + int i2 = 0; + if ((i1--) <= 0) { break; /* ifle L0 */} + a[i2++] = -1; + // goto L3 + } + } + } +} From ce83f6af64efd673b83c945765f68e8a3bf89774 Mon Sep 17 00:00:00 2001 From: Roland Westrelin Date: Mon, 26 Aug 2024 07:32:19 +0000 Subject: [PATCH 24/72] 8338844: C2: remove useless code in PhaseIdealLoop::place_outside_loop() after 8335709 Reviewed-by: chagedorn, thartmann --- src/hotspot/share/opto/loopopts.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/hotspot/share/opto/loopopts.cpp b/src/hotspot/share/opto/loopopts.cpp index 0e4fa00f1a273..b838b15032f70 100644 --- a/src/hotspot/share/opto/loopopts.cpp +++ b/src/hotspot/share/opto/loopopts.cpp @@ -1272,9 +1272,7 @@ Node* PhaseIdealLoop::place_outside_loop(Node* useblock, IdealLoopTree* loop) co // Pick control right outside the loop for (;;) { Node* dom = idom(useblock); - if (loop->is_member(get_loop(dom)) || - // NeverBranch nodes are not assigned to the loop when constructed - (dom->is_NeverBranch() && loop->is_member(get_loop(dom->in(0))))) { + if (loop->is_member(get_loop(dom))) { break; } useblock = dom; From 20d8f58c92009a46dfb91b951e7d87b4cb8e8b41 Mon Sep 17 00:00:00 2001 From: Maurizio Cimadamore Date: Mon, 26 Aug 2024 09:17:45 +0000 Subject: [PATCH 25/72] 8331671: Implement JEP 472: Prepare to Restrict the Use of JNI Reviewed-by: jpai, prr, ihse, kcr, alanb --- make/conf/module-loader-map.conf | 32 ++---------- make/test/BuildTestLib.gmk | 2 +- src/hotspot/share/classfile/vmClassMacros.hpp | 1 + src/hotspot/share/classfile/vmSymbols.hpp | 3 +- src/hotspot/share/prims/nativeLookup.cpp | 24 +++++++-- src/hotspot/share/runtime/arguments.cpp | 7 +++ .../share/classes/java/lang/ClassLoader.java | 21 +++++++- .../share/classes/java/lang/Module.java | 43 +++++++++++----- .../share/classes/java/lang/ModuleLayer.java | 2 +- .../share/classes/java/lang/Runtime.java | 17 +++++-- .../share/classes/java/lang/System.java | 23 ++++++--- .../java/lang/foreign/AddressLayout.java | 2 +- .../classes/java/lang/foreign/Linker.java | 6 +-- .../java/lang/foreign/SymbolLookup.java | 8 +-- .../java/lang/foreign/package-info.java | 9 ++-- .../jdk/internal/access/JavaLangAccess.java | 12 +++-- .../foreign/AbstractMemorySegmentImpl.java | 2 +- .../internal/foreign/abi/AbstractLinker.java | 6 +-- .../foreign/abi/fallback/LibFallback.java | 2 +- .../internal/foreign/layout/ValueLayouts.java | 2 +- .../internal/jimage/NativeImageBuffer.java | 1 + .../jdk/internal/module/ModuleBootstrap.java | 35 +++++++++++-- .../jdk/internal/reflect/Reflection.java | 7 ++- .../launcher/resources/launcher.properties | 5 ++ src/java.base/share/man/java.1 | 46 ++++++++++++++--- .../classes/com/apple/eio/FileManager.java | 2 +- .../classes/com/apple/laf/AquaFileView.java | 2 +- .../com/apple/laf/AquaLookAndFeel.java | 2 +- .../classes/com/apple/laf/AquaMenuBarUI.java | 2 +- .../com/apple/laf/AquaNativeResources.java | 2 +- .../classes/com/apple/laf/ScreenMenu.java | 2 +- .../classes/sun/awt/PlatformGraphicsInfo.java | 2 +- .../sun/lwawt/macosx/CAccessibility.java | 2 +- .../classes/sun/lwawt/macosx/LWCToolkit.java | 2 +- .../imageio/plugins/jpeg/JPEGImageReader.java | 2 +- .../imageio/plugins/jpeg/JPEGImageWriter.java | 2 +- .../classes/com/sun/media/sound/Platform.java | 2 +- .../share/classes/java/awt/SplashScreen.java | 2 +- .../share/classes/java/awt/Toolkit.java | 2 +- .../java/awt/event/NativeLibLoader.java | 2 +- .../classes/java/awt/image/ColorModel.java | 2 +- .../classes/sun/awt/NativeLibLoader.java | 2 +- .../classes/sun/awt/image/ImagingLib.java | 2 +- .../sun/awt/image/JPEGImageDecoder.java | 2 +- .../sun/awt/image/NativeLibLoader.java | 2 +- .../sun/font/FontManagerNativeLibrary.java | 2 +- .../share/classes/sun/java2d/Disposer.java | 2 +- .../classes/sun/java2d/cmm/lcms/LCMS.java | 2 +- .../sun/awt/X11GraphicsEnvironment.java | 2 +- .../unix/classes/sun/print/CUPSPrinter.java | 2 +- .../classes/sun/awt/PlatformGraphicsInfo.java | 2 +- .../classes/sun/awt/windows/WToolkit.java | 2 +- .../sun/print/PrintServiceLookupProvider.java | 2 +- .../sun/instrument/InstrumentationImpl.java | 1 + .../lang/management/ManagementFactory.java | 2 +- .../util/prefs/MacOSXPreferencesFile.java | 2 +- .../util/prefs/FileSystemPreferences.java | 2 +- .../java/util/prefs/WindowsPreferences.java | 2 +- .../share/classes/sun/rmi/transport/GC.java | 2 +- .../jgss/wrapper/SunNativeProvider.java | 2 +- .../sun/security/krb5/Credentials.java | 2 +- .../security/krb5/SCDynamicStoreConfig.java | 2 +- .../security/smartcardio/PlatformPCSC.java | 2 +- .../security/smartcardio/PlatformPCSC.java | 2 +- .../accessibility/internal/AccessBridge.java | 2 +- .../sun/tools/attach/VirtualMachineImpl.java | 1 + .../sun/tools/attach/VirtualMachineImpl.java | 1 + .../sun/tools/attach/VirtualMachineImpl.java | 1 + .../sun/tools/attach/AttachProviderImpl.java | 1 + .../sun/tools/attach/VirtualMachineImpl.java | 1 + .../sun/security/pkcs11/wrapper/PKCS11.java | 2 +- .../sun/security/mscapi/SunMSCAPI.java | 2 +- .../debugger/bsd/BsdDebuggerLocal.java | 1 + .../debugger/linux/LinuxDebuggerLocal.java | 1 + .../debugger/windbg/WindbgDebuggerLocal.java | 1 + .../jdi/SharedMemoryTransportService.java | 1 + .../internal/ExecutableRebrander.java | 2 +- .../jdk/jpackage/internal/WinExeBundler.java | 1 + .../jpackage/internal/WindowsRegistry.java | 1 + .../jdk/internal/agent/FileSystemImpl.java | 2 +- .../jdk/internal/agent/FileSystemImpl.java | 2 +- .../com/sun/management/internal/Flag.java | 2 +- .../internal/PlatformMBeanProviderImpl.java | 2 +- .../aix/classes/jdk/net/AIXSocketOptions.java | 2 +- .../classes/jdk/net/LinuxSocketOptions.java | 2 +- .../classes/jdk/net/MacOSXSocketOptions.java | 2 +- .../classes/jdk/net/WindowsSocketOptions.java | 2 +- .../sun/nio/ch/sctp/SctpChannelImpl.java | 2 +- .../unix/classes/sun/nio/ch/sctp/SctpNet.java | 2 +- .../sun/security/auth/module/NTSystem.java | 2 +- .../sun/security/auth/module/UnixSystem.java | 1 + .../TestCheckedReleaseArrayElements.java | 1 + test/jdk/java/foreign/TestRestricted.java | 6 ++- .../TestEnableNativeAccess.java | 49 ++++++++++++++++--- .../TestEnableNativeAccessBase.java | 12 ++++- .../TestEnableNativeAccessDynamic.java | 3 +- .../panama_jni_def_module/module-info.java | 26 ++++++++++ .../org/openjdk/jni/def/PanamaJNIDef.java | 32 ++++++++++++ .../jni/def}/libLinkerInvokerModule.cpp | 2 +- .../panama_jni_load_module/module-info.java | 27 ++++++++++ .../org/openjdk/jni/PanamaMainJNI.java | 34 +++++++++++++ .../panama_jni_use_module/module-info.java | 27 ++++++++++ .../org/openjdk/jni/use/PanamaJNIUse.java | 41 ++++++++++++++++ .../openjdk/foreigntest/PanamaMainJNI.java | 23 --------- test/jdk/java/foreign/handles/Driver.java | 2 +- .../handle/invoker/MethodHandleInvoker.java | 1 + .../handle/lookup/MethodHandleLookup.java | 12 +++++ 107 files changed, 552 insertions(+), 183 deletions(-) create mode 100644 test/jdk/java/foreign/enablenativeaccess/panama_jni_def_module/module-info.java create mode 100644 test/jdk/java/foreign/enablenativeaccess/panama_jni_def_module/org/openjdk/jni/def/PanamaJNIDef.java rename test/jdk/java/foreign/enablenativeaccess/{panama_module/org/openjdk/foreigntest => panama_jni_def_module/org/openjdk/jni/def}/libLinkerInvokerModule.cpp (94%) create mode 100644 test/jdk/java/foreign/enablenativeaccess/panama_jni_load_module/module-info.java create mode 100644 test/jdk/java/foreign/enablenativeaccess/panama_jni_load_module/org/openjdk/jni/PanamaMainJNI.java create mode 100644 test/jdk/java/foreign/enablenativeaccess/panama_jni_use_module/module-info.java create mode 100644 test/jdk/java/foreign/enablenativeaccess/panama_jni_use_module/org/openjdk/jni/use/PanamaJNIUse.java delete mode 100644 test/jdk/java/foreign/enablenativeaccess/panama_module/org/openjdk/foreigntest/PanamaMainJNI.java diff --git a/make/conf/module-loader-map.conf b/make/conf/module-loader-map.conf index e904031186dee..1062b780a79ff 100644 --- a/make/conf/module-loader-map.conf +++ b/make/conf/module-loader-map.conf @@ -94,48 +94,26 @@ PLATFORM_MODULES_windows= \ NATIVE_ACCESS_MODULES= \ java.base \ - java.datatransfer \ java.desktop \ java.instrument \ - java.logging \ java.management \ - java.management.rmi \ - java.naming \ - java.net.http \ java.prefs \ java.rmi \ - java.scripting \ - java.se \ java.security.jgss \ - java.security.sasl \ java.smartcardio \ - java.sql \ - java.sql.rowset \ - java.transaction.xa \ - java.xml \ - java.xml.crypto \ jdk.accessibility \ - jdk.charsets \ + jdk.attach \ jdk.crypto.cryptoki \ - jdk.dynalink \ - jdk.httpserver \ - jdk.incubator.vector \ + jdk.crypto.mscapi \ + jdk.hotspot.agent \ jdk.internal.le \ jdk.internal.vm.ci \ + jdk.jdi \ jdk.jfr \ - jdk.jsobject \ - jdk.localedata \ + jdk.jpackage \ jdk.management \ jdk.management.agent \ - jdk.management.jfr \ - jdk.naming.dns \ - jdk.naming.rmi \ jdk.net \ - jdk.nio.mapmode \ jdk.sctp \ jdk.security.auth \ - jdk.security.jgss \ - jdk.unsupported \ - jdk.xml.dom \ - jdk.zipfs \ # diff --git a/make/test/BuildTestLib.gmk b/make/test/BuildTestLib.gmk index d48f263f6f96d..dceae073ff385 100644 --- a/make/test/BuildTestLib.gmk +++ b/make/test/BuildTestLib.gmk @@ -64,7 +64,7 @@ $(eval $(call SetupJavaCompilation, BUILD_TEST_LIB_JAR, \ BIN := $(TEST_LIB_SUPPORT)/test-lib_classes, \ HEADERS := $(TEST_LIB_SUPPORT)/test-lib_headers, \ JAR := $(TEST_LIB_SUPPORT)/test-lib.jar, \ - DISABLED_WARNINGS := try deprecation rawtypes unchecked serial cast removal preview dangling-doc-comments, \ + DISABLED_WARNINGS := try deprecation rawtypes unchecked serial cast removal preview restricted dangling-doc-comments, \ JAVAC_FLAGS := --add-exports java.base/sun.security.util=ALL-UNNAMED \ --add-exports java.base/jdk.internal.classfile=ALL-UNNAMED \ --add-exports java.base/jdk.internal.classfile.attribute=ALL-UNNAMED \ diff --git a/src/hotspot/share/classfile/vmClassMacros.hpp b/src/hotspot/share/classfile/vmClassMacros.hpp index 503b595074d80..10fa89007957c 100644 --- a/src/hotspot/share/classfile/vmClassMacros.hpp +++ b/src/hotspot/share/classfile/vmClassMacros.hpp @@ -77,6 +77,7 @@ do_klass(StackOverflowError_klass, java_lang_StackOverflowError ) \ do_klass(IllegalMonitorStateException_klass, java_lang_IllegalMonitorStateException ) \ do_klass(Reference_klass, java_lang_ref_Reference ) \ + do_klass(IllegalCallerException_klass, java_lang_IllegalCallerException ) \ \ /* ref klasses and set reference types */ \ do_klass(SoftReference_klass, java_lang_ref_SoftReference ) \ diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp index c1ba050b454c3..a65ab86fa0a8d 100644 --- a/src/hotspot/share/classfile/vmSymbols.hpp +++ b/src/hotspot/share/classfile/vmSymbols.hpp @@ -203,6 +203,7 @@ class SerializeClosure; template(java_lang_CloneNotSupportedException, "java/lang/CloneNotSupportedException") \ template(java_lang_IllegalAccessException, "java/lang/IllegalAccessException") \ template(java_lang_IllegalArgumentException, "java/lang/IllegalArgumentException") \ + template(java_lang_IllegalCallerException, "java/lang/IllegalCallerException") \ template(java_lang_IllegalStateException, "java/lang/IllegalStateException") \ template(java_lang_IllegalMonitorStateException, "java/lang/IllegalMonitorStateException") \ template(java_lang_IllegalThreadStateException, "java/lang/IllegalThreadStateException") \ @@ -588,7 +589,7 @@ class SerializeClosure; template(string_boolean_class_signature, "(Ljava/lang/String;Z)Ljava/lang/Class;") \ template(object_object_object_signature, "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;") \ template(string_string_signature, "(Ljava/lang/String;)Ljava/lang/String;") \ - template(classloader_string_long_signature, "(Ljava/lang/ClassLoader;Ljava/lang/String;)J") \ + template(classloader_class_string_string_long_signature, "(Ljava/lang/ClassLoader;Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;)J") \ template(byte_array_void_signature, "([B)V") \ template(long_long_void_signature, "(JJ)V") \ template(void_byte_array_signature, "()[B") \ diff --git a/src/hotspot/share/prims/nativeLookup.cpp b/src/hotspot/share/prims/nativeLookup.cpp index 78cf7481abfd0..368d5eb264e1c 100644 --- a/src/hotspot/share/prims/nativeLookup.cpp +++ b/src/hotspot/share/prims/nativeLookup.cpp @@ -273,16 +273,22 @@ address NativeLookup::lookup_style(const methodHandle& method, char* pure_name, // Otherwise call static method findNative in ClassLoader Klass* klass = vmClasses::ClassLoader_klass(); - Handle name_arg = java_lang_String::create_from_str(jni_name, CHECK_NULL); + Handle jni_class(THREAD, method->method_holder()->java_mirror()); + Handle jni_name_arg = java_lang_String::create_from_str(jni_name, CHECK_NULL); + Handle java_name_arg = java_lang_String::create_from_str(method->name()->as_C_string(), CHECK_NULL); + + JavaCallArguments args; + args.push_oop(loader); + args.push_oop(jni_class); + args.push_oop(jni_name_arg); + args.push_oop(java_name_arg); JavaValue result(T_LONG); JavaCalls::call_static(&result, klass, vmSymbols::findNative_name(), - vmSymbols::classloader_string_long_signature(), - // Arguments - loader, - name_arg, + vmSymbols::classloader_class_string_string_long_signature(), + &args, CHECK_NULL); entry = (address) (intptr_t) result.get_jlong(); @@ -409,6 +415,14 @@ address NativeLookup::lookup_base(const methodHandle& method, TRAPS) { entry = lookup_entry_prefixed(method, CHECK_NULL); if (entry != nullptr) return entry; + if (THREAD->has_pending_exception()) { + oop exception = THREAD->pending_exception(); + if (exception->is_a(vmClasses::IllegalCallerException_klass())) { + // we already have a pending exception from the restricted method check, just return + return nullptr; + } + } + // Native function not found, throw UnsatisfiedLinkError stringStream ss; ss.print("'"); diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index 81b40e76a31f2..f4f3d30f3b871 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -305,6 +305,8 @@ bool needs_module_property_warning = false; #define UPGRADE_PATH_LEN 12 #define ENABLE_NATIVE_ACCESS "enable.native.access" #define ENABLE_NATIVE_ACCESS_LEN 20 +#define ILLEGAL_NATIVE_ACCESS "illegal.native.access" +#define ILLEGAL_NATIVE_ACCESS_LEN 21 // Return TRUE if option matches 'property', or 'property=', or 'property.'. static bool matches_property_suffix(const char* option, const char* property, size_t len) { @@ -326,6 +328,7 @@ bool Arguments::is_internal_module_property(const char* property) { matches_property_suffix(property_suffix, LIMITMODS, LIMITMODS_LEN) || matches_property_suffix(property_suffix, PATH, PATH_LEN) || matches_property_suffix(property_suffix, UPGRADE_PATH, UPGRADE_PATH_LEN) || + matches_property_suffix(property_suffix, ILLEGAL_NATIVE_ACCESS, ILLEGAL_NATIVE_ACCESS_LEN) || matches_property_suffix(property_suffix, ENABLE_NATIVE_ACCESS, ENABLE_NATIVE_ACCESS_LEN)) { return true; } @@ -2243,6 +2246,10 @@ jint Arguments::parse_each_vm_init_arg(const JavaVMInitArgs* args, bool* patch_m if (!create_numbered_module_property("jdk.module.enable.native.access", tail, enable_native_access_count++)) { return JNI_ENOMEM; } + } else if (match_option(option, "--illegal-native-access=", &tail)) { + if (!create_module_property("jdk.module.illegal.native.access", tail, InternalProperty)) { + return JNI_ENOMEM; + } } else if (match_option(option, "--limit-modules=", &tail)) { if (!create_module_property("jdk.module.limitmods", tail, InternalProperty)) { return JNI_ENOMEM; diff --git a/src/java.base/share/classes/java/lang/ClassLoader.java b/src/java.base/share/classes/java/lang/ClassLoader.java index 665c7a2567027..5817c37d6f62b 100644 --- a/src/java.base/share/classes/java/lang/ClassLoader.java +++ b/src/java.base/share/classes/java/lang/ClassLoader.java @@ -2442,10 +2442,27 @@ static NativeLibrary loadLibrary(Class fromClass, String name) { " in java.library.path: " + StaticProperty.javaLibraryPath()); } - /* + /** * Invoked in the VM class linking code. + * @param loader the class loader used to look up the native library symbol + * @param clazz the class in which the native method is declared + * @param entryName the native method's mangled name (this is the name used for the native lookup) + * @param javaName the native method's declared name + */ + static long findNative(ClassLoader loader, Class clazz, String entryName, String javaName) { + long addr = findNativeInternal(loader, entryName); + if (addr != 0 && loader != null) { + Reflection.ensureNativeAccess(clazz, clazz, javaName, true); + } + return addr; + } + + /* + * This is also called by SymbolLookup::loaderLookup. In that case, we need + * to avoid a restricted check, as that check has already been performed when + * obtaining the lookup. */ - static long findNative(ClassLoader loader, String entryName) { + static long findNativeInternal(ClassLoader loader, String entryName) { if (loader == null) { return BootLoader.getNativeLibraries().find(entryName); } else { diff --git a/src/java.base/share/classes/java/lang/Module.java b/src/java.base/share/classes/java/lang/Module.java index d7a7081861834..e4cf4a3a6c370 100644 --- a/src/java.base/share/classes/java/lang/Module.java +++ b/src/java.base/share/classes/java/lang/Module.java @@ -62,7 +62,9 @@ import jdk.internal.loader.ClassLoaders; import jdk.internal.misc.CDS; import jdk.internal.misc.Unsafe; +import jdk.internal.misc.VM; import jdk.internal.module.ModuleBootstrap; +import jdk.internal.module.ModuleBootstrap.IllegalNativeAccess; import jdk.internal.module.ModuleLoaderMap; import jdk.internal.module.ServicesCatalog; import jdk.internal.module.Resources; @@ -300,26 +302,43 @@ private Module moduleForNativeAccess() { } // This is invoked from Reflection.ensureNativeAccess - void ensureNativeAccess(Class owner, String methodName, Class currentClass) { + void ensureNativeAccess(Class owner, String methodName, Class currentClass, boolean jni) { // The target module whose enableNativeAccess flag is ensured Module target = moduleForNativeAccess(); - if (!EnableNativeAccess.isNativeAccessEnabled(target)) { - if (ModuleBootstrap.hasEnableNativeAccessFlag()) { - throw new IllegalCallerException("Illegal native access from: " + this); + ModuleBootstrap.IllegalNativeAccess illegalNativeAccess = ModuleBootstrap.illegalNativeAccess(); + if (illegalNativeAccess != ModuleBootstrap.IllegalNativeAccess.ALLOW && + !EnableNativeAccess.isNativeAccessEnabled(target)) { + String mod = isNamed() ? "module " + getName() : "an unnamed module"; + if (currentClass != null) { + // try to extract location of the current class (e.g. jar or folder) + URL url = System.codeSource(currentClass); + if (url != null) { + mod += " (" + url + ")"; + } } - if (EnableNativeAccess.trySetEnableNativeAccess(target)) { + if (illegalNativeAccess == ModuleBootstrap.IllegalNativeAccess.DENY) { + throw new IllegalCallerException("Illegal native access from " + mod); + } else if (EnableNativeAccess.trySetEnableNativeAccess(target)) { // warn and set flag, so that only one warning is reported per module String cls = owner.getName(); String mtd = cls + "::" + methodName; - String mod = isNamed() ? "module " + getName() : "an unnamed module"; String modflag = isNamed() ? getName() : "ALL-UNNAMED"; String caller = currentClass != null ? currentClass.getName() : "code"; - System.err.printf(""" - WARNING: A restricted method in %s has been called - WARNING: %s has been called by %s in %s - WARNING: Use --enable-native-access=%s to avoid a warning for callers in this module - WARNING: Restricted methods will be blocked in a future release unless native access is enabled - %n""", cls, mtd, caller, mod, modflag); + if (jni) { + VM.initialErr().printf(""" + WARNING: A native method in %s has been bound + WARNING: %s is declared in %s + WARNING: Use --enable-native-access=%s to avoid a warning for native methods declared in this module + WARNING: Restricted methods will be blocked in a future release unless native access is enabled + %n""", cls, mtd, mod, modflag); + } else { + VM.initialErr().printf(""" + WARNING: A restricted method in %s has been called + WARNING: %s has been called by %s in %s + WARNING: Use --enable-native-access=%s to avoid a warning for callers in this module + WARNING: Restricted methods will be blocked in a future release unless native access is enabled + %n""", cls, mtd, caller, mod, modflag); + } } } } diff --git a/src/java.base/share/classes/java/lang/ModuleLayer.java b/src/java.base/share/classes/java/lang/ModuleLayer.java index 3a13982c9b93c..80d392470cb33 100644 --- a/src/java.base/share/classes/java/lang/ModuleLayer.java +++ b/src/java.base/share/classes/java/lang/ModuleLayer.java @@ -323,7 +323,7 @@ public Controller addOpens(Module source, String pn, Module target) { public Controller enableNativeAccess(Module target) { ensureInLayer(target); Reflection.ensureNativeAccess(Reflection.getCallerClass(), Module.class, - "enableNativeAccess"); + "enableNativeAccess", false); target.implAddEnableNativeAccess(); return this; } diff --git a/src/java.base/share/classes/java/lang/Runtime.java b/src/java.base/share/classes/java/lang/Runtime.java index e77bf4c41e36c..19cbfa42e0035 100644 --- a/src/java.base/share/classes/java/lang/Runtime.java +++ b/src/java.base/share/classes/java/lang/Runtime.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1995, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1995, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2019, Azul Systems, Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -36,6 +36,7 @@ import java.util.StringTokenizer; import jdk.internal.access.SharedSecrets; +import jdk.internal.javac.Restricted; import jdk.internal.reflect.CallerSensitive; import jdk.internal.reflect.Reflection; @@ -828,14 +829,19 @@ public void runFinalization() { * a native library image by the host system. * @throws NullPointerException if {@code filename} is * {@code null} + * @throws IllegalCallerException if the caller is in a module that + * does not have native access enabled. * @spec jni/index.html Java Native Interface Specification * @see java.lang.Runtime#getRuntime() * @see java.lang.SecurityException * @see java.lang.SecurityManager#checkLink(java.lang.String) */ @CallerSensitive + @Restricted public void load(String filename) { - load0(Reflection.getCallerClass(), filename); + Class caller = Reflection.getCallerClass(); + Reflection.ensureNativeAccess(caller, Runtime.class, "load", false); + load0(caller, filename); } void load0(Class fromClass, String filename) { @@ -894,13 +900,18 @@ void load0(Class fromClass, String filename) { * native library image by the host system. * @throws NullPointerException if {@code libname} is * {@code null} + * @throws IllegalCallerException if the caller is in a module that + * does not have native access enabled. * @spec jni/index.html Java Native Interface Specification * @see java.lang.SecurityException * @see java.lang.SecurityManager#checkLink(java.lang.String) */ @CallerSensitive + @Restricted public void loadLibrary(String libname) { - loadLibrary0(Reflection.getCallerClass(), libname); + Class caller = Reflection.getCallerClass(); + Reflection.ensureNativeAccess(caller, Runtime.class, "loadLibrary", false); + loadLibrary0(caller, libname); } void loadLibrary0(Class fromClass, String libname) { diff --git a/src/java.base/share/classes/java/lang/System.java b/src/java.base/share/classes/java/lang/System.java index 5ff4796505b58..503167bc2dd0d 100644 --- a/src/java.base/share/classes/java/lang/System.java +++ b/src/java.base/share/classes/java/lang/System.java @@ -69,6 +69,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Stream; +import jdk.internal.javac.Restricted; import jdk.internal.logger.LoggerFinderLoader.TemporaryLoggerFinder; import jdk.internal.misc.Blocker; import jdk.internal.misc.CarrierThreadLocal; @@ -355,7 +356,7 @@ private static class CallersHolder { = Collections.synchronizedMap(new WeakHashMap<>()); } - private static URL codeSource(Class clazz) { + static URL codeSource(Class clazz) { PrivilegedAction pa = clazz::getProtectionDomain; @SuppressWarnings("removal") CodeSource cs = AccessController.doPrivileged(pa).getCodeSource(); @@ -2017,14 +2018,19 @@ public static void runFinalization() { * linked with the VM, or the library cannot be mapped to * a native library image by the host system. * @throws NullPointerException if {@code filename} is {@code null} + * @throws IllegalCallerException if the caller is in a module that + * does not have native access enabled. * * @spec jni/index.html Java Native Interface Specification * @see java.lang.Runtime#load(java.lang.String) * @see java.lang.SecurityManager#checkLink(java.lang.String) */ @CallerSensitive + @Restricted public static void load(String filename) { - Runtime.getRuntime().load0(Reflection.getCallerClass(), filename); + Class caller = Reflection.getCallerClass(); + Reflection.ensureNativeAccess(caller, System.class, "load", false); + Runtime.getRuntime().load0(caller, filename); } /** @@ -2055,14 +2061,19 @@ public static void load(String filename) { * linked with the VM, or the library cannot be mapped to a * native library image by the host system. * @throws NullPointerException if {@code libname} is {@code null} + * @throws IllegalCallerException if the caller is in a module that + * does not have native access enabled. * * @spec jni/index.html Java Native Interface Specification * @see java.lang.Runtime#loadLibrary(java.lang.String) * @see java.lang.SecurityManager#checkLink(java.lang.String) */ @CallerSensitive + @Restricted public static void loadLibrary(String libname) { - Runtime.getRuntime().loadLibrary0(Reflection.getCallerClass(), libname); + Class caller = Reflection.getCallerClass(); + Reflection.ensureNativeAccess(caller, System.class, "loadLibrary", false); + Runtime.getRuntime().loadLibrary0(caller, libname); } /** @@ -2539,8 +2550,8 @@ public boolean addEnableNativeAccess(ModuleLayer layer, String name) { public void addEnableNativeAccessToAllUnnamed() { Module.implAddEnableNativeAccessToAllUnnamed(); } - public void ensureNativeAccess(Module m, Class owner, String methodName, Class currentClass) { - m.ensureNativeAccess(owner, methodName, currentClass); + public void ensureNativeAccess(Module m, Class owner, String methodName, Class currentClass, boolean jni) { + m.ensureNativeAccess(owner, methodName, currentClass, jni); } public ServicesCatalog getServicesCatalog(ModuleLayer layer) { return layer.getServicesCatalog(); @@ -2645,7 +2656,7 @@ public Object classData(Class c) { @Override public long findNative(ClassLoader loader, String entry) { - return ClassLoader.findNative(loader, entry); + return ClassLoader.findNativeInternal(loader, entry); } @Override diff --git a/src/java.base/share/classes/java/lang/foreign/AddressLayout.java b/src/java.base/share/classes/java/lang/foreign/AddressLayout.java index 49cab25a75fb5..6e188c5a3171e 100644 --- a/src/java.base/share/classes/java/lang/foreign/AddressLayout.java +++ b/src/java.base/share/classes/java/lang/foreign/AddressLayout.java @@ -108,7 +108,7 @@ public sealed interface AddressLayout extends ValueLayout permits ValueLayouts.O * @param layout the target layout * @return an address layout with same characteristics as this layout, but with the * provided target layout - * @throws IllegalCallerException If the caller is in a module that does not have + * @throws IllegalCallerException if the caller is in a module that does not have * native access enabled * @see #targetLayout() */ diff --git a/src/java.base/share/classes/java/lang/foreign/Linker.java b/src/java.base/share/classes/java/lang/foreign/Linker.java index fd6e820d01662..5474fef66da13 100644 --- a/src/java.base/share/classes/java/lang/foreign/Linker.java +++ b/src/java.base/share/classes/java/lang/foreign/Linker.java @@ -613,7 +613,7 @@ static Linker nativeLinker() { * {@code address.equals(MemorySegment.NULL)} * @throws IllegalArgumentException if an invalid combination of linker options * is given - * @throws IllegalCallerException If the caller is in a module that does not have + * @throws IllegalCallerException if the caller is in a module that does not have * native access enabled * * @see SymbolLookup @@ -684,7 +684,7 @@ MethodHandle downcallHandle(MemorySegment address, * supported by this linker * @throws IllegalArgumentException if an invalid combination of linker options * is given - * @throws IllegalCallerException If the caller is in a module that does not have + * @throws IllegalCallerException if the caller is in a module that does not have * native access enabled */ @CallerSensitive @@ -733,7 +733,7 @@ MethodHandle downcallHandle(MemorySegment address, * @throws IllegalStateException if {@code arena.scope().isAlive() == false} * @throws WrongThreadException if {@code arena} is a confined arena, and this method * is called from a thread {@code T}, other than the arena's owner thread - * @throws IllegalCallerException If the caller is in a module that does not have + * @throws IllegalCallerException if the caller is in a module that does not have * native access enabled */ @CallerSensitive diff --git a/src/java.base/share/classes/java/lang/foreign/SymbolLookup.java b/src/java.base/share/classes/java/lang/foreign/SymbolLookup.java index a8838ea715c92..c6e4443b57007 100644 --- a/src/java.base/share/classes/java/lang/foreign/SymbolLookup.java +++ b/src/java.base/share/classes/java/lang/foreign/SymbolLookup.java @@ -285,14 +285,14 @@ static SymbolLookup loaderLookup() { * @throws WrongThreadException if {@code arena} is a confined arena, and this method * is called from a thread {@code T}, other than the arena's owner thread * @throws IllegalArgumentException if {@code name} does not identify a valid library - * @throws IllegalCallerException If the caller is in a module that does not have + * @throws IllegalCallerException if the caller is in a module that does not have * native access enabled */ @CallerSensitive @Restricted static SymbolLookup libraryLookup(String name, Arena arena) { Reflection.ensureNativeAccess(Reflection.getCallerClass(), - SymbolLookup.class, "libraryLookup"); + SymbolLookup.class, "libraryLookup", false); if (Utils.containsNullChars(name)) { throw new IllegalArgumentException("Cannot open library: " + name); } @@ -319,14 +319,14 @@ static SymbolLookup libraryLookup(String name, Arena arena) { * is called from a thread {@code T}, other than the arena's owner thread * @throws IllegalArgumentException if {@code path} does not point to a valid library * in the default file system - * @throws IllegalCallerException If the caller is in a module that does not have + * @throws IllegalCallerException if the caller is in a module that does not have * native access enabled */ @CallerSensitive @Restricted static SymbolLookup libraryLookup(Path path, Arena arena) { Reflection.ensureNativeAccess(Reflection.getCallerClass(), - SymbolLookup.class, "libraryLookup"); + SymbolLookup.class, "libraryLookup", false); if (path.getFileSystem() != FileSystems.getDefault()) { throw new IllegalArgumentException("Path not in default file system: " + path); } diff --git a/src/java.base/share/classes/java/lang/foreign/package-info.java b/src/java.base/share/classes/java/lang/foreign/package-info.java index 6594826e40524..1f31301638e05 100644 --- a/src/java.base/share/classes/java/lang/foreign/package-info.java +++ b/src/java.base/share/classes/java/lang/foreign/package-info.java @@ -165,10 +165,11 @@ * In the reference implementation, access to restricted methods can be granted to * specific modules using the command line option {@code --enable-native-access=M1,M2, ... Mn}, * where {@code M1}, {@code M2}, {@code ... Mn} are module names (for the unnamed module, - * the special value {@code ALL-UNNAMED} can be used). If this option is specified, - * access to restricted methods are only granted to the modules listed by that option. - * If this option is not specified, access to restricted methods is enabled for all - * modules, but access to restricted methods will result in runtime warnings. + * the special value {@code ALL-UNNAMED} can be used). Access to restricted methods + * from modules not listed by that option is deemed illegal. Clients can + * control how access to restricted methods is handled, using the command line + * option {@code --illegal-native-access}. If this option is not specified, + * illegal access to restricted methods will result in runtime warnings. * * @spec jni/index.html Java Native Interface Specification * diff --git a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java index e4d322a20d729..2a89386f742dc 100644 --- a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java +++ b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java @@ -281,10 +281,14 @@ public interface JavaLangAccess { void addEnableNativeAccessToAllUnnamed(); /** - * Ensure that the given module has native access. If not, warn or - * throw exception depending on the configuration. - */ - void ensureNativeAccess(Module m, Class owner, String methodName, Class currentClass); + * Ensure that the given module has native access. If not, warn or throw exception depending on the configuration. + * @param m the module in which native access occurred + * @param owner the owner of the restricted method being called (or the JNI method being bound) + * @param methodName the name of the restricted method being called (or the JNI method being bound) + * @param currentClass the class calling the restricted method (for JNI, this is the same as {@code owner}) + * @param jni {@code true}, if this event is related to a JNI method being bound + */ + void ensureNativeAccess(Module m, Class owner, String methodName, Class currentClass, boolean jni); /** * Returns the ServicesCatalog for the given Layer. diff --git a/src/java.base/share/classes/jdk/internal/foreign/AbstractMemorySegmentImpl.java b/src/java.base/share/classes/jdk/internal/foreign/AbstractMemorySegmentImpl.java index 42857decf63b1..75be22ac454fa 100644 --- a/src/java.base/share/classes/jdk/internal/foreign/AbstractMemorySegmentImpl.java +++ b/src/java.base/share/classes/jdk/internal/foreign/AbstractMemorySegmentImpl.java @@ -152,7 +152,7 @@ public final MemorySegment reinterpret(Arena arena, Consumer clea } public MemorySegment reinterpretInternal(Class callerClass, long newSize, Scope scope, Consumer cleanup) { - Reflection.ensureNativeAccess(callerClass, MemorySegment.class, "reinterpret"); + Reflection.ensureNativeAccess(callerClass, MemorySegment.class, "reinterpret", false); Utils.checkNonNegativeArgument(newSize, "newSize"); if (!isNative()) throw new UnsupportedOperationException("Not a native segment"); Runnable action = cleanup != null ? diff --git a/src/java.base/share/classes/jdk/internal/foreign/abi/AbstractLinker.java b/src/java.base/share/classes/jdk/internal/foreign/abi/AbstractLinker.java index 4f3baaa0e7118..28391df7a0bd8 100644 --- a/src/java.base/share/classes/jdk/internal/foreign/abi/AbstractLinker.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/AbstractLinker.java @@ -80,7 +80,7 @@ private record LinkRequest(FunctionDescriptor descriptor, LinkerOptions options) @Override @CallerSensitive public final MethodHandle downcallHandle(MemorySegment symbol, FunctionDescriptor function, Option... options) { - Reflection.ensureNativeAccess(Reflection.getCallerClass(), Linker.class, "downcallHandle"); + Reflection.ensureNativeAccess(Reflection.getCallerClass(), Linker.class, "downcallHandle", false); SharedUtils.checkSymbol(symbol); return downcallHandle0(function, options).bindTo(symbol); } @@ -88,7 +88,7 @@ public final MethodHandle downcallHandle(MemorySegment symbol, FunctionDescripto @Override @CallerSensitive public final MethodHandle downcallHandle(FunctionDescriptor function, Option... options) { - Reflection.ensureNativeAccess(Reflection.getCallerClass(), Linker.class, "downcallHandle"); + Reflection.ensureNativeAccess(Reflection.getCallerClass(), Linker.class, "downcallHandle", false); return downcallHandle0(function, options); } @@ -115,7 +115,7 @@ private MethodHandle downcallHandle0(FunctionDescriptor function, Option... opti @Override @CallerSensitive public final MemorySegment upcallStub(MethodHandle target, FunctionDescriptor function, Arena arena, Linker.Option... options) { - Reflection.ensureNativeAccess(Reflection.getCallerClass(), Linker.class, "upcallStub"); + Reflection.ensureNativeAccess(Reflection.getCallerClass(), Linker.class, "upcallStub", false); Objects.requireNonNull(arena); Objects.requireNonNull(target); Objects.requireNonNull(function); diff --git a/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/LibFallback.java b/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/LibFallback.java index 58d6baf852554..19682d6c43f5b 100644 --- a/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/LibFallback.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/LibFallback.java @@ -36,7 +36,7 @@ private LibFallback() {} static final boolean SUPPORTED = tryLoadLibrary(); - @SuppressWarnings("removal") + @SuppressWarnings({"removal", "restricted"}) private static boolean tryLoadLibrary() { return java.security.AccessController.doPrivileged( new java.security.PrivilegedAction<>() { diff --git a/src/java.base/share/classes/jdk/internal/foreign/layout/ValueLayouts.java b/src/java.base/share/classes/jdk/internal/foreign/layout/ValueLayouts.java index 4d19879b01acd..e546773c42903 100644 --- a/src/java.base/share/classes/jdk/internal/foreign/layout/ValueLayouts.java +++ b/src/java.base/share/classes/jdk/internal/foreign/layout/ValueLayouts.java @@ -332,7 +332,7 @@ public int hashCode() { @Override @CallerSensitive public AddressLayout withTargetLayout(MemoryLayout layout) { - Reflection.ensureNativeAccess(Reflection.getCallerClass(), AddressLayout.class, "withTargetLayout"); + Reflection.ensureNativeAccess(Reflection.getCallerClass(), AddressLayout.class, "withTargetLayout", false); Objects.requireNonNull(layout); return new OfAddressImpl(order(), byteSize(), byteAlignment(), layout, name()); } diff --git a/src/java.base/share/classes/jdk/internal/jimage/NativeImageBuffer.java b/src/java.base/share/classes/jdk/internal/jimage/NativeImageBuffer.java index 8d228c050c605..0e3b178c32b0a 100644 --- a/src/java.base/share/classes/jdk/internal/jimage/NativeImageBuffer.java +++ b/src/java.base/share/classes/jdk/internal/jimage/NativeImageBuffer.java @@ -38,6 +38,7 @@ class NativeImageBuffer { static { java.security.AccessController.doPrivileged( new java.security.PrivilegedAction() { + @SuppressWarnings("restricted") public Void run() { System.loadLibrary("jimage"); return null; diff --git a/src/java.base/share/classes/jdk/internal/module/ModuleBootstrap.java b/src/java.base/share/classes/jdk/internal/module/ModuleBootstrap.java index b97b0a2de40b0..facff0d6fdce8 100644 --- a/src/java.base/share/classes/jdk/internal/module/ModuleBootstrap.java +++ b/src/java.base/share/classes/jdk/internal/module/ModuleBootstrap.java @@ -787,17 +787,23 @@ private static void addExtraExportsOrOpens(ModuleLayer bootLayer, } } - private static final boolean HAS_ENABLE_NATIVE_ACCESS_FLAG; private static final Set USER_NATIVE_ACCESS_MODULES; private static final Set JDK_NATIVE_ACCESS_MODULES; + private static final IllegalNativeAccess ILLEGAL_NATIVE_ACCESS; - public static boolean hasEnableNativeAccessFlag() { - return HAS_ENABLE_NATIVE_ACCESS_FLAG; + public enum IllegalNativeAccess { + ALLOW, + WARN, + DENY + } + + public static IllegalNativeAccess illegalNativeAccess() { + return ILLEGAL_NATIVE_ACCESS; } static { + ILLEGAL_NATIVE_ACCESS = addIllegalNativeAccess(); USER_NATIVE_ACCESS_MODULES = decodeEnableNativeAccess(); - HAS_ENABLE_NATIVE_ACCESS_FLAG = !USER_NATIVE_ACCESS_MODULES.isEmpty(); JDK_NATIVE_ACCESS_MODULES = ModuleLoaderMap.nativeAccessModules(); } @@ -847,6 +853,27 @@ private static Set decodeEnableNativeAccess() { return modules; } + /** + * Process the --illegal-native-access option (and its default). + */ + private static IllegalNativeAccess addIllegalNativeAccess() { + String value = getAndRemoveProperty("jdk.module.illegal.native.access"); + // don't use a switch: bootstrapping issues! + if (value == null) { + return IllegalNativeAccess.WARN; // default + } else if (value.equals("deny")) { + return IllegalNativeAccess.DENY; + } else if (value.equals("allow")) { + return IllegalNativeAccess.ALLOW; + } else if (value.equals("warn")) { + return IllegalNativeAccess.WARN; + } else { + fail("Value specified to --illegal-native-access not recognized:" + + " '" + value + "'"); + return null; + } + } + /** * Decodes the values of --add-reads, -add-exports, --add-opens or * --patch-modules options that are encoded in system properties. diff --git a/src/java.base/share/classes/jdk/internal/reflect/Reflection.java b/src/java.base/share/classes/jdk/internal/reflect/Reflection.java index f6fa499538834..88098b11942be 100644 --- a/src/java.base/share/classes/jdk/internal/reflect/Reflection.java +++ b/src/java.base/share/classes/jdk/internal/reflect/Reflection.java @@ -111,7 +111,7 @@ public static void ensureMemberAccess(Class currentClass, } @ForceInline - public static void ensureNativeAccess(Class currentClass, Class owner, String methodName) { + public static void ensureNativeAccess(Class currentClass, Class owner, String methodName, boolean jni) { // if there is no caller class, act as if the call came from unnamed module of system class loader Module module = currentClass != null ? currentClass.getModule() : @@ -119,7 +119,10 @@ public static void ensureNativeAccess(Class currentClass, Class owner, Str class Holder { static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess(); } - Holder.JLA.ensureNativeAccess(module, owner, methodName, currentClass); + if (module != null) { + // not in init phase + Holder.JLA.ensureNativeAccess(module, owner, methodName, currentClass, jni); + } } /** diff --git a/src/java.base/share/classes/sun/launcher/resources/launcher.properties b/src/java.base/share/classes/sun/launcher/resources/launcher.properties index cd524955419a5..71cdb161fc029 100644 --- a/src/java.base/share/classes/sun/launcher/resources/launcher.properties +++ b/src/java.base/share/classes/sun/launcher/resources/launcher.properties @@ -65,6 +65,11 @@ java.launcher.opt.footer = \ \ --enable-native-access [,...]\n\ \ allow code in modules to access code and data outside the Java runtime.\n\ \ can also be ALL-UNNAMED to indicate code on the class path.\n\ +\ --illegal-native-access=\n\ +\ allow or deny access to code and data outside the Java runtime\n\ +\ by code in modules for which native access is not explicitly enabled.\n\ +\ is one of "deny", "warn" or "allow". The default value is "warn".\n\ +\ This option will be removed in a future release.\n\ \ --list-modules\n\ \ list observable modules and exit\n\ \ -d \n\ diff --git a/src/java.base/share/man/java.1 b/src/java.base/share/man/java.1 index 133710abc25db..5896a07304375 100644 --- a/src/java.base/share/man/java.1 +++ b/src/java.base/share/man/java.1 @@ -552,15 +552,45 @@ of the release. Native access involves access to code or data outside the Java runtime. This is generally unsafe and, if done incorrectly, might crash the JVM or result in memory corruption. -Methods that provide native access are restricted, and by default their -use causes warnings. -This option allows code in the specified modules to use restricted -methods without warnings. -\f[I]module\f[R] can be \f[V]ALL-UNNAMED\f[R] to indicate code on the -class path. -When this option is present, any use of restricted methods by code -outside the specified modules causes an +Native access can occur as a result of calling a method that is either +\f[B]restricted\f[R] [https://openjdk.org/jeps/454#Safety], or +\f[V]native\f[R]. +This option allows code in the specified modules to perform native +access. +Native access occurring in a module that has not been explicitly enabled +is deemed \f[I]illegal\f[R]. +.RS +.PP +\f[I]module\f[R] can be a module name, or \f[V]ALL-UNNAMED\f[R] to +indicate code on the class path. +.RE +.TP +-\f[V]--illegal-native-access=\f[R]\f[I]parameter\f[R] +This option specifies a mode for how illegal native access is handled: +.RS +.RS +.PP +\f[B]Note:\f[R] This option will be removed in a future release. +.RE +.IP \[bu] 2 +\f[V]allow\f[R]: This mode allows illegal native access in all modules, +without any warings. +.IP \[bu] 2 +\f[V]warn\f[R]: This mode is identical to \f[V]allow\f[R] except that a +warning message is issued for the first illegal native access found in a +module. +This mode is the default for the current JDK but will change in a future +release. +.IP \[bu] 2 +\f[V]deny\f[R]: This mode disables illegal native access. +That is, any illegal native access causes an \f[V]IllegalCallerException\f[R]. +This mode will become the default in a future release. +.PP +To verify that your application is ready for a future version of the +JDK, run it with \f[V]--illegal-native-access=deny\f[R] along with any +necessary \f[V]--enable-native-access\f[R] options. +.RE .TP \f[V]--finalization=\f[R]\f[I]value\f[R] Controls whether the JVM performs finalization of objects. diff --git a/src/java.desktop/macosx/classes/com/apple/eio/FileManager.java b/src/java.desktop/macosx/classes/com/apple/eio/FileManager.java index 8967a99f15e39..1a77ddd760049 100644 --- a/src/java.desktop/macosx/classes/com/apple/eio/FileManager.java +++ b/src/java.desktop/macosx/classes/com/apple/eio/FileManager.java @@ -58,7 +58,7 @@ public class FileManager { loadOSXLibrary(); } - @SuppressWarnings("removal") + @SuppressWarnings({"removal", "restricted"}) private static void loadOSXLibrary() { java.security.AccessController.doPrivileged( new java.security.PrivilegedAction() { diff --git a/src/java.desktop/macosx/classes/com/apple/laf/AquaFileView.java b/src/java.desktop/macosx/classes/com/apple/laf/AquaFileView.java index 4177e32f63d81..34e5b7c9b7c87 100644 --- a/src/java.desktop/macosx/classes/com/apple/laf/AquaFileView.java +++ b/src/java.desktop/macosx/classes/com/apple/laf/AquaFileView.java @@ -65,7 +65,7 @@ class AquaFileView extends FileView { loadOSXUILibrary(); } - @SuppressWarnings("removal") + @SuppressWarnings({"removal", "restricted"}) private static void loadOSXUILibrary() { java.security.AccessController.doPrivileged( new java.security.PrivilegedAction() { diff --git a/src/java.desktop/macosx/classes/com/apple/laf/AquaLookAndFeel.java b/src/java.desktop/macosx/classes/com/apple/laf/AquaLookAndFeel.java index 83604e5d835cd..9748f9008272f 100644 --- a/src/java.desktop/macosx/classes/com/apple/laf/AquaLookAndFeel.java +++ b/src/java.desktop/macosx/classes/com/apple/laf/AquaLookAndFeel.java @@ -154,7 +154,7 @@ public boolean isSupportedLookAndFeel() { * @see #uninitialize * @see UIManager#setLookAndFeel */ - @SuppressWarnings("removal") + @SuppressWarnings({"removal", "restricted"}) public void initialize() { java.security.AccessController.doPrivileged(new PrivilegedAction() { public Void run() { diff --git a/src/java.desktop/macosx/classes/com/apple/laf/AquaMenuBarUI.java b/src/java.desktop/macosx/classes/com/apple/laf/AquaMenuBarUI.java index 5d6b15671775d..f252e310750c7 100644 --- a/src/java.desktop/macosx/classes/com/apple/laf/AquaMenuBarUI.java +++ b/src/java.desktop/macosx/classes/com/apple/laf/AquaMenuBarUI.java @@ -41,7 +41,7 @@ import sun.security.action.GetBooleanAction; // MenuBar implementation for Mac L&F -@SuppressWarnings("removal") +@SuppressWarnings({"removal", "restricted"}) public class AquaMenuBarUI extends BasicMenuBarUI implements ScreenMenuBarProvider { static { diff --git a/src/java.desktop/macosx/classes/com/apple/laf/AquaNativeResources.java b/src/java.desktop/macosx/classes/com/apple/laf/AquaNativeResources.java index 3e31eca43d0e3..a8abf1a6cd8d0 100644 --- a/src/java.desktop/macosx/classes/com/apple/laf/AquaNativeResources.java +++ b/src/java.desktop/macosx/classes/com/apple/laf/AquaNativeResources.java @@ -32,7 +32,7 @@ import com.apple.laf.AquaUtils.RecyclableSingleton; -@SuppressWarnings("removal") +@SuppressWarnings({"removal", "restricted"}) public class AquaNativeResources { static { java.security.AccessController.doPrivileged( diff --git a/src/java.desktop/macosx/classes/com/apple/laf/ScreenMenu.java b/src/java.desktop/macosx/classes/com/apple/laf/ScreenMenu.java index b3ec61b5c7913..d8d0928c55fcf 100644 --- a/src/java.desktop/macosx/classes/com/apple/laf/ScreenMenu.java +++ b/src/java.desktop/macosx/classes/com/apple/laf/ScreenMenu.java @@ -45,7 +45,7 @@ final class ScreenMenu extends Menu loadAWTLibrary(); } - @SuppressWarnings("removal") + @SuppressWarnings({"removal", "restricted"}) private static void loadAWTLibrary() { java.security.AccessController.doPrivileged( new java.security.PrivilegedAction() { diff --git a/src/java.desktop/macosx/classes/sun/awt/PlatformGraphicsInfo.java b/src/java.desktop/macosx/classes/sun/awt/PlatformGraphicsInfo.java index 796dee199ad01..6af002fc04f70 100644 --- a/src/java.desktop/macosx/classes/sun/awt/PlatformGraphicsInfo.java +++ b/src/java.desktop/macosx/classes/sun/awt/PlatformGraphicsInfo.java @@ -30,7 +30,7 @@ import java.security.AccessController; import java.security.PrivilegedAction; -@SuppressWarnings("removal") +@SuppressWarnings({"removal", "restricted"}) public class PlatformGraphicsInfo { static { diff --git a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CAccessibility.java b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CAccessibility.java index 2da02e34b450f..436ab6138faa4 100644 --- a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CAccessibility.java +++ b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CAccessibility.java @@ -77,7 +77,7 @@ class CAccessibility implements PropertyChangeListener { loadAWTLibrary(); } - @SuppressWarnings("removal") + @SuppressWarnings({"removal", "restricted"}) private static void loadAWTLibrary() { // Need to load the native library for this code. java.security.AccessController.doPrivileged( diff --git a/src/java.desktop/macosx/classes/sun/lwawt/macosx/LWCToolkit.java b/src/java.desktop/macosx/classes/sun/lwawt/macosx/LWCToolkit.java index d430825bb8565..9a9be0e65c7e4 100644 --- a/src/java.desktop/macosx/classes/sun/lwawt/macosx/LWCToolkit.java +++ b/src/java.desktop/macosx/classes/sun/lwawt/macosx/LWCToolkit.java @@ -146,7 +146,7 @@ public final class LWCToolkit extends LWToolkit { static { System.err.flush(); - @SuppressWarnings("removal") + @SuppressWarnings({"removal", "restricted"}) ResourceBundle platformResources = java.security.AccessController.doPrivileged( new java.security.PrivilegedAction() { @Override diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageReader.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageReader.java index e2b3b3537ac06..c28759058c086 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageReader.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageReader.java @@ -90,7 +90,7 @@ public class JPEGImageReader extends ImageReader { initStatic(); } - @SuppressWarnings("removal") + @SuppressWarnings({"removal", "restricted"}) private static void initStatic() { java.security.AccessController.doPrivileged( new java.security.PrivilegedAction() { diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageWriter.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageWriter.java index 0d41c4d2961ee..39189130be356 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageWriter.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageWriter.java @@ -175,7 +175,7 @@ public class JPEGImageWriter extends ImageWriter { initStatic(); } - @SuppressWarnings("removal") + @SuppressWarnings({"removal", "restricted"}) private static void initStatic() { java.security.AccessController.doPrivileged( new java.security.PrivilegedAction() { diff --git a/src/java.desktop/share/classes/com/sun/media/sound/Platform.java b/src/java.desktop/share/classes/com/sun/media/sound/Platform.java index c4387a2109cc9..727718c6ca90f 100644 --- a/src/java.desktop/share/classes/com/sun/media/sound/Platform.java +++ b/src/java.desktop/share/classes/com/sun/media/sound/Platform.java @@ -74,7 +74,7 @@ static boolean isBigEndian() { /** * Load the native library or libraries. */ - @SuppressWarnings("removal") + @SuppressWarnings({"removal", "restricted"}) private static void loadLibraries() { // load the native library isNativeLibLoaded = true; diff --git a/src/java.desktop/share/classes/java/awt/SplashScreen.java b/src/java.desktop/share/classes/java/awt/SplashScreen.java index a7939f4a385c5..78ec4ed7aa0e9 100644 --- a/src/java.desktop/share/classes/java/awt/SplashScreen.java +++ b/src/java.desktop/share/classes/java/awt/SplashScreen.java @@ -121,7 +121,7 @@ public final class SplashScreen { * @return the {@link SplashScreen} instance, or {@code null} if there is * none or it has already been closed */ - @SuppressWarnings("removal") + @SuppressWarnings({"removal", "restricted"}) public static SplashScreen getSplashScreen() { synchronized (SplashScreen.class) { if (GraphicsEnvironment.isHeadless()) { diff --git a/src/java.desktop/share/classes/java/awt/Toolkit.java b/src/java.desktop/share/classes/java/awt/Toolkit.java index 54053fdc2e179..cf6260e9bda21 100644 --- a/src/java.desktop/share/classes/java/awt/Toolkit.java +++ b/src/java.desktop/share/classes/java/awt/Toolkit.java @@ -1375,7 +1375,7 @@ private static void setPlatformResources(ResourceBundle bundle) { * directly. -hung */ private static boolean loaded = false; - @SuppressWarnings("removal") + @SuppressWarnings({"removal", "restricted"}) static void loadLibraries() { if (!loaded) { java.security.AccessController.doPrivileged( diff --git a/src/java.desktop/share/classes/java/awt/event/NativeLibLoader.java b/src/java.desktop/share/classes/java/awt/event/NativeLibLoader.java index 04414f4fa2f3c..27a2c4397476b 100644 --- a/src/java.desktop/share/classes/java/awt/event/NativeLibLoader.java +++ b/src/java.desktop/share/classes/java/awt/event/NativeLibLoader.java @@ -52,7 +52,7 @@ class NativeLibLoader { * For now, we know it's done by the implementation, and we assume * that the name of the library is "awt". -br. */ - @SuppressWarnings("removal") + @SuppressWarnings({"removal", "restricted"}) static void loadLibraries() { java.security.AccessController.doPrivileged( new java.security.PrivilegedAction() { diff --git a/src/java.desktop/share/classes/java/awt/image/ColorModel.java b/src/java.desktop/share/classes/java/awt/image/ColorModel.java index 03ee479951d82..a0aa8fddc10f8 100644 --- a/src/java.desktop/share/classes/java/awt/image/ColorModel.java +++ b/src/java.desktop/share/classes/java/awt/image/ColorModel.java @@ -202,7 +202,7 @@ public abstract class ColorModel implements Transparency{ * that the name of the library is "awt". -br. */ private static boolean loaded = false; - @SuppressWarnings("removal") + @SuppressWarnings({"removal", "restricted"}) static void loadLibraries() { if (!loaded) { java.security.AccessController.doPrivileged( diff --git a/src/java.desktop/share/classes/sun/awt/NativeLibLoader.java b/src/java.desktop/share/classes/sun/awt/NativeLibLoader.java index ef524f0b39aea..868ebdf3699bd 100644 --- a/src/java.desktop/share/classes/sun/awt/NativeLibLoader.java +++ b/src/java.desktop/share/classes/sun/awt/NativeLibLoader.java @@ -52,7 +52,7 @@ class NativeLibLoader { * For now, we know it's done by the implementation, and we assume * that the name of the library is "awt". -br. */ - @SuppressWarnings("removal") + @SuppressWarnings({"removal", "restricted"}) static void loadLibraries() { java.security.AccessController.doPrivileged( new java.security.PrivilegedAction() { diff --git a/src/java.desktop/share/classes/sun/awt/image/ImagingLib.java b/src/java.desktop/share/classes/sun/awt/image/ImagingLib.java index ed09560fc9904..a98e6fc81fbad 100644 --- a/src/java.desktop/share/classes/sun/awt/image/ImagingLib.java +++ b/src/java.desktop/share/classes/sun/awt/image/ImagingLib.java @@ -51,7 +51,7 @@ * (in which case our java code will be executed) or may throw * an exception. */ -@SuppressWarnings("removal") +@SuppressWarnings({"removal", "restricted"}) public class ImagingLib { static boolean useLib = true; diff --git a/src/java.desktop/share/classes/sun/awt/image/JPEGImageDecoder.java b/src/java.desktop/share/classes/sun/awt/image/JPEGImageDecoder.java index eb28029589770..b976a716deda8 100644 --- a/src/java.desktop/share/classes/sun/awt/image/JPEGImageDecoder.java +++ b/src/java.desktop/share/classes/sun/awt/image/JPEGImageDecoder.java @@ -42,7 +42,7 @@ * * @author Jim Graham */ -@SuppressWarnings("removal") +@SuppressWarnings({"removal", "restricted"}) public class JPEGImageDecoder extends ImageDecoder { private static ColorModel RGBcolormodel; private static ColorModel ARGBcolormodel; diff --git a/src/java.desktop/share/classes/sun/awt/image/NativeLibLoader.java b/src/java.desktop/share/classes/sun/awt/image/NativeLibLoader.java index 3cf7f1ba5ef0c..e1fe5658e6377 100644 --- a/src/java.desktop/share/classes/sun/awt/image/NativeLibLoader.java +++ b/src/java.desktop/share/classes/sun/awt/image/NativeLibLoader.java @@ -52,7 +52,7 @@ class NativeLibLoader { * For now, we know it's done by the implementation, and we assume * that the name of the library is "awt". -br. */ - @SuppressWarnings("removal") + @SuppressWarnings({"removal", "restricted"}) static void loadLibraries() { java.security.AccessController.doPrivileged( new java.security.PrivilegedAction() { diff --git a/src/java.desktop/share/classes/sun/font/FontManagerNativeLibrary.java b/src/java.desktop/share/classes/sun/font/FontManagerNativeLibrary.java index ede2d0348b7f6..9439f6ed8574a 100644 --- a/src/java.desktop/share/classes/sun/font/FontManagerNativeLibrary.java +++ b/src/java.desktop/share/classes/sun/font/FontManagerNativeLibrary.java @@ -27,7 +27,7 @@ import sun.awt.OSInfo; -@SuppressWarnings("removal") +@SuppressWarnings({"removal", "restricted"}) public class FontManagerNativeLibrary { static { java.security.AccessController.doPrivileged( diff --git a/src/java.desktop/share/classes/sun/java2d/Disposer.java b/src/java.desktop/share/classes/sun/java2d/Disposer.java index 6b152dda3b028..5a80a61604544 100644 --- a/src/java.desktop/share/classes/sun/java2d/Disposer.java +++ b/src/java.desktop/share/classes/sun/java2d/Disposer.java @@ -50,7 +50,7 @@ * * @see DisposerRecord */ -@SuppressWarnings("removal") +@SuppressWarnings({"removal", "restricted"}) public class Disposer implements Runnable { private static final ReferenceQueue queue = new ReferenceQueue<>(); private static final Hashtable, DisposerRecord> records = diff --git a/src/java.desktop/share/classes/sun/java2d/cmm/lcms/LCMS.java b/src/java.desktop/share/classes/sun/java2d/cmm/lcms/LCMS.java index 9da28ab1548a9..2fe86b3500cf9 100644 --- a/src/java.desktop/share/classes/sun/java2d/cmm/lcms/LCMS.java +++ b/src/java.desktop/share/classes/sun/java2d/cmm/lcms/LCMS.java @@ -143,7 +143,7 @@ private LCMS() {} private static LCMS theLcms = null; - @SuppressWarnings("removal") + @SuppressWarnings({"removal", "restricted"}) static synchronized PCMM getModule() { if (theLcms != null) { return theLcms; diff --git a/src/java.desktop/unix/classes/sun/awt/X11GraphicsEnvironment.java b/src/java.desktop/unix/classes/sun/awt/X11GraphicsEnvironment.java index 93f5eaf6656c0..42b20f6684398 100644 --- a/src/java.desktop/unix/classes/sun/awt/X11GraphicsEnvironment.java +++ b/src/java.desktop/unix/classes/sun/awt/X11GraphicsEnvironment.java @@ -59,7 +59,7 @@ public final class X11GraphicsEnvironment extends SunGraphicsEnvironment { initStatic(); } - @SuppressWarnings("removal") + @SuppressWarnings({"removal", "restricted"}) private static void initStatic() { java.security.AccessController.doPrivileged( new java.security.PrivilegedAction() { diff --git a/src/java.desktop/unix/classes/sun/print/CUPSPrinter.java b/src/java.desktop/unix/classes/sun/print/CUPSPrinter.java index 4d2a4d616aa41..8be118c42a570 100644 --- a/src/java.desktop/unix/classes/sun/print/CUPSPrinter.java +++ b/src/java.desktop/unix/classes/sun/print/CUPSPrinter.java @@ -90,7 +90,7 @@ public class CUPSPrinter { initStatic(); } - @SuppressWarnings("removal") + @SuppressWarnings({"removal", "restricted"}) private static void initStatic() { // load awt library to access native code java.security.AccessController.doPrivileged( diff --git a/src/java.desktop/windows/classes/sun/awt/PlatformGraphicsInfo.java b/src/java.desktop/windows/classes/sun/awt/PlatformGraphicsInfo.java index 4644a2e5f46ac..6d14a72400c0c 100644 --- a/src/java.desktop/windows/classes/sun/awt/PlatformGraphicsInfo.java +++ b/src/java.desktop/windows/classes/sun/awt/PlatformGraphicsInfo.java @@ -39,7 +39,7 @@ public class PlatformGraphicsInfo { hasDisplays = hasDisplays0(); } - @SuppressWarnings("removal") + @SuppressWarnings({"removal", "restricted"}) private static void loadAWTLibrary() { java.security.AccessController.doPrivileged( new java.security.PrivilegedAction() { diff --git a/src/java.desktop/windows/classes/sun/awt/windows/WToolkit.java b/src/java.desktop/windows/classes/sun/awt/windows/WToolkit.java index eff6f930a5be7..ad643ccbd3dfd 100644 --- a/src/java.desktop/windows/classes/sun/awt/windows/WToolkit.java +++ b/src/java.desktop/windows/classes/sun/awt/windows/WToolkit.java @@ -168,7 +168,7 @@ public final class WToolkit extends SunToolkit implements Runnable { */ private static native void initIDs(); private static boolean loaded = false; - @SuppressWarnings("removal") + @SuppressWarnings({"removal", "restricted"}) public static void loadLibraries() { if (!loaded) { java.security.AccessController.doPrivileged( diff --git a/src/java.desktop/windows/classes/sun/print/PrintServiceLookupProvider.java b/src/java.desktop/windows/classes/sun/print/PrintServiceLookupProvider.java index 28055c1afece6..9079e7c2bfeff 100644 --- a/src/java.desktop/windows/classes/sun/print/PrintServiceLookupProvider.java +++ b/src/java.desktop/windows/classes/sun/print/PrintServiceLookupProvider.java @@ -54,7 +54,7 @@ public class PrintServiceLookupProvider extends PrintServiceLookup { loadAWTLibrary(); } - @SuppressWarnings("removal") + @SuppressWarnings({"removal", "restricted"}) private static void loadAWTLibrary() { java.security.AccessController.doPrivileged( new java.security.PrivilegedAction() { diff --git a/src/java.instrument/share/classes/sun/instrument/InstrumentationImpl.java b/src/java.instrument/share/classes/sun/instrument/InstrumentationImpl.java index bd610c3648069..3aeddd1c90d9c 100644 --- a/src/java.instrument/share/classes/sun/instrument/InstrumentationImpl.java +++ b/src/java.instrument/share/classes/sun/instrument/InstrumentationImpl.java @@ -63,6 +63,7 @@ * Keeps a pointer to the native data structure in a scalar field to allow native * processing behind native methods. */ +@SuppressWarnings("restricted") public class InstrumentationImpl implements Instrumentation { private static final String TRACE_USAGE_PROP_NAME = "jdk.instrument.traceUsage"; private static final boolean TRACE_USAGE; diff --git a/src/java.management/share/classes/java/lang/management/ManagementFactory.java b/src/java.management/share/classes/java/lang/management/ManagementFactory.java index 8e290f373b72b..b1ac1d0391e54 100644 --- a/src/java.management/share/classes/java/lang/management/ManagementFactory.java +++ b/src/java.management/share/classes/java/lang/management/ManagementFactory.java @@ -1020,7 +1020,7 @@ static PlatformComponent findSingleton(Class mbeanIntf) loadNativeLib(); } - @SuppressWarnings("removal") + @SuppressWarnings({"removal", "restricted"}) private static void loadNativeLib() { AccessController.doPrivileged((PrivilegedAction) () -> { System.loadLibrary("management"); diff --git a/src/java.prefs/macosx/classes/java/util/prefs/MacOSXPreferencesFile.java b/src/java.prefs/macosx/classes/java/util/prefs/MacOSXPreferencesFile.java index 61ee92cf6013a..c222bc3d81f02 100644 --- a/src/java.prefs/macosx/classes/java/util/prefs/MacOSXPreferencesFile.java +++ b/src/java.prefs/macosx/classes/java/util/prefs/MacOSXPreferencesFile.java @@ -82,7 +82,7 @@ class MacOSXPreferencesFile { loadPrefsLib(); } - @SuppressWarnings("removal") + @SuppressWarnings({"removal", "restricted"}) private static void loadPrefsLib() { java.security.AccessController.doPrivileged( new java.security.PrivilegedAction() { diff --git a/src/java.prefs/unix/classes/java/util/prefs/FileSystemPreferences.java b/src/java.prefs/unix/classes/java/util/prefs/FileSystemPreferences.java index 2a8947146666c..ed76ce57f9472 100644 --- a/src/java.prefs/unix/classes/java/util/prefs/FileSystemPreferences.java +++ b/src/java.prefs/unix/classes/java/util/prefs/FileSystemPreferences.java @@ -53,7 +53,7 @@ class FileSystemPreferences extends AbstractPreferences { loadPrefsLib(); } - @SuppressWarnings("removal") + @SuppressWarnings({"removal", "restricted"}) private static void loadPrefsLib() { PrivilegedAction load = () -> { System.loadLibrary("prefs"); diff --git a/src/java.prefs/windows/classes/java/util/prefs/WindowsPreferences.java b/src/java.prefs/windows/classes/java/util/prefs/WindowsPreferences.java index df1f29c9c1353..885535755a2aa 100644 --- a/src/java.prefs/windows/classes/java/util/prefs/WindowsPreferences.java +++ b/src/java.prefs/windows/classes/java/util/prefs/WindowsPreferences.java @@ -50,7 +50,7 @@ class WindowsPreferences extends AbstractPreferences { loadPrefsLib(); } - @SuppressWarnings("removal") + @SuppressWarnings({"removal", "restricted"}) private static void loadPrefsLib() { PrivilegedAction load = () -> { System.loadLibrary("prefs"); diff --git a/src/java.rmi/share/classes/sun/rmi/transport/GC.java b/src/java.rmi/share/classes/sun/rmi/transport/GC.java index f15a46beacaca..4bd50511dd45a 100644 --- a/src/java.rmi/share/classes/sun/rmi/transport/GC.java +++ b/src/java.rmi/share/classes/sun/rmi/transport/GC.java @@ -39,7 +39,7 @@ * @since 1.2 */ -@SuppressWarnings("removal") +@SuppressWarnings({"removal", "restricted"}) class GC { private GC() { } /* To prevent instantiation */ diff --git a/src/java.security.jgss/share/classes/sun/security/jgss/wrapper/SunNativeProvider.java b/src/java.security.jgss/share/classes/sun/security/jgss/wrapper/SunNativeProvider.java index 2099eaf779e0b..11545a25a6351 100644 --- a/src/java.security.jgss/share/classes/sun/security/jgss/wrapper/SunNativeProvider.java +++ b/src/java.security.jgss/share/classes/sun/security/jgss/wrapper/SunNativeProvider.java @@ -68,7 +68,7 @@ static void debug(String message) { System.err.println(NAME + ": " + message); } - @SuppressWarnings("removal") + @SuppressWarnings({"removal", "restricted"}) private static final HashMap MECH_MAP = AccessController.doPrivileged( new PrivilegedAction<>() { diff --git a/src/java.security.jgss/share/classes/sun/security/krb5/Credentials.java b/src/java.security.jgss/share/classes/sun/security/krb5/Credentials.java index 9ec0b7b7c12fa..d31418ac35104 100644 --- a/src/java.security.jgss/share/classes/sun/security/krb5/Credentials.java +++ b/src/java.security.jgss/share/classes/sun/security/krb5/Credentials.java @@ -524,7 +524,7 @@ public static void printDebug(Credentials c) { } - @SuppressWarnings("removal") + @SuppressWarnings({"removal", "restricted"}) static void ensureLoaded() { java.security.AccessController.doPrivileged( new java.security.PrivilegedAction () { diff --git a/src/java.security.jgss/share/classes/sun/security/krb5/SCDynamicStoreConfig.java b/src/java.security.jgss/share/classes/sun/security/krb5/SCDynamicStoreConfig.java index 41c93afcf4074..1d917c226c600 100644 --- a/src/java.security.jgss/share/classes/sun/security/krb5/SCDynamicStoreConfig.java +++ b/src/java.security.jgss/share/classes/sun/security/krb5/SCDynamicStoreConfig.java @@ -45,7 +45,7 @@ public class SCDynamicStoreConfig { private static native List getKerberosConfig(); static { - @SuppressWarnings("removal") + @SuppressWarnings({"removal", "restricted"}) boolean isMac = java.security.AccessController.doPrivileged( new java.security.PrivilegedAction() { public Boolean run() { diff --git a/src/java.smartcardio/unix/classes/sun/security/smartcardio/PlatformPCSC.java b/src/java.smartcardio/unix/classes/sun/security/smartcardio/PlatformPCSC.java index 10a27e7a1b2eb..93d213bc1495e 100644 --- a/src/java.smartcardio/unix/classes/sun/security/smartcardio/PlatformPCSC.java +++ b/src/java.smartcardio/unix/classes/sun/security/smartcardio/PlatformPCSC.java @@ -61,7 +61,7 @@ class PlatformPCSC { // empty } - @SuppressWarnings("removal") + @SuppressWarnings({"removal", "restricted"}) static final Throwable initException = AccessController.doPrivileged(new PrivilegedAction() { public Throwable run() { diff --git a/src/java.smartcardio/windows/classes/sun/security/smartcardio/PlatformPCSC.java b/src/java.smartcardio/windows/classes/sun/security/smartcardio/PlatformPCSC.java index 8541b67f61312..01cddcf8aa376 100644 --- a/src/java.smartcardio/windows/classes/sun/security/smartcardio/PlatformPCSC.java +++ b/src/java.smartcardio/windows/classes/sun/security/smartcardio/PlatformPCSC.java @@ -41,7 +41,7 @@ class PlatformPCSC { initException = loadLibrary(); } - @SuppressWarnings("removal") + @SuppressWarnings({"removal", "restricted"}) private static Throwable loadLibrary() { try { AccessController.doPrivileged(new PrivilegedAction() { diff --git a/src/jdk.accessibility/windows/classes/com/sun/java/accessibility/internal/AccessBridge.java b/src/jdk.accessibility/windows/classes/com/sun/java/accessibility/internal/AccessBridge.java index 8488fb526ee09..b5a5943d2d907 100644 --- a/src/jdk.accessibility/windows/classes/com/sun/java/accessibility/internal/AccessBridge.java +++ b/src/jdk.accessibility/windows/classes/com/sun/java/accessibility/internal/AccessBridge.java @@ -160,7 +160,7 @@ public final class AccessBridge { initStatic(); } - @SuppressWarnings("removal") + @SuppressWarnings({"removal", "restricted"}) private static void initStatic() { // Load the appropriate DLLs boolean is32on64 = false; diff --git a/src/jdk.attach/aix/classes/sun/tools/attach/VirtualMachineImpl.java b/src/jdk.attach/aix/classes/sun/tools/attach/VirtualMachineImpl.java index d0a6dac40c86f..9f9f96a9416df 100644 --- a/src/jdk.attach/aix/classes/sun/tools/attach/VirtualMachineImpl.java +++ b/src/jdk.attach/aix/classes/sun/tools/attach/VirtualMachineImpl.java @@ -38,6 +38,7 @@ /* * Aix implementation of HotSpotVirtualMachine */ +@SuppressWarnings("restricted") public class VirtualMachineImpl extends HotSpotVirtualMachine { // "/tmp" is used as a global well-known location for the files // .java_pid. and .attach_pid. It is important that this diff --git a/src/jdk.attach/linux/classes/sun/tools/attach/VirtualMachineImpl.java b/src/jdk.attach/linux/classes/sun/tools/attach/VirtualMachineImpl.java index 81d4fd259ed00..4eb29482e29ec 100644 --- a/src/jdk.attach/linux/classes/sun/tools/attach/VirtualMachineImpl.java +++ b/src/jdk.attach/linux/classes/sun/tools/attach/VirtualMachineImpl.java @@ -40,6 +40,7 @@ /* * Linux implementation of HotSpotVirtualMachine */ +@SuppressWarnings("restricted") public class VirtualMachineImpl extends HotSpotVirtualMachine { // "/tmp" is used as a global well-known location for the files // .java_pid. and .attach_pid. It is important that this diff --git a/src/jdk.attach/macosx/classes/sun/tools/attach/VirtualMachineImpl.java b/src/jdk.attach/macosx/classes/sun/tools/attach/VirtualMachineImpl.java index 891a0561a8abd..19104d49014ac 100644 --- a/src/jdk.attach/macosx/classes/sun/tools/attach/VirtualMachineImpl.java +++ b/src/jdk.attach/macosx/classes/sun/tools/attach/VirtualMachineImpl.java @@ -37,6 +37,7 @@ /* * Bsd implementation of HotSpotVirtualMachine */ +@SuppressWarnings("restricted") public class VirtualMachineImpl extends HotSpotVirtualMachine { // "tmpdir" is used as a global well-known location for the files // .java_pid. and .attach_pid. It is important that this diff --git a/src/jdk.attach/windows/classes/sun/tools/attach/AttachProviderImpl.java b/src/jdk.attach/windows/classes/sun/tools/attach/AttachProviderImpl.java index cd40cdc8266c6..503d9592370ab 100644 --- a/src/jdk.attach/windows/classes/sun/tools/attach/AttachProviderImpl.java +++ b/src/jdk.attach/windows/classes/sun/tools/attach/AttachProviderImpl.java @@ -34,6 +34,7 @@ import java.net.InetAddress; import java.net.UnknownHostException; +@SuppressWarnings("restricted") public class AttachProviderImpl extends HotSpotAttachProvider { public AttachProviderImpl() { diff --git a/src/jdk.attach/windows/classes/sun/tools/attach/VirtualMachineImpl.java b/src/jdk.attach/windows/classes/sun/tools/attach/VirtualMachineImpl.java index 184d07137e289..9c30f5618d66e 100644 --- a/src/jdk.attach/windows/classes/sun/tools/attach/VirtualMachineImpl.java +++ b/src/jdk.attach/windows/classes/sun/tools/attach/VirtualMachineImpl.java @@ -35,6 +35,7 @@ /* * Windows implementation of HotSpotVirtualMachine */ +@SuppressWarnings("restricted") public class VirtualMachineImpl extends HotSpotVirtualMachine { // the enqueue code stub (copied into each target VM) diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/PKCS11.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/PKCS11.java index db4a471f33474..f7dfd05500093 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/PKCS11.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/PKCS11.java @@ -83,7 +83,7 @@ public class PKCS11 { // cannot use LoadLibraryAction because that would make the native // library available to the bootclassloader, but we run in the // extension classloader. - @SuppressWarnings("removal") + @SuppressWarnings({"removal", "restricted"}) var dummy = AccessController.doPrivileged(new PrivilegedAction() { public Object run() { System.loadLibrary(PKCS11_WRAPPER); diff --git a/src/jdk.crypto.mscapi/windows/classes/sun/security/mscapi/SunMSCAPI.java b/src/jdk.crypto.mscapi/windows/classes/sun/security/mscapi/SunMSCAPI.java index c66258628210c..e57fe331f2832 100644 --- a/src/jdk.crypto.mscapi/windows/classes/sun/security/mscapi/SunMSCAPI.java +++ b/src/jdk.crypto.mscapi/windows/classes/sun/security/mscapi/SunMSCAPI.java @@ -50,7 +50,7 @@ public final class SunMSCAPI extends Provider { private static final String INFO = "Sun's Microsoft Crypto API provider"; static { - @SuppressWarnings("removal") + @SuppressWarnings({"removal", "restricted"}) var dummy = AccessController.doPrivileged(new PrivilegedAction() { public Void run() { System.loadLibrary("sunmscapi"); diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/bsd/BsdDebuggerLocal.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/bsd/BsdDebuggerLocal.java index 163d253e36051..0ce2a4507ae74 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/bsd/BsdDebuggerLocal.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/bsd/BsdDebuggerLocal.java @@ -62,6 +62,7 @@ can be fetched. The readJ(Type) routines here will throw a RuntimeException if they are called before the debugger is configured with the Java primitive type sizes.

*/ +@SuppressWarnings("restricted") public class BsdDebuggerLocal extends DebuggerBase implements BsdDebugger { private boolean useGCC32ABI; private boolean attached; diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/LinuxDebuggerLocal.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/LinuxDebuggerLocal.java index 5a91c06e83ea4..5bab3d252d03a 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/LinuxDebuggerLocal.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/LinuxDebuggerLocal.java @@ -67,6 +67,7 @@ can be fetched. The readJ(Type) routines here will throw a RuntimeException if they are called before the debugger is configured with the Java primitive type sizes.

*/ +@SuppressWarnings("restricted") public class LinuxDebuggerLocal extends DebuggerBase implements LinuxDebugger { private boolean useGCC32ABI; private boolean attached; diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/windbg/WindbgDebuggerLocal.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/windbg/WindbgDebuggerLocal.java index fa00159f6a86c..f082b7e52c639 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/windbg/WindbgDebuggerLocal.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/windbg/WindbgDebuggerLocal.java @@ -54,6 +54,7 @@ can be fetched. The readJ(Type) routines here will throw a RuntimeException if they are called before the debugger is configured with the Java primitive type sizes.

*/ +@SuppressWarnings("restricted") public class WindbgDebuggerLocal extends DebuggerBase implements WindbgDebugger { private PageCache cache; private boolean attached; diff --git a/src/jdk.jdi/windows/classes/com/sun/tools/jdi/SharedMemoryTransportService.java b/src/jdk.jdi/windows/classes/com/sun/tools/jdi/SharedMemoryTransportService.java index d16ee6e1dd546..ae3ccf7f14921 100644 --- a/src/jdk.jdi/windows/classes/com/sun/tools/jdi/SharedMemoryTransportService.java +++ b/src/jdk.jdi/windows/classes/com/sun/tools/jdi/SharedMemoryTransportService.java @@ -65,6 +65,7 @@ public String toString() { } } + @SuppressWarnings("restricted") SharedMemoryTransportService() { System.loadLibrary("dt_shmem"); initialize(); diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/ExecutableRebrander.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/ExecutableRebrander.java index 060c94b12cf8a..a297f507da84a 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/ExecutableRebrander.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/ExecutableRebrander.java @@ -48,7 +48,7 @@ import static jdk.jpackage.internal.StandardBundlerParam.VERSION; import static jdk.jpackage.internal.WindowsAppImageBuilder.ICON_ICO; - +@SuppressWarnings("restricted") final class ExecutableRebrander { private static final ResourceBundle I18N = ResourceBundle.getBundle( "jdk.jpackage.internal.resources.WinResources"); diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinExeBundler.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinExeBundler.java index c8aae5922873a..fa81b4278b06a 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinExeBundler.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinExeBundler.java @@ -31,6 +31,7 @@ import java.text.MessageFormat; import java.util.Map; +@SuppressWarnings("restricted") public class WinExeBundler extends AbstractBundler { static { diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WindowsRegistry.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WindowsRegistry.java index a62d9c3b68775..7c4b6092901b4 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WindowsRegistry.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WindowsRegistry.java @@ -28,6 +28,7 @@ import java.util.ArrayList; import java.util.List; +@SuppressWarnings("restricted") final class WindowsRegistry { // Currently we only support HKEY_LOCAL_MACHINE. Native implementation will diff --git a/src/jdk.management.agent/unix/classes/jdk/internal/agent/FileSystemImpl.java b/src/jdk.management.agent/unix/classes/jdk/internal/agent/FileSystemImpl.java index 9582a97cb8f79..a11b580d443d8 100644 --- a/src/jdk.management.agent/unix/classes/jdk/internal/agent/FileSystemImpl.java +++ b/src/jdk.management.agent/unix/classes/jdk/internal/agent/FileSystemImpl.java @@ -31,7 +31,7 @@ /* * Linux implementation of jdk.internal.agent.FileSystem */ -@SuppressWarnings("removal") +@SuppressWarnings({"removal", "restricted"}) public class FileSystemImpl extends FileSystem { public boolean supportsFileSecurity(File f) throws IOException { diff --git a/src/jdk.management.agent/windows/classes/jdk/internal/agent/FileSystemImpl.java b/src/jdk.management.agent/windows/classes/jdk/internal/agent/FileSystemImpl.java index f0fd31c0f9e52..5a913f9c0aa01 100644 --- a/src/jdk.management.agent/windows/classes/jdk/internal/agent/FileSystemImpl.java +++ b/src/jdk.management.agent/windows/classes/jdk/internal/agent/FileSystemImpl.java @@ -31,7 +31,7 @@ /* * Windows implementation of sun.management.FileSystem */ -@SuppressWarnings("removal") +@SuppressWarnings({"removal", "restricted"}) public class FileSystemImpl extends FileSystem { public boolean supportsFileSecurity(File f) throws IOException { diff --git a/src/jdk.management/share/classes/com/sun/management/internal/Flag.java b/src/jdk.management/share/classes/com/sun/management/internal/Flag.java index 6fb2c80247dcc..bf83f40f722c1 100644 --- a/src/jdk.management/share/classes/com/sun/management/internal/Flag.java +++ b/src/jdk.management/share/classes/com/sun/management/internal/Flag.java @@ -36,7 +36,7 @@ * corresponds to one VMOption. * */ -@SuppressWarnings("removal") +@SuppressWarnings({"removal", "restricted"}) class Flag { private String name; private Object value; diff --git a/src/jdk.management/share/classes/com/sun/management/internal/PlatformMBeanProviderImpl.java b/src/jdk.management/share/classes/com/sun/management/internal/PlatformMBeanProviderImpl.java index cddb9127d2e9b..cdc5998426d9a 100644 --- a/src/jdk.management/share/classes/com/sun/management/internal/PlatformMBeanProviderImpl.java +++ b/src/jdk.management/share/classes/com/sun/management/internal/PlatformMBeanProviderImpl.java @@ -44,7 +44,7 @@ import sun.management.ManagementFactoryHelper; import sun.management.spi.PlatformMBeanProvider; -@SuppressWarnings("removal") +@SuppressWarnings({"removal", "restricted"}) public final class PlatformMBeanProviderImpl extends PlatformMBeanProvider { static final String DIAGNOSTIC_COMMAND_MBEAN_NAME = "com.sun.management:type=DiagnosticCommand"; diff --git a/src/jdk.net/aix/classes/jdk/net/AIXSocketOptions.java b/src/jdk.net/aix/classes/jdk/net/AIXSocketOptions.java index 9ace5aa33f2e0..086c346ff8f97 100644 --- a/src/jdk.net/aix/classes/jdk/net/AIXSocketOptions.java +++ b/src/jdk.net/aix/classes/jdk/net/AIXSocketOptions.java @@ -32,7 +32,7 @@ import jdk.net.ExtendedSocketOptions.PlatformSocketOptions; import sun.nio.fs.UnixUserPrincipals; -@SuppressWarnings("removal") +@SuppressWarnings({"removal", "restricted"}) class AIXSocketOptions extends PlatformSocketOptions { public AIXSocketOptions() { diff --git a/src/jdk.net/linux/classes/jdk/net/LinuxSocketOptions.java b/src/jdk.net/linux/classes/jdk/net/LinuxSocketOptions.java index 20241bc5f334d..8d3ceeebfa9a3 100644 --- a/src/jdk.net/linux/classes/jdk/net/LinuxSocketOptions.java +++ b/src/jdk.net/linux/classes/jdk/net/LinuxSocketOptions.java @@ -32,7 +32,7 @@ import jdk.net.ExtendedSocketOptions.PlatformSocketOptions; import sun.nio.fs.UnixUserPrincipals; -@SuppressWarnings("removal") +@SuppressWarnings({"removal", "restricted"}) class LinuxSocketOptions extends PlatformSocketOptions { public LinuxSocketOptions() { diff --git a/src/jdk.net/macosx/classes/jdk/net/MacOSXSocketOptions.java b/src/jdk.net/macosx/classes/jdk/net/MacOSXSocketOptions.java index 7ad4bc7650ae7..c2912e8b80808 100644 --- a/src/jdk.net/macosx/classes/jdk/net/MacOSXSocketOptions.java +++ b/src/jdk.net/macosx/classes/jdk/net/MacOSXSocketOptions.java @@ -32,7 +32,7 @@ import jdk.net.ExtendedSocketOptions.PlatformSocketOptions; import sun.nio.fs.UnixUserPrincipals; -@SuppressWarnings("removal") +@SuppressWarnings({"removal", "restricted"}) class MacOSXSocketOptions extends PlatformSocketOptions { public MacOSXSocketOptions() { diff --git a/src/jdk.net/windows/classes/jdk/net/WindowsSocketOptions.java b/src/jdk.net/windows/classes/jdk/net/WindowsSocketOptions.java index 543d584bfe5a1..f5f69e205176f 100644 --- a/src/jdk.net/windows/classes/jdk/net/WindowsSocketOptions.java +++ b/src/jdk.net/windows/classes/jdk/net/WindowsSocketOptions.java @@ -30,7 +30,7 @@ import jdk.net.ExtendedSocketOptions.PlatformSocketOptions; -@SuppressWarnings("removal") +@SuppressWarnings({"removal", "restricted"}) class WindowsSocketOptions extends PlatformSocketOptions { public WindowsSocketOptions() { diff --git a/src/jdk.sctp/unix/classes/sun/nio/ch/sctp/SctpChannelImpl.java b/src/jdk.sctp/unix/classes/sun/nio/ch/sctp/SctpChannelImpl.java index 4355605e25815..2e12e67c6c72c 100644 --- a/src/jdk.sctp/unix/classes/sun/nio/ch/sctp/SctpChannelImpl.java +++ b/src/jdk.sctp/unix/classes/sun/nio/ch/sctp/SctpChannelImpl.java @@ -1094,7 +1094,7 @@ static native int send0(int fd, long address, int length, loadSctpLibrary(); } - @SuppressWarnings("removal") + @SuppressWarnings({"removal", "restricted"}) private static void loadSctpLibrary() { IOUtil.load(); /* loads nio & net native libraries */ AccessController.doPrivileged( diff --git a/src/jdk.sctp/unix/classes/sun/nio/ch/sctp/SctpNet.java b/src/jdk.sctp/unix/classes/sun/nio/ch/sctp/SctpNet.java index 239e09837f7a9..decf964c6cb72 100644 --- a/src/jdk.sctp/unix/classes/sun/nio/ch/sctp/SctpNet.java +++ b/src/jdk.sctp/unix/classes/sun/nio/ch/sctp/SctpNet.java @@ -333,7 +333,7 @@ static native void setInitMsgOption0(int fd, int arg1, int arg2) loadSctpLibrary(); } - @SuppressWarnings("removal") + @SuppressWarnings({"removal", "restricted"}) private static void loadSctpLibrary() { IOUtil.load(); // loads nio & net native libraries java.security.AccessController.doPrivileged( diff --git a/src/jdk.security.auth/share/classes/com/sun/security/auth/module/NTSystem.java b/src/jdk.security.auth/share/classes/com/sun/security/auth/module/NTSystem.java index 0814f0f733282..677d4aefc8663 100644 --- a/src/jdk.security.auth/share/classes/com/sun/security/auth/module/NTSystem.java +++ b/src/jdk.security.auth/share/classes/com/sun/security/auth/module/NTSystem.java @@ -129,7 +129,7 @@ public synchronized long getImpersonationToken() { return impersonationToken; } - + @SuppressWarnings("restricted") private void loadNative() { System.loadLibrary("jaas"); } diff --git a/src/jdk.security.auth/share/classes/com/sun/security/auth/module/UnixSystem.java b/src/jdk.security.auth/share/classes/com/sun/security/auth/module/UnixSystem.java index 96bc1fb32fce4..f3741c1040401 100644 --- a/src/jdk.security.auth/share/classes/com/sun/security/auth/module/UnixSystem.java +++ b/src/jdk.security.auth/share/classes/com/sun/security/auth/module/UnixSystem.java @@ -53,6 +53,7 @@ public class UnixSystem { * Instantiate a {@code UnixSystem} and load * the native library to access the underlying system information. */ + @SuppressWarnings("restricted") public UnixSystem() { System.loadLibrary("jaas"); getUnixInfo(); diff --git a/test/hotspot/jtreg/runtime/jni/checked/TestCheckedReleaseArrayElements.java b/test/hotspot/jtreg/runtime/jni/checked/TestCheckedReleaseArrayElements.java index 6bc09b8a03451..07e46a1dfcac0 100644 --- a/test/hotspot/jtreg/runtime/jni/checked/TestCheckedReleaseArrayElements.java +++ b/test/hotspot/jtreg/runtime/jni/checked/TestCheckedReleaseArrayElements.java @@ -49,6 +49,7 @@ public static void main(String[] args) throws Throwable { // that might generate output on stderr (which should be empty for this test). ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder("-Xcheck:jni", + "--enable-native-access=ALL-UNNAMED", "-Djava.library.path=" + Utils.TEST_NATIVE_PATH, "TestCheckedReleaseArrayElements"); OutputAnalyzer output = ProcessTools.executeProcess(pb); diff --git a/test/jdk/java/foreign/TestRestricted.java b/test/jdk/java/foreign/TestRestricted.java index beccd89582978..b5b0974e57ab2 100644 --- a/test/jdk/java/foreign/TestRestricted.java +++ b/test/jdk/java/foreign/TestRestricted.java @@ -88,7 +88,11 @@ static RestrictedMethod of(Class owner, String name, Class returnType, Cla RestrictedMethod.of(MemorySegment.class, "reinterpret", MemorySegment.class, Arena.class, Consumer.class), RestrictedMethod.of(MemorySegment.class, "reinterpret", MemorySegment.class, long.class, Arena.class, Consumer.class), RestrictedMethod.of(AddressLayout.class, "withTargetLayout", AddressLayout.class, MemoryLayout.class), - RestrictedMethod.of(ModuleLayer.Controller.class, "enableNativeAccess", ModuleLayer.Controller.class, Module.class) + RestrictedMethod.of(ModuleLayer.Controller.class, "enableNativeAccess", ModuleLayer.Controller.class, Module.class), + RestrictedMethod.of(System.class, "load", void.class, String.class), + RestrictedMethod.of(System.class, "loadLibrary", void.class, String.class), + RestrictedMethod.of(Runtime.class, "load", void.class, String.class), + RestrictedMethod.of(Runtime.class, "loadLibrary", void.class, String.class) ); @Test diff --git a/test/jdk/java/foreign/enablenativeaccess/TestEnableNativeAccess.java b/test/jdk/java/foreign/enablenativeaccess/TestEnableNativeAccess.java index 9dd228f5152b6..230b32968da0a 100644 --- a/test/jdk/java/foreign/enablenativeaccess/TestEnableNativeAccess.java +++ b/test/jdk/java/foreign/enablenativeaccess/TestEnableNativeAccess.java @@ -28,6 +28,10 @@ * @library /test/lib * @build TestEnableNativeAccess * panama_module/* + * panama_jni_load_module/* + * panama_jni_def_module/* + * panama_jni_use_module/* + * * org.openjdk.foreigntest.unnamed.PanamaMainUnnamedModule * @run testng/othervm/timeout=180 TestEnableNativeAccess * @summary Basic test for java --enable-native-access @@ -62,20 +66,25 @@ public Object[][] succeedCases() { { "panama_enable_native_access", PANAMA_MAIN, successNoWarning(), new String[]{"--enable-native-access=panama_module"} }, { "panama_enable_native_access_reflection", PANAMA_REFLECTION, successNoWarning(), new String[]{"--enable-native-access=panama_module"} }, { "panama_enable_native_access_invoke", PANAMA_INVOKE, successNoWarning(), new String[]{"--enable-native-access=panama_module"} }, - { "panama_enable_native_access_jni", PANAMA_JNI, successNoWarning(), new String[]{"--enable-native-access=ALL-UNNAMED"} }, { "panama_comma_separated_enable", PANAMA_MAIN, successNoWarning(), new String[]{"--enable-native-access=java.base,panama_module"} }, { "panama_comma_separated_enable_reflection", PANAMA_REFLECTION, successNoWarning(), new String[]{"--enable-native-access=java.base,panama_module"} }, { "panama_comma_separated_enable_invoke", PANAMA_INVOKE, successNoWarning(), new String[]{"--enable-native-access=java.base,panama_module"} }, - { "panama_comma_separated_enable_jni", PANAMA_JNI, successNoWarning(), new String[]{"--enable-native-access=java.base,ALL-UNNAMED"} }, + { "panama_comma_separated_enable_jni", PANAMA_JNI, successNoWarning(), new String[]{"--enable-native-access=panama_jni_load_module,panama_jni_def_module,ALL-UNNAMED"} }, { "panama_enable_native_access_warn", PANAMA_MAIN, successWithWarning("panama"), new String[]{} }, { "panama_enable_native_access_warn_reflection", PANAMA_REFLECTION, successWithWarning("panama"), new String[]{} }, { "panama_enable_native_access_warn_invoke", PANAMA_INVOKE, successWithWarning("panama"), new String[]{} }, - { "panama_enable_native_access_warn_jni", PANAMA_JNI, successWithWarning("ALL-UNNAMED"), new String[]{} }, + { "panama_enable_native_access_warn_jni", PANAMA_JNI, successWithWarnings("panama_jni_load_module", "panama_jni_def_module", "ALL-UNNAMED"), new String[]{} }, + + { "panama_enable_native_access_allow", PANAMA_MAIN, successNoWarning(), new String[]{"--illegal-native-access=allow"} }, + { "panama_enable_native_access_allow_reflection", PANAMA_REFLECTION, successNoWarning(), new String[]{"--illegal-native-access=allow"} }, + { "panama_enable_native_access_allow_invoke", PANAMA_INVOKE, successNoWarning(), new String[]{"--illegal-native-access=allow"} }, + { "panama_enable_native_access_allow_jni", PANAMA_JNI, successNoWarning(), new String[]{"--illegal-native-access=allow"} }, { "panama_no_unnamed_module_native_access", UNNAMED, successWithWarning("ALL-UNNAMED"), new String[]{} }, { "panama_all_unnamed_module_native_access", UNNAMED, successNoWarning(), new String[]{"--enable-native-access=ALL-UNNAMED"} }, + { "panama_allow_unnamed_module_native_access", UNNAMED, successNoWarning(), new String[]{"--illegal-native-access=allow"} }, }; } @@ -131,12 +140,38 @@ public void testRepeatedOption() throws Exception { * Specifies bad value to --enable-native-access. */ public void testBadValue() throws Exception { - run("panama_enable_native_access_warn_unknown_module", PANAMA_MAIN, + run("panama_deny_bad_unknown_module", PANAMA_MAIN, failWithWarning("WARNING: Unknown module: BAD specified to --enable-native-access"), - "--enable-native-access=BAD"); - run("panama_no_all_module_path_blanket_native_access", PANAMA_MAIN, + "--illegal-native-access=deny", "--enable-native-access=BAD"); + run("panama_deny_bad_all_module_path_module", PANAMA_MAIN, failWithWarning("WARNING: Unknown module: ALL-MODULE-PATH specified to --enable-native-access"), - "--enable-native-access=ALL-MODULE-PATH" ); + "--illegal-native-access=deny", "--enable-native-access=ALL-MODULE-PATH" ); + run("panama_deny_no_module_main", PANAMA_MAIN, + failWithError("module panama_module"), + "--illegal-native-access=deny"); + run("panama_deny_no_module_invoke", PANAMA_INVOKE, + failWithError("module panama_module"), + "--illegal-native-access=deny"); + run("panama_deny_no_module_reflection", PANAMA_REFLECTION, + failWithError("module panama_module"), + "--illegal-native-access=deny"); + run("panama_deny_no_module_jni", PANAMA_JNI, + failWithError("module panama_jni_load_module"), + "--illegal-native-access=deny"); + } + + public void testDetailedWarningMessage() throws Exception { + run("panama_enable_native_access_warn_jni", PANAMA_JNI, + success() + // call to System::loadLibrary from panama_jni_load_module + .expect("WARNING: A restricted method in java.lang.System has been called") + .expect("WARNING: java.lang.System::loadLibrary has been called by org.openjdk.jni.PanamaMainJNI in module panama_jni_load_module") + // JNI native method binding in panama_jni_def_module + .expect("WARNING: A native method in org.openjdk.jni.def.PanamaJNIDef has been bound") + .expect("WARNING: org.openjdk.jni.def.PanamaJNIDef::nativeLinker0 is declared in module panama_jni_def_module") + // upcall to Linker::downcallHandle from JNI code + .expect("WARNING: A restricted method in java.lang.foreign.Linker has been called") + .expect("WARNING: java.lang.foreign.Linker::downcallHandle has been called by code in an unnamed module")); } private int count(Iterable lines, CharSequence cs) { diff --git a/test/jdk/java/foreign/enablenativeaccess/TestEnableNativeAccessBase.java b/test/jdk/java/foreign/enablenativeaccess/TestEnableNativeAccessBase.java index b5afb727997ba..5f02e7cc4ace7 100644 --- a/test/jdk/java/foreign/enablenativeaccess/TestEnableNativeAccessBase.java +++ b/test/jdk/java/foreign/enablenativeaccess/TestEnableNativeAccessBase.java @@ -38,8 +38,8 @@ public class TestEnableNativeAccessBase { static final String PANAMA_REFLECTION = "panama_module/" + PANAMA_REFLECTION_CLS; static final String PANAMA_INVOKE_CLS = "org.openjdk.foreigntest.PanamaMainInvoke"; static final String PANAMA_INVOKE = "panama_module/" + PANAMA_INVOKE_CLS; - static final String PANAMA_JNI_CLS = "org.openjdk.foreigntest.PanamaMainJNI"; - static final String PANAMA_JNI = "panama_module/" + PANAMA_JNI_CLS; + static final String PANAMA_JNI_CLS = "org.openjdk.jni.PanamaMainJNI"; + static final String PANAMA_JNI = "panama_jni_load_module/" + PANAMA_JNI_CLS; static final String UNNAMED = "org.openjdk.foreigntest.unnamed.PanamaMainUnnamedModule"; /** @@ -99,6 +99,14 @@ static Result successWithWarning(String moduleName) { return success().expect("WARNING").expect("--enable-native-access=" + moduleName); } + static Result successWithWarnings(String... moduleNames) { + Result result = success(); + for (String moduleName : moduleNames) { + result = result.expect("WARNING").expect("--enable-native-access=" + moduleName); + } + return result; + } + static Result failWithWarning(String expectedOutput) { return new Result(false).expect(expectedOutput).expect("WARNING"); } diff --git a/test/jdk/java/foreign/enablenativeaccess/TestEnableNativeAccessDynamic.java b/test/jdk/java/foreign/enablenativeaccess/TestEnableNativeAccessDynamic.java index fa09b0eb41454..f0a8d463a7fc0 100644 --- a/test/jdk/java/foreign/enablenativeaccess/TestEnableNativeAccessDynamic.java +++ b/test/jdk/java/foreign/enablenativeaccess/TestEnableNativeAccessDynamic.java @@ -56,7 +56,7 @@ public Object[][] succeedCases() { @DataProvider(name = "failureCases") public Object[][] failureCases() { - String errMsg = "Illegal native access from: module panama_module"; + String errMsg = "Illegal native access from module panama_module"; return new Object[][] { { "panama_enable_native_access_fail", PANAMA_MAIN, failWithError(errMsg) }, { "panama_enable_native_access_fail_reflection", PANAMA_REFLECTION, failWithError(errMsg) }, @@ -73,6 +73,7 @@ OutputAnalyzer run(String action, String moduleAndCls, boolean enableNativeAcces Result expectedResult, boolean panamaModuleInBootLayer) throws Exception { List list = new ArrayList<>(); + list.add("--illegal-native-access=deny"); if (panamaModuleInBootLayer) { list.addAll(List.of("-p", MODULE_PATH)); list.add("--add-modules=panama_module"); diff --git a/test/jdk/java/foreign/enablenativeaccess/panama_jni_def_module/module-info.java b/test/jdk/java/foreign/enablenativeaccess/panama_jni_def_module/module-info.java new file mode 100644 index 0000000000000..85c00ce5daec3 --- /dev/null +++ b/test/jdk/java/foreign/enablenativeaccess/panama_jni_def_module/module-info.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +module panama_jni_def_module { + exports org.openjdk.jni.def; +} diff --git a/test/jdk/java/foreign/enablenativeaccess/panama_jni_def_module/org/openjdk/jni/def/PanamaJNIDef.java b/test/jdk/java/foreign/enablenativeaccess/panama_jni_def_module/org/openjdk/jni/def/PanamaJNIDef.java new file mode 100644 index 0000000000000..402ce9278e9ee --- /dev/null +++ b/test/jdk/java/foreign/enablenativeaccess/panama_jni_def_module/org/openjdk/jni/def/PanamaJNIDef.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.jni.def; + +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.Linker; + +public class PanamaJNIDef { + + public static native void nativeLinker0(Linker linker, FunctionDescriptor desc, Linker.Option[] options); +} diff --git a/test/jdk/java/foreign/enablenativeaccess/panama_module/org/openjdk/foreigntest/libLinkerInvokerModule.cpp b/test/jdk/java/foreign/enablenativeaccess/panama_jni_def_module/org/openjdk/jni/def/libLinkerInvokerModule.cpp similarity index 94% rename from test/jdk/java/foreign/enablenativeaccess/panama_module/org/openjdk/foreigntest/libLinkerInvokerModule.cpp rename to test/jdk/java/foreign/enablenativeaccess/panama_jni_def_module/org/openjdk/jni/def/libLinkerInvokerModule.cpp index 4591d7a506ae3..c46e6e6acdba8 100644 --- a/test/jdk/java/foreign/enablenativeaccess/panama_module/org/openjdk/foreigntest/libLinkerInvokerModule.cpp +++ b/test/jdk/java/foreign/enablenativeaccess/panama_jni_def_module/org/openjdk/jni/def/libLinkerInvokerModule.cpp @@ -47,7 +47,7 @@ void call(void* arg) { extern "C" { JNIEXPORT void JNICALL - Java_org_openjdk_foreigntest_PanamaMainJNI_nativeLinker0(JNIEnv *env, jclass cls, jobject linker, jobject desc, jobjectArray opts) { + Java_org_openjdk_jni_def_PanamaJNIDef_nativeLinker0(JNIEnv *env, jclass cls, jobject linker, jobject desc, jobjectArray opts) { Context context; env->GetJavaVM(&context.jvm); context.linker = env->NewGlobalRef(linker); diff --git a/test/jdk/java/foreign/enablenativeaccess/panama_jni_load_module/module-info.java b/test/jdk/java/foreign/enablenativeaccess/panama_jni_load_module/module-info.java new file mode 100644 index 0000000000000..87e4d8dac050b --- /dev/null +++ b/test/jdk/java/foreign/enablenativeaccess/panama_jni_load_module/module-info.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +module panama_jni_load_module { + exports org.openjdk.jni; + requires panama_jni_use_module; +} diff --git a/test/jdk/java/foreign/enablenativeaccess/panama_jni_load_module/org/openjdk/jni/PanamaMainJNI.java b/test/jdk/java/foreign/enablenativeaccess/panama_jni_load_module/org/openjdk/jni/PanamaMainJNI.java new file mode 100644 index 0000000000000..e50dedf181e9b --- /dev/null +++ b/test/jdk/java/foreign/enablenativeaccess/panama_jni_load_module/org/openjdk/jni/PanamaMainJNI.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.jni; + +import org.openjdk.jni.use.PanamaJNIUse; + +public class PanamaMainJNI { + + public static void main(String[] args) { + System.loadLibrary("LinkerInvokerModule"); + PanamaJNIUse.run(); + } +} diff --git a/test/jdk/java/foreign/enablenativeaccess/panama_jni_use_module/module-info.java b/test/jdk/java/foreign/enablenativeaccess/panama_jni_use_module/module-info.java new file mode 100644 index 0000000000000..daeca16c132fd --- /dev/null +++ b/test/jdk/java/foreign/enablenativeaccess/panama_jni_use_module/module-info.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +module panama_jni_use_module { + exports org.openjdk.jni.use; + requires panama_jni_def_module; +} diff --git a/test/jdk/java/foreign/enablenativeaccess/panama_jni_use_module/org/openjdk/jni/use/PanamaJNIUse.java b/test/jdk/java/foreign/enablenativeaccess/panama_jni_use_module/org/openjdk/jni/use/PanamaJNIUse.java new file mode 100644 index 0000000000000..2445b4951dc59 --- /dev/null +++ b/test/jdk/java/foreign/enablenativeaccess/panama_jni_use_module/org/openjdk/jni/use/PanamaJNIUse.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.jni.use; + +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.Linker; + +import org.openjdk.jni.def.PanamaJNIDef; + +public class PanamaJNIUse { + public static void run() { + testDirectAccessCLinker(); + } + + public static void testDirectAccessCLinker() { + System.out.println("Trying to get downcall handle"); + PanamaJNIDef.nativeLinker0(Linker.nativeLinker(), FunctionDescriptor.ofVoid(), new Linker.Option[0]); + System.out.println("Got downcall handle"); + } +} diff --git a/test/jdk/java/foreign/enablenativeaccess/panama_module/org/openjdk/foreigntest/PanamaMainJNI.java b/test/jdk/java/foreign/enablenativeaccess/panama_module/org/openjdk/foreigntest/PanamaMainJNI.java deleted file mode 100644 index 164ee5852ccfa..0000000000000 --- a/test/jdk/java/foreign/enablenativeaccess/panama_module/org/openjdk/foreigntest/PanamaMainJNI.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.openjdk.foreigntest; - -import java.lang.foreign.FunctionDescriptor; -import java.lang.foreign.Linker; - -public class PanamaMainJNI { - - static { - System.loadLibrary("LinkerInvokerModule"); - } - - public static void main(String[] args) { - testDirectAccessCLinker(); - } - - public static void testDirectAccessCLinker() { - System.out.println("Trying to get downcall handle"); - nativeLinker0(Linker.nativeLinker(), FunctionDescriptor.ofVoid(), new Linker.Option[0]); - System.out.println("Got downcall handle"); - } - - static native void nativeLinker0(Linker linker, FunctionDescriptor desc, Linker.Option[] options); -} diff --git a/test/jdk/java/foreign/handles/Driver.java b/test/jdk/java/foreign/handles/Driver.java index 1abfa6963fb9a..ca0545e2fd48a 100644 --- a/test/jdk/java/foreign/handles/Driver.java +++ b/test/jdk/java/foreign/handles/Driver.java @@ -24,6 +24,6 @@ /* * @test * @build invoker_module/* lookup_module/* - * @run testng/othervm --enable-native-access=invoker_module + * @run testng/othervm --illegal-native-access=deny --enable-native-access=invoker_module * lookup_module/handle.lookup.MethodHandleLookup */ diff --git a/test/jdk/java/foreign/handles/invoker_module/handle/invoker/MethodHandleInvoker.java b/test/jdk/java/foreign/handles/invoker_module/handle/invoker/MethodHandleInvoker.java index 9b696caa82d1e..fbb85cc54dfc5 100644 --- a/test/jdk/java/foreign/handles/invoker_module/handle/invoker/MethodHandleInvoker.java +++ b/test/jdk/java/foreign/handles/invoker_module/handle/invoker/MethodHandleInvoker.java @@ -90,6 +90,7 @@ static void addDefaultMapping(Class carrier, Object value) { addDefaultMapping(Consumer.class, (Consumer)(Object o) -> {}); addDefaultMapping(FunctionDescriptor.class, FunctionDescriptor.ofVoid()); addDefaultMapping(Linker.Option[].class, null); + addDefaultMapping(Runtime.class, Runtime.getRuntime()); addDefaultMapping(byte.class, (byte)0); addDefaultMapping(boolean.class, true); addDefaultMapping(char.class, (char)0); diff --git a/test/jdk/java/foreign/handles/lookup_module/handle/lookup/MethodHandleLookup.java b/test/jdk/java/foreign/handles/lookup_module/handle/lookup/MethodHandleLookup.java index ed916c1fe2419..42e6d7a7d8403 100644 --- a/test/jdk/java/foreign/handles/lookup_module/handle/lookup/MethodHandleLookup.java +++ b/test/jdk/java/foreign/handles/lookup_module/handle/lookup/MethodHandleLookup.java @@ -71,6 +71,18 @@ static Object[][] restrictedMethods() { { MethodHandles.lookup().findStatic(SymbolLookup.class, "libraryLookup", MethodType.methodType(SymbolLookup.class, Path.class, Arena.class)), "SymbolLookup::libraryLookup(Path)" }, + { MethodHandles.lookup().findStatic(System.class, "load", + MethodType.methodType(void.class, String.class)), + "System::load" }, + { MethodHandles.lookup().findStatic(System.class, "loadLibrary", + MethodType.methodType(void.class, String.class)), + "System::loadLibrary" }, + { MethodHandles.lookup().findVirtual(Runtime.class, "load", + MethodType.methodType(void.class, String.class)), + "Runtime::load" }, + { MethodHandles.lookup().findVirtual(Runtime.class, "loadLibrary", + MethodType.methodType(void.class, String.class)), + "Runtime::loadLibrary" } }; } catch (Throwable ex) { throw new ExceptionInInitializerError((ex)); From e63418ee017def80689c88671e5d124b2d453fda Mon Sep 17 00:00:00 2001 From: Claes Redestad Date: Mon, 26 Aug 2024 14:29:09 +0000 Subject: [PATCH 26/72] 8338979: Avoid bootstrapped switches in the classfile API Reviewed-by: liach, asotona --- .../classfile/impl/ClassFileImpl.java | 35 ++++++++++++------- .../classfile/impl/StackMapDecoder.java | 13 +++---- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/ClassFileImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/ClassFileImpl.java index 8de62e7a12ba6..1fd4c5cb1a0b0 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/ClassFileImpl.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/ClassFileImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -83,17 +83,28 @@ public ClassFileImpl withOptions(Option... options) { var chro = classHierarchyResolverOption; var amo = attributeMapperOption; for (var o : options) { - switch (o) { - case StackMapsOption oo -> smo = oo; - case DebugElementsOption oo -> deo = oo; - case LineNumbersOption oo -> lno = oo; - case AttributesProcessingOption oo -> apo = oo; - case ConstantPoolSharingOption oo -> cpso = oo; - case ShortJumpsOption oo -> sjo = oo; - case DeadCodeOption oo -> dco = oo; - case DeadLabelsOption oo -> dlo = oo; - case ClassHierarchyResolverOption oo -> chro = oo; - case AttributeMapperOption oo -> amo = oo; + if (o instanceof StackMapsOption oo) { + smo = oo; + } else if (o instanceof DebugElementsOption oo) { + deo = oo; + } else if (o instanceof LineNumbersOption oo) { + lno = oo; + } else if (o instanceof AttributesProcessingOption oo) { + apo = oo; + } else if (o instanceof ConstantPoolSharingOption oo) { + cpso = oo; + } else if (o instanceof ShortJumpsOption oo) { + sjo = oo; + } else if (o instanceof DeadCodeOption oo) { + dco = oo; + } else if (o instanceof DeadLabelsOption oo) { + dlo = oo; + } else if (o instanceof ClassHierarchyResolverOption oo) { + chro = oo; + } else if (o instanceof AttributeMapperOption oo) { + amo = oo; + } else { // null or unknown Option type + throw new IllegalArgumentException("Invalid option: " + o); } } return new ClassFileImpl(smo, deo, lno, apo, cpso, sjo, dco, dlo, chro, amo); diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/StackMapDecoder.java b/src/java.base/share/classes/jdk/internal/classfile/impl/StackMapDecoder.java index d0bf91171acfe..609333048d0d3 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/StackMapDecoder.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/StackMapDecoder.java @@ -161,13 +161,14 @@ private static boolean equals(List l1, List + switch (vti.tag()) { + case VT_TOP, VT_INTEGER, VT_FLOAT, VT_DOUBLE, VT_LONG, VT_NULL, VT_UNINITIALIZED_THIS -> {} - case ObjectVerificationTypeInfo ovti -> - bw.writeIndex(ovti.className()); - case UninitializedVerificationTypeInfo uvti -> - bw.writeU2(bw.labelContext().labelToBci(uvti.newTarget())); + case VT_OBJECT -> + bw.writeIndex(((ObjectVerificationTypeInfo)vti).className()); + case VT_UNINITIALIZED -> + bw.writeU2(bw.labelContext().labelToBci(((UninitializedVerificationTypeInfo)vti).newTarget())); + default -> throw new IllegalArgumentException("Invalid verification type tag: " + vti.tag()); } } From 3f00da84b3e6fb001e7d56acb198292b28d40c8b Mon Sep 17 00:00:00 2001 From: Claes Redestad Date: Mon, 26 Aug 2024 15:58:25 +0000 Subject: [PATCH 27/72] 8338906: Avoid passing EnumDescs and extra classes to type switch methods that don't use them Reviewed-by: liach, jlahoda --- .../java/lang/runtime/SwitchBootstraps.java | 136 +++++++++++------- 1 file changed, 85 insertions(+), 51 deletions(-) diff --git a/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java b/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java index 43f7339c75a96..bfdb76e2ef1b6 100644 --- a/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java +++ b/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java @@ -43,13 +43,14 @@ import java.util.Optional; import java.util.function.BiPredicate; import java.util.function.Consumer; -import java.util.stream.Stream; + import jdk.internal.access.SharedSecrets; import java.lang.classfile.ClassFile; import java.lang.classfile.Label; import java.lang.classfile.instruction.SwitchCase; import jdk.internal.constant.ConstantUtils; +import jdk.internal.constant.MethodTypeDescImpl; import jdk.internal.constant.ReferenceClassDescImpl; import jdk.internal.misc.PreviewFeatures; import jdk.internal.vm.annotation.Stable; @@ -81,19 +82,27 @@ private SwitchBootstraps() {} private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); private static final boolean previewEnabled = PreviewFeatures.isEnabled(); + private static final ClassDesc CD_BiPredicate = ReferenceClassDescImpl.ofValidated("Ljava/util/function/BiPredicate;"); + private static final ClassDesc CD_Objects = ReferenceClassDescImpl.ofValidated("Ljava/util/Objects;"); - private static final MethodType TYPES_SWITCH_TYPE = MethodType.methodType(int.class, + private static final MethodTypeDesc CHECK_INDEX_DESCRIPTOR = + MethodTypeDescImpl.ofValidated(ConstantDescs.CD_int, ConstantDescs.CD_int, ConstantDescs.CD_int); + private static final MethodTypeDesc MTD_TYPE_SWITCH = MethodTypeDescImpl.ofValidated(ConstantDescs.CD_int, + ConstantDescs.CD_Object, + ConstantDescs.CD_int); + private static final MethodTypeDesc MTD_TYPE_SWITCH_EXTRA = MethodTypeDescImpl.ofValidated(ConstantDescs.CD_int, + ConstantDescs.CD_Object, + ConstantDescs.CD_int, + CD_BiPredicate, + ConstantDescs.CD_List); + private static final MethodType MT_TYPE_SWITCH_EXTRA = MethodType.methodType(int.class, Object.class, int.class, BiPredicate.class, List.class); - - private static final MethodTypeDesc TYPES_SWITCH_DESCRIPTOR = - MethodTypeDesc.ofDescriptor("(Ljava/lang/Object;ILjava/util/function/BiPredicate;Ljava/util/List;)I"); - private static final MethodTypeDesc CHECK_INDEX_DESCRIPTOR = - MethodTypeDesc.ofDescriptor("(II)I"); - - private static final ClassDesc CD_Objects = ReferenceClassDescImpl.ofValidated("Ljava/util/Objects;"); + private static final MethodType MT_TYPE_SWITCH = MethodType.methodType(int.class, + Object.class, + int.class); private static class StaticHolders { private static final MethodHandle MAPPED_ENUM_SWITCH; @@ -180,7 +189,7 @@ public static CallSite typeSwitch(MethodHandles.Lookup lookup, } MethodHandle target = generateTypeSwitch(lookup, selectorType, labels); - + target = target.asType(invocationType); return new ConstantCallSite(target); } @@ -272,9 +281,8 @@ public static CallSite enumSwitch(MethodHandles.Lookup lookup, || !invocationType.parameterType(0).isEnum() || !invocationType.parameterType(1).equals(int.class)) throw new IllegalArgumentException("Illegal invocation type " + invocationType); - requireNonNull(labels); - labels = labels.clone(); + labels = labels.clone(); // implicit null check Class enumClass = invocationType.parameterType(0); boolean constantsOnly = true; @@ -301,7 +309,6 @@ public static CallSite enumSwitch(MethodHandles.Lookup lookup, } else { target = generateTypeSwitch(lookup, invocationType.parameterType(0), labels); } - target = target.asType(invocationType); return new ConstantCallSite(target); @@ -434,6 +441,33 @@ private static final class MappedEnumCache { public MethodHandle generatedSwitch; } + /** + * Check if the labelConstants can be converted statically to bytecode, or + * whether we'll need to compute and pass in extra information at the call site. + */ + private static boolean needsExtraInfo(Class selectorType, Object[] labelConstants) { + for (int idx = labelConstants.length - 1; idx >= 0; idx--) { + Object currentLabel = labelConstants[idx]; + if (currentLabel instanceof Class classLabel) { + // No extra info needed for exact matches or primitives + if (unconditionalExactnessCheck(selectorType, classLabel) || classLabel.isPrimitive()) { + continue; + } + // Hidden classes - or arrays thereof - can't be nominally + // represented. Passed in as arguments. + while (classLabel.isArray()) { + classLabel = classLabel.getComponentType(); + } + if (classLabel.isHidden()) { + return true; + } + } else if (currentLabel instanceof EnumDesc) { + // EnumDescs labels needs late binding + return true; + } + } + return false; + } /* * Construct test chains for labels inside switch, to handle switch repeats: * switch (idx) { @@ -467,9 +501,10 @@ private static Consumer generateTypeSwitchSkeleton(Class selecto } cb.iload(RESTART_IDX); Label dflt = cb.newLabel(); - record Element(Label target, Label next, Object caseLabel) { } - List cases = new ArrayList<>(); - List switchCases = new ArrayList<>(); + Label[] caseTargets = new Label[labelConstants.length]; + Label[] caseNext = new Label[labelConstants.length]; + Object[] caseLabels = new Object[labelConstants.length]; + SwitchCase[] switchCases = new SwitchCase[labelConstants.length]; Object lastLabel = null; for (int idx = labelConstants.length - 1; idx >= 0; idx--) { Object currentLabel = labelConstants[idx]; @@ -478,22 +513,22 @@ record Element(Label target, Label next, Object caseLabel) { } if (lastLabel == null) { next = dflt; } else if (lastLabel.equals(currentLabel)) { - next = cases.getLast().next(); + next = caseNext[idx + 1]; } else { - next = cases.getLast().target(); + next = caseTargets[idx + 1]; } lastLabel = currentLabel; - cases.add(new Element(target, next, currentLabel)); - switchCases.add(SwitchCase.of(idx, target)); + caseTargets[idx] = target; + caseNext[idx] = next; + caseLabels[idx] = currentLabel; + switchCases[idx] = SwitchCase.of(idx, target); } - cases = cases.reversed(); - switchCases = switchCases.reversed(); - cb.tableswitch(0, labelConstants.length - 1, dflt, switchCases); - for (int idx = 0; idx < cases.size(); idx++) { - Element element = cases.get(idx); - Label next = element.next(); - cb.labelBinding(element.target()); - if (element.caseLabel() instanceof Class classLabel) { + cb.tableswitch(0, labelConstants.length - 1, dflt, Arrays.asList(switchCases)); + for (int idx = 0; idx < labelConstants.length; idx++) { + Label next = caseNext[idx]; + Object caseLabel = caseLabels[idx]; + cb.labelBinding(caseTargets[idx]); + if (caseLabel instanceof Class classLabel) { if (unconditionalExactnessCheck(selectorType, classLabel)) { //nothing - unconditionally use this case } else if (classLabel.isPrimitive()) { @@ -577,7 +612,7 @@ record Element(Label target, Label next, Object caseLabel) { } extraClassLabels.add(classLabel); } } - } else if (element.caseLabel() instanceof EnumDesc enumLabel) { + } else if (caseLabel instanceof EnumDesc enumLabel) { int enumIdx = enumDescs.size(); enumDescs.add(enumLabel); cb.aload(ENUM_CACHE); @@ -587,13 +622,13 @@ record Element(Label target, Label next, Object caseLabel) { } MethodTypeDesc.of(ConstantDescs.CD_Integer, ConstantDescs.CD_int)); cb.aload(SELECTOR_OBJ); - cb.invokeinterface(referenceClassDesc(BiPredicate.class), + cb.invokeinterface(CD_BiPredicate, "test", MethodTypeDesc.of(ConstantDescs.CD_boolean, ConstantDescs.CD_Object, ConstantDescs.CD_Object)); cb.ifeq(next); - } else if (element.caseLabel() instanceof String stringLabel) { + } else if (caseLabel instanceof String stringLabel) { cb.ldc(stringLabel); cb.aload(SELECTOR_OBJ); cb.invokevirtual(ConstantDescs.CD_Object, @@ -601,7 +636,7 @@ record Element(Label target, Label next, Object caseLabel) { } MethodTypeDesc.of(ConstantDescs.CD_boolean, ConstantDescs.CD_Object)); cb.ifeq(next); - } else if (element.caseLabel() instanceof Integer integerLabel) { + } else if (caseLabel instanceof Integer integerLabel) { Label compare = cb.newLabel(); Label notNumber = cb.newLabel(); cb.aload(SELECTOR_OBJ); @@ -626,16 +661,16 @@ record Element(Label target, Label next, Object caseLabel) { } cb.ldc(integerLabel); cb.if_icmpne(next); - } else if ((element.caseLabel() instanceof Long || - element.caseLabel() instanceof Float || - element.caseLabel() instanceof Double || - element.caseLabel() instanceof Boolean)) { - if (element.caseLabel() instanceof Boolean c) { + } else if ((caseLabel instanceof Long || + caseLabel instanceof Float || + caseLabel instanceof Double || + caseLabel instanceof Boolean)) { + if (caseLabel instanceof Boolean c) { cb.loadConstant(c ? 1 : 0); } else { - cb.loadConstant((ConstantDesc) element.caseLabel()); + cb.loadConstant((ConstantDesc) caseLabel); } - var caseLabelWrapper = Wrapper.forWrapperType(element.caseLabel().getClass()); + var caseLabelWrapper = Wrapper.forWrapperType(caseLabel.getClass()); cb.invokestatic(caseLabelWrapper.wrapperClassDescriptor(), "valueOf", MethodTypeDesc.of(caseLabelWrapper.wrapperClassDescriptor(), @@ -648,13 +683,13 @@ record Element(Label target, Label next, Object caseLabel) { } cb.ifeq(next); } else { throw new InternalError("Unsupported label type: " + - element.caseLabel().getClass()); + caseLabel.getClass()); } cb.loadConstant(idx); cb.ireturn(); } cb.labelBinding(dflt); - cb.loadConstant(cases.size()); + cb.loadConstant(labelConstants.length); cb.ireturn(); }; } @@ -663,14 +698,15 @@ record Element(Label target, Label next, Object caseLabel) { } * Construct the method handle that represents the method int typeSwitch(Object, int, BiPredicate, List) */ private static MethodHandle generateTypeSwitch(MethodHandles.Lookup caller, Class selectorType, Object[] labelConstants) { - List> enumDescs = new ArrayList<>(); - List> extraClassLabels = new ArrayList<>(); + boolean addExtraInfo = needsExtraInfo(selectorType, labelConstants); + List> enumDescs = addExtraInfo ? new ArrayList<>() : null; + List> extraClassLabels = addExtraInfo ? new ArrayList<>() : null; byte[] classBytes = ClassFile.of().build(ConstantUtils.binaryNameToDesc(typeSwitchClassName(caller.lookupClass())), clb -> { clb.withFlags(AccessFlag.FINAL, AccessFlag.SUPER, AccessFlag.SYNTHETIC) .withMethodBody("typeSwitch", - TYPES_SWITCH_DESCRIPTOR, + addExtraInfo ? MTD_TYPE_SWITCH_EXTRA : MTD_TYPE_SWITCH, ClassFile.ACC_FINAL | ClassFile.ACC_PUBLIC | ClassFile.ACC_STATIC, generateTypeSwitchSkeleton(selectorType, labelConstants, enumDescs, extraClassLabels)); }); @@ -681,13 +717,11 @@ private static MethodHandle generateTypeSwitch(MethodHandles.Lookup caller, Clas lookup = caller.defineHiddenClass(classBytes, true, NESTMATE, STRONG); MethodHandle typeSwitch = lookup.findStatic(lookup.lookupClass(), "typeSwitch", - TYPES_SWITCH_TYPE); - typeSwitch = MethodHandles.insertArguments(typeSwitch, 2, new ResolvedEnumLabels(caller, enumDescs.toArray(new EnumDesc[0])), - List.copyOf(extraClassLabels)); - typeSwitch = MethodHandles.explicitCastArguments(typeSwitch, - MethodType.methodType(int.class, - selectorType, - int.class)); + addExtraInfo ? MT_TYPE_SWITCH_EXTRA : MT_TYPE_SWITCH); + if (addExtraInfo) { + typeSwitch = MethodHandles.insertArguments(typeSwitch, 2, new ResolvedEnumLabels(caller, enumDescs.toArray(new EnumDesc[0])), + List.copyOf(extraClassLabels)); + } return typeSwitch; } catch (Throwable t) { throw new IllegalArgumentException(t); From a15af6998e8f7adac2ded94ef5a47e22ddb53452 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zezula?= Date: Mon, 26 Aug 2024 16:49:48 +0000 Subject: [PATCH 28/72] 8338538: [JVMCI] Allow HotSpotJVMCIRuntime#getJObjectValue to be called by a HotSpot CompileBroker compiler thread Reviewed-by: dnsimon --- src/hotspot/share/jvmci/jvmciCompilerToVM.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp b/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp index af72322ff4bf2..d231fbe8a6a0e 100644 --- a/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp +++ b/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp @@ -710,8 +710,10 @@ C2V_END C2V_VMENTRY_0(jlong, getJObjectValue, (JNIEnv* env, jobject, jobject constant_jobject)) requireNotInHotSpot("getJObjectValue", JVMCI_CHECK_0); - if (!THREAD->has_last_Java_frame()) { - JVMCI_THROW_MSG_0(IllegalStateException, err_msg("Cannot call getJObjectValue without Java frame anchor")); + // Ensure that current JNI handle scope is not the top-most JNIHandleBlock as handles + // in that scope are only released when the thread exits. + if (!THREAD->has_last_Java_frame() && THREAD->active_handles()->pop_frame_link() == nullptr) { + JVMCI_THROW_MSG_0(IllegalStateException, err_msg("Cannot call getJObjectValue without Java frame anchor or a pushed JNI handle block")); } JVMCIObject constant = JVMCIENV->wrap(constant_jobject); Handle constant_value = JVMCIENV->asConstant(constant, JVMCI_CHECK_0); From 0c744ea7e75ba117503afe9c03993f3532742bb3 Mon Sep 17 00:00:00 2001 From: Phil Race Date: Mon, 26 Aug 2024 18:52:36 +0000 Subject: [PATCH 29/72] 8338928: Update SwingSet2 "About" image to reference openjdk.org Reviewed-by: abhiscxk, honkar --- .../jfc/SwingSet2/resources/images/About.jpg | Bin 92437 -> 131408 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/demo/share/jfc/SwingSet2/resources/images/About.jpg b/src/demo/share/jfc/SwingSet2/resources/images/About.jpg index 272173c67463cb64b80af6abbb27eb954299b145..c6c8e22970bff3f7d4de064897da53bf9d8652a8 100644 GIT binary patch literal 131408 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

( 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≈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

Z+-6To5yx-v<{f5%1W=k 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

wo~^z#q}W>Cr(h+@1Hv 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 L00ck%9e@AXzpHW{ From 5ecbecfbcac681e9e6750be37ca4bc2591db21e6 Mon Sep 17 00:00:00 2001 From: Shaojin Wen Date: Mon, 26 Aug 2024 20:26:17 +0000 Subject: [PATCH 30/72] 8338936: StringConcatFactory optimize the construction of MethodType and MethodTypeDesc Reviewed-by: redestad, liach --- .../java/lang/invoke/StringConcatFactory.java | 99 ++++++++++--------- 1 file changed, 52 insertions(+), 47 deletions(-) diff --git a/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java b/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java index dd2621935749f..870d4c063ec2e 100644 --- a/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java +++ b/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java @@ -29,6 +29,8 @@ import jdk.internal.access.JavaLangAccess; import jdk.internal.access.SharedSecrets; import jdk.internal.constant.ConstantUtils; +import jdk.internal.constant.MethodTypeDescImpl; +import jdk.internal.constant.ReferenceClassDescImpl; import jdk.internal.misc.VM; import jdk.internal.util.ClassFileDumper; import jdk.internal.util.ReferenceKey; @@ -1085,32 +1087,32 @@ private static final class InlineHiddenClassStrategy { static final MethodHandles.Lookup STR_LOOKUP = new MethodHandles.Lookup(String.class); static final ClassDesc CD_CONCAT = ConstantUtils.binaryNameToDesc(CLASS_NAME); - static final ClassDesc CD_StringConcatHelper = ClassDesc.ofDescriptor("Ljava/lang/StringConcatHelper;"); - static final ClassDesc CD_StringConcatBase = ClassDesc.ofDescriptor("Ljava/lang/StringConcatHelper$StringConcatBase;"); - static final ClassDesc CD_Array_byte = ClassDesc.ofDescriptor("[B"); - static final ClassDesc CD_Array_String = ClassDesc.ofDescriptor("[Ljava/lang/String;"); - - static final MethodTypeDesc MTD_byte_char = MethodTypeDesc.of(CD_byte, CD_char); - static final MethodTypeDesc MTD_byte = MethodTypeDesc.of(CD_byte); - static final MethodTypeDesc MTD_int = MethodTypeDesc.of(CD_int); - static final MethodTypeDesc MTD_int_int_boolean = MethodTypeDesc.of(CD_int, CD_int, CD_boolean); - static final MethodTypeDesc MTD_int_int_char = MethodTypeDesc.of(CD_int, CD_int, CD_char); - static final MethodTypeDesc MTD_int_int_int = MethodTypeDesc.of(CD_int, CD_int, CD_int); - static final MethodTypeDesc MTD_int_int_long = MethodTypeDesc.of(CD_int, CD_int, CD_long); - static final MethodTypeDesc MTD_int_int_String = MethodTypeDesc.of(CD_int, CD_int, CD_String); - static final MethodTypeDesc MTD_String_float = MethodTypeDesc.of(CD_String, CD_float); - static final MethodTypeDesc MTD_String_double = MethodTypeDesc.of(CD_String, CD_double); - static final MethodTypeDesc MTD_String_Object = MethodTypeDesc.of(CD_String, CD_Object); - - static final MethodTypeDesc MTD_INIT = MethodTypeDesc.of(CD_void, CD_Array_String); - static final MethodTypeDesc MTD_NEW_ARRAY_SUFFIX = MethodTypeDesc.of(CD_Array_byte, CD_String, CD_int, CD_byte); - static final MethodTypeDesc MTD_STRING_INIT = MethodTypeDesc.of(CD_void, CD_Array_byte, CD_byte); - - static final MethodTypeDesc PREPEND_int = MethodTypeDesc.of(CD_int, CD_int, CD_byte, CD_Array_byte, CD_int, CD_String); - static final MethodTypeDesc PREPEND_long = MethodTypeDesc.of(CD_int, CD_int, CD_byte, CD_Array_byte, CD_long, CD_String); - static final MethodTypeDesc PREPEND_boolean = MethodTypeDesc.of(CD_int, CD_int, CD_byte, CD_Array_byte, CD_boolean, CD_String); - static final MethodTypeDesc PREPEND_char = MethodTypeDesc.of(CD_int, CD_int, CD_byte, CD_Array_byte, CD_char, CD_String); - static final MethodTypeDesc PREPEND_String = MethodTypeDesc.of(CD_int, CD_int, CD_byte, CD_Array_byte, CD_String, CD_String); + static final ClassDesc CD_StringConcatHelper = ReferenceClassDescImpl.ofValidated("Ljava/lang/StringConcatHelper;"); + static final ClassDesc CD_StringConcatBase = ReferenceClassDescImpl.ofValidated("Ljava/lang/StringConcatHelper$StringConcatBase;"); + static final ClassDesc CD_Array_byte = ReferenceClassDescImpl.ofValidated("[B"); + static final ClassDesc CD_Array_String = ReferenceClassDescImpl.ofValidated("[Ljava/lang/String;"); + + static final MethodTypeDesc MTD_byte_char = MethodTypeDescImpl.ofValidated(CD_byte, CD_char); + static final MethodTypeDesc MTD_byte = MethodTypeDescImpl.ofValidated(CD_byte); + static final MethodTypeDesc MTD_int = MethodTypeDescImpl.ofValidated(CD_int); + static final MethodTypeDesc MTD_int_int_boolean = MethodTypeDescImpl.ofValidated(CD_int, CD_int, CD_boolean); + static final MethodTypeDesc MTD_int_int_char = MethodTypeDescImpl.ofValidated(CD_int, CD_int, CD_char); + static final MethodTypeDesc MTD_int_int_int = MethodTypeDescImpl.ofValidated(CD_int, CD_int, CD_int); + static final MethodTypeDesc MTD_int_int_long = MethodTypeDescImpl.ofValidated(CD_int, CD_int, CD_long); + static final MethodTypeDesc MTD_int_int_String = MethodTypeDescImpl.ofValidated(CD_int, CD_int, CD_String); + static final MethodTypeDesc MTD_String_float = MethodTypeDescImpl.ofValidated(CD_String, CD_float); + static final MethodTypeDesc MTD_String_double = MethodTypeDescImpl.ofValidated(CD_String, CD_double); + static final MethodTypeDesc MTD_String_Object = MethodTypeDescImpl.ofValidated(CD_String, CD_Object); + + static final MethodTypeDesc MTD_INIT = MethodTypeDescImpl.ofValidated(CD_void, CD_Array_String); + static final MethodTypeDesc MTD_NEW_ARRAY_SUFFIX = MethodTypeDescImpl.ofValidated(CD_Array_byte, CD_String, CD_int, CD_byte); + static final MethodTypeDesc MTD_STRING_INIT = MethodTypeDescImpl.ofValidated(CD_void, CD_Array_byte, CD_byte); + + static final MethodTypeDesc PREPEND_int = MethodTypeDescImpl.ofValidated(CD_int, CD_int, CD_byte, CD_Array_byte, CD_int, CD_String); + static final MethodTypeDesc PREPEND_long = MethodTypeDescImpl.ofValidated(CD_int, CD_int, CD_byte, CD_Array_byte, CD_long, CD_String); + static final MethodTypeDesc PREPEND_boolean = MethodTypeDescImpl.ofValidated(CD_int, CD_int, CD_byte, CD_Array_byte, CD_boolean, CD_String); + static final MethodTypeDesc PREPEND_char = MethodTypeDescImpl.ofValidated(CD_int, CD_int, CD_byte, CD_Array_byte, CD_char, CD_String); + static final MethodTypeDesc PREPEND_String = MethodTypeDescImpl.ofValidated(CD_int, CD_int, CD_byte, CD_Array_byte, CD_String, CD_String); static final RuntimeVisibleAnnotationsAttribute FORCE_INLINE = RuntimeVisibleAnnotationsAttribute.of(Annotation.of(ClassDesc.ofDescriptor("Ljdk/internal/vm/annotation/ForceInline;"))); @@ -1166,7 +1168,7 @@ private static MethodType erasedArgs(MethodType args) { } paramTypes[i] = cl; } - return changed ? MethodType.methodType(args.returnType(), paramTypes) : args; + return changed ? MethodType.methodType(args.returnType(), paramTypes, true) : args; } /** @@ -1191,24 +1193,36 @@ private static MethodTypeDesc prependArgs(MethodType concatArgs) { var cl = concatArgs.parameterType(i); paramTypes[i + 4] = needStringOf(cl) ? CD_String : ConstantUtils.classDesc(cl); } - return MethodTypeDesc.of(CD_int, paramTypes); + return MethodTypeDescImpl.ofValidated(CD_int, paramTypes); } /** - * Construct the MethodType of the coder method, - * The first parameter is the initialized coder, Only parameter types that can be UTF16 are added. + * Construct the MethodType of the coder method. The first parameter is the initialized coder. + * Only parameter types which can be UTF16 are added. Returns null if no such parameter exists. */ - private static MethodTypeDesc coderArgs(MethodType concatArgs) { + private static MethodTypeDesc coderArgsIfMaybeUTF16(MethodType concatArgs) { int parameterCount = concatArgs.parameterCount(); - List paramTypes = new ArrayList<>(); - paramTypes.add(CD_int); // init coder + + int maybeUTF16Count = 0; for (int i = 0; i < parameterCount; i++) { + if (maybeUTF16(concatArgs.parameterType(i))) { + maybeUTF16Count++; + } + } + + if (maybeUTF16Count == 0) { + return null; + } + + var paramTypes = new ClassDesc[maybeUTF16Count + 1]; + paramTypes[0] = CD_int; // init coder + for (int i = 0, paramIndex = 1; i < parameterCount; i++) { var cl = concatArgs.parameterType(i); if (maybeUTF16(cl)) { - paramTypes.add(cl == char.class ? CD_char : CD_String); + paramTypes[paramIndex++] = cl == char.class ? CD_char : CD_String; } } - return MethodTypeDesc.of(CD_int, paramTypes); + return MethodTypeDescImpl.ofValidated(CD_int, paramTypes); } /** @@ -1223,7 +1237,7 @@ private static MethodTypeDesc lengthArgs(MethodType concatArgs) { var cl = concatArgs.parameterType(i); paramTypes[i + 1] = needStringOf(cl) ? CD_String : ConstantUtils.classDesc(cl); } - return MethodTypeDesc.of(CD_int, paramTypes); + return MethodTypeDescImpl.ofValidated(CD_int, paramTypes); } private static MethodHandle generate(Lookup lookup, MethodType args, String[] constants) throws Exception { @@ -1250,7 +1264,7 @@ private static MethodHandle generate(Lookup lookup, MethodType args, String[] co } } MethodTypeDesc lengthArgs = lengthArgs(concatArgs), - coderArgs = parameterMaybeUTF16(concatArgs) ? coderArgs(concatArgs) : null, + coderArgs = coderArgsIfMaybeUTF16(concatArgs), prependArgs = prependArgs(concatArgs); byte[] classBytes = ClassFile.of().build(CD_CONCAT, @@ -1478,7 +1492,7 @@ public void accept(CodeBuilder cb) { /* * String[] constants = this.constants; - * suffix = constants[paranCount]; + * suffix = constants[paramCount]; * length -= suffix.length(); */ cb.aload(thisSlot) @@ -1692,14 +1706,5 @@ static boolean needStringOf(Class cl) { static boolean maybeUTF16(Class cl) { return cl == char.class || !cl.isPrimitive(); } - - static boolean parameterMaybeUTF16(MethodType args) { - for (int i = 0; i < args.parameterCount(); i++) { - if (maybeUTF16(args.parameterType(i))) { - return true; - } - } - return false; - } } } From a827ff05dba0c9b7c74d83053a35c8041c1ac5cc Mon Sep 17 00:00:00 2001 From: Calvin Cheung Date: Mon, 26 Aug 2024 21:26:12 +0000 Subject: [PATCH 31/72] 8335577: runtime/cds/appcds/TestParallelGCWithCDS.java still fails with JNI error Reviewed-by: dholmes, iklam --- .../jtreg/runtime/cds/appcds/TestParallelGCWithCDS.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/hotspot/jtreg/runtime/cds/appcds/TestParallelGCWithCDS.java b/test/hotspot/jtreg/runtime/cds/appcds/TestParallelGCWithCDS.java index 3f759d13a4ca9..691c87fef360c 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/TestParallelGCWithCDS.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/TestParallelGCWithCDS.java @@ -116,8 +116,9 @@ static void test(boolean dumpWithParallel, boolean execWithParallel, boolean use } else { String pattern = "((Too small maximum heap)" + "|(GC triggered before VM initialization completed)" + - "|(java.lang.OutOfMemoryError: Java heap space)" + - "|(Initial heap size set to a larger value than the maximum heap size))"; + "|(Initial heap size set to a larger value than the maximum heap size)" + + "|(java.lang.OutOfMemoryError)" + + "|(Error: A JNI error has occurred, please check your installation and try again))"; out.shouldMatch(pattern); out.shouldNotHaveFatalError(); } From 16df0907842af4729e72fe706c76681c8c799c03 Mon Sep 17 00:00:00 2001 From: David Holmes Date: Mon, 26 Aug 2024 22:26:40 +0000 Subject: [PATCH 32/72] 8338947: Deprecate the UseLinuxPosixThreadCPUClocks flag and remove it in a future release Reviewed-by: kbarrett, stuefe --- src/hotspot/os/linux/globals_linux.hpp | 4 +--- src/hotspot/share/runtime/arguments.cpp | 3 +++ .../jtreg/runtime/CommandLine/VMDeprecatedOptions.java | 3 +++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/hotspot/os/linux/globals_linux.hpp b/src/hotspot/os/linux/globals_linux.hpp index 1cb0b553c5273..90cb00fb7e683 100644 --- a/src/hotspot/os/linux/globals_linux.hpp +++ b/src/hotspot/os/linux/globals_linux.hpp @@ -38,10 +38,8 @@ product(bool, UseOprofile, false, \ "enable support for Oprofile profiler") \ \ - /* NB: The default value of UseLinuxPosixThreadCPUClocks may be */ \ - /* overridden in Arguments::parse_each_vm_init_arg. */ \ product(bool, UseLinuxPosixThreadCPUClocks, true, \ - "enable fast Linux Posix clocks where available") \ + "(Deprecated) enable fast Linux Posix clocks where available") \ \ product(bool, UseTransparentHugePages, false, \ "Use MADV_HUGEPAGE for large pages") \ diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index f4f3d30f3b871..0d0b58412aefa 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -506,6 +506,9 @@ static SpecialFlag const special_jvm_flags[] = { { "RequireSharedSpaces", JDK_Version::jdk(18), JDK_Version::jdk(19), JDK_Version::undefined() }, { "UseSharedSpaces", JDK_Version::jdk(18), JDK_Version::jdk(19), JDK_Version::undefined() }, { "DontYieldALot", JDK_Version::jdk(23), JDK_Version::jdk(24), JDK_Version::jdk(25) }, +#ifdef LINUX + { "UseLinuxPosixThreadCPUClocks", JDK_Version::jdk(24), JDK_Version::jdk(25), JDK_Version::jdk(26) }, +#endif { "LockingMode", JDK_Version::jdk(24), JDK_Version::jdk(26), JDK_Version::jdk(27) }, // --- Deprecated alias flags (see also aliased_jvm_flags) - sorted by obsolete_in then expired_in: { "CreateMinidumpOnCrash", JDK_Version::jdk(9), JDK_Version::undefined(), JDK_Version::undefined() }, diff --git a/test/hotspot/jtreg/runtime/CommandLine/VMDeprecatedOptions.java b/test/hotspot/jtreg/runtime/CommandLine/VMDeprecatedOptions.java index 4e6252ae20510..96bc22dfb1d05 100644 --- a/test/hotspot/jtreg/runtime/CommandLine/VMDeprecatedOptions.java +++ b/test/hotspot/jtreg/runtime/CommandLine/VMDeprecatedOptions.java @@ -70,6 +70,9 @@ public class VMDeprecatedOptions { }) ); } + if (Platform.isLinux()) { + deprecated.add(new String[] { "UseLinuxPosixThreadCPUClocks", "true" }); + } if (wb.isJFRIncluded()) { deprecated.add(new String[] {"FlightRecorder", "false"}); } From 78f53efcd6a886375fac3fad69f428ecc852fcd6 Mon Sep 17 00:00:00 2001 From: Chihiro Ito Date: Tue, 27 Aug 2024 00:24:46 +0000 Subject: [PATCH 33/72] 8338938: The result of the combine method of SettingsControl is not used Reviewed-by: egahlin --- .../classes/jdk/jfr/internal/Control.java | 2 +- .../jfr/api/settings/TestFilterEvents.java | 31 ++++++++++++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/Control.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/Control.java index 05c75a0e48e9b..8f246948710fc 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/Control.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/Control.java @@ -141,7 +141,7 @@ public String combine(Set values) { @Override public String run() { try { - delegate.combine(Collections.unmodifiableSet(values)); + return delegate.combine(Collections.unmodifiableSet(values)); } catch (Throwable t) { // Prevent malicious user to propagate exception callback in the wrong context Logger.log(LogTag.JFR_SETTING, LogLevel.WARN, "Exception occurred when combining " + values + " for " + getClass()); diff --git a/test/jdk/jdk/jfr/api/settings/TestFilterEvents.java b/test/jdk/jdk/jfr/api/settings/TestFilterEvents.java index 42bfc31767e98..67619fb10b4e9 100644 --- a/test/jdk/jdk/jfr/api/settings/TestFilterEvents.java +++ b/test/jdk/jdk/jfr/api/settings/TestFilterEvents.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -70,6 +70,7 @@ public static void main(String[] args) throws Exception { continuous.enable(HTTPGetEvent.class).with("threadNames", "\"unused-threadname-1\""); assertEquals(0, makeProfilingRecording("\"unused-threadname-2\"")); assertEquals(1, makeProfilingRecording("\"" + Thread.currentThread().getName() + "\"")); + assertEquals(2, makeCombineControl()); continuous.close(); } @@ -94,4 +95,32 @@ private static int makeProfilingRecording(String threadNames) throws Exception { } } + private static int makeCombineControl() throws Exception { + try (Recording r1 = new Recording()) { + r1.enable(HTTPPostEvent.class).with("uriFilter", "https://www.example.com/list"); + r1.start(); + + try (Recording r2 = new Recording()) { + r2.enable(HTTPPostEvent.class).with("uriFilter", "https://www.example.com/get"); + r2.start(); + + HTTPPostEvent e1 = new HTTPPostEvent(); + e1.uri = "https://www.example.com/list"; + e1.commit(); + + HTTPPostEvent e2 = new HTTPPostEvent(); + e2.uri = "https://www.example.com/get"; + e2.commit(); + + HTTPPostEvent e3 = new HTTPPostEvent(); + e3.uri = "https://www.example.com/put"; + e3.commit(); + } + + r1.stop(); + + return Events.fromRecording(r1).size(); + } + } + } From cd9e241f0ec10c7b31d36cbfb994bc20d81a0517 Mon Sep 17 00:00:00 2001 From: Julian Waters Date: Tue, 27 Aug 2024 04:13:54 +0000 Subject: [PATCH 34/72] 8336289: Obliterate most references to _snprintf in the Windows JDK Reviewed-by: kbarrett, dholmes, jpai, mullan, djelinski, prr --- .../os/windows/attachListener_windows.cpp | 2 +- src/hotspot/os/windows/os_windows.cpp | 12 ++-- src/hotspot/os/windows/perfMemory_windows.cpp | 8 +-- .../windows/native/libjli/cmdtoargs.c | 4 +- .../libsplashscreen/splashscreen_impl.c | 7 +-- .../native/libawt/windows/awt_Debug.cpp | 2 +- .../jaccessinspector/jaccessinspector.cpp | 6 +- .../windows/native/libj2pkcs11/j2secmod_md.c | 4 +- .../share/native/libsaproc/sadis.c | 8 +-- .../windows/native/libjdwp/linker_md.c | 4 +- .../windows/native/libjdwp/util_md.h | 7 +-- .../libmanagement_ext/OperatingSystemImpl.c | 60 +++++++++---------- 12 files changed, 54 insertions(+), 70 deletions(-) diff --git a/src/hotspot/os/windows/attachListener_windows.cpp b/src/hotspot/os/windows/attachListener_windows.cpp index 7e455cf0a49f2..3f6ca941c209b 100644 --- a/src/hotspot/os/windows/attachListener_windows.cpp +++ b/src/hotspot/os/windows/attachListener_windows.cpp @@ -318,7 +318,7 @@ void Win32AttachOperation::complete(jint result, bufferedStream* result_stream) BOOL fSuccess; char msg[32]; - _snprintf(msg, sizeof(msg), "%d\n", result); + os::snprintf(msg, sizeof(msg), "%d\n", result); msg[sizeof(msg) - 1] = '\0'; fSuccess = write_pipe(hPipe, msg, (int)strlen(msg)); diff --git a/src/hotspot/os/windows/os_windows.cpp b/src/hotspot/os/windows/os_windows.cpp index cb0fe95269b9f..1d1d9d3e1a4ed 100644 --- a/src/hotspot/os/windows/os_windows.cpp +++ b/src/hotspot/os/windows/os_windows.cpp @@ -1776,14 +1776,14 @@ void * os::dll_load(const char *name, char *ebuf, int ebuflen) { } if (lib_arch_str != nullptr) { - ::_snprintf(ebuf, ebuflen - 1, - "Can't load %s-bit .dll on a %s-bit platform", - lib_arch_str, running_arch_str); + os::snprintf(ebuf, ebuflen - 1, + "Can't load %s-bit .dll on a %s-bit platform", + lib_arch_str, running_arch_str); } else { // don't know what architecture this dll was build for - ::_snprintf(ebuf, ebuflen - 1, - "Can't load this .dll (machine code=0x%x) on a %s-bit platform", - lib_arch, running_arch_str); + os::snprintf(ebuf, ebuflen - 1, + "Can't load this .dll (machine code=0x%x) on a %s-bit platform", + lib_arch, running_arch_str); } JFR_ONLY(load_event.set_error_msg(ebuf);) return nullptr; diff --git a/src/hotspot/os/windows/perfMemory_windows.cpp b/src/hotspot/os/windows/perfMemory_windows.cpp index a71101731fb98..06b057315cbdd 100644 --- a/src/hotspot/os/windows/perfMemory_windows.cpp +++ b/src/hotspot/os/windows/perfMemory_windows.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -165,7 +165,7 @@ static char* get_user_tmp_dir(const char* user) { char* dirname = NEW_C_HEAP_ARRAY(char, nbytes, mtInternal); // construct the path name to user specific tmp directory - _snprintf(dirname, nbytes, "%s\\%s_%s", tmpdir, perfdir, user); + os::snprintf(dirname, nbytes, "%s\\%s_%s", tmpdir, perfdir, user); return dirname; } @@ -455,7 +455,7 @@ static char *get_sharedmem_objectname(const char* user, int vmid) { // nbytes += UINT_CHARS; char* name = NEW_C_HEAP_ARRAY(char, nbytes, mtInternal); - _snprintf(name, nbytes, "%s_%s_%u", PERFDATA_NAME, user, vmid); + os::snprintf(name, nbytes, "%s_%s_%u", PERFDATA_NAME, user, vmid); return name; } @@ -471,7 +471,7 @@ static char* get_sharedmem_filename(const char* dirname, int vmid) { size_t nbytes = strlen(dirname) + UINT_CHARS + 2; char* name = NEW_C_HEAP_ARRAY(char, nbytes, mtInternal); - _snprintf(name, nbytes, "%s\\%d", dirname, vmid); + os::snprintf(name, nbytes, "%s\\%d", dirname, vmid); return name; } diff --git a/src/java.base/windows/native/libjli/cmdtoargs.c b/src/java.base/windows/native/libjli/cmdtoargs.c index 548e70b9bbf7e..27a28b4cd450e 100644 --- a/src/java.base/windows/native/libjli/cmdtoargs.c +++ b/src/java.base/windows/native/libjli/cmdtoargs.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -328,7 +328,7 @@ class Vector { printf("*** cannot allocate memory\n"); doabort(); } - _snprintf(cptr, MAX_PATH, "\"%s\" %s", argv[0], cmdline); + snprintf(cptr, MAX_PATH, "\"%s\" %s", argv[0], cmdline); JLI_CmdToArgs(cptr); free(cptr); StdArg *kargv = JLI_GetStdArgs(); diff --git a/src/java.desktop/share/native/libsplashscreen/splashscreen_impl.c b/src/java.desktop/share/native/libsplashscreen/splashscreen_impl.c index 5891a66d6ac76..05ef8c33a83c2 100644 --- a/src/java.desktop/share/native/libsplashscreen/splashscreen_impl.c +++ b/src/java.desktop/share/native/libsplashscreen/splashscreen_impl.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,11 +26,6 @@ #include "splashscreen_impl.h" #include "splashscreen_gfx_impl.h" #define BUFF_SIZE 1024 -#ifdef _MSC_VER -# ifndef snprintf -# define snprintf _snprintf -# endif -#endif int splashIsVisible = 0; Splash * diff --git a/src/java.desktop/windows/native/libawt/windows/awt_Debug.cpp b/src/java.desktop/windows/native/libawt/windows/awt_Debug.cpp index 9e4b1ff1bf05c..3b8043385a1ce 100644 --- a/src/java.desktop/windows/native/libawt/windows/awt_Debug.cpp +++ b/src/java.desktop/windows/native/libawt/windows/awt_Debug.cpp @@ -141,7 +141,7 @@ void AwtDebugSupport::AssertCallback(const char * expr, const char * file, int l msgBuffer = (LPSTR)""; } // format the assertion message - _snprintf(assertMsg, ASSERT_MSG_SIZE, AssertFmt, expr, file, line, lastError, msgBuffer); + snprintf(assertMsg, ASSERT_MSG_SIZE, AssertFmt, expr, file, line, lastError, msgBuffer); if (fret != 0) { LocalFree(msgBuffer); } diff --git a/src/jdk.accessibility/windows/native/jaccessinspector/jaccessinspector.cpp b/src/jdk.accessibility/windows/native/jaccessinspector/jaccessinspector.cpp index 22992d481edef..38e093eda0948 100644 --- a/src/jdk.accessibility/windows/native/jaccessinspector/jaccessinspector.cpp +++ b/src/jdk.accessibility/windows/native/jaccessinspector/jaccessinspector.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -1526,8 +1526,8 @@ BOOL UpdateMessageNumber () { size_t messageNumber = g_MessageHistory.GetCurrentMessageIndex() + 1; char text [32] = {0}; if ( 0 != messageCount ) { - ::_snprintf(text, sizeof(text), "%d of %d", (int)messageNumber, - (int) messageCount); + ::snprintf(text, sizeof(text), "%d of %d", (int)messageNumber, + (int) messageCount); } return ::SetWindowText(dlgItem, text); } diff --git a/src/jdk.crypto.cryptoki/windows/native/libj2pkcs11/j2secmod_md.c b/src/jdk.crypto.cryptoki/windows/native/libj2pkcs11/j2secmod_md.c index 305d202030bf3..a5cd5e63d02b3 100644 --- a/src/jdk.crypto.cryptoki/windows/native/libj2pkcs11/j2secmod_md.c +++ b/src/jdk.crypto.cryptoki/windows/native/libj2pkcs11/j2secmod_md.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -39,7 +39,7 @@ void *p11FindFunction(JNIEnv *env, jlong jHandle, const char *functionName) { void *fAddress = GetProcAddress(hModule, functionName); if (fAddress == NULL) { char errorMessage[256]; - _snprintf(errorMessage, sizeof(errorMessage), "Symbol not found: %s", functionName); + snprintf(errorMessage, sizeof(errorMessage), "Symbol not found: %s", functionName); p11ThrowNullPointerException(env, errorMessage); return NULL; } diff --git a/src/jdk.hotspot.agent/share/native/libsaproc/sadis.c b/src/jdk.hotspot.agent/share/native/libsaproc/sadis.c index 62f6303c32b68..cf66b7184e3ac 100644 --- a/src/jdk.hotspot.agent/share/native/libsaproc/sadis.c +++ b/src/jdk.hotspot.agent/share/native/libsaproc/sadis.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -33,12 +33,6 @@ */ #ifdef _WINDOWS -// Disable CRT security warning against _snprintf -#pragma warning (disable : 4996) - -#define snprintf _snprintf -#define vsnprintf _vsnprintf - #include #include #include diff --git a/src/jdk.jdwp.agent/windows/native/libjdwp/linker_md.c b/src/jdk.jdwp.agent/windows/native/libjdwp/linker_md.c index 97eb1498f5873..a2cc952db815d 100644 --- a/src/jdk.jdwp.agent/windows/native/libjdwp/linker_md.c +++ b/src/jdk.jdwp.agent/windows/native/libjdwp/linker_md.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -56,7 +56,7 @@ static void dll_build_name(char* buffer, size_t buflen, path = strtok_s(paths_copy, PATH_SEPARATOR, &next_token); while (path != NULL) { - size_t result_len = (size_t)_snprintf(buffer, buflen, "%s\\%s.dll", path, fname); + size_t result_len = (size_t) snprintf(buffer, buflen, "%s\\%s.dll", path, fname); if (result_len >= buflen) { EXIT_ERROR(JVMTI_ERROR_INVALID_LOCATION, "One or more of the library paths supplied to jdwp, " "likely by sun.boot.library.path, is too long."); diff --git a/src/jdk.jdwp.agent/windows/native/libjdwp/util_md.h b/src/jdk.jdwp.agent/windows/native/libjdwp/util_md.h index 4af5aced1143d..cefb301e09503 100644 --- a/src/jdk.jdwp.agent/windows/native/libjdwp/util_md.h +++ b/src/jdk.jdwp.agent/windows/native/libjdwp/util_md.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2008, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -34,11 +34,6 @@ typedef unsigned long UNSIGNED_JINT; #define MAXPATHLEN _MAX_PATH -/* Needed on Windows because names seem to be hidden in stdio.h. */ - -#define snprintf _snprintf -#define vsnprintf _vsnprintf - /* On little endian machines, convert java big endian numbers. */ #define HOST_TO_JAVA_CHAR(x) (((x & 0xff) << 8) | ((x >> 8) & (0xff))) diff --git a/src/jdk.management/windows/native/libmanagement_ext/OperatingSystemImpl.c b/src/jdk.management/windows/native/libmanagement_ext/OperatingSystemImpl.c index 746bd97c7ff42..3cfd46791343d 100644 --- a/src/jdk.management/windows/native/libmanagement_ext/OperatingSystemImpl.c +++ b/src/jdk.management/windows/native/libmanagement_ext/OperatingSystemImpl.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -436,13 +436,13 @@ makeFullCounterPath(const char* const objectName, return NULL; } - _snprintf(fullCounterPath, - fullCounterPathLen, - PROCESS_OBJECT_INSTANCE_COUNTER_FMT, - objectName, - imageName, - instance, - counterName); + snprintf(fullCounterPath, + fullCounterPathLen, + PROCESS_OBJECT_INSTANCE_COUNTER_FMT, + objectName, + imageName, + instance, + counterName); } else { if (instance) { /* @@ -472,18 +472,18 @@ makeFullCounterPath(const char* const objectName, } if (instance) { - _snprintf(fullCounterPath, - fullCounterPathLen, - OBJECT_WITH_INSTANCES_COUNTER_FMT, - objectName, - instance, - counterName); + snprintf(fullCounterPath, + fullCounterPathLen, + OBJECT_WITH_INSTANCES_COUNTER_FMT, + objectName, + instance, + counterName); } else { - _snprintf(fullCounterPath, - fullCounterPathLen, - OBJECT_COUNTER_FMT, - objectName, - counterName); + snprintf(fullCounterPath, + fullCounterPathLen, + OBJECT_COUNTER_FMT, + objectName, + counterName); } } @@ -719,10 +719,10 @@ currentQueryIndexForProcess(void) { PDH_FMT_COUNTERVALUE counterValue; PDH_STATUS res; - _snprintf(fullIDProcessCounterPath, - MAX_PATH, - pdhIDProcessCounterFmt, - index); + snprintf(fullIDProcessCounterPath, + MAX_PATH, + pdhIDProcessCounterFmt, + index); if (addCounter(tmpQuery, fullIDProcessCounterPath, &handleCounter) != 0) { break; @@ -1059,13 +1059,13 @@ allocateAndInitializePdhConstants() { } /* "\Process(java#%d)\ID Process" */ - _snprintf(pdhIDProcessCounterFmt, - pdhIDProcessCounterFmtLen, - PROCESS_OBJECT_INSTANCE_COUNTER_FMT, - pdhLocalizedProcessObject, - pdhProcessImageName, - "%d", - pdhLocalizedIDProcessCounter); + snprintf(pdhIDProcessCounterFmt, + pdhIDProcessCounterFmtLen, + PROCESS_OBJECT_INSTANCE_COUNTER_FMT, + pdhLocalizedProcessObject, + pdhProcessImageName, + "%d", + pdhLocalizedIDProcessCounter); pdhIDProcessCounterFmt[pdhIDProcessCounterFmtLen] = '\0'; From b8e8e965e541881605f9dbcd4d9871d4952b9232 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Tue, 27 Aug 2024 04:15:08 +0000 Subject: [PATCH 35/72] 8338668: Test javax/swing/JFileChooser/8080628/bug8080628.java doesn't test for GTK L&F Reviewed-by: aivanov, honkar, prr --- .../swing/JFileChooser/8080628/bug8080628.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/test/jdk/javax/swing/JFileChooser/8080628/bug8080628.java b/test/jdk/javax/swing/JFileChooser/8080628/bug8080628.java index 21490282550e6..426ed7456484a 100644 --- a/test/jdk/javax/swing/JFileChooser/8080628/bug8080628.java +++ b/test/jdk/javax/swing/JFileChooser/8080628/bug8080628.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -33,7 +33,17 @@ * @test * @bug 8080628 * @summary No mnemonics on Open and Save buttons in JFileChooser. - * @author Alexey Ivanov + * @requires os.family != "linux" + * @modules java.desktop/sun.swing + * @run main bug8080628 + */ + +/* + * @test + * @bug 8080628 + * @key headful + * @summary No mnemonics on Open and Save buttons in JFileChooser. + * @requires os.family == "linux" * @modules java.desktop/sun.swing * @run main bug8080628 */ @@ -81,8 +91,10 @@ private static void runTest() { try { UIManager.setLookAndFeel(info.getClassName()); } catch (final UnsupportedLookAndFeelException ignored) { + System.out.println("Unsupported L&F: " + info.getClassName()); continue; } + System.out.println("Testing L&F: " + info.getClassName()); for (Locale locale : LOCALES) { for (String key : MNEMONIC_KEYS) { From b704bfa205bbd8c56f128ce5d727d40c8a3ec613 Mon Sep 17 00:00:00 2001 From: Jan Lahoda Date: Tue, 27 Aug 2024 07:23:15 +0000 Subject: [PATCH 36/72] 8298920: Improve microbenchmark build times Reviewed-by: erikj, ihse, djelinski --- make/common/JavaCompilation.gmk | 36 +++++++++++++++++-------------- make/test/BuildMicrobenchmark.gmk | 1 + 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/make/common/JavaCompilation.gmk b/make/common/JavaCompilation.gmk index 1503c86aff1df..ca363f7cb5474 100644 --- a/make/common/JavaCompilation.gmk +++ b/make/common/JavaCompilation.gmk @@ -282,8 +282,26 @@ define SetupJavaCompilationBody $1_FLAGS += -Xlint:$$(call CommaList, $$(addprefix -, $$($1_DISABLED_WARNINGS))) endif - ifneq ($$($1_CLASSPATH), ) - $1_FLAGS += -cp $$(call PathList, $$($1_CLASSPATH)) + $1_AUGMENTED_CLASSPATH := $$($1_CLASSPATH) + $1_API_TARGET := $$($1_BIN)$$($1_MODULE_SUBDIR)/_the.$1_pubapi + $1_API_INTERNAL := $$($1_BIN)$$($1_MODULE_SUBDIR)/_the.$1_internalapi + + ifeq ($$($1_CREATE_API_DIGEST), true) + $1_API_DIGEST_FLAGS := \ + -Xplugin:"depend $$($1_API_TARGET)" \ + "-XDinternalAPIPath=$$($1_API_INTERNAL)" \ + "-XDLOG_LEVEL=$(LOG_LEVEL)" \ + # + + $1_EXTRA_DEPS := $$(BUILDTOOLS_OUTPUTDIR)/depend/_the.COMPILE_DEPEND_batch + # including the compilation output on the classpath, so that incremental + # compilations in unnamed module can refer to other classes from the same + # source root, which are not being recompiled in this compilation: + $1_AUGMENTED_CLASSPATH += $$(BUILDTOOLS_OUTPUTDIR)/depend $$($1_BIN) + endif + + ifneq ($$($1_AUGMENTED_CLASSPATH), ) + $1_FLAGS += -cp $$(call PathList, $$($1_AUGMENTED_CLASSPATH)) endif # Make sure the dirs exist, or that one of the EXTRA_FILES, that may not @@ -411,9 +429,6 @@ define SetupJavaCompilationBody $1_MODFILELIST := $$($1_BIN)$$($1_MODULE_SUBDIR)/_the.$1_batch.modfiles $1_MODFILELIST_FIXED := $$($1_MODFILELIST).fixed - $1_API_TARGET := $$($1_BIN)$$($1_MODULE_SUBDIR)/_the.$1_pubapi - $1_API_INTERNAL := $$($1_BIN)$$($1_MODULE_SUBDIR)/_the.$1_internalapi - # Put headers in a temp dir to filter out those that actually # changed before copying them to the real header dir. ifneq (,$$($1_HEADERS)) @@ -442,17 +457,6 @@ define SetupJavaCompilationBody $1_VARDEPS_FILE := $$(call DependOnVariable, $1_VARDEPS, \ $$($1_BIN)$$($1_MODULE_SUBDIR)/_the.$1.vardeps) - ifeq ($$($1_CREATE_API_DIGEST), true) - $1_API_DIGEST_FLAGS := \ - -classpath $$(BUILDTOOLS_OUTPUTDIR)/depend \ - -Xplugin:"depend $$($1_API_TARGET)" \ - "-XDinternalAPIPath=$$($1_API_INTERNAL)" \ - "-XDLOG_LEVEL=$(LOG_LEVEL)" \ - # - - $1_EXTRA_DEPS := $$(BUILDTOOLS_OUTPUTDIR)/depend/_the.COMPILE_DEPEND_batch - endif - # Create a file with all sources, to pass to javac in an @file. # $$($1_VARDEPS_FILE) is used as dependency to track changes in set of # list of files. diff --git a/make/test/BuildMicrobenchmark.gmk b/make/test/BuildMicrobenchmark.gmk index 32d26be22702b..61c84b31a22d6 100644 --- a/make/test/BuildMicrobenchmark.gmk +++ b/make/test/BuildMicrobenchmark.gmk @@ -94,6 +94,7 @@ $(eval $(call SetupJavaCompilation, BUILD_JDK_MICROBENCHMARK, \ TARGET_RELEASE := $(TARGET_RELEASE_NEWJDK_UPGRADED), \ SMALL_JAVA := false, \ CLASSPATH := $(JMH_COMPILE_JARS), \ + CREATE_API_DIGEST := true, \ DISABLED_WARNINGS := restricted this-escape processing rawtypes removal cast \ serial preview dangling-doc-comments, \ SRC := $(MICROBENCHMARK_SRC), \ From aefdbdc7e54ae92b5c2113504ce17abf00681e62 Mon Sep 17 00:00:00 2001 From: Robbin Ehn Date: Tue, 27 Aug 2024 08:42:06 +0000 Subject: [PATCH 37/72] 8338727: RISC-V: Avoid synthetic data dependency in nmethod barrier on Ztso Reviewed-by: mli, fyang --- .../gc/shared/barrierSetAssembler_riscv.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/hotspot/cpu/riscv/gc/shared/barrierSetAssembler_riscv.cpp b/src/hotspot/cpu/riscv/gc/shared/barrierSetAssembler_riscv.cpp index efdf3765965f7..d96d405aa2282 100644 --- a/src/hotspot/cpu/riscv/gc/shared/barrierSetAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/gc/shared/barrierSetAssembler_riscv.cpp @@ -265,7 +265,7 @@ void BarrierSetAssembler::nmethod_entry_barrier(MacroAssembler* masm, Label* slo } case NMethodPatchingType::conc_instruction_and_data_patch: { - // If we patch code we need both a code patching and a loadload + // If we patch code we need both a cmodx fence and a loadload // fence. It's not super cheap, so we use a global epoch mechanism // to hide them in a slow path. // The high level idea of the global epoch mechanism is to detect @@ -273,11 +273,19 @@ void BarrierSetAssembler::nmethod_entry_barrier(MacroAssembler* masm, Label* slo // last nmethod was disarmed. This implies that the required // fencing has been performed for all preceding nmethod disarms // as well. Therefore, we do not need any further fencing. + __ la(t1, ExternalAddress((address)&_patching_epoch)); - // Embed an artificial data dependency to order the guard load - // before the epoch load. - __ srli(ra, t0, 32); - __ orr(t1, t1, ra); + if (!UseZtso) { + // Embed a synthetic data dependency between the load of the guard and + // the load of the epoch. This guarantees that these loads occur in + // order, while allowing other independent instructions to be reordered. + // Note: This may be slower than using a membar(load|load) (fence r,r). + // Because processors will not start the second load until the first comes back. + // This means you can’t overlap the two loads, + // which is stronger than needed for ordering (stronger than TSO). + __ srli(ra, t0, 32); + __ orr(t1, t1, ra); + } // Read the global epoch value. __ lwu(t1, t1); // Combine the guard value (low order) with the epoch value (high order). From 2edf574f62837678e621e1dfdd8d8a77dbe17ad6 Mon Sep 17 00:00:00 2001 From: Martin Doerr Date: Tue, 27 Aug 2024 11:51:28 +0000 Subject: [PATCH 38/72] 8338814: [PPC64] Unify interface of cmpxchg for different types Reviewed-by: lucy --- src/hotspot/cpu/ppc/assembler_ppc.cpp | 41 ++++++++++--- src/hotspot/cpu/ppc/assembler_ppc.hpp | 10 +++- src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp | 2 +- .../shenandoahBarrierSetAssembler_ppc.cpp | 2 +- .../cpu/ppc/gc/z/zBarrierSetAssembler_ppc.cpp | 4 +- src/hotspot/cpu/ppc/macroAssembler_ppc.cpp | 31 +++++----- src/hotspot/cpu/ppc/macroAssembler_ppc.hpp | 53 ++++++++-------- src/hotspot/cpu/ppc/ppc.ad | 60 +++++++++---------- src/hotspot/cpu/ppc/vtableStubs_ppc_64.cpp | 2 +- 9 files changed, 120 insertions(+), 85 deletions(-) diff --git a/src/hotspot/cpu/ppc/assembler_ppc.cpp b/src/hotspot/cpu/ppc/assembler_ppc.cpp index 26fc1aacad3a3..dc8180bfedf50 100644 --- a/src/hotspot/cpu/ppc/assembler_ppc.cpp +++ b/src/hotspot/cpu/ppc/assembler_ppc.cpp @@ -292,31 +292,54 @@ void Assembler::stb(Register d, RegisterOrConstant roc, Register s1, Register tm } } -void Assembler::add(Register d, RegisterOrConstant roc, Register s1) { +void Assembler::add(Register d, Register s, RegisterOrConstant roc) { if (roc.is_constant()) { intptr_t c = roc.as_constant(); assert(is_simm(c, 16), "too big"); - addi(d, s1, (int)c); + addi(d, s, (int)c); + } else { + add(d, s, roc.as_register()); } - else add(d, roc.as_register(), s1); } -void Assembler::subf(Register d, RegisterOrConstant roc, Register s1) { +void Assembler::sub(Register d, Register s, RegisterOrConstant roc) { if (roc.is_constant()) { intptr_t c = roc.as_constant(); assert(is_simm(-c, 16), "too big"); - addi(d, s1, (int)-c); + addi(d, s, (int)-c); + } else { + sub(d, s, roc.as_register()); + } +} + +void Assembler::xorr(Register d, Register s, RegisterOrConstant roc) { + if (roc.is_constant()) { + intptr_t c = roc.as_constant(); + assert(is_uimm(c, 16), "too big"); + xori(d, s, (int)c); + } else { + xorr(d, s, roc.as_register()); } - else subf(d, roc.as_register(), s1); } -void Assembler::cmpd(ConditionRegister d, RegisterOrConstant roc, Register s1) { +void Assembler::cmpw(ConditionRegister d, Register s, RegisterOrConstant roc) { if (roc.is_constant()) { intptr_t c = roc.as_constant(); assert(is_simm(c, 16), "too big"); - cmpdi(d, s1, (int)c); + cmpwi(d, s, (int)c); + } else { + cmpw(d, s, roc.as_register()); + } +} + +void Assembler::cmpd(ConditionRegister d, Register s, RegisterOrConstant roc) { + if (roc.is_constant()) { + intptr_t c = roc.as_constant(); + assert(is_simm(c, 16), "too big"); + cmpdi(d, s, (int)c); + } else { + cmpd(d, s, roc.as_register()); } - else cmpd(d, roc.as_register(), s1); } // Load a 64 bit constant. Patchable. diff --git a/src/hotspot/cpu/ppc/assembler_ppc.hpp b/src/hotspot/cpu/ppc/assembler_ppc.hpp index d18574f50a949..d445108098b86 100644 --- a/src/hotspot/cpu/ppc/assembler_ppc.hpp +++ b/src/hotspot/cpu/ppc/assembler_ppc.hpp @@ -2512,9 +2512,13 @@ class Assembler : public AbstractAssembler { void stw( Register d, RegisterOrConstant roc, Register s1 = noreg, Register tmp = noreg); void sth( Register d, RegisterOrConstant roc, Register s1 = noreg, Register tmp = noreg); void stb( Register d, RegisterOrConstant roc, Register s1 = noreg, Register tmp = noreg); - void add( Register d, RegisterOrConstant roc, Register s1); - void subf(Register d, RegisterOrConstant roc, Register s1); - void cmpd(ConditionRegister d, RegisterOrConstant roc, Register s1); + void add( Register d, Register s, RegisterOrConstant roc); + void add( Register d, RegisterOrConstant roc, Register s) { add(d, s, roc); } + void sub( Register d, Register s, RegisterOrConstant roc); + void xorr(Register d, Register s, RegisterOrConstant roc); + void xorr(Register d, RegisterOrConstant roc, Register s) { xorr(d, s, roc); } + void cmpw(ConditionRegister d, Register s, RegisterOrConstant roc); + void cmpd(ConditionRegister d, Register s, RegisterOrConstant roc); // Load pointer d from s1+roc. void ld_ptr(Register d, RegisterOrConstant roc, Register s1 = noreg) { ld(d, roc, s1); } diff --git a/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp b/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp index 0c1e23c635329..88d635f2b856f 100644 --- a/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp +++ b/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp @@ -2620,7 +2620,7 @@ void LIR_Assembler::emit_compare_and_swap(LIR_OpCompareAndSwap* op) { __ cmpxchgw(BOOL_RESULT, /*current_value=*/R0, cmp_value, new_value, addr, MacroAssembler::MemBarNone, MacroAssembler::cmpxchgx_hint_atomic_update(), - noreg, /*check without ldarx first*/true); + noreg, nullptr, /*check without ldarx first*/true); } if (support_IRIW_for_not_multiple_copy_atomic_cpu) { diff --git a/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp b/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp index 62aa9a2bf5930..3cb5c5a628f39 100644 --- a/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp +++ b/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp @@ -669,7 +669,7 @@ void ShenandoahBarrierSetAssembler::cmpxchg_oop(MacroAssembler *masm, Register b // no special processing is required. if (UseCompressedOops) { __ cmpxchgw(CCR0, current_value, expected, new_val, base_addr, MacroAssembler::MemBarNone, - false, success_flag, true); + false, success_flag, nullptr, true); } else { __ cmpxchgd(CCR0, current_value, expected, new_val, base_addr, MacroAssembler::MemBarNone, false, success_flag, nullptr, true); diff --git a/src/hotspot/cpu/ppc/gc/z/zBarrierSetAssembler_ppc.cpp b/src/hotspot/cpu/ppc/gc/z/zBarrierSetAssembler_ppc.cpp index 9e606054fe910..89ab1b1edeeb4 100644 --- a/src/hotspot/cpu/ppc/gc/z/zBarrierSetAssembler_ppc.cpp +++ b/src/hotspot/cpu/ppc/gc/z/zBarrierSetAssembler_ppc.cpp @@ -335,7 +335,7 @@ void ZBarrierSetAssembler::store_barrier_medium(MacroAssembler* masm, // Try to self-heal null values for atomic accesses bool need_restore = false; if (!ind_or_offs.is_constant() || ind_or_offs.as_constant() != 0) { - __ add(ref_base, ind_or_offs, ref_base); + __ add(ref_base, ref_base, ind_or_offs); need_restore = true; } __ ld(R0, in_bytes(ZThreadLocalData::store_good_mask_offset()), R16_thread); @@ -343,7 +343,7 @@ void ZBarrierSetAssembler::store_barrier_medium(MacroAssembler* masm, MacroAssembler::MemBarNone, MacroAssembler::cmpxchgx_hint_atomic_update(), noreg, need_restore ? nullptr : &slow_path); if (need_restore) { - __ subf(ref_base, ind_or_offs, ref_base); + __ sub(ref_base, ref_base, ind_or_offs); __ bne(CCR0, slow_path); } } else { diff --git a/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp b/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp index 9fb4a43097dd5..0af384a36fabe 100644 --- a/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp +++ b/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp @@ -1620,7 +1620,7 @@ void MacroAssembler::atomic_get_and_modify_generic(Register dest_current_value, // Temps, addr_base and exchange_value are killed if size < 4 and processor does not support respective instructions. // Only signed types are supported with size < 4. void MacroAssembler::cmpxchg_loop_body(ConditionRegister flag, Register dest_current_value, - Register compare_value, Register exchange_value, + RegisterOrConstant compare_value, Register exchange_value, Register addr_base, Register tmp1, Register tmp2, Label &retry, Label &failed, bool cmpxchgx_hint, int size) { // Sub-word instructions are available since Power 8. @@ -1634,7 +1634,7 @@ void MacroAssembler::cmpxchg_loop_body(ConditionRegister flag, Register dest_cur modval = exchange_value; if (instruction_type != size) { - assert_different_registers(tmp1, tmp2, dest_current_value, compare_value, exchange_value, addr_base); + assert_different_registers(tmp1, tmp2, dest_current_value, compare_value.register_or_noreg(), exchange_value, addr_base); shift_amount = tmp1; val32 = tmp2; modval = tmp2; @@ -1695,21 +1695,23 @@ void MacroAssembler::cmpxchg_loop_body(ConditionRegister flag, Register dest_cur // CmpxchgX sets condition register to cmpX(current, compare). void MacroAssembler::cmpxchg_generic(ConditionRegister flag, Register dest_current_value, - Register compare_value, Register exchange_value, + RegisterOrConstant compare_value, Register exchange_value, Register addr_base, Register tmp1, Register tmp2, - int semantics, bool cmpxchgx_hint, - Register int_flag_success, bool contention_hint, bool weak, int size) { + int semantics, bool cmpxchgx_hint, Register int_flag_success, + Label* failed_ext, bool contention_hint, bool weak, int size) { Label retry; - Label failed; + Label failed_int; + Label& failed = (failed_ext != nullptr) ? *failed_ext : failed_int; Label done; // Save one branch if result is returned via register and // result register is different from the other ones. bool use_result_reg = (int_flag_success != noreg); - bool preset_result_reg = (int_flag_success != dest_current_value && int_flag_success != compare_value && + bool preset_result_reg = (int_flag_success != dest_current_value && int_flag_success != compare_value.register_or_noreg() && int_flag_success != exchange_value && int_flag_success != addr_base && int_flag_success != tmp1 && int_flag_success != tmp2); assert(!weak || flag == CCR0, "weak only supported with CCR0"); + assert(int_flag_success == noreg || failed_ext == nullptr, "cannot have both"); assert(size == 1 || size == 2 || size == 4, "unsupported"); if (use_result_reg && preset_result_reg) { @@ -1760,7 +1762,7 @@ void MacroAssembler::cmpxchg_generic(ConditionRegister flag, Register dest_curre b(done); } - bind(failed); + bind(failed_int); if (use_result_reg && !preset_result_reg) { li(int_flag_success, 0); } @@ -1787,10 +1789,11 @@ void MacroAssembler::cmpxchg_generic(ConditionRegister flag, Register dest_curre // To avoid the costly compare exchange the value is tested beforehand. // Several special cases exist to avoid that unnecessary information is generated. // -void MacroAssembler::cmpxchgd(ConditionRegister flag, - Register dest_current_value, RegisterOrConstant compare_value, Register exchange_value, - Register addr_base, int semantics, bool cmpxchgx_hint, - Register int_flag_success, Label* failed_ext, bool contention_hint, bool weak) { +void MacroAssembler::cmpxchgd(ConditionRegister flag, Register dest_current_value, + RegisterOrConstant compare_value, Register exchange_value, + Register addr_base, + int semantics, bool cmpxchgx_hint, Register int_flag_success, + Label* failed_ext, bool contention_hint, bool weak) { Label retry; Label failed_int; Label& failed = (failed_ext != nullptr) ? *failed_ext : failed_int; @@ -1810,7 +1813,7 @@ void MacroAssembler::cmpxchgd(ConditionRegister flag, // Add simple guard in order to reduce risk of starving under high contention (recommended by IBM). if (contention_hint) { // Don't try to reserve if cmp fails. ld(dest_current_value, 0, addr_base); - cmpd(flag, compare_value, dest_current_value); + cmpd(flag, dest_current_value, compare_value); bne(flag, failed); } @@ -1823,7 +1826,7 @@ void MacroAssembler::cmpxchgd(ConditionRegister flag, bind(retry); ldarx(dest_current_value, addr_base, cmpxchgx_hint); - cmpd(flag, compare_value, dest_current_value); + cmpd(flag, dest_current_value, compare_value); if (UseStaticBranchPredictionInCompareAndSwapPPC64) { bne_predict_not_taken(flag, failed); } else { diff --git a/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp b/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp index 15b5e26f8f634..9a0cf3d8da0e2 100644 --- a/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp +++ b/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp @@ -482,13 +482,14 @@ class MacroAssembler: public Assembler { Register addr_base, Register tmp1, Register tmp2, Register tmp3, bool cmpxchgx_hint, bool is_add, int size); void cmpxchg_loop_body(ConditionRegister flag, Register dest_current_value, - Register compare_value, Register exchange_value, + RegisterOrConstant compare_value, Register exchange_value, Register addr_base, Register tmp1, Register tmp2, Label &retry, Label &failed, bool cmpxchgx_hint, int size); - void cmpxchg_generic(ConditionRegister flag, - Register dest_current_value, Register compare_value, Register exchange_value, Register addr_base, - Register tmp1, Register tmp2, - int semantics, bool cmpxchgx_hint, Register int_flag_success, bool contention_hint, bool weak, int size); + void cmpxchg_generic(ConditionRegister flag, Register dest_current_value, + RegisterOrConstant compare_value, Register exchange_value, + Register addr_base, Register tmp1, Register tmp2, + int semantics, bool cmpxchgx_hint, Register int_flag_success, + Label* failed_ext, bool contention_hint, bool weak, int size); public: // Temps and addr_base are killed if processor does not support Power 8 instructions. // Result will be sign extended. @@ -528,33 +529,37 @@ class MacroAssembler: public Assembler { Register tmp, bool cmpxchgx_hint); // Temps, addr_base and exchange_value are killed if processor does not support Power 8 instructions. // compare_value must be at least 32 bit sign extended. Result will be sign extended. - void cmpxchgb(ConditionRegister flag, - Register dest_current_value, Register compare_value, Register exchange_value, Register addr_base, - Register tmp1, Register tmp2, int semantics, bool cmpxchgx_hint = false, - Register int_flag_success = noreg, bool contention_hint = false, bool weak = false) { + void cmpxchgb(ConditionRegister flag, Register dest_current_value, + RegisterOrConstant compare_value, Register exchange_value, + Register addr_base, Register tmp1, Register tmp2, + int semantics, bool cmpxchgx_hint = false, Register int_flag_success = noreg, + Label* failed = nullptr, bool contention_hint = false, bool weak = false) { cmpxchg_generic(flag, dest_current_value, compare_value, exchange_value, addr_base, tmp1, tmp2, - semantics, cmpxchgx_hint, int_flag_success, contention_hint, weak, 1); + semantics, cmpxchgx_hint, int_flag_success, failed, contention_hint, weak, 1); } // Temps, addr_base and exchange_value are killed if processor does not support Power 8 instructions. // compare_value must be at least 32 bit sign extended. Result will be sign extended. - void cmpxchgh(ConditionRegister flag, - Register dest_current_value, Register compare_value, Register exchange_value, Register addr_base, - Register tmp1, Register tmp2, int semantics, bool cmpxchgx_hint = false, - Register int_flag_success = noreg, bool contention_hint = false, bool weak = false) { + void cmpxchgh(ConditionRegister flag, Register dest_current_value, + RegisterOrConstant compare_value, Register exchange_value, + Register addr_base, Register tmp1, Register tmp2, + int semantics, bool cmpxchgx_hint = false, Register int_flag_success = noreg, + Label* failed = nullptr, bool contention_hint = false, bool weak = false) { cmpxchg_generic(flag, dest_current_value, compare_value, exchange_value, addr_base, tmp1, tmp2, - semantics, cmpxchgx_hint, int_flag_success, contention_hint, weak, 2); + semantics, cmpxchgx_hint, int_flag_success, failed, contention_hint, weak, 2); } - void cmpxchgw(ConditionRegister flag, - Register dest_current_value, Register compare_value, Register exchange_value, Register addr_base, - int semantics, bool cmpxchgx_hint = false, - Register int_flag_success = noreg, bool contention_hint = false, bool weak = false) { + void cmpxchgw(ConditionRegister flag, Register dest_current_value, + RegisterOrConstant compare_value, Register exchange_value, + Register addr_base, + int semantics, bool cmpxchgx_hint = false, Register int_flag_success = noreg, + Label* failed = nullptr, bool contention_hint = false, bool weak = false) { cmpxchg_generic(flag, dest_current_value, compare_value, exchange_value, addr_base, noreg, noreg, - semantics, cmpxchgx_hint, int_flag_success, contention_hint, weak, 4); + semantics, cmpxchgx_hint, int_flag_success, failed, contention_hint, weak, 4); } - void cmpxchgd(ConditionRegister flag, - Register dest_current_value, RegisterOrConstant compare_value, Register exchange_value, - Register addr_base, int semantics, bool cmpxchgx_hint = false, - Register int_flag_success = noreg, Label* failed = nullptr, bool contention_hint = false, bool weak = false); + void cmpxchgd(ConditionRegister flag, Register dest_current_value, + RegisterOrConstant compare_value, Register exchange_value, + Register addr_base, + int semantics, bool cmpxchgx_hint = false, Register int_flag_success = noreg, + Label* failed = nullptr, bool contention_hint = false, bool weak = false); // interface method calling void lookup_interface_method(Register recv_klass, diff --git a/src/hotspot/cpu/ppc/ppc.ad b/src/hotspot/cpu/ppc/ppc.ad index b34d3ef150140..e7e066ebcc6d3 100644 --- a/src/hotspot/cpu/ppc/ppc.ad +++ b/src/hotspot/cpu/ppc/ppc.ad @@ -7390,7 +7390,7 @@ instruct compareAndSwapB_regP_regI_regI(iRegIdst res, iRegPdst mem_ptr, iRegIsrc // CmpxchgX sets CCR0 to cmpX(src1, src2) and Rres to 'true'/'false'. __ cmpxchgb(CCR0, R0, $src1$$Register, $src2$$Register, $mem_ptr$$Register, noreg, noreg, MacroAssembler::MemBarNone, MacroAssembler::cmpxchgx_hint_atomic_update(), - $res$$Register, true); + $res$$Register, nullptr, true); if (support_IRIW_for_not_multiple_copy_atomic_cpu) { __ isync(); } else { @@ -7409,7 +7409,7 @@ instruct compareAndSwapB4_regP_regI_regI(iRegIdst res, rarg3RegP mem_ptr, iRegIs // CmpxchgX sets CCR0 to cmpX(src1, src2) and Rres to 'true'/'false'. __ cmpxchgb(CCR0, R0, $src1$$Register, $src2$$Register, $mem_ptr$$Register, $tmp1$$Register, $tmp2$$Register, MacroAssembler::MemBarNone, MacroAssembler::cmpxchgx_hint_atomic_update(), - $res$$Register, true); + $res$$Register, nullptr, true); if (support_IRIW_for_not_multiple_copy_atomic_cpu) { __ isync(); } else { @@ -7428,7 +7428,7 @@ instruct compareAndSwapS_regP_regI_regI(iRegIdst res, iRegPdst mem_ptr, iRegIsrc // CmpxchgX sets CCR0 to cmpX(src1, src2) and Rres to 'true'/'false'. __ cmpxchgh(CCR0, R0, $src1$$Register, $src2$$Register, $mem_ptr$$Register, noreg, noreg, MacroAssembler::MemBarNone, MacroAssembler::cmpxchgx_hint_atomic_update(), - $res$$Register, true); + $res$$Register, nullptr, true); if (support_IRIW_for_not_multiple_copy_atomic_cpu) { __ isync(); } else { @@ -7447,7 +7447,7 @@ instruct compareAndSwapS4_regP_regI_regI(iRegIdst res, rarg3RegP mem_ptr, iRegIs // CmpxchgX sets CCR0 to cmpX(src1, src2) and Rres to 'true'/'false'. __ cmpxchgh(CCR0, R0, $src1$$Register, $src2$$Register, $mem_ptr$$Register, $tmp1$$Register, $tmp2$$Register, MacroAssembler::MemBarNone, MacroAssembler::cmpxchgx_hint_atomic_update(), - $res$$Register, true); + $res$$Register, nullptr, true); if (support_IRIW_for_not_multiple_copy_atomic_cpu) { __ isync(); } else { @@ -7465,7 +7465,7 @@ instruct compareAndSwapI_regP_regI_regI(iRegIdst res, iRegPdst mem_ptr, iRegIsrc // CmpxchgX sets CCR0 to cmpX(src1, src2) and Rres to 'true'/'false'. __ cmpxchgw(CCR0, R0, $src1$$Register, $src2$$Register, $mem_ptr$$Register, MacroAssembler::MemBarNone, MacroAssembler::cmpxchgx_hint_atomic_update(), - $res$$Register, true); + $res$$Register, nullptr, true); if (support_IRIW_for_not_multiple_copy_atomic_cpu) { __ isync(); } else { @@ -7483,7 +7483,7 @@ instruct compareAndSwapN_regP_regN_regN(iRegIdst res, iRegPdst mem_ptr, iRegNsrc // CmpxchgX sets CCR0 to cmpX(src1, src2) and Rres to 'true'/'false'. __ cmpxchgw(CCR0, R0, $src1$$Register, $src2$$Register, $mem_ptr$$Register, MacroAssembler::MemBarNone, MacroAssembler::cmpxchgx_hint_atomic_update(), - $res$$Register, true); + $res$$Register, nullptr, true); if (support_IRIW_for_not_multiple_copy_atomic_cpu) { __ isync(); } else { @@ -7541,7 +7541,7 @@ instruct weakCompareAndSwapB_regP_regI_regI(iRegIdst res, iRegPdst mem_ptr, iReg // CmpxchgX sets CCR0 to cmpX(src1, src2) and Rres to 'true'/'false'. __ cmpxchgb(CCR0, R0, $src1$$Register, $src2$$Register, $mem_ptr$$Register, noreg, noreg, MacroAssembler::MemBarNone, - MacroAssembler::cmpxchgx_hint_atomic_update(), $res$$Register, true, /*weak*/ true); + MacroAssembler::cmpxchgx_hint_atomic_update(), $res$$Register, nullptr, true, /*weak*/ true); %} ins_pipe(pipe_class_default); %} @@ -7555,7 +7555,7 @@ instruct weakCompareAndSwapB4_regP_regI_regI(iRegIdst res, rarg3RegP mem_ptr, iR // CmpxchgX sets CCR0 to cmpX(src1, src2) and Rres to 'true'/'false'. __ cmpxchgb(CCR0, R0, $src1$$Register, $src2$$Register, $mem_ptr$$Register, $tmp1$$Register, $tmp2$$Register, MacroAssembler::MemBarNone, - MacroAssembler::cmpxchgx_hint_atomic_update(), $res$$Register, true, /*weak*/ true); + MacroAssembler::cmpxchgx_hint_atomic_update(), $res$$Register, nullptr, true, /*weak*/ true); %} ins_pipe(pipe_class_default); %} @@ -7569,7 +7569,7 @@ instruct weakCompareAndSwapB_acq_regP_regI_regI(iRegIdst res, iRegPdst mem_ptr, // CmpxchgX sets CCR0 to cmpX(src1, src2) and Rres to 'true'/'false'. __ cmpxchgb(CCR0, R0, $src1$$Register, $src2$$Register, $mem_ptr$$Register, noreg, noreg, support_IRIW_for_not_multiple_copy_atomic_cpu ? MacroAssembler::MemBarAcq : MacroAssembler::MemBarFenceAfter, - MacroAssembler::cmpxchgx_hint_atomic_update(), $res$$Register, true, /*weak*/ true); + MacroAssembler::cmpxchgx_hint_atomic_update(), $res$$Register, nullptr, true, /*weak*/ true); %} ins_pipe(pipe_class_default); %} @@ -7583,7 +7583,7 @@ instruct weakCompareAndSwapB4_acq_regP_regI_regI(iRegIdst res, rarg3RegP mem_ptr // CmpxchgX sets CCR0 to cmpX(src1, src2) and Rres to 'true'/'false'. __ cmpxchgb(CCR0, R0, $src1$$Register, $src2$$Register, $mem_ptr$$Register, $tmp1$$Register, $tmp2$$Register, support_IRIW_for_not_multiple_copy_atomic_cpu ? MacroAssembler::MemBarAcq : MacroAssembler::MemBarFenceAfter, - MacroAssembler::cmpxchgx_hint_atomic_update(), $res$$Register, true, /*weak*/ true); + MacroAssembler::cmpxchgx_hint_atomic_update(), $res$$Register, nullptr, true, /*weak*/ true); %} ins_pipe(pipe_class_default); %} @@ -7597,7 +7597,7 @@ instruct weakCompareAndSwapS_regP_regI_regI(iRegIdst res, iRegPdst mem_ptr, iReg // CmpxchgX sets CCR0 to cmpX(src1, src2) and Rres to 'true'/'false'. __ cmpxchgh(CCR0, R0, $src1$$Register, $src2$$Register, $mem_ptr$$Register, noreg, noreg, MacroAssembler::MemBarNone, - MacroAssembler::cmpxchgx_hint_atomic_update(), $res$$Register, true, /*weak*/ true); + MacroAssembler::cmpxchgx_hint_atomic_update(), $res$$Register, nullptr, true, /*weak*/ true); %} ins_pipe(pipe_class_default); %} @@ -7611,7 +7611,7 @@ instruct weakCompareAndSwapS4_regP_regI_regI(iRegIdst res, rarg3RegP mem_ptr, iR // CmpxchgX sets CCR0 to cmpX(src1, src2) and Rres to 'true'/'false'. __ cmpxchgh(CCR0, R0, $src1$$Register, $src2$$Register, $mem_ptr$$Register, $tmp1$$Register, $tmp2$$Register, MacroAssembler::MemBarNone, - MacroAssembler::cmpxchgx_hint_atomic_update(), $res$$Register, true, /*weak*/ true); + MacroAssembler::cmpxchgx_hint_atomic_update(), $res$$Register, nullptr, true, /*weak*/ true); %} ins_pipe(pipe_class_default); %} @@ -7625,7 +7625,7 @@ instruct weakCompareAndSwapS_acq_regP_regI_regI(iRegIdst res, iRegPdst mem_ptr, // CmpxchgX sets CCR0 to cmpX(src1, src2) and Rres to 'true'/'false'. __ cmpxchgh(CCR0, R0, $src1$$Register, $src2$$Register, $mem_ptr$$Register, noreg, noreg, support_IRIW_for_not_multiple_copy_atomic_cpu ? MacroAssembler::MemBarAcq : MacroAssembler::MemBarFenceAfter, - MacroAssembler::cmpxchgx_hint_atomic_update(), $res$$Register, true, /*weak*/ true); + MacroAssembler::cmpxchgx_hint_atomic_update(), $res$$Register, nullptr, true, /*weak*/ true); %} ins_pipe(pipe_class_default); %} @@ -7639,7 +7639,7 @@ instruct weakCompareAndSwapS4_acq_regP_regI_regI(iRegIdst res, rarg3RegP mem_ptr // CmpxchgX sets CCR0 to cmpX(src1, src2) and Rres to 'true'/'false'. __ cmpxchgh(CCR0, R0, $src1$$Register, $src2$$Register, $mem_ptr$$Register, $tmp1$$Register, $tmp2$$Register, support_IRIW_for_not_multiple_copy_atomic_cpu ? MacroAssembler::MemBarAcq : MacroAssembler::MemBarFenceAfter, - MacroAssembler::cmpxchgx_hint_atomic_update(), $res$$Register, true, /*weak*/ true); + MacroAssembler::cmpxchgx_hint_atomic_update(), $res$$Register, nullptr, true, /*weak*/ true); %} ins_pipe(pipe_class_default); %} @@ -7653,7 +7653,7 @@ instruct weakCompareAndSwapI_regP_regI_regI(iRegIdst res, iRegPdst mem_ptr, iReg // CmpxchgX sets CCR0 to cmpX(src1, src2) and Rres to 'true'/'false'. __ cmpxchgw(CCR0, R0, $src1$$Register, $src2$$Register, $mem_ptr$$Register, MacroAssembler::MemBarNone, - MacroAssembler::cmpxchgx_hint_atomic_update(), $res$$Register, true, /*weak*/ true); + MacroAssembler::cmpxchgx_hint_atomic_update(), $res$$Register, nullptr, true, /*weak*/ true); %} ins_pipe(pipe_class_default); %} @@ -7669,7 +7669,7 @@ instruct weakCompareAndSwapI_acq_regP_regI_regI(iRegIdst res, iRegPdst mem_ptr, // value is never passed to caller. __ cmpxchgw(CCR0, R0, $src1$$Register, $src2$$Register, $mem_ptr$$Register, support_IRIW_for_not_multiple_copy_atomic_cpu ? MacroAssembler::MemBarAcq : MacroAssembler::MemBarFenceAfter, - MacroAssembler::cmpxchgx_hint_atomic_update(), $res$$Register, true, /*weak*/ true); + MacroAssembler::cmpxchgx_hint_atomic_update(), $res$$Register, nullptr, true, /*weak*/ true); %} ins_pipe(pipe_class_default); %} @@ -7683,7 +7683,7 @@ instruct weakCompareAndSwapN_regP_regN_regN(iRegIdst res, iRegPdst mem_ptr, iReg // CmpxchgX sets CCR0 to cmpX(src1, src2) and Rres to 'true'/'false'. __ cmpxchgw(CCR0, R0, $src1$$Register, $src2$$Register, $mem_ptr$$Register, MacroAssembler::MemBarNone, - MacroAssembler::cmpxchgx_hint_atomic_update(), $res$$Register, true, /*weak*/ true); + MacroAssembler::cmpxchgx_hint_atomic_update(), $res$$Register, nullptr, true, /*weak*/ true); %} ins_pipe(pipe_class_default); %} @@ -7699,7 +7699,7 @@ instruct weakCompareAndSwapN_acq_regP_regN_regN(iRegIdst res, iRegPdst mem_ptr, // value is never passed to caller. __ cmpxchgw(CCR0, R0, $src1$$Register, $src2$$Register, $mem_ptr$$Register, support_IRIW_for_not_multiple_copy_atomic_cpu ? MacroAssembler::MemBarAcq : MacroAssembler::MemBarFenceAfter, - MacroAssembler::cmpxchgx_hint_atomic_update(), $res$$Register, true, /*weak*/ true); + MacroAssembler::cmpxchgx_hint_atomic_update(), $res$$Register, nullptr, true, /*weak*/ true); %} ins_pipe(pipe_class_default); %} @@ -7776,7 +7776,7 @@ instruct compareAndExchangeB_regP_regI_regI(iRegIdst res, iRegPdst mem_ptr, iReg // CmpxchgX sets CCR0 to cmpX(src1, src2) and Rres to 'true'/'false'. __ cmpxchgb(CCR0, $res$$Register, $src1$$Register, $src2$$Register, $mem_ptr$$Register, noreg, noreg, MacroAssembler::MemBarNone, MacroAssembler::cmpxchgx_hint_atomic_update(), - noreg, true); + noreg, nullptr, true); %} ins_pipe(pipe_class_default); %} @@ -7790,7 +7790,7 @@ instruct compareAndExchangeB4_regP_regI_regI(iRegIdst res, rarg3RegP mem_ptr, iR // CmpxchgX sets CCR0 to cmpX(src1, src2) and Rres to 'true'/'false'. __ cmpxchgb(CCR0, $res$$Register, $src1$$Register, $src2$$Register, $mem_ptr$$Register, $tmp1$$Register, R0, MacroAssembler::MemBarNone, MacroAssembler::cmpxchgx_hint_atomic_update(), - noreg, true); + noreg, nullptr, true); %} ins_pipe(pipe_class_default); %} @@ -7804,7 +7804,7 @@ instruct compareAndExchangeB_acq_regP_regI_regI(iRegIdst res, iRegPdst mem_ptr, // CmpxchgX sets CCR0 to cmpX(src1, src2) and Rres to 'true'/'false'. __ cmpxchgb(CCR0, $res$$Register, $src1$$Register, $src2$$Register, $mem_ptr$$Register, noreg, noreg, MacroAssembler::MemBarNone, MacroAssembler::cmpxchgx_hint_atomic_update(), - noreg, true); + noreg, nullptr, true); if (support_IRIW_for_not_multiple_copy_atomic_cpu) { __ isync(); } else { @@ -7824,7 +7824,7 @@ instruct compareAndExchangeB4_acq_regP_regI_regI(iRegIdst res, rarg3RegP mem_ptr // CmpxchgX sets CCR0 to cmpX(src1, src2) and Rres to 'true'/'false'. __ cmpxchgb(CCR0, $res$$Register, $src1$$Register, $src2$$Register, $mem_ptr$$Register, $tmp1$$Register, R0, MacroAssembler::MemBarNone, MacroAssembler::cmpxchgx_hint_atomic_update(), - noreg, true); + noreg, nullptr, true); if (support_IRIW_for_not_multiple_copy_atomic_cpu) { __ isync(); } else { @@ -7844,7 +7844,7 @@ instruct compareAndExchangeS_regP_regI_regI(iRegIdst res, iRegPdst mem_ptr, iReg // CmpxchgX sets CCR0 to cmpX(src1, src2) and Rres to 'true'/'false'. __ cmpxchgh(CCR0, $res$$Register, $src1$$Register, $src2$$Register, $mem_ptr$$Register, noreg, noreg, MacroAssembler::MemBarNone, MacroAssembler::cmpxchgx_hint_atomic_update(), - noreg, true); + noreg, nullptr, true); %} ins_pipe(pipe_class_default); %} @@ -7858,7 +7858,7 @@ instruct compareAndExchangeS4_regP_regI_regI(iRegIdst res, rarg3RegP mem_ptr, iR // CmpxchgX sets CCR0 to cmpX(src1, src2) and Rres to 'true'/'false'. __ cmpxchgh(CCR0, $res$$Register, $src1$$Register, $src2$$Register, $mem_ptr$$Register, $tmp1$$Register, R0, MacroAssembler::MemBarNone, MacroAssembler::cmpxchgx_hint_atomic_update(), - noreg, true); + noreg, nullptr, true); %} ins_pipe(pipe_class_default); %} @@ -7872,7 +7872,7 @@ instruct compareAndExchangeS_acq_regP_regI_regI(iRegIdst res, iRegPdst mem_ptr, // CmpxchgX sets CCR0 to cmpX(src1, src2) and Rres to 'true'/'false'. __ cmpxchgh(CCR0, $res$$Register, $src1$$Register, $src2$$Register, $mem_ptr$$Register, noreg, noreg, MacroAssembler::MemBarNone, MacroAssembler::cmpxchgx_hint_atomic_update(), - noreg, true); + noreg, nullptr, true); if (support_IRIW_for_not_multiple_copy_atomic_cpu) { __ isync(); } else { @@ -7892,7 +7892,7 @@ instruct compareAndExchangeS4_acq_regP_regI_regI(iRegIdst res, rarg3RegP mem_ptr // CmpxchgX sets CCR0 to cmpX(src1, src2) and Rres to 'true'/'false'. __ cmpxchgh(CCR0, $res$$Register, $src1$$Register, $src2$$Register, $mem_ptr$$Register, $tmp1$$Register, R0, MacroAssembler::MemBarNone, MacroAssembler::cmpxchgx_hint_atomic_update(), - noreg, true); + noreg, nullptr, true); if (support_IRIW_for_not_multiple_copy_atomic_cpu) { __ isync(); } else { @@ -7912,7 +7912,7 @@ instruct compareAndExchangeI_regP_regI_regI(iRegIdst res, iRegPdst mem_ptr, iReg // CmpxchgX sets CCR0 to cmpX(src1, src2) and Rres to 'true'/'false'. __ cmpxchgw(CCR0, $res$$Register, $src1$$Register, $src2$$Register, $mem_ptr$$Register, MacroAssembler::MemBarNone, MacroAssembler::cmpxchgx_hint_atomic_update(), - noreg, true); + noreg, nullptr, true); %} ins_pipe(pipe_class_default); %} @@ -7926,7 +7926,7 @@ instruct compareAndExchangeI_acq_regP_regI_regI(iRegIdst res, iRegPdst mem_ptr, // CmpxchgX sets CCR0 to cmpX(src1, src2) and Rres to 'true'/'false'. __ cmpxchgw(CCR0, $res$$Register, $src1$$Register, $src2$$Register, $mem_ptr$$Register, MacroAssembler::MemBarNone, MacroAssembler::cmpxchgx_hint_atomic_update(), - noreg, true); + noreg, nullptr, true); if (support_IRIW_for_not_multiple_copy_atomic_cpu) { __ isync(); } else { @@ -7946,7 +7946,7 @@ instruct compareAndExchangeN_regP_regN_regN(iRegNdst res, iRegPdst mem_ptr, iReg // CmpxchgX sets CCR0 to cmpX(src1, src2) and Rres to 'true'/'false'. __ cmpxchgw(CCR0, $res$$Register, $src1$$Register, $src2$$Register, $mem_ptr$$Register, MacroAssembler::MemBarNone, MacroAssembler::cmpxchgx_hint_atomic_update(), - noreg, true); + noreg, nullptr, true); %} ins_pipe(pipe_class_default); %} @@ -7960,7 +7960,7 @@ instruct compareAndExchangeN_acq_regP_regN_regN(iRegNdst res, iRegPdst mem_ptr, // CmpxchgX sets CCR0 to cmpX(src1, src2) and Rres to 'true'/'false'. __ cmpxchgw(CCR0, $res$$Register, $src1$$Register, $src2$$Register, $mem_ptr$$Register, MacroAssembler::MemBarNone, MacroAssembler::cmpxchgx_hint_atomic_update(), - noreg, true); + noreg, nullptr, true); if (support_IRIW_for_not_multiple_copy_atomic_cpu) { __ isync(); } else { diff --git a/src/hotspot/cpu/ppc/vtableStubs_ppc_64.cpp b/src/hotspot/cpu/ppc/vtableStubs_ppc_64.cpp index 28ba04d833bed..1e6f748dec800 100644 --- a/src/hotspot/cpu/ppc/vtableStubs_ppc_64.cpp +++ b/src/hotspot/cpu/ppc/vtableStubs_ppc_64.cpp @@ -195,7 +195,7 @@ VtableStub* VtableStubs::create_itable_stub(int itable_index) { #ifndef PRODUCT if (DebugVtables) { Label ok; - __ cmpd(CCR0, R19_method, 0); + __ cmpdi(CCR0, R19_method, 0); __ bne(CCR0, ok); __ stop("method is null"); __ bind(ok); From d5c6158cedfd96a9f97d83355b10730b81274648 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Nordstr=C3=B6m?= Date: Tue, 27 Aug 2024 13:17:21 +0000 Subject: [PATCH 39/72] 8338389: [JFR] Long strings should be added to the string pool Reviewed-by: mgronlun --- .../classes/jdk/jfr/internal/StringPool.java | 6 +- .../jdk/jfr/jvm/TestLongStringsInPool.java | 116 ++++++++++++++++++ 2 files changed, 119 insertions(+), 3 deletions(-) create mode 100644 test/jdk/jdk/jfr/jvm/TestLongStringsInPool.java diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/StringPool.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/StringPool.java index d16bd9ab2a9cd..c0dc4e6ba5884 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/StringPool.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/StringPool.java @@ -30,8 +30,8 @@ public final class StringPool { public static final int MIN_LIMIT = 16; - public static final int MAX_LIMIT = 128; /* 0 MAX means disabled */ - + public static final int MAX_LIMIT = 131072; /* 0 MAX means disabled */ + private static final int PRECACHE_THRESHOLD = 128; private static final long DO_NOT_POOL = -1; /* max size */ private static final int MAX_SIZE = 32 * 1024; @@ -131,7 +131,7 @@ public static long addString(String s, boolean pinVirtualThread) { if (lsid != null) { return ensureCurrentGeneration(s, lsid, pinVirtualThread); } - if (!preCache(s)) { + if (s.length() <= PRECACHE_THRESHOLD && !preCache(s)) { /* we should not pool this string */ return DO_NOT_POOL; } diff --git a/test/jdk/jdk/jfr/jvm/TestLongStringsInPool.java b/test/jdk/jdk/jfr/jvm/TestLongStringsInPool.java new file mode 100644 index 0000000000000..8ce3fd3859fdb --- /dev/null +++ b/test/jdk/jdk/jfr/jvm/TestLongStringsInPool.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.jfr.jvm; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import jdk.jfr.Event; +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordingFile; +import jdk.test.lib.Asserts; +import jdk.test.lib.jfr.EventNames; + +/** + * @test + * @summary Verify that duplicate longer strings doesn't take up unneccessary space + * @key jfr + * @requires vm.hasJFR + * @library /test/lib + * @run main/othervm jdk.jfr.jvm.TestLongStringsInPool + */ +public class TestLongStringsInPool { + private static class StringEvent extends Event { + String message; + } + + public static void main(String[] args) throws Exception { + // Create two recordings; first has only one large + // string, second has several occurences of the same + // string. With long strings (>128 chars) being pooled, + // the two recording should be roughly the same size. + final int numEvents = 10; + final String longString = generateString(); + final int strLen = longString.length(); + final StringEvent event = new StringEvent(); + event.message = longString; + + Recording firstRec = new Recording(); + firstRec.start(); + // commit events with empty message (both recordings + // will have the same number of events) + for (int i = 0; i < numEvents - 1; i++) { + event.message = ""; + event.commit(); + } + // commit 1 event with a long string + event.message = longString; + event.commit(); + + firstRec.stop(); + Path rec1 = Paths.get(".", "rec1.jfr"); + firstRec.dump(rec1); + firstRec.close(); + + + Recording secondRec = new Recording(); + secondRec.start(); + // commit events with the same long string + for (int i = 0; i < numEvents - 1; i++) { + event.message = longString; + event.commit(); + } + // commit 1 event with a long string + event.message = longString; + event.commit(); + + secondRec.stop(); + Path rec2 = Paths.get(".", "rec2.jfr"); + secondRec.dump(rec2); + secondRec.close(); + + // the files aren't exactly the same size, but rec2 should + // not take up space for all strings if they're pooled correctly + long maxAllowedDiff = (numEvents - 1) * strLen; + long diff = Math.abs(Files.size(rec2) - Files.size(rec1)); + + Asserts.assertTrue(diff <= maxAllowedDiff, "Size difference between recordings is too large: "+ diff +" > " + maxAllowedDiff); + Asserts.assertFalse(RecordingFile.readAllEvents(rec1).isEmpty(), "No events found in recording 1"); + Asserts.assertFalse(RecordingFile.readAllEvents(rec2).isEmpty(), "No events found in recording 2"); + Asserts.assertEquals(RecordingFile.readAllEvents(rec1).size(), RecordingFile.readAllEvents(rec2).size(), "The recordings don't have the same number of events"); + } + + /** + * Generate a string of 256 chars length. + * @return + */ + private static String generateString() { + final StringBuilder builder = new StringBuilder(); + for (int i = 0; i < 32; i++) { + builder.append("abcdefgh"); + } + return builder.toString(); + } +} From 414d23cb8f3c2765ac6ba2da930f2cfe7a9ad419 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Tue, 27 Aug 2024 13:23:02 +0000 Subject: [PATCH 40/72] 8338765: ScheduledThreadPoolExecutor struggles with extremely long delays Reviewed-by: alanb --- .../ScheduledThreadPoolExecutor.java | 25 +++++-------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/ScheduledThreadPoolExecutor.java b/src/java.base/share/classes/java/util/concurrent/ScheduledThreadPoolExecutor.java index 87e2ea4920599..d2bfa40c9b9d0 100644 --- a/src/java.base/share/classes/java/util/concurrent/ScheduledThreadPoolExecutor.java +++ b/src/java.base/share/classes/java/util/concurrent/ScheduledThreadPoolExecutor.java @@ -182,6 +182,11 @@ public class ScheduledThreadPoolExecutor */ private static final AtomicLong sequencer = new AtomicLong(); + /** + * Maximum delay is effectively 146 years + */ + private static final long MAX_NANOS = (Long.MAX_VALUE >>> 1) - 1; + private class ScheduledFutureTask extends FutureTask implements RunnableScheduledFuture { @@ -525,25 +530,7 @@ private long triggerTime(long delay, TimeUnit unit) { * Returns the nanoTime-based trigger time of a delayed action. */ long triggerTime(long delay) { - return System.nanoTime() + - ((delay < (Long.MAX_VALUE >> 1)) ? delay : overflowFree(delay)); - } - - /** - * Constrains the values of all delays in the queue to be within - * Long.MAX_VALUE of each other, to avoid overflow in compareTo. - * This may occur if a task is eligible to be dequeued, but has - * not yet been, while some other task is added with a delay of - * Long.MAX_VALUE. - */ - private long overflowFree(long delay) { - Delayed head = (Delayed) super.getQueue().peek(); - if (head != null) { - long headDelay = head.getDelay(NANOSECONDS); - if (headDelay < 0 && (delay - headDelay < 0)) - delay = Long.MAX_VALUE + headDelay; - } - return delay; + return System.nanoTime() + Math.min(delay, MAX_NANOS); } /** From b25095b08e4d21b95177a5fa3fff3807b2cf81e0 Mon Sep 17 00:00:00 2001 From: Maurizio Cimadamore Date: Tue, 27 Aug 2024 14:26:31 +0000 Subject: [PATCH 41/72] 8338728: Misc issues in memory layout javadoc Reviewed-by: pminborg, psandoz --- .../java/lang/foreign/MemoryLayout.java | 15 ++++++------ .../jdk/java/foreign/TestDereferencePath.java | 24 +++++++++++++++++++ 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/java.base/share/classes/java/lang/foreign/MemoryLayout.java b/src/java.base/share/classes/java/lang/foreign/MemoryLayout.java index 989fc134a2628..b130ca8e031e5 100644 --- a/src/java.base/share/classes/java/lang/foreign/MemoryLayout.java +++ b/src/java.base/share/classes/java/lang/foreign/MemoryLayout.java @@ -664,14 +664,15 @@ public sealed interface MemoryLayout *

* If the provided layout path has size {@code m} and contains a dereference path * element in position {@code k} (where {@code k <= m}) then two layout paths - * {@code P} and {@code P'} are derived, where P contains all the path elements from - * 0 to {@code k - 1} and {@code P'} contains all the path elements from {@code k + 1} - * to {@code m} (if any). Then, the returned var handle is computed as follows: + * {@code P} and {@code Q} are derived, where P contains all the path elements from + * 0 to {@code k - 1} and {@code Q} contains all the path elements from {@code k + 1} + * to {@code m} ({@code Q} could be an empty layout path if {@code k == m}). + * Then, the returned var handle is computed as follows: * * {@snippet lang = "java": * VarHandle baseHandle = this.varHandle(P); * MemoryLayout target = ((AddressLayout)this.select(P)).targetLayout().get(); - * VarHandle targetHandle = target.varHandle(P); + * VarHandle targetHandle = target.varHandle(Q); * targetHandle = MethodHandles.insertCoordinates(targetHandle, 1, 0L); // always access nested targets at offset 0 * targetHandle = MethodHandles.collectCoordinates(targetHandle, 0, * baseHandle.toMethodHandle(VarHandle.AccessMode.GET)); @@ -944,7 +945,7 @@ static PathElement sequenceElement(long index) { * is computed as follows: *

* That is, the size of the returned open path element is {@code B}. * @@ -972,8 +973,8 @@ static PathElement sequenceElement() { } /** - * {@return a path element that dereferences an address layout as its - * {@linkplain AddressLayout#targetLayout() target layout} (where set)} + * {@return a path element that selects the {@linkplain AddressLayout#targetLayout() target layout} of + * an address layout (where set)} */ static PathElement dereferenceElement() { return LayoutPath.DereferenceElement.instance(); diff --git a/test/jdk/java/foreign/TestDereferencePath.java b/test/jdk/java/foreign/TestDereferencePath.java index 1b26cce9c6fcf..116f2d318fc6f 100644 --- a/test/jdk/java/foreign/TestDereferencePath.java +++ b/test/jdk/java/foreign/TestDereferencePath.java @@ -119,6 +119,30 @@ public void testMulti() { } } + static final MemoryLayout A_VALUE = MemoryLayout.structLayout( + ValueLayout.ADDRESS.withName("b") + .withTargetLayout(ValueLayout.JAVA_INT) + ); + + static final VarHandle a_value = A_VALUE.varHandle( + PathElement.groupElement("b"), PathElement.dereferenceElement()); + + @Test + public void testDerefValue() { + try (Arena arena = Arena.ofConfined()) { + // init structs + MemorySegment a = arena.allocate(A); + MemorySegment b = arena.allocate(ValueLayout.JAVA_INT); + // init struct fields + a.set(ValueLayout.ADDRESS, 0, b); + b.set(ValueLayout.JAVA_INT, 0, 42); + // dereference + int val = (int) a_value.get(a, 0L); + assertEquals(val, 42); + } + } + + @Test(expectedExceptions = IllegalArgumentException.class) void testBadDerefInSelect() { A.select(PathElement.groupElement("b"), PathElement.dereferenceElement()); From 0f667103db7842fe9d3399f56baee0a5def4529e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Gr=C3=B6nlund?= Date: Tue, 27 Aug 2024 14:33:31 +0000 Subject: [PATCH 42/72] 8338939: Simplify processing of hidden class names Reviewed-by: egahlin --- .../share/jfr/support/jfrSymbolTable.cpp | 69 +++++-------------- .../share/jfr/support/jfrSymbolTable.hpp | 7 +- 2 files changed, 19 insertions(+), 57 deletions(-) diff --git a/src/hotspot/share/jfr/support/jfrSymbolTable.cpp b/src/hotspot/share/jfr/support/jfrSymbolTable.cpp index ffb54f4d0caee..e3c34e46a1a4a 100644 --- a/src/hotspot/share/jfr/support/jfrSymbolTable.cpp +++ b/src/hotspot/share/jfr/support/jfrSymbolTable.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,11 +23,10 @@ */ #include "precompiled.hpp" -#include "classfile/javaClasses.inline.hpp" #include "classfile/classLoaderData.hpp" +#include "classfile/javaClasses.hpp" #include "jfr/support/jfrSymbolTable.hpp" -#include "oops/instanceKlass.hpp" -#include "oops/oop.inline.hpp" +#include "oops/klass.hpp" #include "oops/symbol.hpp" #include "runtime/mutexLocker.hpp" @@ -200,7 +199,7 @@ traceid JfrSymbolTable::bootstrap_name(bool leakp) { traceid JfrSymbolTable::mark(const Symbol* sym, bool leakp /* false */) { assert(sym != nullptr, "invariant"); - return mark((uintptr_t)sym->identity_hash(), sym, leakp); + return mark(sym->identity_hash(), sym, leakp); } traceid JfrSymbolTable::mark(uintptr_t hash, const Symbol* sym, bool leakp) { @@ -236,59 +235,25 @@ traceid JfrSymbolTable::mark(uintptr_t hash, const char* str, bool leakp) { } /* -* hidden classes symbol is the external name + -* the address of its InstanceKlass slash appended: -* java.lang.invoke.LambdaForm$BMH/22626602 -* -* caller needs ResourceMark -*/ - -uintptr_t JfrSymbolTable::hidden_klass_name_hash(const InstanceKlass* ik) { - assert(ik != nullptr, "invariant"); - assert(ik->is_hidden(), "invariant"); - const oop mirror = ik->java_mirror_no_keepalive(); - assert(mirror != nullptr, "invariant"); - return (uintptr_t)mirror->identity_hash(); -} - -static const char* create_hidden_klass_symbol(const InstanceKlass* ik, uintptr_t hash) { - assert(ik != nullptr, "invariant"); - assert(ik->is_hidden(), "invariant"); - assert(hash != 0, "invariant"); - char* hidden_symbol = nullptr; - const oop mirror = ik->java_mirror_no_keepalive(); - assert(mirror != nullptr, "invariant"); - char hash_buf[40]; - os::snprintf_checked(hash_buf, sizeof(hash_buf), "/" UINTX_FORMAT, hash); - const size_t hash_len = strlen(hash_buf); - const size_t result_len = ik->name()->utf8_length(); - hidden_symbol = NEW_RESOURCE_ARRAY(char, result_len + hash_len + 1); - ik->name()->as_klass_external_name(hidden_symbol, (int)result_len + 1); - assert(strlen(hidden_symbol) == result_len, "invariant"); - strcpy(hidden_symbol + result_len, hash_buf); - assert(strlen(hidden_symbol) == result_len + hash_len, "invariant"); - return hidden_symbol; -} - -bool JfrSymbolTable::is_hidden_klass(const Klass* k) { + * The hidden class symbol is the external name with the + * address of its Klass slash appended. + * + * "java.lang.invoke.LambdaForm$DMH/0x0000000037144c00" + * + * Caller needs ResourceMark. + */ +traceid JfrSymbolTable::mark_hidden_klass_name(const Klass* k, bool leakp) { assert(k != nullptr, "invariant"); - return k->is_instance_klass() && ((const InstanceKlass*)k)->is_hidden(); -} - -traceid JfrSymbolTable::mark_hidden_klass_name(const InstanceKlass* ik, bool leakp) { - assert(ik != nullptr, "invariant"); - assert(ik->is_hidden(), "invariant"); - const uintptr_t hash = hidden_klass_name_hash(ik); - const char* const hidden_symbol = create_hidden_klass_symbol(ik, hash); - return mark(hash, hidden_symbol, leakp); + assert(k->is_hidden(), "invariant"); + const uintptr_t hash = k->name()->identity_hash(); + return mark(hash, k->external_name(), leakp); } traceid JfrSymbolTable::mark(const Klass* k, bool leakp) { assert(k != nullptr, "invariant"); traceid symbol_id = 0; - if (is_hidden_klass(k)) { - assert(k->is_instance_klass(), "invariant"); - symbol_id = mark_hidden_klass_name((const InstanceKlass*)k, leakp); + if (k->is_hidden()) { + symbol_id = mark_hidden_klass_name(k, leakp); } else { Symbol* const sym = k->name(); if (sym != nullptr) { diff --git a/src/hotspot/share/jfr/support/jfrSymbolTable.hpp b/src/hotspot/share/jfr/support/jfrSymbolTable.hpp index a1951e52cc376..8cefa287c8ade 100644 --- a/src/hotspot/share/jfr/support/jfrSymbolTable.hpp +++ b/src/hotspot/share/jfr/support/jfrSymbolTable.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -100,16 +100,13 @@ class JfrSymbolTable : public JfrCHeapObj { traceid mark(const Symbol* sym, bool leakp = false); traceid mark(const char* str, bool leakp = false); traceid mark(uintptr_t hash, const char* str, bool leakp); + traceid mark_hidden_klass_name(const Klass* k, bool leakp); traceid bootstrap_name(bool leakp); bool has_entries() const { return has_symbol_entries() || has_string_entries(); } bool has_symbol_entries() const { return _symbol_list != nullptr; } bool has_string_entries() const { return _string_list != nullptr; } - traceid mark_hidden_klass_name(const InstanceKlass* k, bool leakp); - bool is_hidden_klass(const Klass* k); - uintptr_t hidden_klass_name_hash(const InstanceKlass* ik); - // hashtable(s) callbacks void on_link(const SymbolEntry* entry); bool on_equals(uintptr_t hash, const SymbolEntry* entry); From 1ff5f8d65cf6153e517ee7a242d10536eee0d637 Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Tue, 27 Aug 2024 15:18:34 +0000 Subject: [PATCH 43/72] 8338440: Parallel: Improve fragmentation mitigation in Full GC Co-authored-by: Guoxiong Li Reviewed-by: iwalulya, zgu, gli --- .../share/gc/parallel/psParallelCompact.cpp | 705 ++++++++++-------- .../share/gc/parallel/psParallelCompact.hpp | 77 +- 2 files changed, 423 insertions(+), 359 deletions(-) diff --git a/src/hotspot/share/gc/parallel/psParallelCompact.cpp b/src/hotspot/share/gc/parallel/psParallelCompact.cpp index 4bff8f8a7d06a..a4884afe25472 100644 --- a/src/hotspot/share/gc/parallel/psParallelCompact.cpp +++ b/src/hotspot/share/gc/parallel/psParallelCompact.cpp @@ -126,73 +126,82 @@ ParallelCompactData::RegionData::dc_claimed = 0x8U << dc_shift; const ParallelCompactData::RegionData::region_sz_t ParallelCompactData::RegionData::dc_completed = 0xcU << dc_shift; +bool ParallelCompactData::RegionData::is_clear() { + return (_destination == nullptr) && + (_source_region == 0) && + (_partial_obj_addr == nullptr) && + (_partial_obj_size == 0) && + (_dc_and_los == 0) && + (_shadow_state == 0); +} + +#ifdef ASSERT +void ParallelCompactData::RegionData::verify_clear() { + assert(_destination == nullptr, "inv"); + assert(_source_region == 0, "inv"); + assert(_partial_obj_addr == nullptr, "inv"); + assert(_partial_obj_size == 0, "inv"); + assert(_dc_and_los == 0, "inv"); + assert(_shadow_state == 0, "inv"); +} +#endif + SpaceInfo PSParallelCompact::_space_info[PSParallelCompact::last_space_id]; SpanSubjectToDiscoveryClosure PSParallelCompact::_span_based_discoverer; ReferenceProcessor* PSParallelCompact::_ref_processor = nullptr; -void SplitInfo::record(size_t src_region_idx, size_t partial_obj_size, - HeapWord* destination) -{ - assert(src_region_idx != 0, "invalid src_region_idx"); - assert(partial_obj_size != 0, "invalid partial_obj_size argument"); - assert(destination != nullptr, "invalid destination argument"); +void SplitInfo::record(size_t split_region_idx, HeapWord* split_point, size_t preceding_live_words) { + assert(split_region_idx != 0, "precondition"); - _src_region_idx = src_region_idx; - _partial_obj_size = partial_obj_size; - _destination = destination; + // Obj denoted by split_point will be deferred to the next space. + assert(split_point != nullptr, "precondition"); - // These fields may not be updated below, so make sure they're clear. - assert(_dest_region_addr == nullptr, "should have been cleared"); - assert(_first_src_addr == nullptr, "should have been cleared"); - - // Determine the number of destination regions for the partial object. - HeapWord* const last_word = destination + partial_obj_size - 1; const ParallelCompactData& sd = PSParallelCompact::summary_data(); - HeapWord* const beg_region_addr = sd.region_align_down(destination); - HeapWord* const end_region_addr = sd.region_align_down(last_word); - - if (beg_region_addr == end_region_addr) { - // One destination region. - _destination_count = 1; - if (end_region_addr == destination) { - // The destination falls on a region boundary, thus the first word of the - // partial object will be the first word copied to the destination region. - _dest_region_addr = end_region_addr; - _first_src_addr = sd.region_to_addr(src_region_idx); - } + + PSParallelCompact::RegionData* split_region_ptr = sd.region(split_region_idx); + assert(preceding_live_words < split_region_ptr->data_size(), "inv"); + + HeapWord* preceding_destination = split_region_ptr->destination(); + assert(preceding_destination != nullptr, "inv"); + + // How many regions does the preceding part occupy + uint preceding_destination_count; + if (preceding_live_words == 0) { + preceding_destination_count = 0; } else { - // Two destination regions. When copied, the partial object will cross a - // destination region boundary, so a word somewhere within the partial - // object will be the first word copied to the second destination region. - _destination_count = 2; - _dest_region_addr = end_region_addr; - const size_t ofs = pointer_delta(end_region_addr, destination); - assert(ofs < _partial_obj_size, "sanity"); - _first_src_addr = sd.region_to_addr(src_region_idx) + ofs; + if (preceding_destination + preceding_live_words > sd.region_align_up(preceding_destination)) { + preceding_destination_count = 2; + } else { + preceding_destination_count = 1; + } } + + _split_region_idx = split_region_idx; + _split_point = split_point; + _preceding_live_words = preceding_live_words; + _preceding_destination = preceding_destination; + _preceding_destination_count = preceding_destination_count; } void SplitInfo::clear() { - _src_region_idx = 0; - _partial_obj_size = 0; - _destination = nullptr; - _destination_count = 0; - _dest_region_addr = nullptr; - _first_src_addr = nullptr; + _split_region_idx = 0; + _split_point = nullptr; + _preceding_live_words = 0; + _preceding_destination = nullptr; + _preceding_destination_count = 0; assert(!is_valid(), "sanity"); } #ifdef ASSERT void SplitInfo::verify_clear() { - assert(_src_region_idx == 0, "not clear"); - assert(_partial_obj_size == 0, "not clear"); - assert(_destination == nullptr, "not clear"); - assert(_destination_count == 0, "not clear"); - assert(_dest_region_addr == nullptr, "not clear"); - assert(_first_src_addr == nullptr, "not clear"); + assert(_split_region_idx == 0, "not clear"); + assert(_split_point == nullptr, "not clear"); + assert(_preceding_live_words == 0, "not clear"); + assert(_preceding_destination == nullptr, "not clear"); + assert(_preceding_destination_count == 0, "not clear"); } #endif // #ifdef ASSERT @@ -295,110 +304,105 @@ ParallelCompactData::summarize_dense_prefix(HeapWord* beg, HeapWord* end) } } -// Find the point at which a space can be split and, if necessary, record the -// split point. -// -// If the current src region (which overflowed the destination space) doesn't -// have a partial object, the split point is at the beginning of the current src -// region (an "easy" split, no extra bookkeeping required). -// -// If the current src region has a partial object, the split point is in the -// region where that partial object starts (call it the split_region). If -// split_region has a partial object, then the split point is just after that -// partial object (a "hard" split where we have to record the split data and -// zero the partial_obj_size field). With a "hard" split, we know that the -// partial_obj ends within split_region because the partial object that caused -// the overflow starts in split_region. If split_region doesn't have a partial -// obj, then the split is at the beginning of split_region (another "easy" -// split). -HeapWord* -ParallelCompactData::summarize_split_space(size_t src_region, - SplitInfo& split_info, - HeapWord* destination, - HeapWord* target_end, - HeapWord** target_next) -{ +// The total live words on src_region would overflow the target space, so find +// the overflowing object and record the split point. The invariant is that an +// obj should not cross space boundary. +HeapWord* ParallelCompactData::summarize_split_space(size_t src_region, + SplitInfo& split_info, + HeapWord* const destination, + HeapWord* const target_end, + HeapWord** target_next) { assert(destination <= target_end, "sanity"); assert(destination + _region_data[src_region].data_size() > target_end, "region should not fit into target space"); assert(is_region_aligned(target_end), "sanity"); - size_t split_region = src_region; - HeapWord* split_destination = destination; size_t partial_obj_size = _region_data[src_region].partial_obj_size(); if (destination + partial_obj_size > target_end) { - // The split point is just after the partial object (if any) in the - // src_region that contains the start of the object that overflowed the - // destination space. + assert(partial_obj_size > 0, "inv"); + // The overflowing obj is from a previous region. + // + // source-regions: // - // Find the start of the "overflow" object and set split_region to the - // region containing it. - HeapWord* const overflow_obj = _region_data[src_region].partial_obj_addr(); - split_region = addr_to_region_idx(overflow_obj); - - // Clear the source_region field of all destination regions whose first word - // came from data after the split point (a non-null source_region field - // implies a region must be filled). + // *************** + // | A|AA | + // *************** + // ^ + // | split-point // - // An alternative to the simple loop below: clear during post_compact(), - // which uses memcpy instead of individual stores, and is easy to - // parallelize. (The downside is that it clears the entire RegionData - // object as opposed to just one field.) + // dest-region: // - // post_compact() would have to clear the summary data up to the highest - // address that was written during the summary phase, which would be + // ******** + // |~~~~A | + // ******** + // ^^ + // || target-space-end + // | + // | destination // - // max(top, max(new_top, clear_top)) + // AAA would overflow target-space. // - // where clear_top is a new field in SpaceInfo. Would have to set clear_top - // to target_end. - const RegionData* const sr = region(split_region); - const size_t beg_idx = - addr_to_region_idx(region_align_up(sr->destination() + - sr->partial_obj_size())); - const size_t end_idx = addr_to_region_idx(target_end); + HeapWord* overflowing_obj = _region_data[src_region].partial_obj_addr(); + size_t split_region = addr_to_region_idx(overflowing_obj); - log_develop_trace(gc, compaction)("split: clearing source_region field in [" SIZE_FORMAT ", " SIZE_FORMAT ")", beg_idx, end_idx); - for (size_t idx = beg_idx; idx < end_idx; ++idx) { - _region_data[idx].set_source_region(0); + // The number of live words before the overflowing object on this split region + size_t preceding_live_words; + if (is_region_aligned(overflowing_obj)) { + preceding_live_words = 0; + } else { + // Words accounted by the overflowing object on the split region + size_t overflowing_size = pointer_delta(region_align_up(overflowing_obj), overflowing_obj); + preceding_live_words = region(split_region)->data_size() - overflowing_size; } - // Set split_destination and partial_obj_size to reflect the split region. - split_destination = sr->destination(); - partial_obj_size = sr->partial_obj_size(); - } + split_info.record(split_region, overflowing_obj, preceding_live_words); + + HeapWord* src_region_start = region_to_addr(src_region); + HeapWord* new_top = destination - pointer_delta(src_region_start, overflowing_obj); + + // If the overflowing obj was relocated to its original destination, + // those destination regions would have their source_region set. Now that + // this overflowing obj is relocated somewhere else, reset the + // source_region. + { + size_t range_start = addr_to_region_idx(region_align_up(new_top)); + size_t range_end = addr_to_region_idx(region_align_up(destination)); + for (size_t i = range_start; i < range_end; ++i) { + region(i)->set_source_region(0); + } + } - // The split is recorded only if a partial object extends onto the region. - if (partial_obj_size != 0) { - _region_data[split_region].set_partial_obj_size(0); - split_info.record(split_region, partial_obj_size, split_destination); + // Update new top of target space + *target_next = new_top; + + return overflowing_obj; } - // Setup the continuation addresses. - *target_next = split_destination + partial_obj_size; - HeapWord* const source_next = region_to_addr(split_region) + partial_obj_size; + // Obj-iteration to locate the overflowing obj + HeapWord* region_start = region_to_addr(src_region); + HeapWord* region_end = region_start + RegionSize; + HeapWord* cur_addr = region_start + partial_obj_size; + size_t live_words = partial_obj_size; - if (log_develop_is_enabled(Trace, gc, compaction)) { - const char * split_type = partial_obj_size == 0 ? "easy" : "hard"; - log_develop_trace(gc, compaction)("%s split: src=" PTR_FORMAT " src_c=" SIZE_FORMAT " pos=" SIZE_FORMAT, - split_type, p2i(source_next), split_region, partial_obj_size); - log_develop_trace(gc, compaction)("%s split: dst=" PTR_FORMAT " dst_c=" SIZE_FORMAT " tn=" PTR_FORMAT, - split_type, p2i(split_destination), - addr_to_region_idx(split_destination), - p2i(*target_next)); + while (true) { + assert(cur_addr < region_end, "inv"); + cur_addr = PSParallelCompact::mark_bitmap()->find_obj_beg(cur_addr, region_end); + // There must be an overflowing obj in this region + assert(cur_addr < region_end, "inv"); - if (partial_obj_size != 0) { - HeapWord* const po_beg = split_info.destination(); - HeapWord* const po_end = po_beg + split_info.partial_obj_size(); - log_develop_trace(gc, compaction)("%s split: po_beg=" PTR_FORMAT " " SIZE_FORMAT " po_end=" PTR_FORMAT " " SIZE_FORMAT, - split_type, - p2i(po_beg), addr_to_region_idx(po_beg), - p2i(po_end), addr_to_region_idx(po_end)); + oop obj = cast_to_oop(cur_addr); + size_t obj_size = obj->size(); + if (destination + live_words + obj_size > target_end) { + // Found the overflowing obj + split_info.record(src_region, cur_addr, live_words); + *target_next = destination + live_words; + return cur_addr; } - } - return source_next; + live_words += obj_size; + cur_addr += obj_size; + } } size_t ParallelCompactData::live_words_in_space(const MutableSpace* space, @@ -450,70 +454,57 @@ bool ParallelCompactData::summarize(SplitInfo& split_info, const size_t end_region = addr_to_region_idx(region_align_up(source_end)); HeapWord *dest_addr = target_beg; - while (cur_region < end_region) { - // The destination must be set even if the region has no data. + for (/* empty */; cur_region < end_region; cur_region++) { + size_t words = _region_data[cur_region].data_size(); + + // Skip empty ones + if (words == 0) { + continue; + } + + if (split_info.is_split(cur_region)) { + assert(words > split_info.preceding_live_words(), "inv"); + words -= split_info.preceding_live_words(); + } + _region_data[cur_region].set_destination(dest_addr); - size_t words = _region_data[cur_region].data_size(); - if (words > 0) { - // If cur_region does not fit entirely into the target space, find a point - // at which the source space can be 'split' so that part is copied to the - // target space and the rest is copied elsewhere. - if (dest_addr + words > target_end) { - assert(source_next != nullptr, "source_next is null when splitting"); - *source_next = summarize_split_space(cur_region, split_info, dest_addr, - target_end, target_next); - return false; - } + // If cur_region does not fit entirely into the target space, find a point + // at which the source space can be 'split' so that part is copied to the + // target space and the rest is copied elsewhere. + if (dest_addr + words > target_end) { + assert(source_next != nullptr, "source_next is null when splitting"); + *source_next = summarize_split_space(cur_region, split_info, dest_addr, + target_end, target_next); + return false; + } - // Compute the destination_count for cur_region, and if necessary, update - // source_region for a destination region. The source_region field is - // updated if cur_region is the first (left-most) region to be copied to a - // destination region. - // - // The destination_count calculation is a bit subtle. A region that has - // data that compacts into itself does not count itself as a destination. - // This maintains the invariant that a zero count means the region is - // available and can be claimed and then filled. - uint destination_count = 0; - if (split_info.is_split(cur_region)) { - // The current region has been split: the partial object will be copied - // to one destination space and the remaining data will be copied to - // another destination space. Adjust the initial destination_count and, - // if necessary, set the source_region field if the partial object will - // cross a destination region boundary. - destination_count = split_info.destination_count(); - if (destination_count == 2) { - size_t dest_idx = addr_to_region_idx(split_info.dest_region_addr()); - _region_data[dest_idx].set_source_region(cur_region); - } - } + uint destination_count = split_info.is_split(cur_region) + ? split_info.preceding_destination_count() + : 0; - HeapWord* const last_addr = dest_addr + words - 1; - const size_t dest_region_1 = addr_to_region_idx(dest_addr); - const size_t dest_region_2 = addr_to_region_idx(last_addr); - - // Initially assume that the destination regions will be the same and - // adjust the value below if necessary. Under this assumption, if - // cur_region == dest_region_2, then cur_region will be compacted - // completely into itself. - destination_count += cur_region == dest_region_2 ? 0 : 1; - if (dest_region_1 != dest_region_2) { - // Destination regions differ; adjust destination_count. - destination_count += 1; - // Data from cur_region will be copied to the start of dest_region_2. - _region_data[dest_region_2].set_source_region(cur_region); - } else if (is_region_aligned(dest_addr)) { - // Data from cur_region will be copied to the start of the destination - // region. - _region_data[dest_region_1].set_source_region(cur_region); - } + HeapWord* const last_addr = dest_addr + words - 1; + const size_t dest_region_1 = addr_to_region_idx(dest_addr); + const size_t dest_region_2 = addr_to_region_idx(last_addr); - _region_data[cur_region].set_destination_count(destination_count); - dest_addr += words; + // Initially assume that the destination regions will be the same and + // adjust the value below if necessary. Under this assumption, if + // cur_region == dest_region_2, then cur_region will be compacted + // completely into itself. + destination_count += cur_region == dest_region_2 ? 0 : 1; + if (dest_region_1 != dest_region_2) { + // Destination regions differ; adjust destination_count. + destination_count += 1; + // Data from cur_region will be copied to the start of dest_region_2. + _region_data[dest_region_2].set_source_region(cur_region); + } else if (is_region_aligned(dest_addr)) { + // Data from cur_region will be copied to the start of the destination + // region. + _region_data[dest_region_1].set_source_region(cur_region); } - ++cur_region; + _region_data[cur_region].set_destination_count(destination_count); + dest_addr += words; } *target_next = dest_addr; @@ -521,12 +512,12 @@ bool ParallelCompactData::summarize(SplitInfo& split_info, } #ifdef ASSERT -void ParallelCompactData::verify_clear() -{ - const size_t* const beg = (const size_t*) _region_vspace->committed_low_addr(); - const size_t* const end = (const size_t*) _region_vspace->committed_high_addr(); - for (const size_t* p = beg; p < end; ++p) { - assert(*p == 0, "not zero"); +void ParallelCompactData::verify_clear() { + for (uint cur_idx = 0; cur_idx < region_count(); ++cur_idx) { + if (!region(cur_idx)->is_clear()) { + log_warning(gc)("Uncleared Region: %u", cur_idx); + region(cur_idx)->verify_clear(); + } } } #endif // #ifdef ASSERT @@ -693,6 +684,13 @@ void PSParallelCompact::post_compact() } } +#ifdef ASSERT + { + mark_bitmap()->verify_clear(); + summary_data().verify_clear(); + } +#endif + ParCompactionManager::flush_all_string_dedup_requests(); MutableSpace* const eden_space = _space_info[eden_space_id].space(); @@ -879,10 +877,10 @@ void PSParallelCompact::summary_phase() bool maximum_compaction = check_maximum_compaction(total_live_words, old_space, full_region_prefix_end); - HeapWord* dense_prefix_end = - maximum_compaction ? full_region_prefix_end - : compute_dense_prefix_for_old_space(old_space, - full_region_prefix_end); + HeapWord* dense_prefix_end = maximum_compaction + ? full_region_prefix_end + : compute_dense_prefix_for_old_space(old_space, + full_region_prefix_end); SpaceId id = old_space_id; _space_info[id].set_dense_prefix(dense_prefix_end); @@ -890,6 +888,8 @@ void PSParallelCompact::summary_phase() fill_dense_prefix_end(id); _summary_data.summarize_dense_prefix(old_space->bottom(), dense_prefix_end); } + + // Compacting objs inn [dense_prefix_end, old_space->top()) _summary_data.summarize(_space_info[id].split_info(), dense_prefix_end, old_space->top(), nullptr, dense_prefix_end, old_space->end(), @@ -1549,6 +1549,30 @@ void PSParallelCompact::forward_to_new_addr() { WorkerTask("PSForward task"), _num_workers(num_workers) {} + static void forward_objs_in_range(ParCompactionManager* cm, + HeapWord* start, + HeapWord* end, + HeapWord* destination) { + HeapWord* cur_addr = start; + HeapWord* new_addr = destination; + + while (cur_addr < end) { + cur_addr = mark_bitmap()->find_obj_beg(cur_addr, end); + if (cur_addr >= end) { + return; + } + assert(mark_bitmap()->is_marked(cur_addr), "inv"); + oop obj = cast_to_oop(cur_addr); + if (new_addr != cur_addr) { + cm->preserved_marks()->push_if_necessary(obj, obj->mark()); + obj->forward_to(cast_to_oop(new_addr)); + } + size_t obj_size = obj->size(); + new_addr += obj_size; + cur_addr += obj_size; + } + } + void work(uint worker_id) override { ParCompactionManager* cm = ParCompactionManager::gc_thread_compaction_manager(worker_id); for (uint id = old_space_id; id < last_space_id; ++id) { @@ -1560,6 +1584,8 @@ void PSParallelCompact::forward_to_new_addr() { continue; } + const SplitInfo& split_info = _space_info[SpaceId(id)].split_info(); + size_t dense_prefix_region = _summary_data.addr_to_region_idx(dense_prefix_addr); size_t top_region = _summary_data.addr_to_region_idx(_summary_data.region_align_up(top)); size_t start_region; @@ -1579,24 +1605,19 @@ void PSParallelCompact::forward_to_new_addr() { HeapWord* region_start = _summary_data.region_to_addr(cur_region); HeapWord* region_end = region_start + ParallelCompactData::RegionSize; - HeapWord* cur_addr = region_start + live_words; - - HeapWord* destination = region_ptr->destination(); - while (cur_addr < region_end) { - cur_addr = mark_bitmap()->find_obj_beg(cur_addr, region_end); - if (cur_addr >= region_end) { - break; - } - assert(mark_bitmap()->is_marked(cur_addr), "inv"); - HeapWord* new_addr = destination + live_words; - oop obj = cast_to_oop(cur_addr); - if (new_addr != cur_addr) { - cm->preserved_marks()->push_if_necessary(obj, obj->mark()); - obj->forward_to(cast_to_oop(new_addr)); - } - size_t obj_size = obj->size(); - live_words += obj_size; - cur_addr += obj_size; + + if (split_info.is_split(cur_region)) { + // Part 1: will be relocated to space-1 + HeapWord* preceding_destination = split_info.preceding_destination(); + HeapWord* split_point = split_info.split_point(); + forward_objs_in_range(cm, region_start + live_words, split_point, preceding_destination + live_words); + + // Part 2: will be relocated to space-2 + HeapWord* destination = region_ptr->destination(); + forward_objs_in_range(cm, split_point, region_end, destination); + } else { + HeapWord* destination = region_ptr->destination(); + forward_objs_in_range(cm, region_start + live_words, region_end, destination + live_words); } } } @@ -1628,13 +1649,16 @@ void PSParallelCompact::verify_forward() { break; } assert(mark_bitmap()->is_marked(cur_addr), "inv"); + assert(bump_ptr <= _space_info[bump_ptr_space].new_top(), "inv"); // Move to the space containing cur_addr if (bump_ptr == _space_info[bump_ptr_space].new_top()) { bump_ptr = space(space_id(cur_addr))->bottom(); bump_ptr_space = space_id(bump_ptr); } oop obj = cast_to_oop(cur_addr); - if (cur_addr != bump_ptr) { + if (cur_addr == bump_ptr) { + assert(!obj->is_forwarded(), "inv"); + } else { assert(obj->forwardee() == cast_to_oop(bump_ptr), "inv"); } bump_ptr += obj->size(); @@ -1944,15 +1968,10 @@ PSParallelCompact::SpaceId PSParallelCompact::space_id(HeapWord* addr) { } // Skip over count live words starting from beg, and return the address of the -// next live word. Unless marked, the word corresponding to beg is assumed to -// be dead. Callers must either ensure beg does not correspond to the middle of -// an object, or account for those live words in some other way. Callers must -// also ensure that there are enough live words in the range [beg, end) to skip. -HeapWord* -PSParallelCompact::skip_live_words(HeapWord* beg, HeapWord* end, size_t count) +// next live word. Callers must also ensure that there are enough live words in +// the range [beg, end) to skip. +HeapWord* PSParallelCompact::skip_live_words(HeapWord* beg, HeapWord* end, size_t count) { - assert(count > 0, "sanity"); - ParMarkBitMap* m = mark_bitmap(); HeapWord* cur_addr = beg; while (true) { @@ -1968,69 +1987,94 @@ PSParallelCompact::skip_live_words(HeapWord* beg, HeapWord* end, size_t count) } } +// On filling a destination region (dest-region), we need to know the location +// of the word that will be at the start of the dest-region after compaction. +// A dest-region can have one or more source regions, but only the first +// source-region contains this location. This location is retrieved by calling +// `first_src_addr` on a dest-region. +// Conversely, a source-region has a dest-region which holds the destination of +// the first live word on this source-region, based on which the destination +// for the rest of live words can be derived. +// +// Note: +// There is some complication due to space-boundary-fragmentation (an obj can't +// cross space-boundary) -- a source-region may be split and behave like two +// distinct regions with their own dest-region, as depicted below. +// +// source-region: region-n +// +// ********************** +// | A|A~~~~B|B | +// ********************** +// n-1 n n+1 +// +// AA, BB denote two live objs. ~~~~ denotes unknown number of live objs. +// +// Assuming the dest-region for region-n is the final region before +// old-space-end and its first-live-word is the middle of AA, the heap content +// will look like the following after compaction: +// +// ************** ************* +// A|A~~~~ | |BB | +// ************** ************* +// ^ ^ +// | old-space-end | eden-space-start +// +// Therefore, in this example, region-n will have two dest-regions, one for +// the final region in old-space and the other for the first region in +// eden-space. +// To handle this special case, we introduce the concept of split-region, whose +// contents are relocated to two spaces. `SplitInfo` captures all necessary +// info about the split, the first part, spliting-point, and the second part. HeapWord* PSParallelCompact::first_src_addr(HeapWord* const dest_addr, SpaceId src_space_id, size_t src_region_idx) { - assert(summary_data().is_region_aligned(dest_addr), "not aligned"); - - const SplitInfo& split_info = _space_info[src_space_id].split_info(); - if (split_info.dest_region_addr() == dest_addr) { - // The partial object ending at the split point contains the first word to - // be copied to dest_addr. - return split_info.first_src_addr(); - } - - const ParallelCompactData& sd = summary_data(); - ParMarkBitMap* const bitmap = mark_bitmap(); const size_t RegionSize = ParallelCompactData::RegionSize; + const ParallelCompactData& sd = summary_data(); + assert(sd.is_region_aligned(dest_addr), "precondition"); - assert(sd.is_region_aligned(dest_addr), "not aligned"); const RegionData* const src_region_ptr = sd.region(src_region_idx); + assert(src_region_ptr->data_size() > 0, "src region cannot be empty"); + const size_t partial_obj_size = src_region_ptr->partial_obj_size(); HeapWord* const src_region_destination = src_region_ptr->destination(); - assert(dest_addr >= src_region_destination, "wrong src region"); - assert(src_region_ptr->data_size() > 0, "src region cannot be empty"); - - HeapWord* const src_region_beg = sd.region_to_addr(src_region_idx); - HeapWord* const src_region_end = src_region_beg + RegionSize; + HeapWord* const region_start = sd.region_to_addr(src_region_idx); + HeapWord* const region_end = sd.region_to_addr(src_region_idx) + RegionSize; - HeapWord* addr = src_region_beg; - if (dest_addr == src_region_destination) { - // Return the first live word in the source region. - if (partial_obj_size == 0) { - addr = bitmap->find_obj_beg(addr, src_region_end); - assert(addr < src_region_end, "no objects start in src region"); + // Identify the actual destination for the first live words on this region, + // taking split-region into account. + HeapWord* region_start_destination; + const SplitInfo& split_info = _space_info[src_space_id].split_info(); + if (split_info.is_split(src_region_idx)) { + // The second part of this split region; use the recorded split point. + if (dest_addr == src_region_destination) { + return split_info.split_point(); } - return addr; + region_start_destination = split_info.preceding_destination(); + } else { + region_start_destination = src_region_destination; } - // Must skip some live data. - size_t words_to_skip = dest_addr - src_region_destination; - assert(src_region_ptr->data_size() > words_to_skip, "wrong src region"); + // Calculate the offset to be skipped + size_t words_to_skip = pointer_delta(dest_addr, region_start_destination); - if (partial_obj_size >= words_to_skip) { - // All the live words to skip are part of the partial object. - addr += words_to_skip; - if (partial_obj_size == words_to_skip) { - // Find the first live word past the partial object. - addr = bitmap->find_obj_beg(addr, src_region_end); - assert(addr < src_region_end, "wrong src region"); - } - return addr; + HeapWord* result; + if (partial_obj_size > words_to_skip) { + result = region_start + words_to_skip; + } else { + words_to_skip -= partial_obj_size; + result = skip_live_words(region_start + partial_obj_size, region_end, words_to_skip); } - // Skip over the partial object (if any). - if (partial_obj_size != 0) { - words_to_skip -= partial_obj_size; - addr += partial_obj_size; + if (split_info.is_split(src_region_idx)) { + assert(result < split_info.split_point(), "postcondition"); + } else { + assert(result < region_end, "postcondition"); } - // Skip over live words due to objects that start in the region. - addr = skip_live_words(addr, src_region_end, words_to_skip); - assert(addr < src_region_end, "wrong src region"); - return addr; + return result; } void PSParallelCompact::decrement_destination_counts(ParCompactionManager* cm, @@ -2081,10 +2125,7 @@ size_t PSParallelCompact::next_src_region(MoveAndUpdateClosure& closure, HeapWord*& src_space_top, HeapWord* end_addr) { - typedef ParallelCompactData::RegionData RegionData; - ParallelCompactData& sd = PSParallelCompact::summary_data(); - const size_t region_size = ParallelCompactData::RegionSize; size_t src_region_idx = 0; @@ -2092,8 +2133,8 @@ size_t PSParallelCompact::next_src_region(MoveAndUpdateClosure& closure, HeapWord* const src_aligned_up = sd.region_align_up(end_addr); RegionData* src_region_ptr = sd.addr_to_region_ptr(src_aligned_up); HeapWord* const top_aligned_up = sd.region_align_up(src_space_top); - const RegionData* const top_region_ptr = - sd.addr_to_region_ptr(top_aligned_up); + const RegionData* const top_region_ptr = sd.addr_to_region_ptr(top_aligned_up); + while (src_region_ptr < top_region_ptr && src_region_ptr->data_size() == 0) { ++src_region_ptr; } @@ -2110,59 +2151,50 @@ size_t PSParallelCompact::next_src_region(MoveAndUpdateClosure& closure, } // Switch to a new source space and find the first non-empty region. - unsigned int space_id = src_space_id + 1; + uint space_id = src_space_id + 1; assert(space_id < last_space_id, "not enough spaces"); - HeapWord* const destination = closure.destination(); - - do { - MutableSpace* space = _space_info[space_id].space(); - HeapWord* const bottom = space->bottom(); - const RegionData* const bottom_cp = sd.addr_to_region_ptr(bottom); - - // Iterate over the spaces that do not compact into themselves. - if (bottom_cp->destination() != bottom) { - HeapWord* const top_aligned_up = sd.region_align_up(space->top()); - const RegionData* const top_cp = sd.addr_to_region_ptr(top_aligned_up); - - for (const RegionData* src_cp = bottom_cp; src_cp < top_cp; ++src_cp) { - if (src_cp->live_obj_size() > 0) { - // Found it. - assert(src_cp->destination() == destination, - "first live obj in the space must match the destination"); - assert(src_cp->partial_obj_size() == 0, - "a space cannot begin with a partial obj"); - - src_space_id = SpaceId(space_id); - src_space_top = space->top(); - const size_t src_region_idx = sd.region(src_cp); - closure.set_source(sd.region_to_addr(src_region_idx)); - return src_region_idx; - } else { - assert(src_cp->data_size() == 0, "sanity"); - } + for (/* empty */; space_id < last_space_id; ++space_id) { + HeapWord* bottom = _space_info[space_id].space()->bottom(); + HeapWord* top = _space_info[space_id].space()->top(); + // Skip empty space + if (bottom == top) { + continue; + } + + // Identify the first region that contains live words in this space + size_t cur_region = sd.addr_to_region_idx(bottom); + size_t end_region = sd.addr_to_region_idx(sd.region_align_up(top)); + + for (/* empty */ ; cur_region < end_region; ++cur_region) { + RegionData* cur = sd.region(cur_region); + if (cur->live_obj_size() > 0) { + HeapWord* region_start_addr = sd.region_to_addr(cur_region); + HeapWord* region_end_addr = region_start_addr + ParallelCompactData::RegionSize; + HeapWord* first_live_word = mark_bitmap()->find_obj_beg(region_start_addr, region_end_addr); + assert(first_live_word < region_end_addr, "inv"); + + src_space_id = SpaceId(space_id); + src_space_top = top; + closure.set_source(first_live_word); + return cur_region; } } - } while (++space_id < last_space_id); + } - assert(false, "no source region was found"); - return 0; + ShouldNotReachHere(); } HeapWord* PSParallelCompact::partial_obj_end(HeapWord* region_start_addr) { ParallelCompactData& sd = summary_data(); assert(sd.is_region_aligned(region_start_addr), "precondition"); - // Use per-region partial_obj_size to locate the end of the obj, that extends to region_start_addr. - SplitInfo& split_info = _space_info[space_id(region_start_addr)].split_info(); + // Use per-region partial_obj_size to locate the end of the obj, that extends + // to region_start_addr. size_t start_region_idx = sd.addr_to_region_idx(region_start_addr); size_t end_region_idx = sd.region_count(); size_t accumulated_size = 0; for (size_t region_idx = start_region_idx; region_idx < end_region_idx; ++region_idx) { - if (split_info.is_split(region_idx)) { - accumulated_size += split_info.partial_obj_size(); - break; - } size_t cur_partial_obj_size = sd.region(region_idx)->partial_obj_size(); accumulated_size += cur_partial_obj_size; if (cur_partial_obj_size != ParallelCompactData::RegionSize) { @@ -2172,6 +2204,8 @@ HeapWord* PSParallelCompact::partial_obj_end(HeapWord* region_start_addr) { return region_start_addr + accumulated_size; } +// Use region_idx as the destination region, and evacuate all live objs on its +// source regions to this destination region. void PSParallelCompact::fill_region(ParCompactionManager* cm, MoveAndUpdateClosure& closure, size_t region_idx) { ParMarkBitMap* const bitmap = mark_bitmap(); @@ -2192,20 +2226,43 @@ void PSParallelCompact::fill_region(ParCompactionManager* cm, MoveAndUpdateClosu src_region_idx += 1; } + // source-region: + // + // ********** + // | ~~~ | + // ********** + // ^ + // |-- closure.source() / first_src_addr + // + // + // ~~~ : live words + // + // destination-region: + // + // ********** + // | | + // ********** + // ^ + // |-- region-start if (bitmap->is_unmarked(closure.source())) { - // The first source word is in the middle of an object; copy the remainder - // of the object or as much as will fit. The fact that pointer updates were - // deferred will be noted when the object header is processed. + // An object overflows the previous destination region, so this + // destination region should copy the remainder of the object or as much as + // will fit. HeapWord* const old_src_addr = closure.source(); { HeapWord* region_start = sd.region_align_down(closure.source()); HeapWord* obj_start = bitmap->find_obj_beg_reverse(region_start, closure.source()); HeapWord* obj_end; - if (bitmap->is_marked(obj_start)) { + if (obj_start != closure.source()) { + assert(bitmap->is_marked(obj_start), "inv"); + // Found the actual obj-start, try to find the obj-end using either + // size() if this obj is completely contained in the current region. HeapWord* next_region_start = region_start + ParallelCompactData::RegionSize; HeapWord* partial_obj_start = (next_region_start >= src_space_top) ? nullptr : sd.addr_to_region_ptr(next_region_start)->partial_obj_addr(); + // This obj extends to next region iff partial_obj_addr of the *next* + // region is the same as obj-start. if (partial_obj_start == obj_start) { // This obj extends to next region. obj_end = partial_obj_end(next_region_start); @@ -2222,39 +2279,41 @@ void PSParallelCompact::fill_region(ParCompactionManager* cm, MoveAndUpdateClosu } if (closure.is_full()) { - decrement_destination_counts(cm, src_space_id, src_region_idx, - closure.source()); + decrement_destination_counts(cm, src_space_id, src_region_idx, closure.source()); closure.complete_region(dest_addr, region_ptr); return; } + // Finished copying without using up the current destination-region HeapWord* const end_addr = sd.region_align_down(closure.source()); if (sd.region_align_down(old_src_addr) != end_addr) { + assert(sd.region_align_up(old_src_addr) == end_addr, "only one region"); // The partial object was copied from more than one source region. decrement_destination_counts(cm, src_space_id, src_region_idx, end_addr); // Move to the next source region, possibly switching spaces as well. All // args except end_addr may be modified. - src_region_idx = next_src_region(closure, src_space_id, src_space_top, - end_addr); + src_region_idx = next_src_region(closure, src_space_id, src_space_top, end_addr); } } + // Handle the rest obj-by-obj, where we know obj-start. do { HeapWord* cur_addr = closure.source(); HeapWord* const end_addr = MIN2(sd.region_align_up(cur_addr + 1), src_space_top); - HeapWord* partial_obj_start = (end_addr == src_space_top) + // To handle the case where the final obj in source region extends to next region. + HeapWord* final_obj_start = (end_addr == src_space_top) ? nullptr : sd.addr_to_region_ptr(end_addr)->partial_obj_addr(); - // apply closure on objs inside [cur_addr, end_addr) + // Apply closure on objs inside [cur_addr, end_addr) do { cur_addr = bitmap->find_obj_beg(cur_addr, end_addr); if (cur_addr == end_addr) { break; } size_t obj_size; - if (partial_obj_start == cur_addr) { + if (final_obj_start == cur_addr) { obj_size = pointer_delta(partial_obj_end(end_addr), cur_addr); } else { // This obj doesn't extend into next region; size() is safe to use. @@ -2265,8 +2324,7 @@ void PSParallelCompact::fill_region(ParCompactionManager* cm, MoveAndUpdateClosu } while (cur_addr < end_addr && !closure.is_full()); if (closure.is_full()) { - decrement_destination_counts(cm, src_space_id, src_region_idx, - closure.source()); + decrement_destination_counts(cm, src_space_id, src_region_idx, closure.source()); closure.complete_region(dest_addr, region_ptr); return; } @@ -2275,8 +2333,7 @@ void PSParallelCompact::fill_region(ParCompactionManager* cm, MoveAndUpdateClosu // Move to the next source region, possibly switching spaces as well. All // args except end_addr may be modified. - src_region_idx = next_src_region(closure, src_space_id, src_space_top, - end_addr); + src_region_idx = next_src_region(closure, src_space_id, src_space_top, end_addr); } while (true); } diff --git a/src/hotspot/share/gc/parallel/psParallelCompact.hpp b/src/hotspot/share/gc/parallel/psParallelCompact.hpp index 3f487ec3ef483..878a09e283b18 100644 --- a/src/hotspot/share/gc/parallel/psParallelCompact.hpp +++ b/src/hotspot/share/gc/parallel/psParallelCompact.hpp @@ -116,51 +116,45 @@ class SplitInfo // Return true if this split info is valid (i.e., if a split has been // recorded). The very first region cannot have a partial object and thus is // never split, so 0 is the 'invalid' value. - bool is_valid() const { return _src_region_idx > 0; } + bool is_valid() const { return _split_region_idx > 0; } // Return true if this split holds data for the specified source region. - inline bool is_split(size_t source_region) const; + inline bool is_split(size_t region_idx) const; - // The index of the split region, the size of the partial object on that - // region and the destination of the partial object. - size_t partial_obj_size() const { return _partial_obj_size; } - HeapWord* destination() const { return _destination; } + // Obj at the split point doesn't fit the previous space and will be relocated to the next space. + HeapWord* split_point() const { return _split_point; } - // The destination count of the partial object referenced by this split - // (either 1 or 2). This must be added to the destination count of the - // remainder of the source region. - unsigned int destination_count() const { return _destination_count; } + // Number of live words before the split point on this region. + size_t preceding_live_words() const { return _preceding_live_words; } - // If a word within the partial object will be written to the first word of a - // destination region, this is the address of the destination region; - // otherwise this is null. - HeapWord* dest_region_addr() const { return _dest_region_addr; } + // A split region has two "destinations", living in two spaces. This method + // returns the first one -- destination for the first live word on + // this split region. + HeapWord* preceding_destination() const { + assert(_preceding_destination != nullptr, "inv"); + return _preceding_destination; + } - // If a word within the partial object will be written to the first word of a - // destination region, this is the address of that word within the partial - // object; otherwise this is null. - HeapWord* first_src_addr() const { return _first_src_addr; } + // Number of regions the preceding live words are relocated into. + uint preceding_destination_count() const { return _preceding_destination_count; } - // Record the data necessary to split the region src_region_idx. - void record(size_t src_region_idx, size_t partial_obj_size, - HeapWord* destination); + void record(size_t split_region_idx, HeapWord* split_point, size_t preceding_live_words); void clear(); DEBUG_ONLY(void verify_clear();) private: - size_t _src_region_idx; - size_t _partial_obj_size; - HeapWord* _destination; - unsigned int _destination_count; - HeapWord* _dest_region_addr; - HeapWord* _first_src_addr; + size_t _split_region_idx; + HeapWord* _split_point; + size_t _preceding_live_words; + HeapWord* _preceding_destination; + uint _preceding_destination_count; }; inline bool SplitInfo::is_split(size_t region_idx) const { - return _src_region_idx == region_idx && is_valid(); + return _split_region_idx == region_idx && is_valid(); } class SpaceInfo @@ -215,10 +209,18 @@ class ParallelCompactData class RegionData { public: - // Destination address of the region. + // Destination for the first live word in this region. + // Therefore, the new addr for every live obj on this region can be calculated as: + // + // new_addr := _destination + live_words_offset(old_addr); + // + // where, live_words_offset is the number of live words accumulated from + // region-start to old_addr. HeapWord* destination() const { return _destination; } - // The first region containing data destined for this region. + // A destination region can have multiple source regions; only the first + // one is recorded. Since all live objs are slided down, subsequent source + // regions can be found via plain heap-region iteration. size_t source_region() const { return _source_region; } // Reuse _source_region to store the corresponding shadow region index @@ -313,9 +315,12 @@ class ParallelCompactData // Return to the normal path here inline void shadow_to_normal(); - int shadow_state() { return _shadow_state; } + bool is_clear(); + + void verify_clear() NOT_DEBUG_RETURN; + private: // The type used to represent object sizes within a region. typedef uint region_sz_t; @@ -873,7 +878,10 @@ class MoveAndUpdateClosure: public StackObj { size_t words_remaining() const { return _words_remaining; } bool is_full() const { return _words_remaining == 0; } HeapWord* source() const { return _source; } - void set_source(HeapWord* addr) { _source = addr; } + void set_source(HeapWord* addr) { + assert(addr != nullptr, "precondition"); + _source = addr; + } // If the object will fit (size <= words_remaining()), copy it to the current // destination, update the interior oops and the start array. @@ -902,9 +910,8 @@ inline size_t MoveAndUpdateClosure::calculate_words_remaining(size_t region) { HeapWord* dest_addr = PSParallelCompact::summary_data().region_to_addr(region); PSParallelCompact::SpaceId dest_space_id = PSParallelCompact::space_id(dest_addr); HeapWord* new_top = PSParallelCompact::new_top(dest_space_id); - assert(dest_addr < new_top, "sanity"); - - return MIN2(pointer_delta(new_top, dest_addr), ParallelCompactData::RegionSize); + return MIN2(pointer_delta(new_top, dest_addr), + ParallelCompactData::RegionSize); } inline From fa4ff78bd4ed029120717142eec6fb6352cb8e79 Mon Sep 17 00:00:00 2001 From: Naoto Sato Date: Tue, 27 Aug 2024 15:34:50 +0000 Subject: [PATCH 44/72] 8338690: CompactNumberInstance.format incorrectly formats some numbers (few vs many) Reviewed-by: joehw, rriggs, jlu --- .../java/text/CompactNumberFormat.java | 87 +++++++++---------- .../classes/java/text/DecimalFormat.java | 8 ++ .../TestCompactNumber.java | 26 +++++- 3 files changed, 73 insertions(+), 48 deletions(-) diff --git a/src/java.base/share/classes/java/text/CompactNumberFormat.java b/src/java.base/share/classes/java/text/CompactNumberFormat.java index 0cb5eb88079e4..35464d6ec91d0 100644 --- a/src/java.base/share/classes/java/text/CompactNumberFormat.java +++ b/src/java.base/share/classes/java/text/CompactNumberFormat.java @@ -652,16 +652,16 @@ private StringBuf format(double number, StringBuf result, double val = getNumberValue(number, divisor); if (checkIncrement(val, compactDataIndex, divisor)) { divisor = (Long) divisors.get(++compactDataIndex); - val = getNumberValue(number, divisor); } + roundedNumber = roundedNumber / divisor; + decimalFormat.setDigitList(roundedNumber, isNegative, getMaximumFractionDigits()); + val = decimalFormat.getDigitList().getDouble(); String prefix = getAffix(false, true, isNegative, compactDataIndex, val); String suffix = getAffix(false, false, isNegative, compactDataIndex, val); if (!prefix.isEmpty() || !suffix.isEmpty()) { appendPrefix(result, prefix, delegate); if (!placeHolderPatterns.get(compactDataIndex).get(val).isEmpty()) { - roundedNumber = roundedNumber / divisor; - decimalFormat.setDigitList(roundedNumber, isNegative, getMaximumFractionDigits()); decimalFormat.subformatNumber(result, delegate, isNegative, false, getMaximumIntegerDigits(), getMinimumIntegerDigits(), getMaximumFractionDigits(), getMinimumFractionDigits()); @@ -734,31 +734,28 @@ private StringBuf format(long number, StringBuf result, FieldDelegate delegate) double val = getNumberValue(number, divisor); if (checkIncrement(val, compactDataIndex, divisor)) { divisor = (Long) divisors.get(++compactDataIndex); - val = getNumberValue(number, divisor); } + var noFraction = number % divisor == 0; + if (noFraction) { + number = number / divisor; + decimalFormat.setDigitList(number, isNegative, 0); + } else { + // To avoid truncation of fractional part store + // the value in double and follow double path instead of + // long path + double dNumber = (double) number / divisor; + decimalFormat.setDigitList(dNumber, isNegative, getMaximumFractionDigits()); + } + val = decimalFormat.getDigitList().getDouble(); String prefix = getAffix(false, true, isNegative, compactDataIndex, val); String suffix = getAffix(false, false, isNegative, compactDataIndex, val); if (!prefix.isEmpty() || !suffix.isEmpty()) { appendPrefix(result, prefix, delegate); if (!placeHolderPatterns.get(compactDataIndex).get(val).isEmpty()) { - if ((number % divisor == 0)) { - number = number / divisor; - decimalFormat.setDigitList(number, isNegative, 0); - decimalFormat.subformatNumber(result, delegate, - isNegative, true, getMaximumIntegerDigits(), - getMinimumIntegerDigits(), getMaximumFractionDigits(), - getMinimumFractionDigits()); - } else { - // To avoid truncation of fractional part store - // the value in double and follow double path instead of - // long path - double dNumber = (double) number / divisor; - decimalFormat.setDigitList(dNumber, isNegative, getMaximumFractionDigits()); - decimalFormat.subformatNumber(result, delegate, - isNegative, false, getMaximumIntegerDigits(), - getMinimumIntegerDigits(), getMaximumFractionDigits(), - getMinimumFractionDigits()); - } + decimalFormat.subformatNumber(result, delegate, + isNegative, noFraction, getMaximumIntegerDigits(), + getMinimumIntegerDigits(), getMaximumFractionDigits(), + getMinimumFractionDigits()); appendSuffix(result, suffix, delegate); } } else { @@ -833,15 +830,15 @@ private StringBuf format(BigDecimal number, StringBuf result, double val = getNumberValue(number.doubleValue(), divisor.doubleValue()); if (checkIncrement(val, compactDataIndex, divisor.doubleValue())) { divisor = divisors.get(++compactDataIndex); - val = getNumberValue(number.doubleValue(), divisor.doubleValue()); } + number = number.divide(new BigDecimal(divisor.toString()), getRoundingMode()); + decimalFormat.setDigitList(number, isNegative, getMaximumFractionDigits()); + val = decimalFormat.getDigitList().getDouble(); String prefix = getAffix(false, true, isNegative, compactDataIndex, val); String suffix = getAffix(false, false, isNegative, compactDataIndex, val); if (!prefix.isEmpty() || !suffix.isEmpty()) { appendPrefix(result, prefix, delegate); if (!placeHolderPatterns.get(compactDataIndex).get(val).isEmpty()) { - number = number.divide(new BigDecimal(divisor.toString()), getRoundingMode()); - decimalFormat.setDigitList(number, isNegative, getMaximumFractionDigits()); decimalFormat.subformatNumber(result, delegate, isNegative, false, getMaximumIntegerDigits(), getMinimumIntegerDigits(), getMaximumFractionDigits(), getMinimumFractionDigits()); @@ -904,34 +901,30 @@ private StringBuf format(BigInteger number, StringBuf result, double val = getNumberValue(number.doubleValue(), divisor.doubleValue()); if (checkIncrement(val, compactDataIndex, divisor.doubleValue())) { divisor = divisors.get(++compactDataIndex); - val = getNumberValue(number.doubleValue(), divisor.doubleValue()); } + var noFraction = number.mod(new BigInteger(divisor.toString())) + .compareTo(BigInteger.ZERO) == 0; + if (noFraction) { + number = number.divide(new BigInteger(divisor.toString())); + decimalFormat.setDigitList(number, isNegative, 0); + } else { + // To avoid truncation of fractional part store the value in + // BigDecimal and follow BigDecimal path instead of + // BigInteger path + BigDecimal nDecimal = new BigDecimal(number) + .divide(new BigDecimal(divisor.toString()), getRoundingMode()); + decimalFormat.setDigitList(nDecimal, isNegative, getMaximumFractionDigits()); + } + val = decimalFormat.getDigitList().getDouble(); String prefix = getAffix(false, true, isNegative, compactDataIndex, val); String suffix = getAffix(false, false, isNegative, compactDataIndex, val); if (!prefix.isEmpty() || !suffix.isEmpty()) { appendPrefix(result, prefix, delegate); if (!placeHolderPatterns.get(compactDataIndex).get(val).isEmpty()) { - if (number.mod(new BigInteger(divisor.toString())) - .compareTo(BigInteger.ZERO) == 0) { - number = number.divide(new BigInteger(divisor.toString())); - - decimalFormat.setDigitList(number, isNegative, 0); - decimalFormat.subformatNumber(result, delegate, - isNegative, true, getMaximumIntegerDigits(), - getMinimumIntegerDigits(), getMaximumFractionDigits(), - getMinimumFractionDigits()); - } else { - // To avoid truncation of fractional part store the value in - // BigDecimal and follow BigDecimal path instead of - // BigInteger path - BigDecimal nDecimal = new BigDecimal(number) - .divide(new BigDecimal(divisor.toString()), getRoundingMode()); - decimalFormat.setDigitList(nDecimal, isNegative, getMaximumFractionDigits()); - decimalFormat.subformatNumber(result, delegate, - isNegative, false, getMaximumIntegerDigits(), - getMinimumIntegerDigits(), getMaximumFractionDigits(), - getMinimumFractionDigits()); - } + decimalFormat.subformatNumber(result, delegate, + isNegative, noFraction, getMaximumIntegerDigits(), + getMinimumIntegerDigits(), getMaximumFractionDigits(), + getMinimumFractionDigits()); appendSuffix(result, suffix, delegate); } } else { diff --git a/src/java.base/share/classes/java/text/DecimalFormat.java b/src/java.base/share/classes/java/text/DecimalFormat.java index c04abbd042f57..b23c5f360bf05 100644 --- a/src/java.base/share/classes/java/text/DecimalFormat.java +++ b/src/java.base/share/classes/java/text/DecimalFormat.java @@ -1799,6 +1799,14 @@ void setDigitList(Number number, boolean isNegative, int maxDigits) { } } + /** + * {@return the {@code DigitList} used by this {@code DecimalFormat} instance} + * Declared as package-private, intended for {@code CompactNumberFormat}. + */ + DigitList getDigitList() { + return digitList; + } + // ======== End fast-path formatting logic for double ========================= /** diff --git a/test/jdk/java/text/Format/CompactNumberFormat/TestCompactNumber.java b/test/jdk/java/text/Format/CompactNumberFormat/TestCompactNumber.java index 40da19adfe7b4..16de224777951 100644 --- a/test/jdk/java/text/Format/CompactNumberFormat/TestCompactNumber.java +++ b/test/jdk/java/text/Format/CompactNumberFormat/TestCompactNumber.java @@ -22,7 +22,7 @@ */ /* * @test - * @bug 8177552 8217721 8222756 8295372 8306116 8319990 + * @bug 8177552 8217721 8222756 8295372 8306116 8319990 8338690 * @summary Checks the functioning of compact number format * @modules jdk.localedata * @run testng/othervm TestCompactNumber @@ -60,6 +60,9 @@ public class TestCompactNumber { private static final NumberFormat FORMAT_IT_SHORT = NumberFormat .getCompactNumberInstance(Locale.ITALIAN, NumberFormat.Style.SHORT); + private static final NumberFormat FORMAT_IT_LONG = NumberFormat + .getCompactNumberInstance(Locale.ITALIAN, NumberFormat.Style.LONG); + private static final NumberFormat FORMAT_CA_LONG = NumberFormat .getCompactNumberInstance(Locale.of("ca"), NumberFormat.Style.LONG); @@ -89,6 +92,13 @@ public class TestCompactNumber { .getCompactNumberInstance(Locale.ITALIAN, NumberFormat.Style.LONG); private static final NumberFormat FORMAT_PT_LONG_FD4 = NumberFormat .getCompactNumberInstance(Locale.of("pt"), NumberFormat.Style.LONG); + + private static final NumberFormat FORMAT_PL_LONG = NumberFormat + .getCompactNumberInstance(Locale.of("pl"), NumberFormat.Style.LONG); + + private static final NumberFormat FORMAT_FR_LONG = NumberFormat + .getCompactNumberInstance(Locale.FRENCH, NumberFormat.Style.LONG); + static { FORMAT_ES_LONG_FD1.setMaximumFractionDigits(1); FORMAT_DE_LONG_FD2.setMaximumFractionDigits(2); @@ -359,6 +369,12 @@ Object[][] compactFormatData() { {FORMAT_DE_LONG_FD2, 1_234_500, "1,23 Millionen"}, {FORMAT_IT_LONG_FD3, 1_234_500, "1,234 milioni"}, {FORMAT_PT_LONG_FD4, 1_234_500, "1,2345 milh\u00f5es"}, + + // 8338690 + {FORMAT_PL_LONG, 5_000, "5 tysi\u0119cy"}, + {FORMAT_PL_LONG, 4_949, "5 tysi\u0119cy"}, + {FORMAT_FR_LONG, 1_949, "2 mille"}, + {FORMAT_IT_LONG, 1_949, "2 mila"}, }; } @@ -466,6 +482,10 @@ Object[][] compactParseData() { {FORMAT_DE_LONG_FD2, "1,23 Millionen", 1_230_000L, Long.class}, {FORMAT_IT_LONG_FD3, "1,234 milioni", 1_234_000L, Long.class}, {FORMAT_PT_LONG_FD4, "1,2345 milh\u00f5es", 1_234_500L, Long.class}, + // 8338690 + {FORMAT_PL_LONG, "5 tysi\u0119cy", 5_000L, Long.class}, + {FORMAT_FR_LONG, "2 mille", 2_000L, Long.class}, + {FORMAT_IT_LONG, "2 mila", 2_000L, Long.class}, }; } @@ -514,6 +534,10 @@ Object[][] invalidParseData() { {FORMAT_SL_LONG, "5 milijon", 5L}, {FORMAT_SL_LONG, "5 milijona", 5L}, {FORMAT_SL_LONG, "5 milijone", 5L}, + // 8338690 + {FORMAT_PL_LONG, "5 tysiÄ…ce", 5L}, + {FORMAT_FR_LONG, "2 millier", 2L}, + {FORMAT_IT_LONG, "2 mille", 2L}, }; } From daf26178be07bfe4a46592bcde092ce297a092bb Mon Sep 17 00:00:00 2001 From: Thomas Stuefe Date: Tue, 27 Aug 2024 15:46:10 +0000 Subject: [PATCH 45/72] 8338929: Make Metaspace::deallocate space-aware Reviewed-by: coleenp, adinn --- .../share/memory/classLoaderMetaspace.cpp | 7 ++-- .../share/memory/classLoaderMetaspace.hpp | 4 +-- src/hotspot/share/memory/metadataFactory.hpp | 7 ++-- src/hotspot/share/memory/metaspace.cpp | 22 ++++++------ src/hotspot/share/memory/metaspace.hpp | 34 +++++++++++++++++-- .../gtest/metaspace/test_metaspace_misc.cpp | 4 +-- 6 files changed, 54 insertions(+), 24 deletions(-) diff --git a/src/hotspot/share/memory/classLoaderMetaspace.cpp b/src/hotspot/share/memory/classLoaderMetaspace.cpp index 3315c15e67749..08bf42da8e3d9 100644 --- a/src/hotspot/share/memory/classLoaderMetaspace.cpp +++ b/src/hotspot/share/memory/classLoaderMetaspace.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -130,9 +130,10 @@ MetaWord* ClassLoaderMetaspace::expand_and_allocate(size_t word_size, Metaspace: // Prematurely returns a metaspace allocation to the _block_freelists // because it is not needed anymore. -void ClassLoaderMetaspace::deallocate(MetaWord* ptr, size_t word_size, bool is_class) { +void ClassLoaderMetaspace::deallocate(MetaWord* ptr, size_t word_size) { MutexLocker fcl(lock(), Mutex::_no_safepoint_check_flag); - if (Metaspace::using_class_space() && is_class) { + const bool is_class = Metaspace::using_class_space() && Metaspace::is_in_class_space(ptr); + if (is_class) { class_space_arena()->deallocate(ptr, word_size); } else { non_class_space_arena()->deallocate(ptr, word_size); diff --git a/src/hotspot/share/memory/classLoaderMetaspace.hpp b/src/hotspot/share/memory/classLoaderMetaspace.hpp index 525c63dde3e4b..176b8c5082acf 100644 --- a/src/hotspot/share/memory/classLoaderMetaspace.hpp +++ b/src/hotspot/share/memory/classLoaderMetaspace.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -92,7 +92,7 @@ class ClassLoaderMetaspace : public CHeapObj { // Prematurely returns a metaspace allocation to the _block_freelists // because it is not needed anymore. - void deallocate(MetaWord* ptr, size_t word_size, bool is_class); + void deallocate(MetaWord* ptr, size_t word_size); // Update statistics. This walks all in-use chunks. void add_to_statistics(metaspace::ClmsStats* out) const; diff --git a/src/hotspot/share/memory/metadataFactory.hpp b/src/hotspot/share/memory/metadataFactory.hpp index 14cac8dc191a7..f5935c588d79b 100644 --- a/src/hotspot/share/memory/metadataFactory.hpp +++ b/src/hotspot/share/memory/metadataFactory.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -54,7 +54,7 @@ class MetadataFactory : AllStatic { assert(loader_data != nullptr, "shouldn't pass null"); assert(!data->is_shared(), "cannot deallocate array in shared spaces"); int size = data->size(); - loader_data->metaspace_non_null()->deallocate((MetaWord*)data, size, false); + loader_data->metaspace_non_null()->deallocate((MetaWord*)data, size); } } @@ -68,7 +68,6 @@ class MetadataFactory : AllStatic { assert(!md->on_stack(), "can't deallocate things on stack"); assert(!md->is_shared(), "cannot deallocate if in shared spaces"); md->deallocate_contents(loader_data); - bool is_klass = md->is_klass(); // Call the destructor. This is currently used for MethodData which has a member // that needs to be destructed to release resources. Most Metadata derived classes have noop // destructors and/or cleanup using deallocate_contents. @@ -76,7 +75,7 @@ class MetadataFactory : AllStatic { // or volatile so we can call the destructor of the type T points to. using U = std::remove_cv_t; md->~U(); - loader_data->metaspace_non_null()->deallocate((MetaWord*)md, size, is_klass); + loader_data->metaspace_non_null()->deallocate((MetaWord*)md, size); } } }; diff --git a/src/hotspot/share/memory/metaspace.cpp b/src/hotspot/share/memory/metaspace.cpp index ad51f5ab7b6db..9b3b36f8be02e 100644 --- a/src/hotspot/share/memory/metaspace.cpp +++ b/src/hotspot/share/memory/metaspace.cpp @@ -538,6 +538,8 @@ void MetaspaceGC::compute_new_size() { ////// Metaspace methods ///// const MetaspaceTracer* Metaspace::_tracer = nullptr; +const void* Metaspace::_class_space_start = nullptr; +const void* Metaspace::_class_space_end = nullptr; bool Metaspace::initialized() { return metaspace::MetaspaceContext::context_nonclass() != nullptr @@ -570,6 +572,8 @@ void Metaspace::initialize_class_space(ReservedSpace rs) { "wrong alignment"); MetaspaceContext::initialize_class_space_context(rs); + _class_space_start = rs.base(); + _class_space_end = rs.end(); } // Returns true if class space has been setup (initialize_class_space). @@ -979,17 +983,15 @@ void Metaspace::purge(bool classes_unloaded) { MetaspaceCriticalAllocation::process(); } -bool Metaspace::contains(const void* ptr) { - if (MetaspaceShared::is_in_shared_metaspace(ptr)) { - return true; - } - return contains_non_shared(ptr); -} -bool Metaspace::contains_non_shared(const void* ptr) { - if (using_class_space() && VirtualSpaceList::vslist_class()->contains((MetaWord*)ptr)) { - return true; - } +// Returns true if pointer points into one of the metaspace regions, or +// into the class space. +bool Metaspace::is_in_shared_metaspace(const void* ptr) { + return MetaspaceShared::is_in_shared_metaspace(ptr); +} +// Returns true if pointer points into one of the non-class-space metaspace regions. +bool Metaspace::is_in_nonclass_metaspace(const void* ptr) { return VirtualSpaceList::vslist_nonclass()->contains((MetaWord*)ptr); } + diff --git a/src/hotspot/share/memory/metaspace.hpp b/src/hotspot/share/memory/metaspace.hpp index a90ff77564763..6de901b25d12b 100644 --- a/src/hotspot/share/memory/metaspace.hpp +++ b/src/hotspot/share/memory/metaspace.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2017, 2021 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -64,6 +64,10 @@ class Metaspace : public AllStatic { static const MetaspaceTracer* _tracer; + // For quick pointer testing: extent of class space; nullptr if no class space. + static const void* _class_space_start; + static const void* _class_space_end; + static bool _initialized; public: @@ -113,8 +117,32 @@ class Metaspace : public AllStatic { static MetaWord* allocate(ClassLoaderData* loader_data, size_t word_size, MetaspaceObj::Type type); - static bool contains(const void* ptr); - static bool contains_non_shared(const void* ptr); + // Returns true if the pointer points into class space, non-class metaspace, or the + // metadata portion of the CDS archive. + static bool contains(const void* ptr) { + return is_in_shared_metaspace(ptr) || // in cds + is_in_class_space(ptr) || // in class space + is_in_nonclass_metaspace(ptr); // in one of the non-class regions? + } + + // Returns true if the pointer points into class space or into non-class metaspace + static bool contains_non_shared(const void* ptr) { + return is_in_class_space(ptr) || // in class space + is_in_nonclass_metaspace(ptr); // in one of the non-class regions? + } + + // Returns true if pointer points into the CDS klass region. + static bool is_in_shared_metaspace(const void* ptr); + + // Returns true if pointer points into one of the non-class-space metaspace regions. + static bool is_in_nonclass_metaspace(const void* ptr); + + // Returns true if pointer points into class space, false if it doesn't or if + // there is no class space. Class space is a contiguous region, which is why + // two address comparisons are enough. + static inline bool is_in_class_space(const void* ptr) { + return ptr < _class_space_end && ptr >= _class_space_start; + } // Free empty virtualspaces static void purge(bool classes_unloaded); diff --git a/test/hotspot/gtest/metaspace/test_metaspace_misc.cpp b/test/hotspot/gtest/metaspace/test_metaspace_misc.cpp index 6a2282960e6f6..4d0a3e0774f50 100644 --- a/test/hotspot/gtest/metaspace/test_metaspace_misc.cpp +++ b/test/hotspot/gtest/metaspace/test_metaspace_misc.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -67,7 +67,7 @@ TEST_VM(metaspace, misc_max_alloc_size) { ASSERT_NOT_NULL(p); } // And also, successfully deallocate it. - cld->metaspace_non_null()->deallocate(p, sz, in_class_space); + cld->metaspace_non_null()->deallocate(p, sz); } } From 44d3a68d8a73c119b64772687d74e5ce25926f4f Mon Sep 17 00:00:00 2001 From: Hamlin Li Date: Tue, 27 Aug 2024 16:20:18 +0000 Subject: [PATCH 46/72] 8314124: RISC-V: implement Base64 intrinsic - decoding Reviewed-by: fyang, rehn, tonyp --- src/hotspot/cpu/riscv/assembler_riscv.hpp | 2 + src/hotspot/cpu/riscv/stubGenerator_riscv.cpp | 274 ++++++++++++++++++ 2 files changed, 276 insertions(+) diff --git a/src/hotspot/cpu/riscv/assembler_riscv.hpp b/src/hotspot/cpu/riscv/assembler_riscv.hpp index 79279be7acc1b..cba3dd919dafb 100644 --- a/src/hotspot/cpu/riscv/assembler_riscv.hpp +++ b/src/hotspot/cpu/riscv/assembler_riscv.hpp @@ -1835,8 +1835,10 @@ enum Nf { // Vector Unit-Stride Segment Load Instructions INSN(vlseg3e8_v, 0b0000111, 0b000, 0b00000, 0b00, 0b0, g3); + INSN(vlseg4e8_v, 0b0000111, 0b000, 0b00000, 0b00, 0b0, g4); // Vector Unit-Stride Segment Store Instructions + INSN(vsseg3e8_v, 0b0100111, 0b000, 0b00000, 0b00, 0b0, g3); INSN(vsseg4e8_v, 0b0100111, 0b000, 0b00000, 0b00, 0b0, g4); #undef INSN diff --git a/src/hotspot/cpu/riscv/stubGenerator_riscv.cpp b/src/hotspot/cpu/riscv/stubGenerator_riscv.cpp index f214c489557d6..8792dea7de5eb 100644 --- a/src/hotspot/cpu/riscv/stubGenerator_riscv.cpp +++ b/src/hotspot/cpu/riscv/stubGenerator_riscv.cpp @@ -5322,6 +5322,279 @@ class StubGenerator: public StubCodeGenerator { return (address) start; } + /** + * vector registers: + * input VectorRegister's: intputV1-V4, for m2 they could be v2, v4, v6, for m1 they could be v2, v4, v6, v8 + * index VectorRegister's: idxV1-V3, for m2 they could be v8, v10, v12, v14, for m1 they could be v10, v12, v14, v16 + * output VectorRegister's: outputV1-V4, for m2 they could be v16, v18, v20, v22, for m1 they could be v18, v20, v22 + * + * NOTE: each field will occupy a single vector register group + */ + void base64_vector_decode_round(Register src, Register dst, Register codec, + Register size, Register stepSrc, Register stepDst, Register failedIdx, Register minusOne, + VectorRegister inputV1, VectorRegister inputV2, VectorRegister inputV3, VectorRegister inputV4, + VectorRegister idxV1, VectorRegister idxV2, VectorRegister idxV3, VectorRegister idxV4, + VectorRegister outputV1, VectorRegister outputV2, VectorRegister outputV3, + Assembler::LMUL lmul) { + // set vector register type/len + __ vsetvli(x0, size, Assembler::e8, lmul, Assembler::ma, Assembler::ta); + + // segmented load src into v registers: mem(src) => vr(4) + __ vlseg4e8_v(inputV1, src); + + // src = src + register_group_len_bytes * 4 + __ add(src, src, stepSrc); + + // decoding + // 1. indexed load: vr(4) => vr(4) + __ vluxei8_v(idxV1, codec, inputV1); + __ vluxei8_v(idxV2, codec, inputV2); + __ vluxei8_v(idxV3, codec, inputV3); + __ vluxei8_v(idxV4, codec, inputV4); + + // 2. check wrong data + __ vor_vv(outputV1, idxV1, idxV2); + __ vor_vv(outputV2, idxV3, idxV4); + __ vor_vv(outputV1, outputV1, outputV2); + __ vmseq_vi(v0, outputV1, -1); + __ vfirst_m(failedIdx, v0); + Label NoFailure; + __ beq(failedIdx, minusOne, NoFailure); + __ vsetvli(x0, failedIdx, Assembler::e8, lmul, Assembler::mu, Assembler::tu); + __ slli(stepDst, failedIdx, 1); + __ add(stepDst, failedIdx, stepDst); + __ BIND(NoFailure); + + // 3. compute the decoded data: vr(4) => vr(3) + __ vsll_vi(idxV1, idxV1, 2); + __ vsrl_vi(outputV1, idxV2, 4); + __ vor_vv(outputV1, outputV1, idxV1); + + __ vsll_vi(idxV2, idxV2, 4); + __ vsrl_vi(outputV2, idxV3, 2); + __ vor_vv(outputV2, outputV2, idxV2); + + __ vsll_vi(idxV3, idxV3, 6); + __ vor_vv(outputV3, idxV4, idxV3); + + // segmented store encoded data in v registers back to dst: vr(3) => mem(dst) + __ vsseg3e8_v(outputV1, dst); + + // dst = dst + register_group_len_bytes * 3 + __ add(dst, dst, stepDst); + } + + /** + * int j.u.Base64.Decoder.decodeBlock(byte[] src, int sp, int sl, byte[] dst, int dp, boolean isURL, boolean isMIME) + * + * Input arguments: + * c_rarg0 - src, source array + * c_rarg1 - sp, src start offset + * c_rarg2 - sl, src end offset + * c_rarg3 - dst, dest array + * c_rarg4 - dp, dst start offset + * c_rarg5 - isURL, Base64 or URL character set + * c_rarg6 - isMIME, Decoding MIME block + */ + address generate_base64_decodeBlock() { + + static const uint8_t fromBase64[256] = { + 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, + 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, + 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 62u, 255u, 255u, 255u, 63u, + 52u, 53u, 54u, 55u, 56u, 57u, 58u, 59u, 60u, 61u, 255u, 255u, 255u, 255u, 255u, 255u, + 255u, 0u, 1u, 2u, 3u, 4u, 5u, 6u, 7u, 8u, 9u, 10u, 11u, 12u, 13u, 14u, + 15u, 16u, 17u, 18u, 19u, 20u, 21u, 22u, 23u, 24u, 25u, 255u, 255u, 255u, 255u, 255u, + 255u, 26u, 27u, 28u, 29u, 30u, 31u, 32u, 33u, 34u, 35u, 36u, 37u, 38u, 39u, 40u, + 41u, 42u, 43u, 44u, 45u, 46u, 47u, 48u, 49u, 50u, 51u, 255u, 255u, 255u, 255u, 255u, + 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, + 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, + 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, + 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, + 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, + 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, + 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, + 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, + }; + + static const uint8_t fromBase64URL[256] = { + 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, + 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, + 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 62u, 255u, 255u, + 52u, 53u, 54u, 55u, 56u, 57u, 58u, 59u, 60u, 61u, 255u, 255u, 255u, 255u, 255u, 255u, + 255u, 0u, 1u, 2u, 3u, 4u, 5u, 6u, 7u, 8u, 9u, 10u, 11u, 12u, 13u, 14u, + 15u, 16u, 17u, 18u, 19u, 20u, 21u, 22u, 23u, 24u, 25u, 255u, 255u, 255u, 255u, 63u, + 255u, 26u, 27u, 28u, 29u, 30u, 31u, 32u, 33u, 34u, 35u, 36u, 37u, 38u, 39u, 40u, + 41u, 42u, 43u, 44u, 45u, 46u, 47u, 48u, 49u, 50u, 51u, 255u, 255u, 255u, 255u, 255u, + 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, + 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, + 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, + 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, + 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, + 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, + 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, + 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, 255u, + }; + + __ align(CodeEntryAlignment); + StubCodeMark mark(this, "StubRoutines", "decodeBlock"); + address start = __ pc(); + __ enter(); + + Register src = c_rarg0; + Register soff = c_rarg1; + Register send = c_rarg2; + Register dst = c_rarg3; + Register doff = c_rarg4; + Register isURL = c_rarg5; + Register isMIME = c_rarg6; + + Register codec = c_rarg7; + Register dstBackup = x31; + Register length = x28; // t3, total length of src data in bytes + + Label ProcessData, Exit; + Label ProcessScalar, ScalarLoop; + + // passed in length (send - soff) is guaranteed to be > 4, + // and in this intrinsic we only process data of length in multiple of 4, + // it's not guaranteed to be multiple of 4 by java level, so do it explicitly + __ sub(length, send, soff); + __ andi(length, length, -4); + // real src/dst to process data + __ add(src, src, soff); + __ add(dst, dst, doff); + // backup of dst, used to calculate the return value at exit + __ mv(dstBackup, dst); + + // load the codec base address + __ la(codec, ExternalAddress((address) fromBase64)); + __ beqz(isURL, ProcessData); + __ la(codec, ExternalAddress((address) fromBase64URL)); + __ BIND(ProcessData); + + // vector version + if (UseRVV) { + // for MIME case, it has a default length limit of 76 which could be + // different(smaller) from (send - soff), so in MIME case, we go through + // the scalar code path directly. + __ bnez(isMIME, ScalarLoop); + + Label ProcessM1, ProcessM2; + + Register failedIdx = soff; + Register stepSrcM1 = send; + Register stepSrcM2 = doff; + Register stepDst = isURL; + Register size = x29; // t4 + Register minusOne = x30; // t5 + + __ mv(minusOne, -1); + __ mv(size, MaxVectorSize * 2); + __ mv(stepSrcM1, MaxVectorSize * 4); + __ slli(stepSrcM2, stepSrcM1, 1); + __ mv(stepDst, MaxVectorSize * 2 * 3); + + __ blt(length, stepSrcM2, ProcessM1); + + + // Assembler::m2 + __ BIND(ProcessM2); + base64_vector_decode_round(src, dst, codec, + size, stepSrcM2, stepDst, failedIdx, minusOne, + v2, v4, v6, v8, // inputs + v10, v12, v14, v16, // indexes + v18, v20, v22, // outputs + Assembler::m2); + __ sub(length, length, stepSrcM2); + + // error check + __ bne(failedIdx, minusOne, Exit); + + __ bge(length, stepSrcM2, ProcessM2); + + + // Assembler::m1 + __ BIND(ProcessM1); + __ blt(length, stepSrcM1, ProcessScalar); + + __ srli(size, size, 1); + __ srli(stepDst, stepDst, 1); + base64_vector_decode_round(src, dst, codec, + size, stepSrcM1, stepDst, failedIdx, minusOne, + v1, v2, v3, v4, // inputs + v5, v6, v7, v8, // indexes + v9, v10, v11, // outputs + Assembler::m1); + __ sub(length, length, stepSrcM1); + + // error check + __ bne(failedIdx, minusOne, Exit); + + __ BIND(ProcessScalar); + __ beqz(length, Exit); + } + + // scalar version + { + Register byte0 = soff, byte1 = send, byte2 = doff, byte3 = isURL; + Register combined32Bits = x29; // t5 + + // encoded: [byte0[5:0] : byte1[5:0] : byte2[5:0]] : byte3[5:0]] => + // plain: [byte0[5:0]+byte1[5:4] : byte1[3:0]+byte2[5:2] : byte2[1:0]+byte3[5:0]] + __ BIND(ScalarLoop); + + // load 4 bytes encoded src data + __ lbu(byte0, Address(src, 0)); + __ lbu(byte1, Address(src, 1)); + __ lbu(byte2, Address(src, 2)); + __ lbu(byte3, Address(src, 3)); + __ addi(src, src, 4); + + // get codec index and decode (ie. load from codec by index) + __ add(byte0, codec, byte0); + __ add(byte1, codec, byte1); + __ lb(byte0, Address(byte0, 0)); + __ lb(byte1, Address(byte1, 0)); + __ add(byte2, codec, byte2); + __ add(byte3, codec, byte3); + __ lb(byte2, Address(byte2, 0)); + __ lb(byte3, Address(byte3, 0)); + __ slliw(byte0, byte0, 18); + __ slliw(byte1, byte1, 12); + __ orr(byte0, byte0, byte1); + __ orr(byte0, byte0, byte3); + __ slliw(byte2, byte2, 6); + // For performance consideration, `combined32Bits` is constructed for 2 purposes at the same time, + // 1. error check below + // 2. decode below + __ orr(combined32Bits, byte0, byte2); + + // error check + __ bltz(combined32Bits, Exit); + + // store 3 bytes decoded data + __ sraiw(byte0, combined32Bits, 16); + __ sraiw(byte1, combined32Bits, 8); + __ sb(byte0, Address(dst, 0)); + __ sb(byte1, Address(dst, 1)); + __ sb(combined32Bits, Address(dst, 2)); + + __ sub(length, length, 4); + __ addi(dst, dst, 3); + // loop back + __ bnez(length, ScalarLoop); + } + + __ BIND(Exit); + __ sub(c_rarg0, dst, dstBackup); + + __ leave(); + __ ret(); + + return (address) start; + } + void adler32_process_bytes(Register buff, Register s1, Register s2, VectorRegister vtable, VectorRegister vzero, VectorRegister vbytes, VectorRegister vs1acc, VectorRegister vs2acc, Register temp0, Register temp1, Register temp2, Register temp3, @@ -5980,6 +6253,7 @@ static const int64_t right_3_bits = right_n_bits(3); if (UseBASE64Intrinsics) { StubRoutines::_base64_encodeBlock = generate_base64_encodeBlock(); + StubRoutines::_base64_decodeBlock = generate_base64_decodeBlock(); } if (UseAdler32Intrinsics) { From 2e96f159aaee782a627902c04dbd51daa3406ab5 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 27 Aug 2024 16:24:50 +0000 Subject: [PATCH 47/72] 8338489: Typo in MemorySegment doc Reviewed-by: rriggs, mcimadamore, iris --- .../share/classes/java/lang/foreign/MemorySegment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/foreign/MemorySegment.java b/src/java.base/share/classes/java/lang/foreign/MemorySegment.java index 82f6b358a0752..8f171fd8bfbc9 100644 --- a/src/java.base/share/classes/java/lang/foreign/MemorySegment.java +++ b/src/java.base/share/classes/java/lang/foreign/MemorySegment.java @@ -475,7 +475,7 @@ * MemorySegment ptr = null; * try (Arena arena = Arena.ofConfined()) { * MemorySegment z = segment.get(ValueLayout.ADDRESS, ...); // size = 0, scope = always alive - * ptr = z.reinterpret(16, arena, null); // size = 4, scope = arena.scope() + * ptr = z.reinterpret(16, arena, null); // size = 16, scope = arena.scope() * int x = ptr.getAtIndex(ValueLayout.JAVA_INT, 3); // ok * } * int x = ptr.getAtIndex(ValueLayout.JAVA_INT, 3); // throws IllegalStateException From 284c3cde5e1b7115fb17c51f3ed17c1be95845bc Mon Sep 17 00:00:00 2001 From: Neethu Prasad Date: Tue, 27 Aug 2024 16:45:34 +0000 Subject: [PATCH 48/72] 8336299: Improve GCLocker stall diagnostics Reviewed-by: ayang, shade, tschatzl --- src/hotspot/share/gc/shared/gcLocker.cpp | 59 +++++++++++++++++------- 1 file changed, 43 insertions(+), 16 deletions(-) diff --git a/src/hotspot/share/gc/shared/gcLocker.cpp b/src/hotspot/share/gc/shared/gcLocker.cpp index 6f1961758222f..42b40309d9869 100644 --- a/src/hotspot/share/gc/shared/gcLocker.cpp +++ b/src/hotspot/share/gc/shared/gcLocker.cpp @@ -33,11 +33,33 @@ #include "runtime/javaThread.inline.hpp" #include "runtime/safepoint.hpp" #include "runtime/threadSMR.hpp" +#include "utilities/ticks.hpp" volatile jint GCLocker::_jni_lock_count = 0; volatile bool GCLocker::_needs_gc = false; unsigned int GCLocker::_total_collections = 0; +// GCLockerTimingDebugLogger tracks specific timing information for GC lock waits. +class GCLockerTimingDebugLogger : public StackObj { + const char* _log_message; + Ticks _start; + +public: + GCLockerTimingDebugLogger(const char* log_message) : _log_message(log_message) { + assert(_log_message != nullptr, "GC locker debug message must be set."); + _start = Ticks::now(); + } + + ~GCLockerTimingDebugLogger() { + Log(gc, jni) log; + if (log.is_debug()) { + ResourceMark rm; // JavaThread::name() allocates to convert to UTF8 + const Tickspan elapsed_time = Ticks::now() - _start; + log.debug("%s Resumed after " UINT64_FORMAT "ms. Thread \"%s\".", _log_message, elapsed_time.milliseconds(), Thread::current()->name()); + } + } +}; + #ifdef ASSERT volatile jint GCLocker::_debug_jni_lock_count = 0; #endif @@ -110,11 +132,11 @@ void GCLocker::stall_until_clear() { if (needs_gc()) { GCLockerTracer::inc_stall_count(); log_debug_jni("Allocation failed. Thread stalled by JNI critical section."); - } - - // Wait for _needs_gc to be cleared - while (needs_gc()) { - ml.wait(); + GCLockerTimingDebugLogger logger("Thread stalled by JNI critical section."); + // Wait for _needs_gc to be cleared + while (needs_gc()) { + ml.wait(); + } } } @@ -127,16 +149,20 @@ void GCLocker::jni_lock(JavaThread* thread) { assert(!thread->in_critical(), "shouldn't currently be in a critical region"); MonitorLocker ml(JNICritical_lock); // Block entering threads if there's a pending GC request. - while (needs_gc()) { - // There's at least one thread that has not left the critical region (CR) - // completely. When that last thread (no new threads can enter CR due to the - // blocking) exits CR, it calls `jni_unlock`, which sets `_needs_gc` - // to false and wakes up all blocked threads. - // We would like to assert #threads in CR to be > 0, `_jni_lock_count > 0` - // in the code, but it's too strong; it's possible that the last thread - // has called `jni_unlock`, but not yet finished the call, e.g. initiating - // a GCCause::_gc_locker GC. - ml.wait(); + if (needs_gc()) { + log_debug_jni("Blocking thread as there is a pending GC request"); + GCLockerTimingDebugLogger logger("Thread blocked to enter critical region."); + while (needs_gc()) { + // There's at least one thread that has not left the critical region (CR) + // completely. When that last thread (no new threads can enter CR due to the + // blocking) exits CR, it calls `jni_unlock`, which sets `_needs_gc` + // to false and wakes up all blocked threads. + // We would like to assert #threads in CR to be > 0, `_jni_lock_count > 0` + // in the code, but it's too strong; it's possible that the last thread + // has called `jni_unlock`, but not yet finished the call, e.g. initiating + // a GCCause::_gc_locker GC. + ml.wait(); + } } thread->enter_critical(); _jni_lock_count++; @@ -148,6 +174,7 @@ void GCLocker::jni_unlock(JavaThread* thread) { MutexLocker mu(JNICritical_lock); _jni_lock_count--; decrement_debug_jni_lock_count(); + log_debug_jni("Thread exiting critical region."); thread->exit_critical(); if (needs_gc() && !is_active_internal()) { // We're the last thread out. Request a GC. @@ -161,7 +188,7 @@ void GCLocker::jni_unlock(JavaThread* thread) { { // Must give up the lock while at a safepoint MutexUnlocker munlock(JNICritical_lock); - log_debug_jni("Performing GC after exiting critical section."); + log_debug_jni("Last thread exiting. Performing GC after exiting critical section."); Universe::heap()->collect(GCCause::_gc_locker); } _needs_gc = false; From b1b4cd429a4135840966975dd0c068fe428e2ee6 Mon Sep 17 00:00:00 2001 From: Alexander Zvegintsev Date: Tue, 27 Aug 2024 17:16:09 +0000 Subject: [PATCH 49/72] 8332158: [XWayland] test/jdk/java/awt/Mouse/EnterExitEvents/ResizingFrameTest.java Reviewed-by: serb, honkar --- test/jdk/ProblemList.txt | 2 +- .../EnterExitEvents/ResizingFrameTest.java | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/test/jdk/ProblemList.txt b/test/jdk/ProblemList.txt index bbf594f3bc70b..697fe82187ca3 100644 --- a/test/jdk/ProblemList.txt +++ b/test/jdk/ProblemList.txt @@ -375,7 +375,7 @@ java/awt/Modal/MultipleDialogs/MultipleDialogs3Test.java 8198665 macosx-all java/awt/Modal/MultipleDialogs/MultipleDialogs4Test.java 8198665 macosx-all java/awt/Modal/MultipleDialogs/MultipleDialogs5Test.java 8198665 macosx-all java/awt/Mouse/EnterExitEvents/DragWindowOutOfFrameTest.java 8177326 macosx-all -java/awt/Mouse/EnterExitEvents/ResizingFrameTest.java 8005021,8332158 macosx-all,linux-x64 +java/awt/Mouse/EnterExitEvents/ResizingFrameTest.java 8005021 macosx-all java/awt/Mouse/EnterExitEvents/FullscreenEnterEventTest.java 8051455 macosx-all java/awt/Mouse/MouseModifiersUnitTest/MouseModifiersUnitTest_Standard.java 7124407,8302787 macosx-all,windows-all java/awt/Mouse/RemovedComponentMouseListener/RemovedComponentMouseListener.java 8157170 macosx-all diff --git a/test/jdk/java/awt/Mouse/EnterExitEvents/ResizingFrameTest.java b/test/jdk/java/awt/Mouse/EnterExitEvents/ResizingFrameTest.java index 51e5dd353a98d..2918a6a5e0a89 100644 --- a/test/jdk/java/awt/Mouse/EnterExitEvents/ResizingFrameTest.java +++ b/test/jdk/java/awt/Mouse/EnterExitEvents/ResizingFrameTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,12 +27,19 @@ * @bug 7154048 * @summary Programmatically resized window does not receive mouse entered/exited events * @author alexandr.scherbatiy area=awt.event + * @library /test/lib + * @build jdk.test.lib.Platform * @run main ResizingFrameTest */ -import java.awt.*; -import java.awt.event.*; -import javax.swing.*; +import java.awt.Frame; +import java.awt.Robot; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import javax.swing.JFrame; +import javax.swing.SwingUtilities; + +import jdk.test.lib.Platform; public class ResizingFrameTest { @@ -41,6 +48,9 @@ public class ResizingFrameTest { private static JFrame frame; public static void main(String[] args) throws Exception { + if (Platform.isOnWayland()) { + return; + } Robot robot = new Robot(); robot.setAutoDelay(50); From 449ca2c3c1cb5d056a2d259be2ff069ba2a36b80 Mon Sep 17 00:00:00 2001 From: Shaojin Wen Date: Tue, 27 Aug 2024 22:10:05 +0000 Subject: [PATCH 50/72] 8337832: Optimize datetime toString Reviewed-by: scolebourne, liach, naoto --- .../share/classes/java/time/LocalDateTime.java | 9 ++++++++- .../share/classes/java/time/OffsetDateTime.java | 7 +++++-- .../share/classes/java/time/OffsetTime.java | 7 +++++-- .../share/classes/java/time/ZonedDateTime.java | 17 +++++++++++++---- 4 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/java.base/share/classes/java/time/LocalDateTime.java b/src/java.base/share/classes/java/time/LocalDateTime.java index d14afb7d78f9a..cea9123258c6b 100644 --- a/src/java.base/share/classes/java/time/LocalDateTime.java +++ b/src/java.base/share/classes/java/time/LocalDateTime.java @@ -1966,10 +1966,17 @@ public int hashCode() { @Override public String toString() { var buf = new StringBuilder(29); + formatTo(buf); + return buf.toString(); + } + + /** + * Prints the toString result to the given buf, avoiding extra string allocations. + */ + void formatTo(StringBuilder buf) { date.formatTo(buf); buf.append('T'); time.formatTo(buf); - return buf.toString(); } //----------------------------------------------------------------------- diff --git a/src/java.base/share/classes/java/time/OffsetDateTime.java b/src/java.base/share/classes/java/time/OffsetDateTime.java index bd75fffb24e55..fd8355e36cda5 100644 --- a/src/java.base/share/classes/java/time/OffsetDateTime.java +++ b/src/java.base/share/classes/java/time/OffsetDateTime.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -1923,7 +1923,10 @@ public int hashCode() { */ @Override public String toString() { - return dateTime.toString() + offset.toString(); + var offsetStr = offset.toString(); + var buf = new StringBuilder(29 + offsetStr.length()); + dateTime.formatTo(buf); + return buf.append(offsetStr).toString(); } //----------------------------------------------------------------------- diff --git a/src/java.base/share/classes/java/time/OffsetTime.java b/src/java.base/share/classes/java/time/OffsetTime.java index b9fe5e533d80e..1f6feb4180fc5 100644 --- a/src/java.base/share/classes/java/time/OffsetTime.java +++ b/src/java.base/share/classes/java/time/OffsetTime.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -1398,7 +1398,10 @@ public int hashCode() { */ @Override public String toString() { - return time.toString() + offset.toString(); + var offsetStr = offset.toString(); + var buf = new StringBuilder(18 + offsetStr.length()); + time.formatTo(buf); + return buf.append(offsetStr).toString(); } //----------------------------------------------------------------------- diff --git a/src/java.base/share/classes/java/time/ZonedDateTime.java b/src/java.base/share/classes/java/time/ZonedDateTime.java index 555392cf513ce..b1426efc914a4 100644 --- a/src/java.base/share/classes/java/time/ZonedDateTime.java +++ b/src/java.base/share/classes/java/time/ZonedDateTime.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -2214,11 +2214,20 @@ public int hashCode() { */ @Override // override for Javadoc public String toString() { - String str = dateTime.toString() + offset.toString(); + var offsetStr = offset.toString(); + var zoneStr = (String) null; + int length = 29 + offsetStr.length(); if (offset != zone) { - str += '[' + zone.toString() + ']'; + zoneStr = zone.toString(); + length += zoneStr.length() + 2; } - return str; + var buf = new StringBuilder(length); + dateTime.formatTo(buf); + buf.append(offsetStr); + if (zoneStr != null) { + buf.append('[').append(zoneStr).append(']'); + } + return buf.toString(); } //----------------------------------------------------------------------- From 8e88da05b9966892e117b779d59a2e311a557a8d Mon Sep 17 00:00:00 2001 From: Tejesh R Date: Wed, 28 Aug 2024 04:43:10 +0000 Subject: [PATCH 51/72] 8338041: Keyboard Navigation of JTable, Ctrl Shift RIGHT/LEFT doesn't follow native action in GTK L&F Reviewed-by: honkar, prr, abhiscxk --- .../java/swing/plaf/gtk/GTKLookAndFeel.java | 4 +- .../gtk/JTableCtrlShiftRightLeftKeyTest.java | 139 ++++++++++++++++++ 2 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 test/jdk/javax/swing/plaf/gtk/JTableCtrlShiftRightLeftKeyTest.java diff --git a/src/java.desktop/share/classes/com/sun/java/swing/plaf/gtk/GTKLookAndFeel.java b/src/java.desktop/share/classes/com/sun/java/swing/plaf/gtk/GTKLookAndFeel.java index 681c0e5195838..a2414da455955 100644 --- a/src/java.desktop/share/classes/com/sun/java/swing/plaf/gtk/GTKLookAndFeel.java +++ b/src/java.desktop/share/classes/com/sun/java/swing/plaf/gtk/GTKLookAndFeel.java @@ -1058,7 +1058,7 @@ public Object createValue(UIDefaults table) { "KP_RIGHT", "selectNextColumn", "shift RIGHT", "selectNextColumnExtendSelection", "shift KP_RIGHT", "selectNextColumnExtendSelection", - "ctrl shift RIGHT", "selectNextColumnExtendSelection", + "ctrl shift RIGHT", "selectLastColumnExtendSelection", "ctrl shift KP_RIGHT", "selectNextColumnExtendSelection", "ctrl RIGHT", "selectNextColumnChangeLead", "ctrl KP_RIGHT", "selectNextColumnChangeLead", @@ -1066,7 +1066,7 @@ public Object createValue(UIDefaults table) { "KP_LEFT", "selectPreviousColumn", "shift LEFT", "selectPreviousColumnExtendSelection", "shift KP_LEFT", "selectPreviousColumnExtendSelection", - "ctrl shift LEFT", "selectPreviousColumnExtendSelection", + "ctrl shift LEFT", "selectFirstColumnExtendSelection", "ctrl shift KP_LEFT", "selectPreviousColumnExtendSelection", "ctrl LEFT", "selectPreviousColumnChangeLead", "ctrl KP_LEFT", "selectPreviousColumnChangeLead", diff --git a/test/jdk/javax/swing/plaf/gtk/JTableCtrlShiftRightLeftKeyTest.java b/test/jdk/javax/swing/plaf/gtk/JTableCtrlShiftRightLeftKeyTest.java new file mode 100644 index 0000000000000..1df2ed4861632 --- /dev/null +++ b/test/jdk/javax/swing/plaf/gtk/JTableCtrlShiftRightLeftKeyTest.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Robot; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; + +import javax.swing.JFrame; +import javax.swing.JTable; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; + +/* + * @test + * @bug 8338041 + * @key headful + * @summary Verify that Ctrl Shift RIGHT/LEFT key extends columns till + * Last/First Columns in JTable + * @requires (os.family == "linux") + * @run main JTableCtrlShiftRightLeftKeyTest + */ + +public class JTableCtrlShiftRightLeftKeyTest { + private static JFrame frame; + private static JTable table; + private static volatile Point tableLoc; + private static volatile Rectangle cellRect; + private static volatile int[] selectedColumnAfterKeyPress; + private static Robot robot; + private static final int SELECTED_COLUMN = 2; + + public static void main(String[] args) throws Exception { + UIManager.setLookAndFeel("com.sun.java.swing.plaf.gtk.GTKLookAndFeel"); + robot = new Robot(); + robot.setAutoDelay(50); + try { + SwingUtilities.invokeAndWait(JTableCtrlShiftRightLeftKeyTest::createAndShowUI); + robot.waitForIdle(); + robot.delay(1000); + + SwingUtilities.invokeAndWait(() -> { + tableLoc = table.getLocationOnScreen(); + cellRect = table.getCellRect(0, SELECTED_COLUMN, true); + }); + + robot.mouseMove(tableLoc.x + cellRect.x + cellRect.width / 2, + tableLoc.y + cellRect.y + cellRect.height / 2); + robot.mousePress(InputEvent.BUTTON1_DOWN_MASK); + robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK); + robot.waitForIdle(); + robot.delay(100); + + testCtrlShift(KeyEvent.VK_RIGHT, SELECTED_COLUMN, + table.getColumnCount() - 1, "RIGHT"); + + robot.waitForIdle(); + robot.delay(100); + robot.mousePress(InputEvent.BUTTON1_DOWN_MASK); + robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK); + robot.waitForIdle(); + robot.delay(100); + + testCtrlShift(KeyEvent.VK_LEFT, 0, + SELECTED_COLUMN, "LEFT"); + robot.waitForIdle(); + robot.delay(100); + System.out.println("Test Passed!"); + + } finally { + SwingUtilities.invokeAndWait(() -> { + if (frame != null) { + frame.dispose(); + } + }); + } + } + + private static void testCtrlShift(int keySelected, int startCellCheck, + int endCellCheck, String key) throws Exception { + robot.keyPress(KeyEvent.VK_SHIFT); + robot.keyPress(KeyEvent.VK_CONTROL); + robot.keyPress(keySelected); + robot.keyRelease(keySelected); + robot.keyRelease(KeyEvent.VK_SHIFT); + robot.keyRelease(KeyEvent.VK_CONTROL); + robot.waitForIdle(); + robot.delay(100); + + SwingUtilities.invokeAndWait(() -> { + selectedColumnAfterKeyPress = table.getSelectedColumns(); + }); + + if (selectedColumnAfterKeyPress[0] != startCellCheck || + selectedColumnAfterKeyPress[selectedColumnAfterKeyPress.length - 1] != + endCellCheck) { + System.out.println("Selected Columns: "); + for (int columnsSelected : selectedColumnAfterKeyPress) { + System.out.println(columnsSelected); + } + String failureMsg = "Test Failure. Failed to select cells for Ctrl" + + " Shift " + key + " selection"; + throw new RuntimeException(failureMsg); + } + } + + private static void createAndShowUI() { + frame = new JFrame("Test Ctrl Shift RIGHT/LEFT Key Press"); + table = new JTable(2, 5); + table.setColumnSelectionAllowed(true); + frame.getContentPane().add(table); + + frame.setSize(200, 200); + frame.setLocationRelativeTo(null); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.setVisible(true); + } +} From 2e174c6367c7755d8541f9669f7f10ed89878f58 Mon Sep 17 00:00:00 2001 From: Jaikiran Pai Date: Wed, 28 Aug 2024 09:29:18 +0000 Subject: [PATCH 52/72] 8338445: jdk.internal.loader.URLClassPath may leak JarFile instance when dealing with unexpected Class-Path entry in manifest Reviewed-by: michaelm, cstein, alanb --- .../jdk/internal/loader/URLClassPath.java | 43 +++-- .../URLClassLoader/JarLoaderCloseTest.java | 148 ++++++++++++++++++ 2 files changed, 180 insertions(+), 11 deletions(-) create mode 100644 test/jdk/java/net/URLClassLoader/JarLoaderCloseTest.java diff --git a/src/java.base/share/classes/jdk/internal/loader/URLClassPath.java b/src/java.base/share/classes/jdk/internal/loader/URLClassPath.java index dbd76e07d4f46..297c77bc106c7 100644 --- a/src/java.base/share/classes/jdk/internal/loader/URLClassPath.java +++ b/src/java.base/share/classes/jdk/internal/loader/URLClassPath.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -59,7 +59,6 @@ import java.util.StringTokenizer; import java.util.jar.JarFile; import java.util.zip.CRC32; -import java.util.zip.ZipEntry; import java.util.jar.JarEntry; import java.util.jar.Manifest; import java.util.jar.Attributes; @@ -439,27 +438,38 @@ private synchronized Loader getLoader(int index) { continue; } // Otherwise, create a new Loader for the URL. - Loader loader; + Loader loader = null; + final URL[] loaderClassPathURLs; try { loader = getLoader(url); // If the loader defines a local class path then add the // URLs as the next URLs to be opened. - URL[] urls = loader.getClassPath(); - if (urls != null) { - push(urls); - } + loaderClassPathURLs = loader.getClassPath(); } catch (IOException e) { - // Silently ignore for now... + // log the error and close the unusable loader (if any) + if (DEBUG) { + System.err.println("Failed to construct a loader or construct its" + + " local classpath for " + url + ", cause:" + e); + } + if (loader != null) { + closeQuietly(loader); + } continue; } catch (SecurityException se) { - // Always silently ignore. The context, if there is one, that - // this URLClassPath was given during construction will never - // have permission to access the URL. + // log the error and close the unusable loader (if any). + // The context, if there is one, that this URLClassPath was + // given during construction will never have permission to access the URL. if (DEBUG) { System.err.println("Failed to access " + url + ", " + se ); } + if (loader != null) { + closeQuietly(loader); + } continue; } + if (loaderClassPathURLs != null) { + push(loaderClassPathURLs); + } // Finally, add the Loader to the search path. loaders.add(loader); lmap.put(urlNoFragString, loader); @@ -467,6 +477,17 @@ private synchronized Loader getLoader(int index) { return loaders.get(index); } + // closes the given loader and ignores any IOException that may occur during close + private static void closeQuietly(final Loader loader) { + try { + loader.close(); + } catch (IOException ioe) { + if (DEBUG) { + System.err.println("ignoring exception " + ioe + " while closing loader " + loader); + } + } + } + /* * Returns the Loader for the specified base URL. */ diff --git a/test/jdk/java/net/URLClassLoader/JarLoaderCloseTest.java b/test/jdk/java/net/URLClassLoader/JarLoaderCloseTest.java new file mode 100644 index 0000000000000..469a682e0392a --- /dev/null +++ b/test/jdk/java/net/URLClassLoader/JarLoaderCloseTest.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.InputStream; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.jar.Attributes; +import java.util.jar.Manifest; + +import jdk.test.lib.util.JarUtils; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import static java.nio.charset.StandardCharsets.US_ASCII; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +/* + * @test + * @bug 8338445 + * @summary verify that the jdk.internal.loader.URLClassPath closes the JarFile + * instances that it no longer uses for loading + * @library /test/lib + * @build jdk.test.lib.util.JarUtils + * @comment This test expects MalformedURLException for some specific URLs. + * We use othervm to prevent interference from other tests which + * might have installed custom URLStreamHandler(s) + * @run junit/othervm JarLoaderCloseTest + */ +public class JarLoaderCloseTest { + + private static final String RESOURCE_NAME = "foo-bar.txt"; + private static final String RESOURCE_CONTENT = "Hello world"; + private static final Path TEST_SCRATCH_DIR = Path.of("."); + + @BeforeAll + static void beforeAll() throws Exception { + // create a file which will be added to the JAR file that gets tested + Files.writeString(TEST_SCRATCH_DIR.resolve(RESOURCE_NAME), RESOURCE_CONTENT); + } + + /* + * Creates a JAR file with a manifest which has a Class-Path entry value with malformed URLs. + * Then uses a URLClassLoader backed by the JAR file in its classpath, loads some resource, + * closes the URLClassLoader and then expects that the underlying JAR file can be deleted + * from the filesystem. + */ + @ParameterizedTest + @ValueSource(strings = { + "C:\\foo\\bar\\hello/world.jar lib2.jar", + "C:/hello/world/foo.jar", + "lib4.jar C:\\bar\\foo\\world/hello.jar" + }) + public void testMalformedClassPathEntry(final String classPathValue) throws Exception { + final Manifest manifest = createManifestWithClassPath(classPathValue); + final Path jar = Files.createTempFile(TEST_SCRATCH_DIR, "8338445", ".jar"); + // create the JAR file with the given manifest and an arbitrary file + JarUtils.createJarFile(jar, manifest, TEST_SCRATCH_DIR, Path.of(RESOURCE_NAME)); + System.out.println("created jar at " + jar + " with manifest:"); + manifest.write(System.out); + final URL[] urlClassPath = new URL[]{jar.toUri().toURL()}; + // Create a URLClassLoader backed by the JAR file and load a non-existent resource just to + // exercise the URLClassPath code of loading the jar and parsing the Class-Path entry. + // Then close the classloader. After the classloader is closed + // issue a delete on the underlying JAR file on the filesystem. The delete is expected + // to succeed. + try (final URLClassLoader cl = new URLClassLoader(urlClassPath)) { + try (final InputStream is = cl.getResourceAsStream("non-existent.txt")) { + assertNull(is, "unexpectedly found a resource in classpath " + + Arrays.toString(urlClassPath)); + } + } + // now delete the JAR file and verify the delete worked + Files.delete(jar); + assertFalse(Files.exists(jar), jar + " exists even after being deleted"); + } + + /* + * Creates a JAR file with a manifest which has a Class-Path entry value with URLs + * that are parsable but point to files that don't exist on the filesystem. + * Then uses a URLClassLoader backed by the JAR file in its classpath, loads some resource, + * closes the URLClassLoader and then expects that the underlying JAR file can be deleted + * from the filesystem. + */ + @ParameterizedTest + @ValueSource(strings = { + "/home/me/hello/world.jar lib9.jar", + "lib10.jar" + }) + public void testParsableClassPathEntry(final String classPathValue) throws Exception { + final Manifest manifest = createManifestWithClassPath(classPathValue); + final Path jar = Files.createTempFile(TEST_SCRATCH_DIR, "8338445", ".jar"); + // create the JAR file with the given manifest and an arbitrary file + JarUtils.createJarFile(jar, manifest, TEST_SCRATCH_DIR, Path.of(RESOURCE_NAME)); + System.out.println("created jar at " + jar + " with manifest:"); + manifest.write(System.out); + final URL[] urlClassPath = new URL[]{jar.toUri().toURL()}; + // Create a URLClassLoader backed by the JAR file and load a resource + // and verify the resource contents. + // Then close the classloader. After the classloader is closed + // issue a delete on the underlying JAR file on the filesystem. The delete is expected + // to succeed. + try (final URLClassLoader cl = new URLClassLoader(urlClassPath)) { + try (final InputStream is = cl.getResourceAsStream(RESOURCE_NAME)) { + assertNotNull(is, RESOURCE_NAME + " not located by classloader in classpath " + + Arrays.toString(urlClassPath)); + final String content = new String(is.readAllBytes(), US_ASCII); + assertEquals(RESOURCE_CONTENT, content, "unexpected content in " + RESOURCE_NAME); + } + } + // now delete the JAR file and verify the delete worked + Files.delete(jar); + assertFalse(Files.exists(jar), jar + " exists even after being deleted"); + } + + private static Manifest createManifestWithClassPath(final String classPathValue) { + final Manifest manifest = new Manifest(); + final Attributes mainAttributes = manifest.getMainAttributes(); + mainAttributes.putValue("Manifest-Version", "1.0"); + mainAttributes.putValue("Class-Path", classPathValue); + return manifest; + } +} From 1ff9ac7233d51a58fd54a92d2c45761478574cc7 Mon Sep 17 00:00:00 2001 From: Maurizio Cimadamore Date: Wed, 28 Aug 2024 10:22:34 +0000 Subject: [PATCH 53/72] 8338731: MemoryLayout::offsetHandle can return a negative offset Reviewed-by: pminborg, psandoz --- .../java/lang/foreign/MemoryLayout.java | 4 +++- .../jdk/internal/foreign/LayoutPath.java | 21 +++++++++++-------- test/jdk/java/foreign/TestLayoutPaths.java | 8 +++++++ 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/java.base/share/classes/java/lang/foreign/MemoryLayout.java b/src/java.base/share/classes/java/lang/foreign/MemoryLayout.java index b130ca8e031e5..2cf0a5e4443cb 100644 --- a/src/java.base/share/classes/java/lang/foreign/MemoryLayout.java +++ b/src/java.base/share/classes/java/lang/foreign/MemoryLayout.java @@ -574,7 +574,9 @@ public sealed interface MemoryLayout *

* For any given dynamic argument {@code x_i}, it must be that {@code 0 <= x_i < size_i}, * where {@code size_i} is the size of the open path element associated with {@code x_i}. - * Otherwise, the returned method handle throws {@link IndexOutOfBoundsException}. + * Otherwise, the returned method handle throws {@link IndexOutOfBoundsException}. Moreover, + * the value of {@code b} must be such that the computation for {@code offset} does not overflow, + * or the returned method handle throws {@link ArithmeticException}. * * @apiNote The returned method handle can be used to compute a layout offset, * similarly to {@link #byteOffset(PathElement...)}, but more flexibly, as diff --git a/src/java.base/share/classes/jdk/internal/foreign/LayoutPath.java b/src/java.base/share/classes/jdk/internal/foreign/LayoutPath.java index 956a5c75875ab..dc995589472b1 100644 --- a/src/java.base/share/classes/jdk/internal/foreign/LayoutPath.java +++ b/src/java.base/share/classes/jdk/internal/foreign/LayoutPath.java @@ -66,7 +66,7 @@ public class LayoutPath { private static final MethodHandle MH_SLICE_LAYOUT; private static final MethodHandle MH_CHECK_ENCL_LAYOUT; private static final MethodHandle MH_SEGMENT_RESIZE; - private static final MethodHandle MH_ADD; + private static final MethodHandle MH_ADD_EXACT; static { try { @@ -81,7 +81,7 @@ public class LayoutPath { MethodType.methodType(void.class, MemorySegment.class, long.class, MemoryLayout.class)); MH_SEGMENT_RESIZE = lookup.findStatic(LayoutPath.class, "resizeSegment", MethodType.methodType(MemorySegment.class, MemorySegment.class)); - MH_ADD = lookup.findStatic(Long.class, "sum", + MH_ADD_EXACT = lookup.findStatic(Math.class, "addExact", MethodType.methodType(long.class, long.class, long.class)); } catch (Throwable ex) { throw new ExceptionInInitializerError(ex); @@ -244,15 +244,18 @@ private static long addScaledOffset(long base, long index, long stride, long bou } public MethodHandle offsetHandle() { - MethodHandle mh = MethodHandles.insertArguments(MH_ADD, 0, offset); + MethodHandle mh = MH_ADD_EXACT; for (int i = strides.length - 1; i >= 0; i--) { MethodHandle collector = MethodHandles.insertArguments(MH_ADD_SCALED_OFFSET, 2, strides[i], bounds[i]); - // (J, ...) -> J to (J, J, ...) -> J - // i.e. new coord is prefixed. Last coord will correspond to innermost layout - mh = MethodHandles.collectArguments(mh, 0, collector); - } - - return mh; + // (J, J, ...) -> J to (J, J, J, ...) -> J + // 1. the leading argument is the base offset (externally provided). + // 2. index arguments are added. The last index correspond to the innermost layout. + // 3. overflow can only occur at the outermost layer, due to the final addition with the base offset. + // This is because the layout API ensures (by construction) that all offsets generated from layout paths + // are always < Long.MAX_VALUE. + mh = MethodHandles.collectArguments(mh, 1, collector); + } + return MethodHandles.insertArguments(mh, 1, offset); } private MemoryLayout rootLayout() { diff --git a/test/jdk/java/foreign/TestLayoutPaths.java b/test/jdk/java/foreign/TestLayoutPaths.java index 414eb4117ced7..6729fbf823682 100644 --- a/test/jdk/java/foreign/TestLayoutPaths.java +++ b/test/jdk/java/foreign/TestLayoutPaths.java @@ -332,6 +332,14 @@ public void testOffsetHandleOOBIndex(MemoryLayout layout, PathElement[] pathElem } } + @Test(dataProvider = "testLayouts", expectedExceptions = ArithmeticException.class) + public void testOffsetHandleOverflow(MemoryLayout layout, PathElement[] pathElements, long[] indexes, + long expectedByteOffset) throws Throwable { + MethodHandle byteOffsetHandle = layout.byteOffsetHandle(pathElements); + byteOffsetHandle = byteOffsetHandle.asSpreader(long[].class, indexes.length); + byteOffsetHandle.invoke(Long.MAX_VALUE, indexes); + } + @Test(dataProvider = "testLayouts") public void testVarHandleBadSegment(MemoryLayout layout, PathElement[] pathElements, long[] indexes, long expectedByteOffset) throws Throwable { From 2150521650d6b730cfe9d3ecb91d589c96862475 Mon Sep 17 00:00:00 2001 From: Nizar Benalla Date: Wed, 28 Aug 2024 11:01:15 +0000 Subject: [PATCH 54/72] 8322036: Improve help output from the javadoc tool Reviewed-by: prappo --- .../formats/html/resources/standard.properties | 17 ++++++++++------- .../jdk/javadoc/internal/tool/ToolOptions.java | 2 +- .../internal/tool/resources/javadoc.properties | 6 +++--- 3 files changed, 14 insertions(+), 11 deletions(-) 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 ecff358ce22c6..6141ce46fe8fc 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 @@ -478,7 +478,8 @@ doclet.usage.author.description=\ Include @author paragraphs doclet.usage.docfilessubdirs.description=\ - Recursively copy doc-file subdirectories + Enables deep copying of 'doc-files' directories. Subdirectories and all\n\ + contents are recursively copied to the destination doclet.usage.splitindex.description=\ Split index into one file per letter @@ -515,7 +516,7 @@ doclet.usage.html5.description=\ doclet.usage.footer.parameters=\ doclet.usage.footer.description=\ - Include footer text for each page + This option is no longer supported and reports a warning doclet.usage.top.parameters=\ @@ -553,7 +554,7 @@ doclet.usage.link-platform-properties.description=\ doclet.usage.excludedocfilessubdir.parameters=\ ,,... doclet.usage.excludedocfilessubdir.description=\ - Exclude any doc-files subdirectories with given name.\n\ + Exclude any 'doc-files' subdirectories with given name.\n\ ':' can also be used anywhere in the argument as a separator. doclet.usage.group.parameters=\ @@ -614,7 +615,7 @@ doclet.usage.nooverview.description=\ Do not generate overview pages doclet.usage.serialwarn.description=\ - Generate warning about @serial tag + Reports compile-time warnings for missing '@serial' tags doclet.usage.since.parameters=\ (,)* @@ -629,7 +630,7 @@ doclet.usage.since-label.description=\ doclet.usage.tag.parameters=\ ::

doclet.usage.tag.description=\ - Specify single argument custom tags + Specifies a custom tag with a single argument doclet.usage.taglet.description=\ The fully qualified name of Taglet to register @@ -654,7 +655,8 @@ doclet.usage.javafx.description=\ doclet.usage.helpfile.parameters=\ doclet.usage.helpfile.description=\ - Include file that help link links to + Specifies a file containing the text that will be displayed when the\n\ + help link in the navigation bar is clicked doclet.usage.linksource.description=\ Generate source in HTML @@ -691,7 +693,8 @@ doclet.usage.override-methods.description=\ The default is 'detail'. doclet.usage.allow-script-in-comments.description=\ - Allow JavaScript in options and comments + Allow JavaScript in documentation comments, and options\n\ + whose value is html-code doclet.usage.xdocrootparent.parameters=\ diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/ToolOptions.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/ToolOptions.java index 17bf628114a67..39d9f2834e183 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/ToolOptions.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/ToolOptions.java @@ -678,7 +678,7 @@ interface ShowHelper { /** * Show command-line help for the extended options, as requested by - * the {@code --help-extended} option and its aliases. + * the {@code --help-extra} option and its aliases. */ void Xusage(); diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/resources/javadoc.properties b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/resources/javadoc.properties index 7db420e46d587..8178380972509 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/resources/javadoc.properties +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/resources/javadoc.properties @@ -67,7 +67,7 @@ main.opt.private.desc=\ main.opt.show.members.arg=\ main.opt.show.members.desc=\ - Specifies which members (fields, methods, etc.) will be\n\ + Specifies which members (fields, methods, or constructors) will be\n\ documented, where value can be one of "public", "protected",\n\ "package" or "private". The default is "protected", which will\n\ show public and protected members, "public" will show only\n\ @@ -87,7 +87,7 @@ main.opt.show.types.desc=\ main.opt.show.packages.arg=\ main.opt.show.packages.desc=\ - Specifies which module's packages will be documented. Possible\n\ + Specifies which module packages will be documented. Possible\n\ values are "exported" or "all" packages. main.opt.show.module.contents.arg=\ @@ -97,7 +97,7 @@ main.opt.show.module.contents.desc=\ declarations. Possible values are "api" or "all". main.opt.expand.requires.arg=\ - + (transitive|all) main.opt.expand.requires.desc=\ Instructs the tool to expand the set of modules to be\n\ documented. By default, only the modules given explicitly on\n\ From 9d183bd02763ee4ff5aa8388e039d8b5a6964328 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Gr=C3=B6nlund?= Date: Wed, 28 Aug 2024 12:19:58 +0000 Subject: [PATCH 55/72] 8339149: jfr_flush_event_writer - return value type mismatch Reviewed-by: egahlin --- src/hotspot/share/jfr/jni/jfrJniMethod.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hotspot/share/jfr/jni/jfrJniMethod.hpp b/src/hotspot/share/jfr/jni/jfrJniMethod.hpp index ca119c1f8c35a..680e6bee1ee68 100644 --- a/src/hotspot/share/jfr/jni/jfrJniMethod.hpp +++ b/src/hotspot/share/jfr/jni/jfrJniMethod.hpp @@ -113,7 +113,7 @@ jobject JNICALL jfr_get_event_writer(JNIEnv* env, jclass jvm); jobject JNICALL jfr_new_event_writer(JNIEnv* env, jclass jvm); -jboolean JNICALL jfr_event_writer_flush(JNIEnv* env, jclass jvm, jobject writer, jint used_size, jint requested_size); +void JNICALL jfr_event_writer_flush(JNIEnv* env, jclass jvm, jobject writer, jint used_size, jint requested_size); jlong JNICALL jfr_commit(JNIEnv* env, jclass cls, jlong next_position); void JNICALL jfr_flush(JNIEnv* env, jclass jvm); From 32c975098521e830ce706b67e7232a007c0846c7 Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Wed, 28 Aug 2024 13:28:01 +0000 Subject: [PATCH 56/72] 8339160: [BACKOUT] JDK-8338440 Parallel: Improve fragmentation mitigation in Full GC Reviewed-by: tschatzl --- .../share/gc/parallel/psParallelCompact.cpp | 705 ++++++++---------- .../share/gc/parallel/psParallelCompact.hpp | 77 +- 2 files changed, 359 insertions(+), 423 deletions(-) diff --git a/src/hotspot/share/gc/parallel/psParallelCompact.cpp b/src/hotspot/share/gc/parallel/psParallelCompact.cpp index a4884afe25472..4bff8f8a7d06a 100644 --- a/src/hotspot/share/gc/parallel/psParallelCompact.cpp +++ b/src/hotspot/share/gc/parallel/psParallelCompact.cpp @@ -126,82 +126,73 @@ ParallelCompactData::RegionData::dc_claimed = 0x8U << dc_shift; const ParallelCompactData::RegionData::region_sz_t ParallelCompactData::RegionData::dc_completed = 0xcU << dc_shift; -bool ParallelCompactData::RegionData::is_clear() { - return (_destination == nullptr) && - (_source_region == 0) && - (_partial_obj_addr == nullptr) && - (_partial_obj_size == 0) && - (_dc_and_los == 0) && - (_shadow_state == 0); -} - -#ifdef ASSERT -void ParallelCompactData::RegionData::verify_clear() { - assert(_destination == nullptr, "inv"); - assert(_source_region == 0, "inv"); - assert(_partial_obj_addr == nullptr, "inv"); - assert(_partial_obj_size == 0, "inv"); - assert(_dc_and_los == 0, "inv"); - assert(_shadow_state == 0, "inv"); -} -#endif - SpaceInfo PSParallelCompact::_space_info[PSParallelCompact::last_space_id]; SpanSubjectToDiscoveryClosure PSParallelCompact::_span_based_discoverer; ReferenceProcessor* PSParallelCompact::_ref_processor = nullptr; -void SplitInfo::record(size_t split_region_idx, HeapWord* split_point, size_t preceding_live_words) { - assert(split_region_idx != 0, "precondition"); - - // Obj denoted by split_point will be deferred to the next space. - assert(split_point != nullptr, "precondition"); - - const ParallelCompactData& sd = PSParallelCompact::summary_data(); +void SplitInfo::record(size_t src_region_idx, size_t partial_obj_size, + HeapWord* destination) +{ + assert(src_region_idx != 0, "invalid src_region_idx"); + assert(partial_obj_size != 0, "invalid partial_obj_size argument"); + assert(destination != nullptr, "invalid destination argument"); - PSParallelCompact::RegionData* split_region_ptr = sd.region(split_region_idx); - assert(preceding_live_words < split_region_ptr->data_size(), "inv"); + _src_region_idx = src_region_idx; + _partial_obj_size = partial_obj_size; + _destination = destination; - HeapWord* preceding_destination = split_region_ptr->destination(); - assert(preceding_destination != nullptr, "inv"); + // These fields may not be updated below, so make sure they're clear. + assert(_dest_region_addr == nullptr, "should have been cleared"); + assert(_first_src_addr == nullptr, "should have been cleared"); - // How many regions does the preceding part occupy - uint preceding_destination_count; - if (preceding_live_words == 0) { - preceding_destination_count = 0; - } else { - if (preceding_destination + preceding_live_words > sd.region_align_up(preceding_destination)) { - preceding_destination_count = 2; - } else { - preceding_destination_count = 1; + // Determine the number of destination regions for the partial object. + HeapWord* const last_word = destination + partial_obj_size - 1; + const ParallelCompactData& sd = PSParallelCompact::summary_data(); + HeapWord* const beg_region_addr = sd.region_align_down(destination); + HeapWord* const end_region_addr = sd.region_align_down(last_word); + + if (beg_region_addr == end_region_addr) { + // One destination region. + _destination_count = 1; + if (end_region_addr == destination) { + // The destination falls on a region boundary, thus the first word of the + // partial object will be the first word copied to the destination region. + _dest_region_addr = end_region_addr; + _first_src_addr = sd.region_to_addr(src_region_idx); } + } else { + // Two destination regions. When copied, the partial object will cross a + // destination region boundary, so a word somewhere within the partial + // object will be the first word copied to the second destination region. + _destination_count = 2; + _dest_region_addr = end_region_addr; + const size_t ofs = pointer_delta(end_region_addr, destination); + assert(ofs < _partial_obj_size, "sanity"); + _first_src_addr = sd.region_to_addr(src_region_idx) + ofs; } - - _split_region_idx = split_region_idx; - _split_point = split_point; - _preceding_live_words = preceding_live_words; - _preceding_destination = preceding_destination; - _preceding_destination_count = preceding_destination_count; } void SplitInfo::clear() { - _split_region_idx = 0; - _split_point = nullptr; - _preceding_live_words = 0; - _preceding_destination = nullptr; - _preceding_destination_count = 0; + _src_region_idx = 0; + _partial_obj_size = 0; + _destination = nullptr; + _destination_count = 0; + _dest_region_addr = nullptr; + _first_src_addr = nullptr; assert(!is_valid(), "sanity"); } #ifdef ASSERT void SplitInfo::verify_clear() { - assert(_split_region_idx == 0, "not clear"); - assert(_split_point == nullptr, "not clear"); - assert(_preceding_live_words == 0, "not clear"); - assert(_preceding_destination == nullptr, "not clear"); - assert(_preceding_destination_count == 0, "not clear"); + assert(_src_region_idx == 0, "not clear"); + assert(_partial_obj_size == 0, "not clear"); + assert(_destination == nullptr, "not clear"); + assert(_destination_count == 0, "not clear"); + assert(_dest_region_addr == nullptr, "not clear"); + assert(_first_src_addr == nullptr, "not clear"); } #endif // #ifdef ASSERT @@ -304,105 +295,110 @@ ParallelCompactData::summarize_dense_prefix(HeapWord* beg, HeapWord* end) } } -// The total live words on src_region would overflow the target space, so find -// the overflowing object and record the split point. The invariant is that an -// obj should not cross space boundary. -HeapWord* ParallelCompactData::summarize_split_space(size_t src_region, - SplitInfo& split_info, - HeapWord* const destination, - HeapWord* const target_end, - HeapWord** target_next) { +// Find the point at which a space can be split and, if necessary, record the +// split point. +// +// If the current src region (which overflowed the destination space) doesn't +// have a partial object, the split point is at the beginning of the current src +// region (an "easy" split, no extra bookkeeping required). +// +// If the current src region has a partial object, the split point is in the +// region where that partial object starts (call it the split_region). If +// split_region has a partial object, then the split point is just after that +// partial object (a "hard" split where we have to record the split data and +// zero the partial_obj_size field). With a "hard" split, we know that the +// partial_obj ends within split_region because the partial object that caused +// the overflow starts in split_region. If split_region doesn't have a partial +// obj, then the split is at the beginning of split_region (another "easy" +// split). +HeapWord* +ParallelCompactData::summarize_split_space(size_t src_region, + SplitInfo& split_info, + HeapWord* destination, + HeapWord* target_end, + HeapWord** target_next) +{ assert(destination <= target_end, "sanity"); assert(destination + _region_data[src_region].data_size() > target_end, "region should not fit into target space"); assert(is_region_aligned(target_end), "sanity"); + size_t split_region = src_region; + HeapWord* split_destination = destination; size_t partial_obj_size = _region_data[src_region].partial_obj_size(); if (destination + partial_obj_size > target_end) { - assert(partial_obj_size > 0, "inv"); - // The overflowing obj is from a previous region. - // - // source-regions: + // The split point is just after the partial object (if any) in the + // src_region that contains the start of the object that overflowed the + // destination space. // - // *************** - // | A|AA | - // *************** - // ^ - // | split-point + // Find the start of the "overflow" object and set split_region to the + // region containing it. + HeapWord* const overflow_obj = _region_data[src_region].partial_obj_addr(); + split_region = addr_to_region_idx(overflow_obj); + + // Clear the source_region field of all destination regions whose first word + // came from data after the split point (a non-null source_region field + // implies a region must be filled). // - // dest-region: + // An alternative to the simple loop below: clear during post_compact(), + // which uses memcpy instead of individual stores, and is easy to + // parallelize. (The downside is that it clears the entire RegionData + // object as opposed to just one field.) // - // ******** - // |~~~~A | - // ******** - // ^^ - // || target-space-end - // | - // | destination + // post_compact() would have to clear the summary data up to the highest + // address that was written during the summary phase, which would be // - // AAA would overflow target-space. + // max(top, max(new_top, clear_top)) // - HeapWord* overflowing_obj = _region_data[src_region].partial_obj_addr(); - size_t split_region = addr_to_region_idx(overflowing_obj); + // where clear_top is a new field in SpaceInfo. Would have to set clear_top + // to target_end. + const RegionData* const sr = region(split_region); + const size_t beg_idx = + addr_to_region_idx(region_align_up(sr->destination() + + sr->partial_obj_size())); + const size_t end_idx = addr_to_region_idx(target_end); - // The number of live words before the overflowing object on this split region - size_t preceding_live_words; - if (is_region_aligned(overflowing_obj)) { - preceding_live_words = 0; - } else { - // Words accounted by the overflowing object on the split region - size_t overflowing_size = pointer_delta(region_align_up(overflowing_obj), overflowing_obj); - preceding_live_words = region(split_region)->data_size() - overflowing_size; - } - - split_info.record(split_region, overflowing_obj, preceding_live_words); - - HeapWord* src_region_start = region_to_addr(src_region); - HeapWord* new_top = destination - pointer_delta(src_region_start, overflowing_obj); - - // If the overflowing obj was relocated to its original destination, - // those destination regions would have their source_region set. Now that - // this overflowing obj is relocated somewhere else, reset the - // source_region. - { - size_t range_start = addr_to_region_idx(region_align_up(new_top)); - size_t range_end = addr_to_region_idx(region_align_up(destination)); - for (size_t i = range_start; i < range_end; ++i) { - region(i)->set_source_region(0); - } + log_develop_trace(gc, compaction)("split: clearing source_region field in [" SIZE_FORMAT ", " SIZE_FORMAT ")", beg_idx, end_idx); + for (size_t idx = beg_idx; idx < end_idx; ++idx) { + _region_data[idx].set_source_region(0); } - // Update new top of target space - *target_next = new_top; + // Set split_destination and partial_obj_size to reflect the split region. + split_destination = sr->destination(); + partial_obj_size = sr->partial_obj_size(); + } - return overflowing_obj; + // The split is recorded only if a partial object extends onto the region. + if (partial_obj_size != 0) { + _region_data[split_region].set_partial_obj_size(0); + split_info.record(split_region, partial_obj_size, split_destination); } - // Obj-iteration to locate the overflowing obj - HeapWord* region_start = region_to_addr(src_region); - HeapWord* region_end = region_start + RegionSize; - HeapWord* cur_addr = region_start + partial_obj_size; - size_t live_words = partial_obj_size; + // Setup the continuation addresses. + *target_next = split_destination + partial_obj_size; + HeapWord* const source_next = region_to_addr(split_region) + partial_obj_size; - while (true) { - assert(cur_addr < region_end, "inv"); - cur_addr = PSParallelCompact::mark_bitmap()->find_obj_beg(cur_addr, region_end); - // There must be an overflowing obj in this region - assert(cur_addr < region_end, "inv"); + if (log_develop_is_enabled(Trace, gc, compaction)) { + const char * split_type = partial_obj_size == 0 ? "easy" : "hard"; + log_develop_trace(gc, compaction)("%s split: src=" PTR_FORMAT " src_c=" SIZE_FORMAT " pos=" SIZE_FORMAT, + split_type, p2i(source_next), split_region, partial_obj_size); + log_develop_trace(gc, compaction)("%s split: dst=" PTR_FORMAT " dst_c=" SIZE_FORMAT " tn=" PTR_FORMAT, + split_type, p2i(split_destination), + addr_to_region_idx(split_destination), + p2i(*target_next)); - oop obj = cast_to_oop(cur_addr); - size_t obj_size = obj->size(); - if (destination + live_words + obj_size > target_end) { - // Found the overflowing obj - split_info.record(src_region, cur_addr, live_words); - *target_next = destination + live_words; - return cur_addr; + if (partial_obj_size != 0) { + HeapWord* const po_beg = split_info.destination(); + HeapWord* const po_end = po_beg + split_info.partial_obj_size(); + log_develop_trace(gc, compaction)("%s split: po_beg=" PTR_FORMAT " " SIZE_FORMAT " po_end=" PTR_FORMAT " " SIZE_FORMAT, + split_type, + p2i(po_beg), addr_to_region_idx(po_beg), + p2i(po_end), addr_to_region_idx(po_end)); } - - live_words += obj_size; - cur_addr += obj_size; } + + return source_next; } size_t ParallelCompactData::live_words_in_space(const MutableSpace* space, @@ -454,57 +450,70 @@ bool ParallelCompactData::summarize(SplitInfo& split_info, const size_t end_region = addr_to_region_idx(region_align_up(source_end)); HeapWord *dest_addr = target_beg; - for (/* empty */; cur_region < end_region; cur_region++) { - size_t words = _region_data[cur_region].data_size(); - - // Skip empty ones - if (words == 0) { - continue; - } - - if (split_info.is_split(cur_region)) { - assert(words > split_info.preceding_live_words(), "inv"); - words -= split_info.preceding_live_words(); - } - + while (cur_region < end_region) { + // The destination must be set even if the region has no data. _region_data[cur_region].set_destination(dest_addr); - // If cur_region does not fit entirely into the target space, find a point - // at which the source space can be 'split' so that part is copied to the - // target space and the rest is copied elsewhere. - if (dest_addr + words > target_end) { - assert(source_next != nullptr, "source_next is null when splitting"); - *source_next = summarize_split_space(cur_region, split_info, dest_addr, - target_end, target_next); - return false; - } + size_t words = _region_data[cur_region].data_size(); + if (words > 0) { + // If cur_region does not fit entirely into the target space, find a point + // at which the source space can be 'split' so that part is copied to the + // target space and the rest is copied elsewhere. + if (dest_addr + words > target_end) { + assert(source_next != nullptr, "source_next is null when splitting"); + *source_next = summarize_split_space(cur_region, split_info, dest_addr, + target_end, target_next); + return false; + } - uint destination_count = split_info.is_split(cur_region) - ? split_info.preceding_destination_count() - : 0; + // Compute the destination_count for cur_region, and if necessary, update + // source_region for a destination region. The source_region field is + // updated if cur_region is the first (left-most) region to be copied to a + // destination region. + // + // The destination_count calculation is a bit subtle. A region that has + // data that compacts into itself does not count itself as a destination. + // This maintains the invariant that a zero count means the region is + // available and can be claimed and then filled. + uint destination_count = 0; + if (split_info.is_split(cur_region)) { + // The current region has been split: the partial object will be copied + // to one destination space and the remaining data will be copied to + // another destination space. Adjust the initial destination_count and, + // if necessary, set the source_region field if the partial object will + // cross a destination region boundary. + destination_count = split_info.destination_count(); + if (destination_count == 2) { + size_t dest_idx = addr_to_region_idx(split_info.dest_region_addr()); + _region_data[dest_idx].set_source_region(cur_region); + } + } - HeapWord* const last_addr = dest_addr + words - 1; - const size_t dest_region_1 = addr_to_region_idx(dest_addr); - const size_t dest_region_2 = addr_to_region_idx(last_addr); + HeapWord* const last_addr = dest_addr + words - 1; + const size_t dest_region_1 = addr_to_region_idx(dest_addr); + const size_t dest_region_2 = addr_to_region_idx(last_addr); + + // Initially assume that the destination regions will be the same and + // adjust the value below if necessary. Under this assumption, if + // cur_region == dest_region_2, then cur_region will be compacted + // completely into itself. + destination_count += cur_region == dest_region_2 ? 0 : 1; + if (dest_region_1 != dest_region_2) { + // Destination regions differ; adjust destination_count. + destination_count += 1; + // Data from cur_region will be copied to the start of dest_region_2. + _region_data[dest_region_2].set_source_region(cur_region); + } else if (is_region_aligned(dest_addr)) { + // Data from cur_region will be copied to the start of the destination + // region. + _region_data[dest_region_1].set_source_region(cur_region); + } - // Initially assume that the destination regions will be the same and - // adjust the value below if necessary. Under this assumption, if - // cur_region == dest_region_2, then cur_region will be compacted - // completely into itself. - destination_count += cur_region == dest_region_2 ? 0 : 1; - if (dest_region_1 != dest_region_2) { - // Destination regions differ; adjust destination_count. - destination_count += 1; - // Data from cur_region will be copied to the start of dest_region_2. - _region_data[dest_region_2].set_source_region(cur_region); - } else if (is_region_aligned(dest_addr)) { - // Data from cur_region will be copied to the start of the destination - // region. - _region_data[dest_region_1].set_source_region(cur_region); + _region_data[cur_region].set_destination_count(destination_count); + dest_addr += words; } - _region_data[cur_region].set_destination_count(destination_count); - dest_addr += words; + ++cur_region; } *target_next = dest_addr; @@ -512,12 +521,12 @@ bool ParallelCompactData::summarize(SplitInfo& split_info, } #ifdef ASSERT -void ParallelCompactData::verify_clear() { - for (uint cur_idx = 0; cur_idx < region_count(); ++cur_idx) { - if (!region(cur_idx)->is_clear()) { - log_warning(gc)("Uncleared Region: %u", cur_idx); - region(cur_idx)->verify_clear(); - } +void ParallelCompactData::verify_clear() +{ + const size_t* const beg = (const size_t*) _region_vspace->committed_low_addr(); + const size_t* const end = (const size_t*) _region_vspace->committed_high_addr(); + for (const size_t* p = beg; p < end; ++p) { + assert(*p == 0, "not zero"); } } #endif // #ifdef ASSERT @@ -684,13 +693,6 @@ void PSParallelCompact::post_compact() } } -#ifdef ASSERT - { - mark_bitmap()->verify_clear(); - summary_data().verify_clear(); - } -#endif - ParCompactionManager::flush_all_string_dedup_requests(); MutableSpace* const eden_space = _space_info[eden_space_id].space(); @@ -877,10 +879,10 @@ void PSParallelCompact::summary_phase() bool maximum_compaction = check_maximum_compaction(total_live_words, old_space, full_region_prefix_end); - HeapWord* dense_prefix_end = maximum_compaction - ? full_region_prefix_end - : compute_dense_prefix_for_old_space(old_space, - full_region_prefix_end); + HeapWord* dense_prefix_end = + maximum_compaction ? full_region_prefix_end + : compute_dense_prefix_for_old_space(old_space, + full_region_prefix_end); SpaceId id = old_space_id; _space_info[id].set_dense_prefix(dense_prefix_end); @@ -888,8 +890,6 @@ void PSParallelCompact::summary_phase() fill_dense_prefix_end(id); _summary_data.summarize_dense_prefix(old_space->bottom(), dense_prefix_end); } - - // Compacting objs inn [dense_prefix_end, old_space->top()) _summary_data.summarize(_space_info[id].split_info(), dense_prefix_end, old_space->top(), nullptr, dense_prefix_end, old_space->end(), @@ -1549,30 +1549,6 @@ void PSParallelCompact::forward_to_new_addr() { WorkerTask("PSForward task"), _num_workers(num_workers) {} - static void forward_objs_in_range(ParCompactionManager* cm, - HeapWord* start, - HeapWord* end, - HeapWord* destination) { - HeapWord* cur_addr = start; - HeapWord* new_addr = destination; - - while (cur_addr < end) { - cur_addr = mark_bitmap()->find_obj_beg(cur_addr, end); - if (cur_addr >= end) { - return; - } - assert(mark_bitmap()->is_marked(cur_addr), "inv"); - oop obj = cast_to_oop(cur_addr); - if (new_addr != cur_addr) { - cm->preserved_marks()->push_if_necessary(obj, obj->mark()); - obj->forward_to(cast_to_oop(new_addr)); - } - size_t obj_size = obj->size(); - new_addr += obj_size; - cur_addr += obj_size; - } - } - void work(uint worker_id) override { ParCompactionManager* cm = ParCompactionManager::gc_thread_compaction_manager(worker_id); for (uint id = old_space_id; id < last_space_id; ++id) { @@ -1584,8 +1560,6 @@ void PSParallelCompact::forward_to_new_addr() { continue; } - const SplitInfo& split_info = _space_info[SpaceId(id)].split_info(); - size_t dense_prefix_region = _summary_data.addr_to_region_idx(dense_prefix_addr); size_t top_region = _summary_data.addr_to_region_idx(_summary_data.region_align_up(top)); size_t start_region; @@ -1605,19 +1579,24 @@ void PSParallelCompact::forward_to_new_addr() { HeapWord* region_start = _summary_data.region_to_addr(cur_region); HeapWord* region_end = region_start + ParallelCompactData::RegionSize; - - if (split_info.is_split(cur_region)) { - // Part 1: will be relocated to space-1 - HeapWord* preceding_destination = split_info.preceding_destination(); - HeapWord* split_point = split_info.split_point(); - forward_objs_in_range(cm, region_start + live_words, split_point, preceding_destination + live_words); - - // Part 2: will be relocated to space-2 - HeapWord* destination = region_ptr->destination(); - forward_objs_in_range(cm, split_point, region_end, destination); - } else { - HeapWord* destination = region_ptr->destination(); - forward_objs_in_range(cm, region_start + live_words, region_end, destination + live_words); + HeapWord* cur_addr = region_start + live_words; + + HeapWord* destination = region_ptr->destination(); + while (cur_addr < region_end) { + cur_addr = mark_bitmap()->find_obj_beg(cur_addr, region_end); + if (cur_addr >= region_end) { + break; + } + assert(mark_bitmap()->is_marked(cur_addr), "inv"); + HeapWord* new_addr = destination + live_words; + oop obj = cast_to_oop(cur_addr); + if (new_addr != cur_addr) { + cm->preserved_marks()->push_if_necessary(obj, obj->mark()); + obj->forward_to(cast_to_oop(new_addr)); + } + size_t obj_size = obj->size(); + live_words += obj_size; + cur_addr += obj_size; } } } @@ -1649,16 +1628,13 @@ void PSParallelCompact::verify_forward() { break; } assert(mark_bitmap()->is_marked(cur_addr), "inv"); - assert(bump_ptr <= _space_info[bump_ptr_space].new_top(), "inv"); // Move to the space containing cur_addr if (bump_ptr == _space_info[bump_ptr_space].new_top()) { bump_ptr = space(space_id(cur_addr))->bottom(); bump_ptr_space = space_id(bump_ptr); } oop obj = cast_to_oop(cur_addr); - if (cur_addr == bump_ptr) { - assert(!obj->is_forwarded(), "inv"); - } else { + if (cur_addr != bump_ptr) { assert(obj->forwardee() == cast_to_oop(bump_ptr), "inv"); } bump_ptr += obj->size(); @@ -1968,10 +1944,15 @@ PSParallelCompact::SpaceId PSParallelCompact::space_id(HeapWord* addr) { } // Skip over count live words starting from beg, and return the address of the -// next live word. Callers must also ensure that there are enough live words in -// the range [beg, end) to skip. -HeapWord* PSParallelCompact::skip_live_words(HeapWord* beg, HeapWord* end, size_t count) +// next live word. Unless marked, the word corresponding to beg is assumed to +// be dead. Callers must either ensure beg does not correspond to the middle of +// an object, or account for those live words in some other way. Callers must +// also ensure that there are enough live words in the range [beg, end) to skip. +HeapWord* +PSParallelCompact::skip_live_words(HeapWord* beg, HeapWord* end, size_t count) { + assert(count > 0, "sanity"); + ParMarkBitMap* m = mark_bitmap(); HeapWord* cur_addr = beg; while (true) { @@ -1987,94 +1968,69 @@ HeapWord* PSParallelCompact::skip_live_words(HeapWord* beg, HeapWord* end, size_ } } -// On filling a destination region (dest-region), we need to know the location -// of the word that will be at the start of the dest-region after compaction. -// A dest-region can have one or more source regions, but only the first -// source-region contains this location. This location is retrieved by calling -// `first_src_addr` on a dest-region. -// Conversely, a source-region has a dest-region which holds the destination of -// the first live word on this source-region, based on which the destination -// for the rest of live words can be derived. -// -// Note: -// There is some complication due to space-boundary-fragmentation (an obj can't -// cross space-boundary) -- a source-region may be split and behave like two -// distinct regions with their own dest-region, as depicted below. -// -// source-region: region-n -// -// ********************** -// | A|A~~~~B|B | -// ********************** -// n-1 n n+1 -// -// AA, BB denote two live objs. ~~~~ denotes unknown number of live objs. -// -// Assuming the dest-region for region-n is the final region before -// old-space-end and its first-live-word is the middle of AA, the heap content -// will look like the following after compaction: -// -// ************** ************* -// A|A~~~~ | |BB | -// ************** ************* -// ^ ^ -// | old-space-end | eden-space-start -// -// Therefore, in this example, region-n will have two dest-regions, one for -// the final region in old-space and the other for the first region in -// eden-space. -// To handle this special case, we introduce the concept of split-region, whose -// contents are relocated to two spaces. `SplitInfo` captures all necessary -// info about the split, the first part, spliting-point, and the second part. HeapWord* PSParallelCompact::first_src_addr(HeapWord* const dest_addr, SpaceId src_space_id, size_t src_region_idx) { - const size_t RegionSize = ParallelCompactData::RegionSize; + assert(summary_data().is_region_aligned(dest_addr), "not aligned"); + + const SplitInfo& split_info = _space_info[src_space_id].split_info(); + if (split_info.dest_region_addr() == dest_addr) { + // The partial object ending at the split point contains the first word to + // be copied to dest_addr. + return split_info.first_src_addr(); + } + const ParallelCompactData& sd = summary_data(); - assert(sd.is_region_aligned(dest_addr), "precondition"); + ParMarkBitMap* const bitmap = mark_bitmap(); + const size_t RegionSize = ParallelCompactData::RegionSize; + assert(sd.is_region_aligned(dest_addr), "not aligned"); const RegionData* const src_region_ptr = sd.region(src_region_idx); - assert(src_region_ptr->data_size() > 0, "src region cannot be empty"); - const size_t partial_obj_size = src_region_ptr->partial_obj_size(); HeapWord* const src_region_destination = src_region_ptr->destination(); - HeapWord* const region_start = sd.region_to_addr(src_region_idx); - HeapWord* const region_end = sd.region_to_addr(src_region_idx) + RegionSize; + assert(dest_addr >= src_region_destination, "wrong src region"); + assert(src_region_ptr->data_size() > 0, "src region cannot be empty"); - // Identify the actual destination for the first live words on this region, - // taking split-region into account. - HeapWord* region_start_destination; - const SplitInfo& split_info = _space_info[src_space_id].split_info(); - if (split_info.is_split(src_region_idx)) { - // The second part of this split region; use the recorded split point. - if (dest_addr == src_region_destination) { - return split_info.split_point(); + HeapWord* const src_region_beg = sd.region_to_addr(src_region_idx); + HeapWord* const src_region_end = src_region_beg + RegionSize; + + HeapWord* addr = src_region_beg; + if (dest_addr == src_region_destination) { + // Return the first live word in the source region. + if (partial_obj_size == 0) { + addr = bitmap->find_obj_beg(addr, src_region_end); + assert(addr < src_region_end, "no objects start in src region"); } - region_start_destination = split_info.preceding_destination(); - } else { - region_start_destination = src_region_destination; + return addr; } - // Calculate the offset to be skipped - size_t words_to_skip = pointer_delta(dest_addr, region_start_destination); + // Must skip some live data. + size_t words_to_skip = dest_addr - src_region_destination; + assert(src_region_ptr->data_size() > words_to_skip, "wrong src region"); - HeapWord* result; - if (partial_obj_size > words_to_skip) { - result = region_start + words_to_skip; - } else { - words_to_skip -= partial_obj_size; - result = skip_live_words(region_start + partial_obj_size, region_end, words_to_skip); + if (partial_obj_size >= words_to_skip) { + // All the live words to skip are part of the partial object. + addr += words_to_skip; + if (partial_obj_size == words_to_skip) { + // Find the first live word past the partial object. + addr = bitmap->find_obj_beg(addr, src_region_end); + assert(addr < src_region_end, "wrong src region"); + } + return addr; } - if (split_info.is_split(src_region_idx)) { - assert(result < split_info.split_point(), "postcondition"); - } else { - assert(result < region_end, "postcondition"); + // Skip over the partial object (if any). + if (partial_obj_size != 0) { + words_to_skip -= partial_obj_size; + addr += partial_obj_size; } - return result; + // Skip over live words due to objects that start in the region. + addr = skip_live_words(addr, src_region_end, words_to_skip); + assert(addr < src_region_end, "wrong src region"); + return addr; } void PSParallelCompact::decrement_destination_counts(ParCompactionManager* cm, @@ -2125,7 +2081,10 @@ size_t PSParallelCompact::next_src_region(MoveAndUpdateClosure& closure, HeapWord*& src_space_top, HeapWord* end_addr) { + typedef ParallelCompactData::RegionData RegionData; + ParallelCompactData& sd = PSParallelCompact::summary_data(); + const size_t region_size = ParallelCompactData::RegionSize; size_t src_region_idx = 0; @@ -2133,8 +2092,8 @@ size_t PSParallelCompact::next_src_region(MoveAndUpdateClosure& closure, HeapWord* const src_aligned_up = sd.region_align_up(end_addr); RegionData* src_region_ptr = sd.addr_to_region_ptr(src_aligned_up); HeapWord* const top_aligned_up = sd.region_align_up(src_space_top); - const RegionData* const top_region_ptr = sd.addr_to_region_ptr(top_aligned_up); - + const RegionData* const top_region_ptr = + sd.addr_to_region_ptr(top_aligned_up); while (src_region_ptr < top_region_ptr && src_region_ptr->data_size() == 0) { ++src_region_ptr; } @@ -2151,50 +2110,59 @@ size_t PSParallelCompact::next_src_region(MoveAndUpdateClosure& closure, } // Switch to a new source space and find the first non-empty region. - uint space_id = src_space_id + 1; + unsigned int space_id = src_space_id + 1; assert(space_id < last_space_id, "not enough spaces"); - for (/* empty */; space_id < last_space_id; ++space_id) { - HeapWord* bottom = _space_info[space_id].space()->bottom(); - HeapWord* top = _space_info[space_id].space()->top(); - // Skip empty space - if (bottom == top) { - continue; - } - - // Identify the first region that contains live words in this space - size_t cur_region = sd.addr_to_region_idx(bottom); - size_t end_region = sd.addr_to_region_idx(sd.region_align_up(top)); - - for (/* empty */ ; cur_region < end_region; ++cur_region) { - RegionData* cur = sd.region(cur_region); - if (cur->live_obj_size() > 0) { - HeapWord* region_start_addr = sd.region_to_addr(cur_region); - HeapWord* region_end_addr = region_start_addr + ParallelCompactData::RegionSize; - HeapWord* first_live_word = mark_bitmap()->find_obj_beg(region_start_addr, region_end_addr); - assert(first_live_word < region_end_addr, "inv"); - - src_space_id = SpaceId(space_id); - src_space_top = top; - closure.set_source(first_live_word); - return cur_region; + HeapWord* const destination = closure.destination(); + + do { + MutableSpace* space = _space_info[space_id].space(); + HeapWord* const bottom = space->bottom(); + const RegionData* const bottom_cp = sd.addr_to_region_ptr(bottom); + + // Iterate over the spaces that do not compact into themselves. + if (bottom_cp->destination() != bottom) { + HeapWord* const top_aligned_up = sd.region_align_up(space->top()); + const RegionData* const top_cp = sd.addr_to_region_ptr(top_aligned_up); + + for (const RegionData* src_cp = bottom_cp; src_cp < top_cp; ++src_cp) { + if (src_cp->live_obj_size() > 0) { + // Found it. + assert(src_cp->destination() == destination, + "first live obj in the space must match the destination"); + assert(src_cp->partial_obj_size() == 0, + "a space cannot begin with a partial obj"); + + src_space_id = SpaceId(space_id); + src_space_top = space->top(); + const size_t src_region_idx = sd.region(src_cp); + closure.set_source(sd.region_to_addr(src_region_idx)); + return src_region_idx; + } else { + assert(src_cp->data_size() == 0, "sanity"); + } } } - } + } while (++space_id < last_space_id); - ShouldNotReachHere(); + assert(false, "no source region was found"); + return 0; } HeapWord* PSParallelCompact::partial_obj_end(HeapWord* region_start_addr) { ParallelCompactData& sd = summary_data(); assert(sd.is_region_aligned(region_start_addr), "precondition"); - // Use per-region partial_obj_size to locate the end of the obj, that extends - // to region_start_addr. + // Use per-region partial_obj_size to locate the end of the obj, that extends to region_start_addr. + SplitInfo& split_info = _space_info[space_id(region_start_addr)].split_info(); size_t start_region_idx = sd.addr_to_region_idx(region_start_addr); size_t end_region_idx = sd.region_count(); size_t accumulated_size = 0; for (size_t region_idx = start_region_idx; region_idx < end_region_idx; ++region_idx) { + if (split_info.is_split(region_idx)) { + accumulated_size += split_info.partial_obj_size(); + break; + } size_t cur_partial_obj_size = sd.region(region_idx)->partial_obj_size(); accumulated_size += cur_partial_obj_size; if (cur_partial_obj_size != ParallelCompactData::RegionSize) { @@ -2204,8 +2172,6 @@ HeapWord* PSParallelCompact::partial_obj_end(HeapWord* region_start_addr) { return region_start_addr + accumulated_size; } -// Use region_idx as the destination region, and evacuate all live objs on its -// source regions to this destination region. void PSParallelCompact::fill_region(ParCompactionManager* cm, MoveAndUpdateClosure& closure, size_t region_idx) { ParMarkBitMap* const bitmap = mark_bitmap(); @@ -2226,43 +2192,20 @@ void PSParallelCompact::fill_region(ParCompactionManager* cm, MoveAndUpdateClosu src_region_idx += 1; } - // source-region: - // - // ********** - // | ~~~ | - // ********** - // ^ - // |-- closure.source() / first_src_addr - // - // - // ~~~ : live words - // - // destination-region: - // - // ********** - // | | - // ********** - // ^ - // |-- region-start if (bitmap->is_unmarked(closure.source())) { - // An object overflows the previous destination region, so this - // destination region should copy the remainder of the object or as much as - // will fit. + // The first source word is in the middle of an object; copy the remainder + // of the object or as much as will fit. The fact that pointer updates were + // deferred will be noted when the object header is processed. HeapWord* const old_src_addr = closure.source(); { HeapWord* region_start = sd.region_align_down(closure.source()); HeapWord* obj_start = bitmap->find_obj_beg_reverse(region_start, closure.source()); HeapWord* obj_end; - if (obj_start != closure.source()) { - assert(bitmap->is_marked(obj_start), "inv"); - // Found the actual obj-start, try to find the obj-end using either - // size() if this obj is completely contained in the current region. + if (bitmap->is_marked(obj_start)) { HeapWord* next_region_start = region_start + ParallelCompactData::RegionSize; HeapWord* partial_obj_start = (next_region_start >= src_space_top) ? nullptr : sd.addr_to_region_ptr(next_region_start)->partial_obj_addr(); - // This obj extends to next region iff partial_obj_addr of the *next* - // region is the same as obj-start. if (partial_obj_start == obj_start) { // This obj extends to next region. obj_end = partial_obj_end(next_region_start); @@ -2279,41 +2222,39 @@ void PSParallelCompact::fill_region(ParCompactionManager* cm, MoveAndUpdateClosu } if (closure.is_full()) { - decrement_destination_counts(cm, src_space_id, src_region_idx, closure.source()); + decrement_destination_counts(cm, src_space_id, src_region_idx, + closure.source()); closure.complete_region(dest_addr, region_ptr); return; } - // Finished copying without using up the current destination-region HeapWord* const end_addr = sd.region_align_down(closure.source()); if (sd.region_align_down(old_src_addr) != end_addr) { - assert(sd.region_align_up(old_src_addr) == end_addr, "only one region"); // The partial object was copied from more than one source region. decrement_destination_counts(cm, src_space_id, src_region_idx, end_addr); // Move to the next source region, possibly switching spaces as well. All // args except end_addr may be modified. - src_region_idx = next_src_region(closure, src_space_id, src_space_top, end_addr); + src_region_idx = next_src_region(closure, src_space_id, src_space_top, + end_addr); } } - // Handle the rest obj-by-obj, where we know obj-start. do { HeapWord* cur_addr = closure.source(); HeapWord* const end_addr = MIN2(sd.region_align_up(cur_addr + 1), src_space_top); - // To handle the case where the final obj in source region extends to next region. - HeapWord* final_obj_start = (end_addr == src_space_top) + HeapWord* partial_obj_start = (end_addr == src_space_top) ? nullptr : sd.addr_to_region_ptr(end_addr)->partial_obj_addr(); - // Apply closure on objs inside [cur_addr, end_addr) + // apply closure on objs inside [cur_addr, end_addr) do { cur_addr = bitmap->find_obj_beg(cur_addr, end_addr); if (cur_addr == end_addr) { break; } size_t obj_size; - if (final_obj_start == cur_addr) { + if (partial_obj_start == cur_addr) { obj_size = pointer_delta(partial_obj_end(end_addr), cur_addr); } else { // This obj doesn't extend into next region; size() is safe to use. @@ -2324,7 +2265,8 @@ void PSParallelCompact::fill_region(ParCompactionManager* cm, MoveAndUpdateClosu } while (cur_addr < end_addr && !closure.is_full()); if (closure.is_full()) { - decrement_destination_counts(cm, src_space_id, src_region_idx, closure.source()); + decrement_destination_counts(cm, src_space_id, src_region_idx, + closure.source()); closure.complete_region(dest_addr, region_ptr); return; } @@ -2333,7 +2275,8 @@ void PSParallelCompact::fill_region(ParCompactionManager* cm, MoveAndUpdateClosu // Move to the next source region, possibly switching spaces as well. All // args except end_addr may be modified. - src_region_idx = next_src_region(closure, src_space_id, src_space_top, end_addr); + src_region_idx = next_src_region(closure, src_space_id, src_space_top, + end_addr); } while (true); } diff --git a/src/hotspot/share/gc/parallel/psParallelCompact.hpp b/src/hotspot/share/gc/parallel/psParallelCompact.hpp index 878a09e283b18..3f487ec3ef483 100644 --- a/src/hotspot/share/gc/parallel/psParallelCompact.hpp +++ b/src/hotspot/share/gc/parallel/psParallelCompact.hpp @@ -116,45 +116,51 @@ class SplitInfo // Return true if this split info is valid (i.e., if a split has been // recorded). The very first region cannot have a partial object and thus is // never split, so 0 is the 'invalid' value. - bool is_valid() const { return _split_region_idx > 0; } + bool is_valid() const { return _src_region_idx > 0; } // Return true if this split holds data for the specified source region. - inline bool is_split(size_t region_idx) const; + inline bool is_split(size_t source_region) const; - // Obj at the split point doesn't fit the previous space and will be relocated to the next space. - HeapWord* split_point() const { return _split_point; } + // The index of the split region, the size of the partial object on that + // region and the destination of the partial object. + size_t partial_obj_size() const { return _partial_obj_size; } + HeapWord* destination() const { return _destination; } - // Number of live words before the split point on this region. - size_t preceding_live_words() const { return _preceding_live_words; } + // The destination count of the partial object referenced by this split + // (either 1 or 2). This must be added to the destination count of the + // remainder of the source region. + unsigned int destination_count() const { return _destination_count; } - // A split region has two "destinations", living in two spaces. This method - // returns the first one -- destination for the first live word on - // this split region. - HeapWord* preceding_destination() const { - assert(_preceding_destination != nullptr, "inv"); - return _preceding_destination; - } + // If a word within the partial object will be written to the first word of a + // destination region, this is the address of the destination region; + // otherwise this is null. + HeapWord* dest_region_addr() const { return _dest_region_addr; } - // Number of regions the preceding live words are relocated into. - uint preceding_destination_count() const { return _preceding_destination_count; } + // If a word within the partial object will be written to the first word of a + // destination region, this is the address of that word within the partial + // object; otherwise this is null. + HeapWord* first_src_addr() const { return _first_src_addr; } - void record(size_t split_region_idx, HeapWord* split_point, size_t preceding_live_words); + // Record the data necessary to split the region src_region_idx. + void record(size_t src_region_idx, size_t partial_obj_size, + HeapWord* destination); void clear(); DEBUG_ONLY(void verify_clear();) private: - size_t _split_region_idx; - HeapWord* _split_point; - size_t _preceding_live_words; - HeapWord* _preceding_destination; - uint _preceding_destination_count; + size_t _src_region_idx; + size_t _partial_obj_size; + HeapWord* _destination; + unsigned int _destination_count; + HeapWord* _dest_region_addr; + HeapWord* _first_src_addr; }; inline bool SplitInfo::is_split(size_t region_idx) const { - return _split_region_idx == region_idx && is_valid(); + return _src_region_idx == region_idx && is_valid(); } class SpaceInfo @@ -209,18 +215,10 @@ class ParallelCompactData class RegionData { public: - // Destination for the first live word in this region. - // Therefore, the new addr for every live obj on this region can be calculated as: - // - // new_addr := _destination + live_words_offset(old_addr); - // - // where, live_words_offset is the number of live words accumulated from - // region-start to old_addr. + // Destination address of the region. HeapWord* destination() const { return _destination; } - // A destination region can have multiple source regions; only the first - // one is recorded. Since all live objs are slided down, subsequent source - // regions can be found via plain heap-region iteration. + // The first region containing data destined for this region. size_t source_region() const { return _source_region; } // Reuse _source_region to store the corresponding shadow region index @@ -315,11 +313,8 @@ class ParallelCompactData // Return to the normal path here inline void shadow_to_normal(); - int shadow_state() { return _shadow_state; } - - bool is_clear(); - void verify_clear() NOT_DEBUG_RETURN; + int shadow_state() { return _shadow_state; } private: // The type used to represent object sizes within a region. @@ -878,10 +873,7 @@ class MoveAndUpdateClosure: public StackObj { size_t words_remaining() const { return _words_remaining; } bool is_full() const { return _words_remaining == 0; } HeapWord* source() const { return _source; } - void set_source(HeapWord* addr) { - assert(addr != nullptr, "precondition"); - _source = addr; - } + void set_source(HeapWord* addr) { _source = addr; } // If the object will fit (size <= words_remaining()), copy it to the current // destination, update the interior oops and the start array. @@ -910,8 +902,9 @@ inline size_t MoveAndUpdateClosure::calculate_words_remaining(size_t region) { HeapWord* dest_addr = PSParallelCompact::summary_data().region_to_addr(region); PSParallelCompact::SpaceId dest_space_id = PSParallelCompact::space_id(dest_addr); HeapWord* new_top = PSParallelCompact::new_top(dest_space_id); - return MIN2(pointer_delta(new_top, dest_addr), - ParallelCompactData::RegionSize); + assert(dest_addr < new_top, "sanity"); + + return MIN2(pointer_delta(new_top, dest_addr), ParallelCompactData::RegionSize); } inline From b6700095c018a67a55b746cd4eee763c68f538e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eirik=20Bj=C3=B8rsn=C3=B8s?= Date: Wed, 28 Aug 2024 15:23:50 +0000 Subject: [PATCH 57/72] 8338729: Retire the test jdk/java/util/zip/TestZipError.java Reviewed-by: lancea --- test/jdk/java/util/zip/TestZipError.java | 116 ----------------------- 1 file changed, 116 deletions(-) delete mode 100644 test/jdk/java/util/zip/TestZipError.java diff --git a/test/jdk/java/util/zip/TestZipError.java b/test/jdk/java/util/zip/TestZipError.java deleted file mode 100644 index 5448add92da78..0000000000000 --- a/test/jdk/java/util/zip/TestZipError.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -/** - * @test - * @bug 4615343 - * @summary Check that ZipError is thrown instead of InternalError when - * iterating entries of an invalid zip file - */ - -import java.io.*; -import java.util.*; -import java.util.zip.*; - -public class TestZipError { - public static void realMain(String[] args) throws Throwable { - // Causing a ZipError is hard, especially on non-Windows systems. See - // comments below. - String osName = System.getProperty("os.name"); - if (!System.getProperty("os.name").startsWith("Windows")) { - return; - } - - String fileName = "error4615343.zip"; - File f = new File(fileName); - f.delete(); - ZipOutputStream zos; - ZipEntry ze; - - // Create a zip file with two entries. - zos = new ZipOutputStream(new FileOutputStream(f)); - ze = new ZipEntry("one"); - zos.putNextEntry(ze); - zos.write("hello".getBytes()); - zos.closeEntry(); - ze = new ZipEntry("two"); - zos.putNextEntry(ze); - zos.write("world".getBytes()); - zos.closeEntry(); - zos.close(); - - // Open the ZipFile. This will read the zip file's central - // directory into in-memory data structures. - ZipFile zf = new ZipFile(fileName); - - // Delete the file; of course this does not change the in-memory data - // structures that represent the central directory! - f.delete(); - - // Re-create zip file, with different entries than earlier. However, - // recall that we have in-memory information about the central - // directory of the file at its previous state. - zos = new ZipOutputStream(new FileOutputStream(f)); - ze = new ZipEntry("uno"); - zos.putNextEntry(ze); - zos.write("hola".getBytes()); - zos.closeEntry(); - zos.close(); - - // Iterate zip file's contents. On Windows, this will result in a - // ZipError, because the data in the file differs from the in-memory - // central directory information we read earlier. - Enumeration entries = zf.entries(); - try { - while (entries.hasMoreElements()) { - ze = entries.nextElement(); - zf.getInputStream(ze).readAllBytes(); - } - fail("Did not get expected exception"); - } catch (ZipException e) { - pass(); - } catch (InternalError e) { - fail("Caught InternalError instead of expected ZipError"); - } catch (Throwable t) { - unexpected(t); - } finally { - zf.close(); - f.delete(); - } - } - - //--------------------- Infrastructure --------------------------- - static volatile int passed = 0, failed = 0; - static void pass() {passed++;} - static void fail() {failed++; Thread.dumpStack();} - static void fail(String msg) {System.out.println(msg); fail();} - static void unexpected(Throwable t) {failed++; t.printStackTrace();} - static void check(boolean cond) {if (cond) pass(); else fail();} - static void equal(Object x, Object y) { - if (x == null ? y == null : x.equals(y)) pass(); - else fail(x + " not equal to " + y);} - public static void main(String[] args) throws Throwable { - try {realMain(args);} catch (Throwable t) {unexpected(t);} - System.out.println("\nPassed = " + passed + " failed = " + failed); - if (failed > 0) throw new AssertionError("Some tests failed");} -} From 379f3db001fe4bffd3a00e0363a98275e7b2eba8 Mon Sep 17 00:00:00 2001 From: "Daniel D. Daugherty" Date: Wed, 28 Aug 2024 16:47:30 +0000 Subject: [PATCH 58/72] 8339175: ProblemList runtime/interpreter/LastJsrTest.java on all platforms with Xcomp Reviewed-by: matsaave --- test/hotspot/jtreg/ProblemList-Xcomp.txt | 2 ++ test/hotspot/jtreg/ProblemList.txt | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/test/hotspot/jtreg/ProblemList-Xcomp.txt b/test/hotspot/jtreg/ProblemList-Xcomp.txt index 9d91cad1ddeae..8d6b74c8132b6 100644 --- a/test/hotspot/jtreg/ProblemList-Xcomp.txt +++ b/test/hotspot/jtreg/ProblemList-Xcomp.txt @@ -51,3 +51,5 @@ vmTestbase/nsk/jvmti/scenarios/capability/CM03/cm03t001/TestDescription.java 829 vmTestbase/nsk/stress/thread/thread006.java 8321476 linux-all gc/arguments/TestNewSizeFlags.java 8299116 macosx-aarch64 + +runtime/interpreter/LastJsrTest.java 8338924 generic-all diff --git a/test/hotspot/jtreg/ProblemList.txt b/test/hotspot/jtreg/ProblemList.txt index a991817f95184..2414f3d090a18 100644 --- a/test/hotspot/jtreg/ProblemList.txt +++ b/test/hotspot/jtreg/ProblemList.txt @@ -119,7 +119,6 @@ runtime/ErrorHandling/TestDwarf.java#checkDecoder 8305489 linux-all runtime/ErrorHandling/MachCodeFramesInErrorFile.java 8313315 linux-ppc64le runtime/Thread/TestAlwaysPreTouchStacks.java 8335167 macosx-aarch64 runtime/cds/appcds/customLoader/HelloCustom_JFR.java 8241075 linux-all,windows-x64 -runtime/interpreter/LastJsrTest.java 8338924 linux-all applications/jcstress/copy.java 8229852 linux-all From 0c2b175898d13b58ffe56e2f9cbc9d88173a61cf Mon Sep 17 00:00:00 2001 From: Anthony Scarpino Date: Wed, 28 Aug 2024 17:24:33 +0000 Subject: [PATCH 59/72] 8328608: Multiple NewSessionTicket support for TLS Reviewed-by: djelinski --- .../classes/sun/security/ssl/Finished.java | 4 +- .../sun/security/ssl/NewSessionTicket.java | 173 ++++++------ .../security/ssl/PreSharedKeyExtension.java | 6 +- .../sun/security/ssl/SSLConfiguration.java | 36 ++- .../sun/security/ssl/SSLEngineImpl.java | 11 +- .../security/ssl/SSLSessionContextImpl.java | 32 ++- .../sun/security/ssl/SSLSessionImpl.java | 33 ++- .../sun/security/ssl/SSLSocketImpl.java | 15 +- .../classes/sun/security/util/Cache.java | 241 ++++++++++++++-- .../net/ssl/SSLSession/CertMsgCheck.java | 8 +- .../ssl/SSLSession/CheckSessionContext.java | 3 +- test/jdk/javax/net/ssl/templates/TLSBase.java | 262 ++++++++---------- .../ssl/SSLSessionImpl/MultiNSTClient.java | 173 ++++++++++++ .../MultiNSTNoSessionCreation.java | 96 +++++++ .../ssl/SSLSessionImpl/MultiNSTParallel.java | 205 ++++++++++++++ .../ssl/SSLSessionImpl/MultiNSTSequence.java | 145 ++++++++++ 16 files changed, 1152 insertions(+), 291 deletions(-) create mode 100644 test/jdk/sun/security/ssl/SSLSessionImpl/MultiNSTClient.java create mode 100644 test/jdk/sun/security/ssl/SSLSessionImpl/MultiNSTNoSessionCreation.java create mode 100644 test/jdk/sun/security/ssl/SSLSessionImpl/MultiNSTParallel.java create mode 100644 test/jdk/sun/security/ssl/SSLSessionImpl/MultiNSTSequence.java diff --git a/src/java.base/share/classes/sun/security/ssl/Finished.java b/src/java.base/share/classes/sun/security/ssl/Finished.java index 604a15fe1a683..6c7c8b4514933 100644 --- a/src/java.base/share/classes/sun/security/ssl/Finished.java +++ b/src/java.base/share/classes/sun/security/ssl/Finished.java @@ -1139,7 +1139,9 @@ private void onConsumeFinished(ServerHandshakeContext shc, // // produce - NewSessionTicket.t13PosthandshakeProducer.produce(shc); + if (SSLConfiguration.serverNewSessionTicketCount > 0) { + NewSessionTicket.t13PosthandshakeProducer.produce(shc); + } } } diff --git a/src/java.base/share/classes/sun/security/ssl/NewSessionTicket.java b/src/java.base/share/classes/sun/security/ssl/NewSessionTicket.java index 0655b0e14ee15..8be021b41114c 100644 --- a/src/java.base/share/classes/sun/security/ssl/NewSessionTicket.java +++ b/src/java.base/share/classes/sun/security/ssl/NewSessionTicket.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,11 +25,11 @@ package sun.security.ssl; import java.io.IOException; -import java.math.BigInteger; import java.nio.ByteBuffer; import java.security.GeneralSecurityException; import java.security.SecureRandom; import java.text.MessageFormat; +import java.util.Arrays; import java.util.Locale; import javax.crypto.SecretKey; import javax.net.ssl.SSLHandshakeException; @@ -118,11 +118,6 @@ static final class T12NewSessionTicketMessage extends NewSessionTicketMessage { this.ticket = Record.getBytes16(m); } - @Override - public SSLHandshake handshakeType() { - return NEW_SESSION_TICKET; - } - @Override public int messageLength() { return 4 + // ticketLifetime @@ -221,11 +216,6 @@ static final class T13NewSessionTicketMessage extends NewSessionTicketMessage { this.extensions = new SSLExtensions(this, m, supportedExtensions); } - @Override - public SSLHandshake handshakeType() { - return NEW_SESSION_TICKET; - } - int getTicketAgeAdd() { return ticketAgeAdd; } @@ -301,7 +291,7 @@ private static SecretKey derivePreSharedKey(CipherSuite.HashAlg hashAlg, "tls13 resumption".getBytes(), nonce, hashAlg.hashLength); return hkdf.expand(resumptionMasterSecret, hkdfInfo, hashAlg.hashLength, "TlsPreSharedKey"); - } catch (GeneralSecurityException gse) { + } catch (GeneralSecurityException gse) { throw new SSLHandshakeException("Could not derive PSK", gse); } } @@ -332,8 +322,7 @@ public byte[] produce(ConnectionContext context) throws IOException { // Is this session resumable? if (!hc.handshakeSession.isRejoinable()) { if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { - SSLLogger.fine( - "No session ticket produced: " + + SSLLogger.fine("No session ticket produced: " + "session is not resumable"); } @@ -351,8 +340,7 @@ public byte[] produce(ConnectionContext context) throws IOException { if (pkemSpec == null || !pkemSpec.contains(PskKeyExchangeMode.PSK_DHE_KE)) { if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { - SSLLogger.fine( - "No session ticket produced: " + + SSLLogger.fine("No session ticket produced: " + "client does not support psk_dhe_ke"); } @@ -363,8 +351,7 @@ public byte[] produce(ConnectionContext context) throws IOException { // using an allowable PSK exchange key mode. if (!hc.handshakeSession.isPSKable()) { if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { - SSLLogger.fine( - "No session ticket produced: " + + SSLLogger.fine("No session ticket produced: " + "No session ticket allowed in this session"); } @@ -375,76 +362,113 @@ public byte[] produce(ConnectionContext context) throws IOException { // get a new session ID SSLSessionContextImpl sessionCache = (SSLSessionContextImpl) hc.sslContext.engineGetServerSessionContext(); - SessionId newId = new SessionId(true, - hc.sslContext.getSecureRandom()); - - SecretKey resumptionMasterSecret = - hc.handshakeSession.getResumptionMasterSecret(); - if (resumptionMasterSecret == null) { + int sessionTimeoutSeconds = sessionCache.getSessionTimeout(); + if (sessionTimeoutSeconds > MAX_TICKET_LIFETIME) { if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { - SSLLogger.fine( - "No session ticket produced: " + - "no resumption secret"); + SSLLogger.fine("No session ticket produced: " + + "session timeout is too long"); } return null; } - // construct the PSK and handshake message - BigInteger nonce = hc.handshakeSession.incrTicketNonceCounter(); - byte[] nonceArr = nonce.toByteArray(); - SecretKey psk = derivePreSharedKey( - hc.negotiatedCipherSuite.hashAlg, - resumptionMasterSecret, nonceArr); - - int sessionTimeoutSeconds = sessionCache.getSessionTimeout(); - if (sessionTimeoutSeconds > MAX_TICKET_LIFETIME) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { - SSLLogger.fine( - "No session ticket produced: " + - "session timeout"); + // Send NewSessionTickets to the client based + if (SSLConfiguration.serverNewSessionTicketCount > 0) { + int i = 0; + NewSessionTicketMessage nstm; + while (i < SSLConfiguration.serverNewSessionTicketCount) { + nstm = generateNST(hc, sessionCache); + if (nstm == null) { + break; + } + nstm.write(hc.handshakeOutput); + i++; } - return null; + hc.handshakeOutput.flush(); + } + /* + * With large NST counts, a client that quickly closes after + * TLS Finished completes can cause SocketExceptions such as: + * Windows servers read-side throwing SocketException: + * "An established connection was aborted by the software in + * your host machine", which relates to error WSAECONNABORTED. + * A SocketException caused by a "broken pipe" has been observed on + * other systems. + * These are very unlikely situations when client and server are on + * different machines. + * + * RFC 8446 does not put requirements when an NST needs to be + * sent, but it should be sent very soon after TLS Finished for + * clients that will quickly resume to create more sessions. + * TLS 1.3 is different from TLS 1.2, there is more data the client + * should be aware of + */ + + // See note on TransportContext.needHandshakeFinishedStatus. + // + // Reset the needHandshakeFinishedStatus flag. The delivery + // of this post-handshake message will indicate the FINISHED + // handshake status. It is not needed to have a follow-on + // SSLEngine.wrap() any longer. + if (hc.conContext.needHandshakeFinishedStatus) { + hc.conContext.needHandshakeFinishedStatus = false; } - NewSessionTicketMessage nstm = null; + // clean the post handshake context + hc.conContext.finishPostHandshake(); + + // The message has been delivered. + return null; + } + + private NewSessionTicketMessage generateNST(HandshakeContext hc, + SSLSessionContextImpl sessionCache) throws IOException { + + NewSessionTicketMessage nstm; + SessionId newId = new SessionId(true, + hc.sslContext.getSecureRandom()); + + // construct the PSK and handshake message + byte[] nonce = hc.handshakeSession.incrTicketNonceCounter(); SSLSessionImpl sessionCopy = - new SSLSessionImpl(hc.handshakeSession, newId); - sessionCopy.setPreSharedKey(psk); + new SSLSessionImpl(hc.handshakeSession, newId); + sessionCopy.setPreSharedKey(derivePreSharedKey( + hc.negotiatedCipherSuite.hashAlg, + hc.handshakeSession.getResumptionMasterSecret(), nonce)); sessionCopy.setPskIdentity(newId.getId()); // If a stateless ticket is allowed, attempt to make one if (hc.statelessResumption && hc.handshakeSession.isStatelessable()) { nstm = new T13NewSessionTicketMessage(hc, - sessionTimeoutSeconds, + sessionCache.getSessionTimeout(), hc.sslContext.getSecureRandom(), - nonceArr, + nonce, new SessionTicketSpec().encrypt(hc, sessionCopy)); // If ticket construction failed, switch to session cache if (!nstm.isValid()) { hc.statelessResumption = false; } else { if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { - SSLLogger.fine( - "Produced NewSessionTicket stateless " + + SSLLogger.fine("Produced NewSessionTicket stateless " + "post-handshake message", nstm); } } + return nstm; } // If a session cache ticket is being used, make one if (!hc.statelessResumption || !hc.handshakeSession.isStatelessable()) { - nstm = new T13NewSessionTicketMessage(hc, sessionTimeoutSeconds, - hc.sslContext.getSecureRandom(), nonceArr, - newId.getId()); + nstm = new T13NewSessionTicketMessage(hc, + sessionCache.getSessionTimeout(), + hc.sslContext.getSecureRandom(), nonce, + newId.getId()); if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { - SSLLogger.fine( - "Produced NewSessionTicket post-handshake message", - nstm); + SSLLogger.fine("Produced NewSessionTicket " + + "post-handshake message", nstm); } // create and cache the new session @@ -453,29 +477,13 @@ public byte[] produce(ConnectionContext context) throws IOException { hc.handshakeSession.addChild(sessionCopy); sessionCopy.setTicketAgeAdd(nstm.getTicketAgeAdd()); sessionCache.put(sessionCopy); + return nstm; } - // Output the handshake message. - if (nstm != null) { - // should never be null - nstm.write(hc.handshakeOutput); - hc.handshakeOutput.flush(); - - // See note on TransportContext.needHandshakeFinishedStatus. - // - // Reset the needHandshakeFinishedStatus flag. The delivery - // of this post-handshake message will indicate the FINISHED - // handshake status. It is not needed to have a follow-on - // SSLEngine.wrap() any longer. - if (hc.conContext.needHandshakeFinishedStatus) { - hc.conContext.needHandshakeFinishedStatus = false; - } + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine("No NewSessionTicket created"); } - // clean the post handshake context - hc.conContext.finishPostHandshake(); - - // The message has been delivered. return null; } } @@ -497,8 +505,9 @@ public byte[] produce(ConnectionContext context, ServerHandshakeContext shc = (ServerHandshakeContext)context; - // Is this session resumable? - if (!shc.handshakeSession.isRejoinable()) { + // Are new tickets allowed? If so, is this session resumable? + if (SSLConfiguration.serverNewSessionTicketCount == 0 || + !shc.handshakeSession.isRejoinable()) { return null; } @@ -578,7 +587,6 @@ public void consume(ConnectionContext context, "Discarding NewSessionTicket with lifetime " + nstm.ticketLifetime, nstm); } - sessionCache.remove(hc.handshakeSession.getSessionId()); return; } @@ -619,13 +627,19 @@ public void consume(ConnectionContext context, sessionCopy.setPreSharedKey(psk); sessionCopy.setTicketAgeAdd(nstm.getTicketAgeAdd()); sessionCopy.setPskIdentity(nstm.ticket); - sessionCache.put(sessionCopy); + sessionCache.put(sessionCopy, sessionCopy.isPSK()); + + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine("MultiNST PSK (Server): " + + Utilities.toHexString(Arrays.copyOf(nstm.ticket, 16))); + } // clean the post handshake context hc.conContext.finishPostHandshake(); } } + /* TLS 1.2 spec does not specify multiple NST behavior.*/ private static final class T12NewSessionTicketConsumer implements SSLConsumer { // Prevent instantiation of this class. @@ -674,8 +688,7 @@ public void consume(ConnectionContext context, hc.handshakeSession.setPskIdentity(nstm.ticket); if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { - SSLLogger.fine("Consuming NewSessionTicket\n" + - nstm.toString()); + SSLLogger.fine("Consuming NewSessionTicket\n" + nstm); } } } diff --git a/src/java.base/share/classes/sun/security/ssl/PreSharedKeyExtension.java b/src/java.base/share/classes/sun/security/ssl/PreSharedKeyExtension.java index 1d3ee2ece9d5a..43214841987f9 100644 --- a/src/java.base/share/classes/sun/security/ssl/PreSharedKeyExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/PreSharedKeyExtension.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -699,11 +699,13 @@ public byte[] produce(ConnectionContext context, //The session cannot be used again. Remove it from the cache. SSLSessionContextImpl sessionCache = (SSLSessionContextImpl) chc.sslContext.engineGetClientSessionContext(); - sessionCache.remove(chc.resumingSession.getSessionId()); + sessionCache.remove(chc.resumingSession.getSessionId(), true); if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Found resumable session. Preparing PSK message."); + SSLLogger.fine( + "MultiNST PSK (Client): " + Utilities.toHexString(Arrays.copyOf(chc.pskIdentity, 16))); } List identities = new ArrayList<>(); diff --git a/src/java.base/share/classes/sun/security/ssl/SSLConfiguration.java b/src/java.base/share/classes/sun/security/ssl/SSLConfiguration.java index fcf93704ef9c1..421867aa4c4b2 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLConfiguration.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -121,6 +121,11 @@ final class SSLConfiguration implements Cloneable { static final boolean enableDtlsResumeCookie = Utilities.getBooleanProperty( "jdk.tls.enableDtlsResumeCookie", true); + // Number of NewSessionTickets that will be sent by the server. + static final int serverNewSessionTicketCount; + // Default for NewSessionTickets + static final int SERVER_NST_DEFAULT = 1; + // Is the extended_master_secret extension supported? static { boolean supportExtendedMasterSecret = Utilities.getBooleanProperty( @@ -182,7 +187,7 @@ final class SSLConfiguration implements Cloneable { * - Otherwise it is set to a default value of 10. */ Integer inboundServerLen = GetIntegerAction.privilegedGetProperty( - "jdk.tls.client.maxInboundCertificateChainLength"); + "jdk.tls.client.maxInboundCertificateChainLength"); // Default for jdk.tls.client.maxInboundCertificateChainLength is 10 if (inboundServerLen == null || inboundServerLen < 0) { @@ -191,6 +196,33 @@ final class SSLConfiguration implements Cloneable { } else { maxInboundServerCertChainLen = inboundServerLen; } + + /* + * jdk.tls.server.newSessionTicketCount system property + * Sets the number of NewSessionTickets sent to a TLS 1.3 resumption + * client. The value must be between 0 and 10. Default is defined by + * SERVER_NST_DEFAULT. + */ + Integer nstServerCount = GetIntegerAction.privilegedGetProperty( + "jdk.tls.server.newSessionTicketCount"); + if (nstServerCount == null || nstServerCount < 0 || + nstServerCount > 10) { + serverNewSessionTicketCount = SERVER_NST_DEFAULT; + if (nstServerCount != null && SSLLogger.isOn && + SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "jdk.tls.server.newSessionTicketCount defaults to " + + SERVER_NST_DEFAULT + " as the property was not " + + "between 0 and 10"); + } + } else { + serverNewSessionTicketCount = nstServerCount; + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "jdk.tls.server.newSessionTicketCount set to " + + serverNewSessionTicketCount); + } + } } SSLConfiguration(SSLContextImpl sslContext, boolean isClientMode) { diff --git a/src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java index a33f8b0d13a5f..36b5bd4a78c20 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java @@ -416,11 +416,12 @@ private HandshakeStatus tryNewSessionTicket( HandshakeStatus currentHandshakeStatus) throws IOException { // Don't bother to kickstart if handshaking is in progress, or if the // connection is not duplex-open. - if ((conContext.handshakeContext == null) && - conContext.protocolVersion.useTLS13PlusSpec() && - !conContext.isOutboundClosed() && - !conContext.isInboundClosed() && - !conContext.isBroken) { + if (SSLConfiguration.serverNewSessionTicketCount > 0 && + conContext.handshakeContext == null && + conContext.protocolVersion.useTLS13PlusSpec() && + !conContext.isOutboundClosed() && + !conContext.isInboundClosed() && + !conContext.isBroken) { if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { SSLLogger.finest("trigger NST"); } diff --git a/src/java.base/share/classes/sun/security/ssl/SSLSessionContextImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLSessionContextImpl.java index eb9eb75debe29..f3e22633ab4bc 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLSessionContextImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLSessionContextImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -63,6 +63,7 @@ final class SSLSessionContextImpl implements SSLSessionContext { private static final int DEFAULT_MAX_CACHE_SIZE = 20480; + private static final int DEFAULT_MAX_QUEUE_SIZE = 10; // Default lifetime of a session. 24 hours static final int DEFAULT_SESSION_TIMEOUT = 86400; @@ -87,14 +88,17 @@ final class SSLSessionContextImpl implements SSLSessionContext { cacheLimit = getDefaults(server); // default cache size // use soft reference - sessionCache = Cache.newSoftMemoryCache(cacheLimit, timeout); - sessionHostPortCache = Cache.newSoftMemoryCache(cacheLimit, timeout); if (server) { + sessionCache = Cache.newSoftMemoryCache(cacheLimit, timeout); + sessionHostPortCache = Cache.newSoftMemoryCache(cacheLimit, timeout); keyHashMap = new ConcurrentHashMap<>(); // Should be "randomly generated" according to RFC 5077, - // but doesn't necessarily has to be a true random number. + // but doesn't necessarily have to be a true random number. currentKeyID = new Random(System.nanoTime()).nextInt(); } else { + sessionCache = Cache.newSoftMemoryCache(cacheLimit, timeout); + sessionHostPortCache = Cache.newSoftMemoryQueue(cacheLimit, timeout, + DEFAULT_MAX_QUEUE_SIZE); keyHashMap = Map.of(); } } @@ -277,12 +281,22 @@ private static String getKey(String hostname, int port) { // time it created, which is a little longer than the expected. So // please do check isTimedout() while getting entry from the cache. void put(SSLSessionImpl s) { + put(s, false); + } + + /** + * Put an entry in the cache + * @param s SSLSessionImpl entry to be stored + * @param canQueue True if multiple entries may exist under one + * session entry. + */ + void put(SSLSessionImpl s, boolean canQueue) { sessionCache.put(s.getSessionId(), s); // If no hostname/port info is available, don't add this one. if ((s.getPeerHost() != null) && (s.getPeerPort() != -1)) { sessionHostPortCache.put( - getKey(s.getPeerHost(), s.getPeerPort()), s); + getKey(s.getPeerHost(), s.getPeerPort()), s, canQueue); } s.setContext(this); @@ -290,11 +304,17 @@ void put(SSLSessionImpl s) { // package-private method, remove a cached SSLSession void remove(SessionId key) { + remove(key, false); + } + void remove(SessionId key, boolean isClient) { SSLSessionImpl s = sessionCache.get(key); if (s != null) { sessionCache.remove(key); - sessionHostPortCache.remove( + // A client keeps the cache entry for queued NST resumption. + if (!isClient) { + sessionHostPortCache.remove( getKey(s.getPeerHost(), s.getPeerPort())); + } } } diff --git a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java index a0708618e157b..b887eedd8dcfd 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -38,7 +38,6 @@ import java.util.Queue; import java.util.Collection; import java.util.Collections; -import java.util.Enumeration; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; @@ -132,7 +131,11 @@ final class SSLSessionImpl extends ExtendedSSLSession { private final List requestedServerNames; // Counter used to create unique nonces in NewSessionTicket - private BigInteger ticketNonceCounter = BigInteger.ONE; + private byte ticketNonceCounter = 1; + + // This boolean is true when a new set of NewSessionTickets are needed after + // the initial ones sent after the handshake. + boolean updateNST = false; // The endpoint identification algorithm used to check certificates // in this session. @@ -492,7 +495,7 @@ final class SSLSessionImpl extends ExtendedSSLSession { // Length of pre-shared key algorithm (one byte) i = buf.get(); b = new byte[i]; - buf.get(b, 0 , i); + buf.get(b, 0, i); String alg = new String(b); // Get length of encoding i = Short.toUnsignedInt(buf.getShort()); @@ -501,8 +504,13 @@ final class SSLSessionImpl extends ExtendedSSLSession { buf.get(b); this.preSharedKey = new SecretKeySpec(b, alg); // Get identity len - this.pskIdentity = new byte[buf.get()]; - buf.get(pskIdentity); + i = buf.get(); + if (i > 0) { + this.pskIdentity = new byte[buf.get()]; + buf.get(pskIdentity); + } else { + this.pskIdentity = null; + } break; default: throw new SSLException("Failed local certs of session."); @@ -715,14 +723,12 @@ void setPskIdentity(byte[] pskIdentity) { this.pskIdentity = pskIdentity; } - BigInteger incrTicketNonceCounter() { - BigInteger result = ticketNonceCounter; - ticketNonceCounter = ticketNonceCounter.add(BigInteger.ONE); - return result; + byte[] incrTicketNonceCounter() { + return new byte[] {ticketNonceCounter++}; } boolean isPSKable() { - return (ticketNonceCounter.compareTo(BigInteger.ZERO) > 0); + return (ticketNonceCounter > 0); } /** @@ -781,6 +787,10 @@ byte[] getPskIdentity() { return pskIdentity; } + public boolean isPSK() { + return (pskIdentity != null && pskIdentity.length > 0); + } + void setPeerCertificates(X509Certificate[] peer) { if (peerCerts == null) { peerCerts = peer; @@ -1230,7 +1240,6 @@ public void invalidate() { * sessions can be shared across different protection domains. */ private final ConcurrentHashMap boundValues; - boolean updateNST; /** * Assigns a session value. Session change events are given if diff --git a/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java index 9f22a5bb455c0..bec6d84484711 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java @@ -1321,7 +1321,6 @@ public void write(byte[] b, } // Check if NewSessionTicket PostHandshake message needs to be sent if (conContext.conSession.updateNST) { - conContext.conSession.updateNST = false; tryNewSessionTicket(); } } @@ -1556,15 +1555,17 @@ private void tryKeyUpdate() throws IOException { private void tryNewSessionTicket() throws IOException { // Don't bother to kickstart if handshaking is in progress, or if the // connection is not duplex-open. - if (!conContext.sslConfig.isClientMode && - conContext.protocolVersion.useTLS13PlusSpec() && - conContext.handshakeContext == null && - !conContext.isOutboundClosed() && - !conContext.isInboundClosed() && - !conContext.isBroken) { + if (SSLConfiguration.serverNewSessionTicketCount > 0 && + !conContext.sslConfig.isClientMode && + conContext.protocolVersion.useTLS13PlusSpec() && + conContext.handshakeContext == null && + !conContext.isOutboundClosed() && + !conContext.isInboundClosed() && + !conContext.isBroken) { if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { SSLLogger.finest("trigger new session ticket"); } + conContext.conSession.updateNST = false; NewSessionTicket.t13PosthandshakeProducer.produce( new PostHandshakeContext(conContext)); } diff --git a/src/java.base/share/classes/sun/security/util/Cache.java b/src/java.base/share/classes/sun/security/util/Cache.java index 29dd19830c0ae..3d8350c1ecd76 100644 --- a/src/java.base/share/classes/sun/security/util/Cache.java +++ b/src/java.base/share/classes/sun/security/util/Cache.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,8 +25,12 @@ package sun.security.util; +import javax.net.ssl.SSLSession; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.SoftReference; import java.util.*; -import java.lang.ref.*; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicInteger; /** * Abstract base class and factory for caches. A cache is a key-value mapping. @@ -90,6 +94,15 @@ protected Cache() { */ public abstract void put(K key, V value); + /** + * Add V to the cache with the option to use a QueueCacheEntry if the + * cache is configured for it. If the cache is not configured for a queue, + * V will silently add the entry directly. + */ + public void put(K key, V value, boolean canQueue) { + put(key, value); + } + /** * Get a value from the cache. */ @@ -137,6 +150,11 @@ public static Cache newSoftMemoryCache(int size, int timeout) { return new MemoryCache<>(true, size, timeout); } + public static Cache newSoftMemoryQueue(int size, int timeout, + int maxQueueSize) { + return new MemoryCache<>(true, size, timeout, maxQueueSize); + } + /** * Return a new memory cache with the specified maximum size, unlimited * lifetime for entries, with the values held by standard references. @@ -248,13 +266,12 @@ public void accept(CacheVisitor visitor) { class MemoryCache extends Cache { - private static final float LOAD_FACTOR = 0.75f; - - // XXXX + // Debugging private static final boolean DEBUG = false; private final Map> cacheMap; private int maxSize; + final private int maxQueueSize; private long lifetime; private long nextExpirationTime = Long.MAX_VALUE; @@ -263,18 +280,25 @@ class MemoryCache extends Cache { private final ReferenceQueue queue; public MemoryCache(boolean soft, int maxSize) { - this(soft, maxSize, 0); + this(soft, maxSize, 0, 0); } public MemoryCache(boolean soft, int maxSize, int lifetime) { + this(soft, maxSize, lifetime, 0); + } + + public MemoryCache(boolean soft, int maxSize, int lifetime, int qSize) { this.maxSize = maxSize; + this.maxQueueSize = qSize; this.lifetime = lifetime * 1000L; - if (soft) + if (soft) { this.queue = new ReferenceQueue<>(); - else + } else { this.queue = null; - - cacheMap = new LinkedHashMap<>(1, LOAD_FACTOR, true); + } + // LinkedHashMap is needed for its access order. 0.75f load factor is + // default. + cacheMap = new LinkedHashMap<>(1, 0.75f, true); } /** @@ -338,6 +362,10 @@ private void expungeExpiredEntries() { cnt++; } else if (nextExpirationTime > entry.getExpirationTime()) { nextExpirationTime = entry.getExpirationTime(); + // If this is a queue, check for some expired entries + if (entry instanceof QueueCacheEntry qe) { + qe.getQueue().removeIf(e -> !e.isValid(time)); + } } } if (DEBUG) { @@ -367,18 +395,60 @@ public synchronized void clear() { cacheMap.clear(); } - public synchronized void put(K key, V value) { + public void put(K key, V value) { + put(key, value, false); + } + + /** + * This puts an entry into the cacheMap. + * + * If canQueue is true, V will be added using a QueueCacheEntry which + * is added to cacheMap. If false, V is added to the cacheMap directly. + * The caller must keep a consistent canQueue value, mixing them can + * result in a queue being replaced with a single entry. + * + * This method is synchronized to avoid multiple QueueCacheEntry + * overwriting the same key. + * + * @param key key to the cacheMap + * @param value value to be stored + * @param canQueue can the value be put into a QueueCacheEntry + */ + public synchronized void put(K key, V value, boolean canQueue) { emptyQueue(); - long expirationTime = (lifetime == 0) ? 0 : - System.currentTimeMillis() + lifetime; + long expirationTime = + (lifetime == 0) ? 0 : System.currentTimeMillis() + lifetime; if (expirationTime < nextExpirationTime) { nextExpirationTime = expirationTime; } CacheEntry newEntry = newEntry(key, value, expirationTime, queue); - CacheEntry oldEntry = cacheMap.put(key, newEntry); - if (oldEntry != null) { - oldEntry.invalidate(); - return; + if (maxQueueSize == 0 || !canQueue) { + CacheEntry oldEntry = cacheMap.put(key, newEntry); + if (oldEntry != null) { + oldEntry.invalidate(); + } + } else { + CacheEntry entry = cacheMap.get(key); + switch (entry) { + case QueueCacheEntry qe -> { + qe.putValue(newEntry); + if (DEBUG) { + System.out.println("QueueCacheEntry= " + qe); + final AtomicInteger i = new AtomicInteger(1); + qe.queue.stream().forEach(e -> + System.out.println(i.getAndIncrement() + "= " + e)); + } + } + case null, default -> + cacheMap.put(key, new QueueCacheEntry<>(key, newEntry, + expirationTime, maxQueueSize)); + } + + if (DEBUG) { + System.out.println("Cache entry added: key=" + + key.toString() + ", class=" + + (entry != null ? entry.getClass().getName() : null)); + } } if (maxSize > 0 && cacheMap.size() > maxSize) { expungeExpiredEntries(); @@ -401,25 +471,37 @@ public synchronized V get(Object key) { if (entry == null) { return null; } - long time = (lifetime == 0) ? 0 : System.currentTimeMillis(); - if (!entry.isValid(time)) { + + if (lifetime > 0 && !entry.isValid(System.currentTimeMillis())) { + cacheMap.remove(key); if (DEBUG) { System.out.println("Ignoring expired entry"); } - cacheMap.remove(key); return null; } + + // If the value is a queue, return a queue entry. + if (entry instanceof QueueCacheEntry qe) { + V result = qe.getValue(lifetime); + if (qe.isEmpty()) { + removeImpl(key); + } + return result; + } return entry.getValue(); } public synchronized void remove(Object key) { emptyQueue(); + removeImpl(key); + } + + private void removeImpl(Object key) { CacheEntry entry = cacheMap.remove(key); if (entry != null) { entry.invalidate(); } } - public synchronized V pull(Object key) { emptyQueue(); CacheEntry entry = cacheMap.remove(key); @@ -550,9 +632,8 @@ public void invalidate() { } } - private static class SoftCacheEntry - extends SoftReference - implements CacheEntry { + private static class SoftCacheEntry extends SoftReference + implements CacheEntry { private K key; private long expirationTime; @@ -589,6 +670,116 @@ public void invalidate() { key = null; expirationTime = -1; } + + @Override + public String toString() { + if (get() instanceof SSLSession se) + return HexFormat.of().formatHex(se.getId()); + return super.toString(); + } } -} + /** + * This CacheEntry type allows multiple V entries to be stored in + * one key in the cacheMap. + * + * This implementation is need for TLS clients that receive multiple + * PSKs or NewSessionTickets for server resumption. + */ + private static class QueueCacheEntry implements CacheEntry { + + // Limit the number of queue entries. + private final int MAXQUEUESIZE; + + final boolean DEBUG = false; + private K key; + private long expirationTime; + final Queue> queue = new ConcurrentLinkedQueue<>(); + + QueueCacheEntry(K key, CacheEntry entry, long expirationTime, + int maxSize) { + this.key = key; + this.expirationTime = expirationTime; + this.MAXQUEUESIZE = maxSize; + queue.add(entry); + } + + public K getKey() { + return key; + } + + public V getValue() { + return getValue(0); + } + + public V getValue(long lifetime) { + long time = (lifetime == 0) ? 0 : System.currentTimeMillis(); + do { + var entry = queue.poll(); + if (entry == null) { + return null; + } + if (entry.isValid(time)) { + return entry.getValue(); + } + entry.invalidate(); + } while (!queue.isEmpty()); + + return null; + } + + public long getExpirationTime() { + return expirationTime; + } + + public void setExpirationTime(long time) { + expirationTime = time; + } + + public void putValue(CacheEntry entry) { + if (DEBUG) { + System.out.println("Added to queue (size=" + queue.size() + + "): " + entry.getKey().toString() + ", " + entry); + } + // Update the cache entry's expiration time to the latest entry. + // The getValue() calls will remove expired tickets. + expirationTime = entry.getExpirationTime(); + // Limit the number of queue entries, removing the oldest. + if (queue.size() >= MAXQUEUESIZE) { + queue.remove(); + } + queue.add(entry); + } + + public boolean isValid(long currentTime) { + boolean valid = (currentTime <= expirationTime) && !queue.isEmpty(); + if (!valid) { + invalidate(); + } + return valid; + } + + public boolean isValid() { + return isValid(System.currentTimeMillis()); + } + + public void invalidate() { + clear(); + key = null; + expirationTime = -1; + } + + public void clear() { + queue.forEach(CacheEntry::invalidate); + queue.clear(); + } + + public boolean isEmpty() { + return queue.isEmpty(); + } + + public Queue> getQueue() { + return queue; + } + } +} \ No newline at end of file diff --git a/test/jdk/javax/net/ssl/SSLSession/CertMsgCheck.java b/test/jdk/javax/net/ssl/SSLSession/CertMsgCheck.java index 498c17672b213..2131b314ce0aa 100644 --- a/test/jdk/javax/net/ssl/SSLSession/CertMsgCheck.java +++ b/test/jdk/javax/net/ssl/SSLSession/CertMsgCheck.java @@ -39,10 +39,12 @@ public static void main(String[] args) throws Exception { build(); // Initial client session - TLSBase.Client client1 = new TLSBase.Client(true, false); + TLSBase.Client client = new TLSBase.Client(true, false); + client.connect(); - server.getSession(client1).getSessionContext(); - server.done(); + // Close must be called to gather all the exceptions thrown + client.close(); + server.close(); var eList = server.getExceptionList(); System.out.println("Exception list size is " + eList.size()); diff --git a/test/jdk/javax/net/ssl/SSLSession/CheckSessionContext.java b/test/jdk/javax/net/ssl/SSLSession/CheckSessionContext.java index 183354ed9db22..a08414b03fd46 100644 --- a/test/jdk/javax/net/ssl/SSLSession/CheckSessionContext.java +++ b/test/jdk/javax/net/ssl/SSLSession/CheckSessionContext.java @@ -47,6 +47,7 @@ public static void main(String[] args) throws Exception { // Initial client session TLSBase.Client client1 = new TLSBase.Client(); + client1.connect(); if (server.getSession(client1).getSessionContext() == null) { throw new Exception("Context was null. Handshake failure."); } else { @@ -66,6 +67,7 @@ public static void main(String[] args) throws Exception { // Resume the client session TLSBase.Client client2 = new TLSBase.Client(); + client2.connect(); if (server.getSession(client2).getSessionContext() == null) { throw new Exception("Context was null on resumption"); } else { @@ -73,6 +75,5 @@ public static void main(String[] args) throws Exception { } server.close(client2); client2.close(); - server.done(); } } diff --git a/test/jdk/javax/net/ssl/templates/TLSBase.java b/test/jdk/javax/net/ssl/templates/TLSBase.java index 5c95253e6f024..8ec4e4e3db46f 100644 --- a/test/jdk/javax/net/ssl/templates/TLSBase.java +++ b/test/jdk/javax/net/ssl/templates/TLSBase.java @@ -25,13 +25,16 @@ import java.io.*; import java.net.InetAddress; import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; import java.security.KeyStore; import java.security.cert.PKIXBuilderParameters; import java.security.cert.X509CertSelector; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; /** * This is a base setup for creating a server and clients. All clients will @@ -39,7 +42,7 @@ * first. The idea is for the test code to be minimal as possible without * this library class being complicated. * - * Server.done() must be called or the server will never exit and hang the test. + * Server.close() must be called so the server will exit and end threading. * * After construction, reading and writing are allowed from either side, * or a combination write/read from both sides for verifying text. @@ -52,24 +55,25 @@ */ abstract public class TLSBase { - static String pathToStores = "../etc"; + static String pathToStores = "javax/net/ssl/etc"; static String keyStoreFile = "keystore"; static String trustStoreFile = "truststore"; static String passwd = "passphrase"; + static final String TESTROOT = + System.getProperty("test.root", "../../../.."); + SSLContext sslContext; // Server's port static int serverPort; // Name shown during read and write ops - String name; + public String name; TLSBase() { - String keyFilename = - System.getProperty("test.src", "./") + "/" + pathToStores + - "/" + keyStoreFile; - String trustFilename = - System.getProperty("test.src", "./") + "/" + pathToStores + - "/" + trustStoreFile; + + String keyFilename = TESTROOT + "/" + pathToStores + "/" + keyStoreFile; + String trustFilename = TESTROOT + "/" + pathToStores + "/" + + trustStoreFile; System.setProperty("javax.net.ssl.keyStore", keyFilename); System.setProperty("javax.net.ssl.keyStorePassword", passwd); System.setProperty("javax.net.ssl.trustStore", trustFilename); @@ -78,26 +82,22 @@ abstract public class TLSBase { // Base read operation byte[] read(SSLSocket sock) throws Exception { - BufferedReader reader = new BufferedReader( - new InputStreamReader(sock.getInputStream())); - String s = reader.readLine(); - System.err.println("(read) " + name + ": " + s); - return s.getBytes(); + BufferedInputStream is = new BufferedInputStream(sock.getInputStream()); + byte[] b = is.readNBytes(5); + System.err.println("(read) " + Thread.currentThread().getName() + ": " + new String(b)); + return b; } // Base write operation public void write(SSLSocket sock, byte[] data) throws Exception { - PrintWriter out = new PrintWriter( - new OutputStreamWriter(sock.getOutputStream())); - out.println(new String(data)); - out.flush(); - System.err.println("(write)" + name + ": " + new String(data)); + sock.getOutputStream().write(data); + System.err.println("(write)" + Thread.currentThread().getName() + ": " + new String(data)); } private static KeyManager[] getKeyManager(boolean empty) throws Exception { FileInputStream fis = null; if (!empty) { - fis = new FileInputStream(System.getProperty("test.src", "./") + + fis = new FileInputStream(System.getProperty("test.root", "./") + "/" + pathToStores + "/" + keyStoreFile); } // Load the keystore @@ -113,7 +113,7 @@ private static KeyManager[] getKeyManager(boolean empty) throws Exception { private static TrustManager[] getTrustManager(boolean empty) throws Exception { FileInputStream fis = null; if (!empty) { - fis = new FileInputStream(System.getProperty("test.src", "./") + + fis = new FileInputStream(System.getProperty("test.root", "./") + "/" + pathToStores + "/" + trustStoreFile); } // Load the keystore @@ -150,6 +150,11 @@ static class Server extends TLSBase { new ConcurrentHashMap<>(); Thread t; List exceptionList = new ArrayList<>(); + ExecutorService threadPool = Executors.newFixedThreadPool(1, + r -> { + Thread t = Executors.defaultThreadFactory().newThread(r); + return t; + }); Server(ServerBuilder builder) { super(); @@ -160,8 +165,10 @@ static class Server extends TLSBase { TLSBase.getTrustManager(builder.tm), null); fac = sslContext.getServerSocketFactory(); ssock = (SSLServerSocket) fac.createServerSocket(0); + ssock.setReuseAddress(true); ssock.setNeedClientAuth(builder.clientauth); serverPort = ssock.getLocalPort(); + System.out.println("Server Port: " + serverPort); } catch (Exception e) { System.err.println("Failure during server initialization"); e.printStackTrace(); @@ -171,117 +178,67 @@ static class Server extends TLSBase { t = new Thread(() -> { try { while (true) { - System.err.println("Server ready on port " + - serverPort); - SSLSocket c = (SSLSocket)ssock.accept(); - clientMap.put(c.getPort(), c); - try { - write(c, read(c)); - } catch (Exception e) { - System.out.println("Caught " + e.getMessage()); - e.printStackTrace(); - exceptionList.add(e); - } + SSLSocket sock = (SSLSocket)ssock.accept(); + threadPool.submit(new ServerThread(sock)); } } catch (Exception ex) { System.err.println("Server Down"); ex.printStackTrace(); + } finally { + threadPool.close(); } }); t.start(); } - Server() { - this(new ServerBuilder()); - } + class ServerThread extends Thread { + SSLSocket sock; - /** - * @param km - true for an empty key manager - * @param tm - true for an empty trust manager - */ - Server(boolean km, boolean tm) { - super(); - name = "server"; - try { - sslContext = SSLContext.getInstance("TLS"); - sslContext.init(TLSBase.getKeyManager(km), - TLSBase.getTrustManager(tm), null); - fac = sslContext.getServerSocketFactory(); - ssock = (SSLServerSocket) fac.createServerSocket(0); - ssock.setNeedClientAuth(true); - serverPort = ssock.getLocalPort(); - } catch (Exception e) { - System.err.println("Failure during server initialization"); - e.printStackTrace(); - } - - // Thread to allow multiple clients to connect - t = new Thread(() -> { - try { - while (true) { - System.err.println("Server ready on port " + - serverPort); - SSLSocket c = (SSLSocket)ssock.accept(); - clientMap.put(c.getPort(), c); - try { - write(c, read(c)); - } catch (Exception e) { - System.out.println("Caught " + e.getMessage()); - e.printStackTrace(); - exceptionList.add(e); - } - } - } catch (Exception ex) { - System.err.println("Server Down"); - ex.printStackTrace(); - } - }); - t.start(); + ServerThread(SSLSocket s) { + this.sock = s; + System.err.println("ServerThread("+sock.getPort()+")"); + clientMap.put(sock.getPort(), sock); } - // Exit test to quit the test. This must be called at the end of the - // test or the test will never end. - void done() { - try { - t.join(5000); - ssock.close(); - } catch (Exception e) { - System.err.println(e.getMessage()); - e.printStackTrace(); - } - } - - // Read from the client - byte[] read(Client client) throws Exception { - SSLSocket s = clientMap.get(Integer.valueOf(client.getPort())); - if (s == null) { - System.err.println("No socket found, port " + client.getPort()); + public void run() { + try { + write(sock, read(sock)); + } catch (Exception e) { + System.out.println("Caught " + e.getMessage()); + e.printStackTrace(); + exceptionList.add(e); + } } - return read(s); } - // Write to the client - void write(Client client, byte[] data) throws Exception { - write(clientMap.get(client.getPort()), data); + Server() { + this(new ServerBuilder()); } - // Server writes to the client, then reads from the client. - // Return true if the read & write data match, false if not. - boolean writeRead(Client client, String s) throws Exception{ - write(client, s.getBytes()); - return (Arrays.compare(s.getBytes(), client.read()) == 0); + public SSLSession getSession(Client client) throws Exception { + System.err.println("getSession("+client.getPort()+")"); + SSLSocket clientSocket = clientMap.get(client.getPort()); + if (clientSocket == null) { + throw new Exception("Server can't find client socket"); + } + return clientSocket.getSession(); } - // Get the SSLSession from the server side socket - SSLSession getSession(Client c) { - SSLSocket s = clientMap.get(Integer.valueOf(c.getPort())); - return s.getSession(); + void close(Client client) { + try { + System.err.println("close("+client.getPort()+")"); + clientMap.remove(client.getPort()).close(); + } catch (Exception e) { + ; + } } - - // Close client socket - void close(Client c) throws IOException { - SSLSocket s = clientMap.get(Integer.valueOf(c.getPort())); - s.close(); + void close() throws InterruptedException { + clientMap.values().stream().forEach(s -> { + try { + s.close(); + } catch (IOException e) {} + }); + threadPool.awaitTermination(500, TimeUnit.MILLISECONDS); } List getExceptionList() { @@ -312,11 +269,11 @@ Server build() { } } /** - * Client side will establish a connection from the constructor and wait. + * Client side will establish a SSLContext instance. * It must be run after the Server constructor is called. */ static class Client extends TLSBase { - SSLSocket sock; + public SSLSocket socket; boolean km, tm; Client() { this(false, false); @@ -330,55 +287,66 @@ static class Client extends TLSBase { super(); this.km = km; this.tm = tm; - connect(); - } - - // Connect to server. Maybe runnable in the future - public SSLSocket connect() { try { sslContext = SSLContext.getInstance("TLS"); sslContext.init(TLSBase.getKeyManager(km), TLSBase.getTrustManager(tm), null); - sock = (SSLSocket)sslContext.getSocketFactory().createSocket(); - sock.connect(new InetSocketAddress(InetAddress.getLoopbackAddress(), serverPort)); - System.err.println("Client connected using port " + - sock.getLocalPort()); - name = "client(" + sock.toString() + ")"; - write("Hello"); - read(); + socket = createSocket(); } catch (Exception ex) { ex.printStackTrace(); } - return sock; } - // Read from the client socket - byte[] read() throws Exception { - return read(sock); + Client(Client cl) { + sslContext = cl.sslContext; + socket = createSocket(); } - // Write to the client socket - void write(byte[] data) throws Exception { - write(sock, data); + public SSLSocket createSocket() { + try { + return (SSLSocket) sslContext.getSocketFactory().createSocket(); + } catch (Exception ex) { + ex.printStackTrace(); + } + return null; } - void write(String s) throws Exception { - write(sock, s.getBytes()); + + public SSLSocket connect() { + try { + socket.connect(new InetSocketAddress(InetAddress.getLoopbackAddress(), serverPort)); + System.err.println("Client (" + Thread.currentThread().getName() + ") connected using port " + + socket.getLocalPort() + " to " + socket.getPort()); + writeRead(); + } catch (Exception ex) { + ex.printStackTrace(); + return null; + } + return socket; } - // Client writes to the server, then reads from the server. - // Return true if the read & write data match, false if not. - boolean writeRead(Server server, String s) throws Exception { - write(s.getBytes()); - return (Arrays.compare(s.getBytes(), server.read(this)) == 0); + public SSLSession getSession() { + return socket.getSession(); + } + public void close() { + try { + socket.close(); + } catch (Exception ex) { + ex.printStackTrace(); + } } - // Get port from the socket - int getPort() { - return sock.getLocalPort(); + public int getPort() { + return socket.getLocalPort(); } - // Close socket - void close() throws IOException { - sock.close(); + private SSLSocket writeRead() { + try { + write(socket, "Hello".getBytes(StandardCharsets.ISO_8859_1)); + read(socket); + } catch (Exception ex) { + ex.printStackTrace(); + } + return socket; } + } } diff --git a/test/jdk/sun/security/ssl/SSLSessionImpl/MultiNSTClient.java b/test/jdk/sun/security/ssl/SSLSessionImpl/MultiNSTClient.java new file mode 100644 index 0000000000000..57f61a6cd8a64 --- /dev/null +++ b/test/jdk/sun/security/ssl/SSLSessionImpl/MultiNSTClient.java @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @library /test/lib + * @library /javax/net/ssl/templates + * @bug 8242008 + * @summary Verifies multiple PSKs are used by JSSE + * @run main/othervm MultiNSTClient -Djdk.tls.client.protocols=TLSv1.3 -Djdk.tls.server.newSessionTicketCount=1 + * @run main/othervm MultiNSTClient -Djdk.tls.client.protocols=TLSv1.3 -Djdk.tls.server.newSessionTicketCount=3 + * @run main/othervm MultiNSTClient -Djdk.tls.client.protocols=TLSv1.3 -Djdk.tls.server.newSessionTicketCount=10 + * @run main/othervm MultiNSTClient -Djdk.tls.client.protocols=TLSv1.3 -Djdk.tls.server.enableSessionTicketExtension=true -Djdk.tls.client.enableSessionTicketExtension=true + * @run main/othervm MultiNSTClient -Djdk.tls.client.protocols=TLSv1.3 -Djdk.tls.server.enableSessionTicketExtension=false -Djdk.tls.client.enableSessionTicketExtension=true + * @run main/othervm MultiNSTClient -Djdk.tls.client.protocols=TLSv1.3 -Djdk.tls.server.enableSessionTicketExtension=true -Djdk.tls.client.enableSessionTicketExtension=false + * @run main/othervm MultiNSTClient -Djdk.tls.client.protocols=TLSv1.3 -Djdk.tls.server.enableSessionTicketExtension=false -Djdk.tls.client.enableSessionTicketExtension=false + * @run main/othervm MultiNSTClient -Djdk.tls.client.protocols=TLSv1.2 -Djdk.tls.server.enableSessionTicketExtension=true -Djdk.tls.client.enableSessionTicketExtension=true + */ + +import jdk.test.lib.Utils; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +import javax.net.ssl.SSLSession; +import java.util.Arrays; +import java.util.HexFormat; +import java.util.List; + +/** + * This test verifies that multiple NSTs and PSKs are sent by a JSSE server. + * Then JSSE client is able to store them all and resume the connection. It + * requires specific text in the TLS debugging to verify the success. + */ + +public class MultiNSTClient { + + static HexFormat hex = HexFormat.of(); + + public static void main(String[] args) throws Exception { + + if (!args[0].equalsIgnoreCase("p")) { + StringBuilder sb = new StringBuilder(); + Arrays.stream(args).forEach(a -> { + sb.append(a); + sb.append(" "); + }); + String params = sb.toString(); + System.setProperty("test.java.opts", + "-Dtest.src=" + System.getProperty("test.src") + + " -Dtest.jdk=" + System.getProperty("test.jdk") + + " -Dtest.root=" + System.getProperty("test.root") + + " -Djavax.net.debug=ssl,handshake " + params + ); + + boolean TLS13 = args[0].contains("1.3"); + + System.out.println("test.java.opts: " + + System.getProperty("test.java.opts")); + + ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder( + Utils.addTestJavaOpts("MultiNSTClient", "p")); + + OutputAnalyzer output = ProcessTools.executeProcess(pb); + System.out.println("I'm here"); + boolean pass = true; + try { + List list = output.stderrShouldContain("MultiNST PSK"). + asLines().stream().filter(s -> + s.contains("MultiNST PSK")).toList(); + List serverPSK = list.stream().filter(s -> + s.contains("MultiNST PSK (Server)")).toList(); + List clientPSK = list.stream().filter(s -> + s.contains("MultiNST PSK (Client)")).toList(); + System.out.println("found list: " + list.size()); + System.out.println("found server: " + serverPSK.size()); + serverPSK.stream().forEach(s -> System.out.println("\t" + s)); + System.out.println("found client: " + clientPSK.size()); + clientPSK.stream().forEach(s -> System.out.println("\t" + s)); + for (int i = 0; i < 2; i++) { + String svr = serverPSK.getFirst(); + String cli = clientPSK.getFirst(); + if (svr.regionMatches(svr.length() - 16, cli, cli.length() - 16, 16)) { + System.out.println("entry " + (i + 1) + " match."); + } else { + System.out.println("entry " + (i + 1) + " server and client PSK didn't match:"); + System.out.println(" server: " + svr); + System.out.println(" client: " + cli); + pass = false; + } + } + } catch (RuntimeException e) { + System.out.println("No MultiNST PSK found."); + pass = false; + } + + if (TLS13) { + if (!pass) { + throw new Exception("Test failed: " + params); + } + } else { + if (pass) { + throw new Exception("Test failed: " + params); + } + } + System.out.println("Test Passed"); + return; + } + + TLSBase.Server server = new TLSBase.Server(); + + System.out.println("------ Start connection"); + TLSBase.Client initial = new TLSBase.Client(); + SSLSession initialSession = initial.connect().getSession(); + System.out.println("id = " + hex.formatHex(initialSession.getId())); + System.out.println("session = " + initialSession); + + System.out.println("------ getNewSession from original client"); + TLSBase.Client resumClient = new TLSBase.Client(initial); + SSLSession resumption = resumClient.connect().getSession(); + System.out.println("id = " + hex.formatHex(resumption.getId())); + System.out.println("session = " + resumption); + if (!initialSession.toString().equalsIgnoreCase(resumption.toString())) { + throw new Exception("Resumed session did not match"); + } + + System.out.println("------ Second getNewSession from original client"); + TLSBase.Client resumClient2 = new TLSBase.Client(initial); + resumption = resumClient2.connect().getSession(); + System.out.println("id = " + hex.formatHex(resumption.getId())); + System.out.println("session = " + resumption); + if (!initialSession.toString().equalsIgnoreCase(resumption.toString())) { + throw new Exception("Resumed session did not match"); + } + + System.out.println("------ New client connection"); + TLSBase.Client newConnection = new TLSBase.Client(); + SSLSession newSession = newConnection.connect().getSession(); + System.out.println("id = " + hex.formatHex(newSession.getId())); + System.out.println("session = " + newSession); + if (initialSession.toString().equalsIgnoreCase(newSession.toString())) { + throw new Exception("new session is the same as the initial."); + } + + System.out.println("------ Closing connections"); + initial.close(); + resumClient.close(); + resumClient2.close(); + newConnection.close(); + server.close(); + System.out.println("------ End"); + System.exit(0); + } +} diff --git a/test/jdk/sun/security/ssl/SSLSessionImpl/MultiNSTNoSessionCreation.java b/test/jdk/sun/security/ssl/SSLSessionImpl/MultiNSTNoSessionCreation.java new file mode 100644 index 0000000000000..f80270afd37dd --- /dev/null +++ b/test/jdk/sun/security/ssl/SSLSessionImpl/MultiNSTNoSessionCreation.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @library /test/lib + * @library /javax/net/ssl/templates + * @bug 8242008 + * @summary Verifies resumption fails with 0 NSTs and session creation off + * @run main/othervm MultiNSTNoSessionCreation -Djdk.tls.client.protocols=TLSv1.3 -Djdk.tls.server.newSessionTicketCount=0 + * @run main/othervm MultiNSTNoSessionCreation -Djdk.tls.client.protocols=TLSv1.2 -Djdk.tls.server.newSessionTicketCount=0 + */ + +import jdk.test.lib.Utils; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +import java.util.Arrays; + +/** + * With no NSTs sent by the server, try to resume the session with + * setEnabledSessionCreation(false). The test should get an exception and + * fail to connect. + */ + +public class MultiNSTNoSessionCreation { + + public static void main(String[] args) throws Exception { + + if (!args[0].equalsIgnoreCase("p")) { + StringBuilder sb = new StringBuilder(); + Arrays.stream(args).forEach(a -> sb.append(a).append(" ")); + String params = sb.toString(); + System.setProperty("test.java.opts", + "-Dtest.src=" + System.getProperty("test.src") + + " -Dtest.jdk=" + System.getProperty("test.jdk") + + " -Dtest.root=" + System.getProperty("test.root") + + " -Djavax.net.debug=ssl,handshake " + params); + + System.out.println("test.java.opts: " + + System.getProperty("test.java.opts")); + + ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder( + Utils.addTestJavaOpts("MultiNSTNoSessionCreation", "p")); + + OutputAnalyzer output = ProcessTools.executeProcess(pb); + try { + if (output.stderrContains( + "(PROTOCOL_VERSION): New session creation is disabled")) { + return; + } + } catch (RuntimeException e) { + throw new Exception("Error collecting data", e); + } + throw new Exception("Disabled creation msg not found"); + } + + TLSBase.Server server = new TLSBase.Server(); + + System.out.println("------ Initial connection"); + TLSBase.Client initial = new TLSBase.Client(); + initial.connect(); + System.out.println( + "------ Resume client w/ setEnableSessionCreation set to false"); + TLSBase.Client resumClient = new TLSBase.Client(initial); + resumClient.socket.setEnableSessionCreation(false); + resumClient.connect(); + + System.out.println("------ Closing connections"); + initial.close(); + resumClient.close(); + server.close(); + System.out.println("------ End"); + System.exit(0); + } +} diff --git a/test/jdk/sun/security/ssl/SSLSessionImpl/MultiNSTParallel.java b/test/jdk/sun/security/ssl/SSLSessionImpl/MultiNSTParallel.java new file mode 100644 index 0000000000000..bff14113ea582 --- /dev/null +++ b/test/jdk/sun/security/ssl/SSLSessionImpl/MultiNSTParallel.java @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @library /test/lib + * @library /javax/net/ssl/templates + * @bug 8242008 + * @summary Verifies multiple PSKs are used by TLSv1.3 + * @run main/othervm MultiNSTParallel 10 -Djdk.tls.client.protocols=TLSv1.3 + */ + +import jdk.test.lib.Utils; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +import javax.net.ssl.SSLSession; +import java.util.ArrayList; +import java.util.HexFormat; +import java.util.List; +import java.util.concurrent.CountDownLatch; + +/** + * This test verifies that parallel resumption connections successfully get + * a PSK entry and not initiate a full handshake. + * + * Note: THe first argument after 'MultiNSTParallel' is the ticket count + * The test will set 'jdk.tls.server.NewSessionTicketCount` to that number and + * will start the same number of resumption client attempts. The ticket count + * must be the same or larger than resumption attempts otherwise the queue runs + * empty and the test will fail. + * + * Because this test runs parallel connections, the thread order finish is not + * guaranteed. Each client NST id is checked with all server NSTs ids until + * a match is found. When a match is found, it is removed from the list to + * verify no NST was used more than once. + * + * TLS 1.2 spec does not specify multiple NST behavior. + */ + +public class MultiNSTParallel { + + static HexFormat hex = HexFormat.of(); + final static CountDownLatch wait = new CountDownLatch(1); + + static class ClientThread extends Thread { + TLSBase.Client client; + + ClientThread(TLSBase.Client c) { + client = c; + } + + public void run() { + String name = Thread.currentThread().getName(); + SSLSession r; + System.err.println("waiting " + Thread.currentThread().getName()); + try { + wait.await(); + r = new TLSBase.Client(client).connect().getSession(); + } catch (Exception e) { + throw new RuntimeException(name + ": " +e); + } + StringBuffer sb = new StringBuffer(100); + sb.append("(").append(name).append(") id = "); + sb.append(hex.formatHex(r.getId())); + sb.append("\n(").append(name).append(") session = ").append(r); + if (!client.getSession().toString().equalsIgnoreCase(r.toString())) { + throw new RuntimeException("(" + name + + ") Resumed session did not match"); + } + } + } + + static boolean pass = true; + + public static void main(String[] args) throws Exception { + + if (!args[0].equalsIgnoreCase("p")) { + int ticketCount = Integer.parseInt(args[0]); + StringBuilder sb = new StringBuilder(); + for (int i = 1; i < args.length; i++) { + sb.append(" ").append(args[i]); + } + String params = sb.toString(); + System.setProperty("test.java.opts", + "-Dtest.src=" + System.getProperty("test.src") + + " -Dtest.jdk=" + System.getProperty("test.jdk") + + " -Dtest.root=" + System.getProperty("test.root") + + " -Djavax.net.debug=ssl,handshake " + + " -Djdk.tls.server.newSessionTicketCount=" + ticketCount + + params); + + boolean TLS13 = args[1].contains("1.3"); + + System.out.println("test.java.opts: " + + System.getProperty("test.java.opts")); + + ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder( + Utils.addTestJavaOpts("MultiNSTParallel", "p")); + + OutputAnalyzer output = ProcessTools.executeProcess(pb); + try { + List list = output.stderrShouldContain("MultiNST PSK"). + asLines().stream().filter(s -> + s.contains("MultiNST PSK")).toList(); + List sp = list.stream().filter(s -> + s.contains("MultiNST PSK (Server)")).toList(); + List serverPSK = new ArrayList<>(sp.stream().toList()); + List clientPSK = list.stream().filter(s -> + s.contains("MultiNST PSK (Client)")).toList(); + System.out.println("found list: " + list.size()); + System.out.println("found server: " + serverPSK.size()); + serverPSK.stream().forEach(s -> System.out.println("\t" + s)); + System.out.println("found client: " + clientPSK.size()); + clientPSK.stream().forEach(s -> System.out.println("\t" + s)); + + // Must search all results as order is not guaranteed. + clientPSK.stream().forEach(cli -> { + for (int i = 0; i < serverPSK.size(); i++) { + String svr = serverPSK.get(i); + if (svr.regionMatches(svr.length() - 16, cli, + cli.length() - 16, 16)) { + System.out.println("entry " + (i + 1) + " match."); + serverPSK.remove(i); + return; + } + } + System.out.println("client entry (" + cli.substring(0, 16) + + ") not found in server list"); + pass = false; + }); + } catch (RuntimeException e) { + System.out.println("Error looking at PSK results."); + throw new Exception(e); + } + + if (TLS13) { + if (!pass) { + throw new Exception("Test failed: " + params); + } + } else { + if (pass) { + throw new Exception("Test failed: " + params); + } + } + System.out.println("Test Passed"); + return; + } + + int ticketCount = Integer.parseInt( + System.getProperty("jdk.tls.server.newSessionTicketCount")); + + TLSBase.Server server = new TLSBase.Server(); + + System.out.println("------ Start connection"); + TLSBase.Client initial = new TLSBase.Client(); + SSLSession initialSession = initial.getSession(); + System.out.println("id = " + hex.formatHex(initialSession.getId())); + System.out.println("session = " + initialSession); + + System.out.println("------ getNewSession from original client"); + + ArrayList slist = new ArrayList<>(ticketCount); + + System.out.println("tx " + ticketCount); + for (int i = 0; ticketCount > i; i++) { + Thread t = new ClientThread(initial); + t.setName("Iteration " + i); + slist.add(t); + t.start(); + } + + wait.countDown(); + for (Thread t : slist) { + t.join(1000); + System.err.println("released: " + t.getName()); + } + + System.out.println("------ Closing connections"); + initial.close(); + server.close(); + System.out.println("------ End"); + System.exit(0); + } +} diff --git a/test/jdk/sun/security/ssl/SSLSessionImpl/MultiNSTSequence.java b/test/jdk/sun/security/ssl/SSLSessionImpl/MultiNSTSequence.java new file mode 100644 index 0000000000000..888dba56a5023 --- /dev/null +++ b/test/jdk/sun/security/ssl/SSLSessionImpl/MultiNSTSequence.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @library /test/lib + * @library /javax/net/ssl/templates + * @bug 8242008 + * @summary Verifies sequence of used NST entries from the cache queue. + * @run main/othervm MultiNSTSequence -Djdk.tls.server.newSessionTicketCount=2 + */ + +import jdk.test.lib.Utils; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +import javax.net.ssl.SSLSession; +import java.util.Arrays; +import java.util.HexFormat; +import java.util.List; + +/** + * This test verifies that multiple NSTs take the oldest PSK from the + * QueueCacheEntry stored in the TLS Session Cache. + * + * Note: Beyond 9 iterations the PSK id verification code becomes complicated + * with a QueueCacheEntry limit set to retain only the 10 newest entries. + * + * TLS 1.2 spec does not specify multiple NST behavior. + */ + +public class MultiNSTSequence { + + static HexFormat hex = HexFormat.of(); + static final int ITERATIONS = 9; + + public static void main(String[] args) throws Exception { + + if (!args[0].equalsIgnoreCase("p")) { + StringBuilder sb = new StringBuilder(); + Arrays.stream(args).forEach(a -> sb.append(a).append(" ")); + String params = sb.toString(); + System.setProperty("test.java.opts", + "-Dtest.src=" + System.getProperty("test.src") + + " -Dtest.jdk=" + System.getProperty("test.jdk") + + " -Dtest.root=" + System.getProperty("test.root") + + " -Djavax.net.debug=ssl,handshake " + params + ); + + System.out.println("test.java.opts: " + + System.getProperty("test.java.opts")); + + ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder( + Utils.addTestJavaOpts("MultiNSTSequence", "p")); + + OutputAnalyzer output = ProcessTools.executeProcess(pb); + boolean pass = true; + try { + List list = output.stderrShouldContain("MultiNST PSK"). + asLines().stream().filter(s -> + s.contains("MultiNST PSK")).toList(); + List serverPSK = list.stream().filter(s -> + s.contains("MultiNST PSK (Server)")).toList(); + List clientPSK = list.stream().filter(s -> + s.contains("MultiNST PSK (Client)")).toList(); + System.out.println("found list: " + list.size()); + System.out.println("found server: " + serverPSK.size()); + serverPSK.stream().forEach(s -> System.out.println("\t" + s)); + System.out.println("found client: " + clientPSK.size()); + clientPSK.stream().forEach(s -> System.out.println("\t" + s)); + int i; + for (i = 0; i < ITERATIONS; i++) { + String svr = serverPSK.get(i); + String cli = clientPSK.get(i); + if (svr.regionMatches(svr.length() - 16, cli, cli.length() - 16, 16)) { + System.out.println("entry " + (i + 1) + " match."); + } else { + System.out.println("entry " + (i + 1) + " server and client PSK didn't match:"); + System.out.println(" server: " + svr); + System.out.println(" client: " + cli); + pass = false; + } + } + } catch (RuntimeException e) { + System.out.println("Server and Client PSK usage order is not" + + " the same."); + pass = false; + } + + if (!pass) { + throw new Exception("Test failed: " + params); + } + System.out.println("Test Passed"); + return; + } + + TLSBase.Server server = new TLSBase.Server(); + + System.out.println("------ Initial connection"); + TLSBase.Client initial = new TLSBase.Client(); + + SSLSession initialSession = initial.connect().getSession(); + System.out.println("id = " + hex.formatHex(initialSession.getId())); + System.out.println("session = " + initialSession); + + System.out.println("------ Resume client"); + for (int i = 0; i < ITERATIONS; i++) { + SSLSession r = new TLSBase.Client(initial).connect().getSession(); + StringBuilder sb = new StringBuilder(100); + sb.append("Iteration: ").append(i); + sb.append("\tid = ").append(hex.formatHex(r.getId())); + sb.append("\tsession = ").append(r); + System.out.println(sb); + if (!initialSession.toString().equalsIgnoreCase(r.toString())) { + throw new Exception("Resumed session did not match"); + } + } + + System.out.println("------ Closing connections"); + initial.close(); + server.close(); + System.out.println("------ End"); + System.exit(0); + } +} From 3d49fb8a17ceec6e23595bc8affc89765899f72b Mon Sep 17 00:00:00 2001 From: Manukumar V S Date: Wed, 28 Aug 2024 17:54:43 +0000 Subject: [PATCH 60/72] 8338103: Stabilize and open source a Swing OGL ButtonResizeTest Reviewed-by: abhiscxk, prr, tr --- .../SwingButtonResizeTestWithOpenGL.java | 352 ++++++++++++++++++ 1 file changed, 352 insertions(+) create mode 100644 test/jdk/javax/swing/JButton/SwingButtonResizeTestWithOpenGL.java diff --git a/test/jdk/javax/swing/JButton/SwingButtonResizeTestWithOpenGL.java b/test/jdk/javax/swing/JButton/SwingButtonResizeTestWithOpenGL.java new file mode 100644 index 0000000000000..96ef9edc57100 --- /dev/null +++ b/test/jdk/javax/swing/JButton/SwingButtonResizeTestWithOpenGL.java @@ -0,0 +1,352 @@ +/* + * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Robot; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.Raster; +import java.io.File; +import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import javax.imageio.ImageIO; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.SwingUtilities; + +/* + * @test + * @bug 8338103 + * @key headful + * @summary Verifies that the OpenGL pipeline does not create artifacts + * with swing components after window is zoomed to maximum size and then + * resized back to normal. The test case simulates this operation using + * a JButton. A file image of the component will be saved before and after + * the window resize if the test fails. The test passes if both the button + * images are the same. + * @run main/othervm -Dsun.java2d.opengl=true -Dsun.java2d.opengl.fbobject=false SwingButtonResizeTestWithOpenGL + * @run main/othervm -Dsun.java2d.opengl=true -Dsun.java2d.opengl.fbobject=true SwingButtonResizeTestWithOpenGL + * @run main/othervm -Dsun.java2d.opengl=false SwingButtonResizeTestWithOpenGL + * @run main/othervm SwingButtonResizeTestWithOpenGL + */ +/* + * @test + * @key headful + * @requires (os.family == "windows") + * @run main/othervm -Dsun.java2d.d3d=false SwingButtonResizeTestWithOpenGL + * @run main/othervm -Dsun.java2d.d3d=true SwingButtonResizeTestWithOpenGL + */ +/* + * @test + * @key headful + * @requires (os.family == "linux") + * @run main/othervm -Dsun.java2d.xrender=false SwingButtonResizeTestWithOpenGL + * @run main/othervm -Dsun.java2d.xrender=true SwingButtonResizeTestWithOpenGL + */ +/* + * @test + * @key headful + * @requires (os.family == "mac") + * @run main/othervm -Dsun.java2d.metal=false SwingButtonResizeTestWithOpenGL + * @run main/othervm -Dsun.java2d.metal=true SwingButtonResizeTestWithOpenGL + */ +public class SwingButtonResizeTestWithOpenGL { + private static Robot robot; + private static CountDownLatch focusGainedLatch; + private JFrame frame; + private JButton button; + + public SwingButtonResizeTestWithOpenGL() { + + try { + SwingUtilities.invokeAndWait(() -> createGUI()); + } catch (Exception e) { + throw new RuntimeException("Problems creating GUI"); + } + } + + private void createGUI() { + frame = new JFrame("SwingButtonResizeTestWithOpenGL"); + button = new JButton("Button A"); + frame.setLocation(200, 200); + frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + button.setPreferredSize(new Dimension(300, 300)); + button.addFocusListener(new FocusAdapter() { + public void focusGained(FocusEvent fe) { + focusGainedLatch.countDown(); + } + }); + frame.getContentPane().setLayout(new FlowLayout()); + frame.getContentPane().add(button); + frame.pack(); + frame.setVisible(true); + frame.toFront(); + } + + public static void main(String[] args) throws Exception { + focusGainedLatch = new CountDownLatch(1); + SwingButtonResizeTestWithOpenGL test = + new SwingButtonResizeTestWithOpenGL(); + test.runTest(); + } + + public void runTest() throws Exception { + BufferedImage bimage1; + BufferedImage bimage2; + + try { + robot = new Robot(); + robot.setAutoWaitForIdle(true); + robot.setAutoDelay(200); + + if (focusGainedLatch.await(3, TimeUnit.SECONDS)) { + System.out.println("Button focus gained..."); + } else { + System.out.println("Button focus not gained..."); + throw new RuntimeException( + "Can't gain focus on button even after waiting " + + "too long.."); + } + + System.out.println("Getting initial button image..image1"); + bimage1 = getButtonImage(); + + // some platforms may not support maximize frame + if (frame.getToolkit().isFrameStateSupported( + JFrame.MAXIMIZED_BOTH)) { + robot.waitForIdle(); + // maximize frame from normal size + frame.setExtendedState(JFrame.MAXIMIZED_BOTH); + System.out.println("Frame is maximized"); + robot.waitForIdle(); + + if (frame.getToolkit().isFrameStateSupported(JFrame.NORMAL)) { + System.out.println("Frame is back to normal"); + // resize from maximum size to normal + frame.setExtendedState(JFrame.NORMAL); + + // capture image of JButton after resize + System.out.println( + "Getting image of JButton after resize..image2"); + bimage2 = getButtonImage(); + + // compare button images from before and after frame resize + DiffImage di = new DiffImage(bimage1.getWidth(), + bimage1.getHeight()); + System.out.println( + "Taking the diff of two images, image1 and image2"); + if (!di.compare(bimage1, bimage2)) { + throw new RuntimeException( + "Button renderings are different after window " + + "resize, num of Diff Pixels=" + + di.getNumDiffPixels()); + } else { + System.out.println("Test passed..."); + } + + } else { + System.out.println( + "Test skipped: JFrame.NORMAL resize is " + + "not supported"); + } + + } else { + System.out.println( + "Test skipped: JFrame.MAXIMIZED_BOTH resize is " + + "not supported"); + } + } finally { + SwingUtilities.invokeAndWait(() -> disposeFrame()); + } + } + + // Capture button rendering as a BufferedImage + private BufferedImage getButtonImage() { + try { + robot.waitForIdle(); + robot.delay(500); + + AtomicReference buttonLocRef = new AtomicReference<>(); + SwingUtilities.invokeAndWait( + () -> buttonLocRef.set(button.getLocationOnScreen())); + Point buttonLoc = buttonLocRef.get(); + System.out.println("Button loc: " + buttonLoc); + return robot.createScreenCapture( + new Rectangle(buttonLoc.x, buttonLoc.y, button.getWidth(), + button.getHeight())); + } catch (Exception e) { + throw new RuntimeException( + "Problems capturing button image from Robot", e); + } + } + + private void disposeFrame() { + if (frame != null) { + frame.dispose(); + frame = null; + } + } + + // Save BufferedImage to PNG file + private void saveButtonImage(BufferedImage image, File file) { + if (image != null) { + try { + System.out.println( + "Saving button image to " + file.getAbsolutePath()); + ImageIO.write(image, "PNG", file); + } catch (Exception e) { + throw new RuntimeException("Could not write image file"); + } + } else { + throw new RuntimeException("BufferedImage was set to null"); + } + } + + private class DiffImage extends BufferedImage { + + public boolean diff = false; + public int nDiff = -1; + + Color bgColor; + + int threshold = 0; + + public DiffImage(int w, int h) { + super(w, h, BufferedImage.TYPE_INT_ARGB); + bgColor = Color.LIGHT_GRAY; + } + + public int getNumDiffPixels() { + return nDiff; + } + + public boolean compare(BufferedImage img1, BufferedImage img2) + throws IOException { + + int minx1 = img1.getMinX(); + int minx2 = img2.getMinX(); + int miny1 = img1.getMinY(); + int miny2 = img2.getMinY(); + + int w1 = img1.getWidth(); + int w2 = img2.getWidth(); + int h1 = img1.getHeight(); + int h2 = img2.getHeight(); + + if ((minx1 != minx2) || (miny1 != miny2) || (w1 != w2) + || (h1 != h2)) { + // image sizes are different + throw new RuntimeException( + "img1: <" + minx1 + "," + miny1 + "," + w1 + "x" + h1 + + ">" + " img2: " + minx2 + "," + miny2 + "," + w2 + "x" + + h2 + ">" + " are different sizes"); + } + // Get the actual data behind the images + Raster ras1 = img1.getData(); + Raster ras2 = img2.getData(); + + ColorModel cm1 = img1.getColorModel(); + ColorModel cm2 = img2.getColorModel(); + + int r1, r2; // red + int g1, g2; // green + int b1, b2; // blue + + Object o1 = null; + Object o2 = null; + nDiff = 0; + for (int x = minx1; x < (minx1 + w1); x++) { + for (int y = miny1; y < (miny1 + h1); y++) { + // Causes rasters to allocate data + o1 = ras1.getDataElements(x, y, o1); + // and we reuse the data on every loop + o2 = ras2.getDataElements(x, y, o2); + + r1 = cm1.getRed(o1); + r2 = cm2.getRed(o2); + g1 = cm1.getGreen(o1); + g2 = cm2.getGreen(o2); + b1 = cm1.getBlue(o1); + b2 = cm2.getBlue(o2); + + int redAbs = Math.abs(r1 - r2); + int greenAbs = Math.abs(g1 - g2); + int blueAbs = Math.abs(b1 - b2); + if ((redAbs > threshold) + || (greenAbs > threshold) + || (blueAbs > threshold)) { + // pixel is different + setDiffPixel(x, y, redAbs, greenAbs, blueAbs); + nDiff++; + } else { + setSamePixel(x, y); + } + + } + } + if (nDiff != 0) { + ImageIO.write(this, "png", + new File("diffImage.png")); + saveButtonImage(img1, new File("image1.png")); + saveButtonImage(img2, new File("image2.png")); + } + return nDiff == 0; + } + + void setDiffPixel(int x, int y, int r, int g, int b) { + diff = true; + setPixelValue(x, y, 255, r, g, b); + } + + void setSamePixel(int x, int y) { + if (bgColor != null) { + setPixelValue(x, y, 255, bgColor.getRed(), + bgColor.getGreen(), + bgColor.getBlue()); + } else { + setPixelValue(x, y, 255, Color.black.getRed(), + Color.black.getGreen(), Color.black.getBlue()); + } + } + + void setPixelValue(int x, int y, int a, int r, int g, int b) { + // setRGB uses BufferedImage.TYPE_INT_ARGB format + int pixel = + ((a & 0xff) << 24) + ((r & 0xff) << 16) + ((g & 0xff) << 8) + + ((b & 0xff)); + setRGB(x, y, pixel); + } + + } + +} + + From a98ecad0a920f12d81386de3d0f549d542014773 Mon Sep 17 00:00:00 2001 From: Claes Redestad Date: Wed, 28 Aug 2024 18:16:00 +0000 Subject: [PATCH 61/72] 8338897: Small startup regression remains after JDK-8309622 and JDK-8331932 Reviewed-by: liach, naoto --- .../share/classes/java/util/Locale.java | 18 ++++++++---- .../classes/sun/util/locale/BaseLocale.java | 28 ++++++------------- 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/src/java.base/share/classes/java/util/Locale.java b/src/java.base/share/classes/java/util/Locale.java index ae6c4af5ec56c..73132b81c09a6 100644 --- a/src/java.base/share/classes/java/util/Locale.java +++ b/src/java.base/share/classes/java/util/Locale.java @@ -988,17 +988,23 @@ static Locale getInstance(BaseLocale baseloc, LocaleExtensions extensions) { if (locale != null) { return locale; } - return LOCALE_CACHE.computeIfAbsent(baseloc, LOCALE_CREATOR); + return LocaleCache.cache(baseloc); } else { LocaleKey key = new LocaleKey(baseloc, extensions); - return LOCALE_CACHE.computeIfAbsent(key, LOCALE_CREATOR); + return LocaleCache.cache(key); } } - private static final ReferencedKeyMap LOCALE_CACHE - = ReferencedKeyMap.create(true, ReferencedKeyMap.concurrentHashMapSupplier()); + private static final class LocaleCache implements Function { + private static final ReferencedKeyMap LOCALE_CACHE + = ReferencedKeyMap.create(true, ReferencedKeyMap.concurrentHashMapSupplier()); + + private static final Function LOCALE_CREATOR = new LocaleCache(); + + public static Locale cache(Object key) { + return LOCALE_CACHE.computeIfAbsent(key, LOCALE_CREATOR); + } - private static final Function LOCALE_CREATOR = new Function<>() { @Override public Locale apply(Object key) { if (key instanceof BaseLocale base) { @@ -1007,7 +1013,7 @@ public Locale apply(Object key) { LocaleKey lk = (LocaleKey)key; return new Locale(lk.base, lk.exts); } - }; + } private static final class LocaleKey { diff --git a/src/java.base/share/classes/sun/util/locale/BaseLocale.java b/src/java.base/share/classes/sun/util/locale/BaseLocale.java index ec2a6a49183ca..a744b6ce39f59 100644 --- a/src/java.base/share/classes/sun/util/locale/BaseLocale.java +++ b/src/java.base/share/classes/sun/util/locale/BaseLocale.java @@ -38,7 +38,6 @@ import jdk.internal.vm.annotation.Stable; import java.util.StringJoiner; -import java.util.function.UnaryOperator; public final class BaseLocale { @@ -91,10 +90,6 @@ public final class BaseLocale { } } - // Interned BaseLocale cache - private static final ReferencedKeySet CACHE = - ReferencedKeySet.create(true, ReferencedKeySet.concurrentHashMapSupplier()); - public static final String SEP = "_"; private final String language; @@ -163,21 +158,16 @@ public static BaseLocale getInstance(String language, String script, // Obtain the "interned" BaseLocale from the cache. The returned // "interned" instance can subsequently be used by the Locale // instance which guarantees the locale components are properly cased/interned. - return CACHE.intern(new BaseLocale(language, script, region, variant), - // Avoid lambdas since this may be on the bootstrap path in many locales - INTERNER); - } - - public static final UnaryOperator INTERNER = new UnaryOperator<>() { - @Override - public BaseLocale apply(BaseLocale b) { - return new BaseLocale( - LocaleUtils.toLowerString(b.language).intern(), - LocaleUtils.toTitleString(b.script).intern(), - LocaleUtils.toUpperString(b.region).intern(), - b.variant.intern()); + class InterningCache { // TODO: StableValue + private static final ReferencedKeySet CACHE = + ReferencedKeySet.create(true, ReferencedKeySet.concurrentHashMapSupplier()); } - }; + return InterningCache.CACHE.intern(new BaseLocale( + language.intern(), // guaranteed to be lower-case + LocaleUtils.toTitleString(script).intern(), + region.intern(), // guaranteed to be upper-case + variant.intern())); + } public static String convertOldISOCodes(String language) { return switch (language) { From eff6d9cd23f9da8720a44ad628aa0a3e6f58facf Mon Sep 17 00:00:00 2001 From: Claes Redestad Date: Wed, 28 Aug 2024 18:22:30 +0000 Subject: [PATCH 62/72] 8339167: Remove AbstractPoolEntry.PrimitiveEntry to reduce boxing overheads Reviewed-by: liach --- .../classfile/impl/AbstractPoolEntry.java | 78 ++++++++++--------- .../classfile/impl/SplitConstantPool.java | 73 ++++++++++++++--- 2 files changed, 105 insertions(+), 46 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractPoolEntry.java b/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractPoolEntry.java index c27968eecab4f..bc885291b44cd 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractPoolEntry.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractPoolEntry.java @@ -1030,34 +1030,13 @@ public boolean equals(Object o) { } - abstract static sealed class PrimitiveEntry - extends AbstractPoolEntry { - protected final T val; - - public PrimitiveEntry(ConstantPool constantPool, int tag, int index, T val) { - super(constantPool, tag, index, hash1(tag, val.hashCode())); - this.val = val; - } + public static final class IntegerEntryImpl extends AbstractPoolEntry implements IntegerEntry { - public T value() { - return val; - } - - public ConstantDesc constantValue() { - return value(); - } - - @Override - public String toString() { - return "" + tag() + value(); - } - } - - public static final class IntegerEntryImpl extends PrimitiveEntry - implements IntegerEntry { + private final int val; IntegerEntryImpl(ConstantPool cpm, int index, int i) { - super(cpm, ClassFile.TAG_INTEGER, index, i); + super(cpm, ClassFile.TAG_INTEGER, index, hash1(ClassFile.TAG_INTEGER, Integer.hashCode(i))); + val = i; } @Override @@ -1073,7 +1052,12 @@ public IntegerEntry clone(ConstantPoolBuilder cp) { @Override public int intValue() { - return value(); + return val; + } + + @Override + public ConstantDesc constantValue() { + return val; } @Override @@ -1086,11 +1070,14 @@ public boolean equals(Object o) { } } - public static final class FloatEntryImpl extends PrimitiveEntry + public static final class FloatEntryImpl extends AbstractPoolEntry implements FloatEntry { + private final float val; + FloatEntryImpl(ConstantPool cpm, int index, float f) { - super(cpm, ClassFile.TAG_FLOAT, index, f); + super(cpm, ClassFile.TAG_FLOAT, index, hash1(ClassFile.TAG_FLOAT, Float.hashCode(f))); + val = f; } @Override @@ -1106,7 +1093,12 @@ public FloatEntry clone(ConstantPoolBuilder cp) { @Override public float floatValue() { - return value(); + return val; + } + + @Override + public ConstantDesc constantValue() { + return val; } @Override @@ -1119,10 +1111,13 @@ public boolean equals(Object o) { } } - public static final class LongEntryImpl extends PrimitiveEntry implements LongEntry { + public static final class LongEntryImpl extends AbstractPoolEntry implements LongEntry { + + private final long val; LongEntryImpl(ConstantPool cpm, int index, long l) { - super(cpm, ClassFile.TAG_LONG, index, l); + super(cpm, ClassFile.TAG_LONG, index, hash1(ClassFile.TAG_LONG, Long.hashCode(l))); + val = l; } @Override @@ -1138,7 +1133,12 @@ public LongEntry clone(ConstantPoolBuilder cp) { @Override public long longValue() { - return value(); + return val; + } + + @Override + public ConstantDesc constantValue() { + return val; } @Override @@ -1151,10 +1151,13 @@ public boolean equals(Object o) { } } - public static final class DoubleEntryImpl extends PrimitiveEntry implements DoubleEntry { + public static final class DoubleEntryImpl extends AbstractPoolEntry implements DoubleEntry { + + private final double val; DoubleEntryImpl(ConstantPool cpm, int index, double d) { - super(cpm, ClassFile.TAG_DOUBLE, index, d); + super(cpm, ClassFile.TAG_DOUBLE, index, hash1(ClassFile.TAG_DOUBLE, Double.hashCode(d))); + val = d; } @Override @@ -1170,7 +1173,12 @@ public DoubleEntry clone(ConstantPoolBuilder cp) { @Override public double doubleValue() { - return value(); + return val; + } + + @Override + public ConstantDesc constantValue() { + return val; } @Override diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/SplitConstantPool.java b/src/java.base/share/classes/jdk/internal/classfile/impl/SplitConstantPool.java index f7905712b18d6..777da61c49d9d 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/SplitConstantPool.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/SplitConstantPool.java @@ -266,19 +266,70 @@ private BootstrapMethodEntryImpl internalAdd(BootstrapMethodEntryImpl bsm, int h return bsm; } - private PoolEntry findPrimitiveEntry(int tag, T val) { - int hash = AbstractPoolEntry.hash1(tag, val.hashCode()); + private IntegerEntry findIntEntry(int val) { + int hash = AbstractPoolEntry.hash1(TAG_INTEGER, Integer.hashCode(val)); EntryMap map = map(); for (int token = map.firstToken(hash); token != -1; token = map.nextToken(hash, token)) { PoolEntry e = map.getElementByToken(token); - if (e.tag() == tag - && e instanceof AbstractPoolEntry.PrimitiveEntry ce - && ce.value().equals(val)) - return e; + if (e.tag() == TAG_INTEGER + && e instanceof AbstractPoolEntry.IntegerEntryImpl ce + && ce.intValue() == val) + return ce; + } + if (!doneFullScan) { + fullScan(); + return findIntEntry(val); + } + return null; + } + + private LongEntry findLongEntry(long val) { + int hash = AbstractPoolEntry.hash1(TAG_LONG, Long.hashCode(val)); + EntryMap map = map(); + for (int token = map.firstToken(hash); token != -1; token = map.nextToken(hash, token)) { + PoolEntry e = map.getElementByToken(token); + if (e.tag() == TAG_LONG + && e instanceof AbstractPoolEntry.LongEntryImpl ce + && ce.longValue() == val) + return ce; + } + if (!doneFullScan) { + fullScan(); + return findLongEntry(val); + } + return null; + } + + private FloatEntry findFloatEntry(float val) { + int hash = AbstractPoolEntry.hash1(TAG_FLOAT, Float.hashCode(val)); + EntryMap map = map(); + for (int token = map.firstToken(hash); token != -1; token = map.nextToken(hash, token)) { + PoolEntry e = map.getElementByToken(token); + if (e.tag() == TAG_FLOAT + && e instanceof AbstractPoolEntry.FloatEntryImpl ce + && ce.floatValue() == val) + return ce; + } + if (!doneFullScan) { + fullScan(); + return findFloatEntry(val); + } + return null; + } + + private DoubleEntry findDoubleEntry(double val) { + int hash = AbstractPoolEntry.hash1(TAG_DOUBLE, Double.hashCode(val)); + EntryMap map = map(); + for (int token = map.firstToken(hash); token != -1; token = map.nextToken(hash, token)) { + PoolEntry e = map.getElementByToken(token); + if (e.tag() == TAG_DOUBLE + && e instanceof AbstractPoolEntry.DoubleEntryImpl ce + && ce.doubleValue() == val) + return ce; } if (!doneFullScan) { fullScan(); - return findPrimitiveEntry(tag, val); + return findDoubleEntry(val); } return null; } @@ -542,25 +593,25 @@ public ConstantDynamicEntry constantDynamicEntry(BootstrapMethodEntry bootstrapM @Override public IntegerEntry intEntry(int value) { - var e = (IntegerEntry) findPrimitiveEntry(TAG_INTEGER, value); + var e = findIntEntry(value); return e == null ? internalAdd(new AbstractPoolEntry.IntegerEntryImpl(this, size, value)) : e; } @Override public FloatEntry floatEntry(float value) { - var e = (FloatEntry) findPrimitiveEntry(TAG_FLOAT, value); + var e = findFloatEntry(value); return e == null ? internalAdd(new AbstractPoolEntry.FloatEntryImpl(this, size, value)) : e; } @Override public LongEntry longEntry(long value) { - var e = (LongEntry) findPrimitiveEntry(TAG_LONG, value); + var e = findLongEntry(value); return e == null ? internalAdd(new AbstractPoolEntry.LongEntryImpl(this, size, value)) : e; } @Override public DoubleEntry doubleEntry(double value) { - var e = (DoubleEntry) findPrimitiveEntry(TAG_DOUBLE, value); + var e = findDoubleEntry(value); return e == null ? internalAdd(new AbstractPoolEntry.DoubleEntryImpl(this, size, value)) : e; } From d03ec7aad41d830b47801b7af75ee5e278128e69 Mon Sep 17 00:00:00 2001 From: Leonid Mesnik Date: Wed, 28 Aug 2024 20:17:25 +0000 Subject: [PATCH 63/72] 8339030: frame::print_value_on(outputStream* st, JavaThread *thread) doesn't need thread argument Reviewed-by: dholmes, coleenp --- src/hotspot/share/oops/instanceStackChunkKlass.cpp | 2 +- src/hotspot/share/prims/whitebox.cpp | 2 +- .../share/runtime/continuationFreezeThaw.cpp | 14 +++++++------- src/hotspot/share/runtime/frame.cpp | 4 ++-- src/hotspot/share/runtime/frame.hpp | 4 ++-- src/hotspot/share/runtime/javaThread.cpp | 2 +- src/hotspot/share/runtime/vframe.cpp | 4 ++-- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/hotspot/share/oops/instanceStackChunkKlass.cpp b/src/hotspot/share/oops/instanceStackChunkKlass.cpp index bf215e1c13e5a..a2227b0a76a5e 100644 --- a/src/hotspot/share/oops/instanceStackChunkKlass.cpp +++ b/src/hotspot/share/oops/instanceStackChunkKlass.cpp @@ -226,7 +226,7 @@ class PrintStackChunkClosure { p2i(fs.sp()), fs.is_interpreted(), f.frame_size(), fs.is_interpreted() ? 0 : f.compiled_frame_stack_argsize()); #ifdef ASSERT - f.print_value_on(_st, nullptr); + f.print_value_on(_st); #else f.print_on(_st); #endif diff --git a/src/hotspot/share/prims/whitebox.cpp b/src/hotspot/share/prims/whitebox.cpp index 544eeabb5c212..8a08a12a0b0e2 100644 --- a/src/hotspot/share/prims/whitebox.cpp +++ b/src/hotspot/share/prims/whitebox.cpp @@ -2582,7 +2582,7 @@ WB_ENTRY(void, WB_VerifyFrames(JNIEnv* env, jobject wb, jboolean log, jboolean u for (StackFrameStream fst(JavaThread::current(), update_map, true); !fst.is_done(); fst.next()) { frame* current_frame = fst.current(); if (log) { - current_frame->print_value_on(&st, nullptr); + current_frame->print_value_on(&st); } current_frame->verify(fst.register_map()); } diff --git a/src/hotspot/share/runtime/continuationFreezeThaw.cpp b/src/hotspot/share/runtime/continuationFreezeThaw.cpp index 0e4640b6a9f4d..0a763d76badbd 100644 --- a/src/hotspot/share/runtime/continuationFreezeThaw.cpp +++ b/src/hotspot/share/runtime/continuationFreezeThaw.cpp @@ -877,7 +877,7 @@ inline void FreezeBase::before_freeze_java_frame(const frame& f, const frame& ca LogStream ls(lt); ls.print_cr("======== FREEZING FRAME interpreted: %d bottom: %d", f.is_interpreted_frame(), is_bottom_frame); ls.print_cr("fsize: %d argsize: %d", fsize, argsize); - f.print_value_on(&ls, nullptr); + f.print_value_on(&ls); } assert(caller.is_interpreted_frame() == Interpreter::contains(caller.pc()), ""); } @@ -886,7 +886,7 @@ inline void FreezeBase::after_freeze_java_frame(const frame& hf, bool is_bottom_ LogTarget(Trace, continuations) lt; if (lt.develop_is_enabled()) { LogStream ls(lt); - DEBUG_ONLY(hf.print_value_on(&ls, nullptr);) + DEBUG_ONLY(hf.print_value_on(&ls);) assert(hf.is_heap_frame(), "should be"); DEBUG_ONLY(print_frame_layout(hf, false, &ls);) if (is_bottom_frame) { @@ -2027,7 +2027,7 @@ NOINLINE intptr_t* ThawBase::thaw_slow(stackChunkOop chunk, bool return_barrier) LogStream ls(lt); ls.print_cr("top hframe before (thaw):"); assert(heap_frame.is_heap_frame(), "should have created a relative frame"); - heap_frame.print_value_on(&ls, nullptr); + heap_frame.print_value_on(&ls); } #if INCLUDE_ZGC || INCLUDE_SHENANDOAHGC @@ -2124,7 +2124,7 @@ inline void ThawBase::before_thaw_java_frame(const frame& hf, const frame& calle LogStream ls(lt); ls.print_cr("======== THAWING FRAME: %d", num_frame); assert(hf.is_heap_frame(), "should be"); - hf.print_value_on(&ls, nullptr); + hf.print_value_on(&ls); } assert(bottom == _cont.is_entry_frame(caller), "bottom: %d is_entry_frame: %d", bottom, _cont.is_entry_frame(hf)); } @@ -2394,7 +2394,7 @@ void ThawBase::finish_thaw(frame& f) { if (lt.develop_is_enabled()) { LogStream ls(lt); ls.print_cr("top hframe after (thaw):"); - _cont.last_frame().print_value_on(&ls, nullptr); + _cont.last_frame().print_value_on(&ls); } } @@ -2406,7 +2406,7 @@ void ThawBase::push_return_frame(frame& f) { // see generate_cont_thaw if (lt.develop_is_enabled()) { LogStream ls(lt); ls.print_cr("push_return_frame"); - f.print_value_on(&ls, nullptr); + f.print_value_on(&ls); } assert(f.sp() - frame::metadata_words_at_bottom >= _top_stack_address, "overwrote past thawing space" @@ -2467,7 +2467,7 @@ static inline intptr_t* thaw_internal(JavaThread* thread, const Continuation::th if (lt.develop_is_enabled()) { LogStream ls(lt); ls.print_cr("Jumping to frame (thaw):"); - frame(sp).print_value_on(&ls, nullptr); + frame(sp).print_value_on(&ls); } #endif diff --git a/src/hotspot/share/runtime/frame.cpp b/src/hotspot/share/runtime/frame.cpp index 1aed46d58804f..e193271eff658 100644 --- a/src/hotspot/share/runtime/frame.cpp +++ b/src/hotspot/share/runtime/frame.cpp @@ -511,7 +511,7 @@ const char* frame::print_name() const { return "C"; } -void frame::print_value_on(outputStream* st, JavaThread *thread) const { +void frame::print_value_on(outputStream* st) const { NOT_PRODUCT(address begin = pc()-40;) NOT_PRODUCT(address end = nullptr;) @@ -550,7 +550,7 @@ void frame::print_value_on(outputStream* st, JavaThread *thread) const { } void frame::print_on(outputStream* st) const { - print_value_on(st,nullptr); + print_value_on(st); if (is_interpreted_frame()) { interpreter_frame_print_on(st); } diff --git a/src/hotspot/share/runtime/frame.hpp b/src/hotspot/share/runtime/frame.hpp index 468437a648464..1c57e3de4daa6 100644 --- a/src/hotspot/share/runtime/frame.hpp +++ b/src/hotspot/share/runtime/frame.hpp @@ -433,8 +433,8 @@ class frame { void describe_pd(FrameValues& values, int frame_no); public: - void print_value() const { print_value_on(tty,nullptr); } - void print_value_on(outputStream* st, JavaThread *thread) const; + void print_value() const { print_value_on(tty); } + void print_value_on(outputStream* st) const; void print_on(outputStream* st) const; void interpreter_frame_print_on(outputStream* st) const; void print_on_error(outputStream* st, char* buf, int buflen, bool verbose = false) const; diff --git a/src/hotspot/share/runtime/javaThread.cpp b/src/hotspot/share/runtime/javaThread.cpp index 0c00ef75c9216..416e79d584477 100644 --- a/src/hotspot/share/runtime/javaThread.cpp +++ b/src/hotspot/share/runtime/javaThread.cpp @@ -1906,7 +1906,7 @@ void JavaThread::trace_frames() { int frame_no = 1; for (StackFrameStream fst(this, true /* update */, true /* process_frames */); !fst.is_done(); fst.next()) { tty->print(" %d. ", frame_no++); - fst.current()->print_value_on(tty, this); + fst.current()->print_value_on(tty); tty->cr(); } } diff --git a/src/hotspot/share/runtime/vframe.cpp b/src/hotspot/share/runtime/vframe.cpp index 58eead692c6fb..d93b380ce92db 100644 --- a/src/hotspot/share/runtime/vframe.cpp +++ b/src/hotspot/share/runtime/vframe.cpp @@ -622,7 +622,7 @@ javaVFrame* vframeStreamCommon::asJavaVFrame() { #ifndef PRODUCT void vframe::print(outputStream* output) { - if (WizardMode) _fr.print_value_on(output, nullptr); + if (WizardMode) _fr.print_value_on(output); } void vframe::print_value(outputStream* output) const { @@ -737,7 +737,7 @@ void javaVFrame::print_activation(int index, outputStream* output) const { // ------------- externalVFrame -------------- void externalVFrame::print(outputStream* output) { - _fr.print_value_on(output, nullptr); + _fr.print_value_on(output); } void externalVFrame::print_value(outputStream* output) const { From d08b5bd9f5f740d75c1acfbd644ce1c822e03833 Mon Sep 17 00:00:00 2001 From: Leonid Mesnik Date: Wed, 28 Aug 2024 20:18:51 +0000 Subject: [PATCH 64/72] 8258483: [TESTBUG] gtest CollectorPolicy.young_scaled_initial_ergo_vm fails if heap is too small Reviewed-by: ayang --- .../gtest/gc/shared/test_collectorPolicy.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/test/hotspot/gtest/gc/shared/test_collectorPolicy.cpp b/test/hotspot/gtest/gc/shared/test_collectorPolicy.cpp index b38586a89753b..06874ee0698e5 100644 --- a/test/hotspot/gtest/gc/shared/test_collectorPolicy.cpp +++ b/test/hotspot/gtest/gc/shared/test_collectorPolicy.cpp @@ -67,7 +67,7 @@ class TestGenCollectorPolicy { MinHeapSize = 40 * M; FLAG_SET_ERGO(InitialHeapSize, 100 * M); FLAG_SET_ERGO(NewSize, 1 * M); - FLAG_SET_ERGO(MaxNewSize, 80 * M); + FLAG_SET_ERGO(MaxNewSize, 40 * M); ASSERT_NO_FATAL_FAILURE(setter1->execute()); @@ -159,10 +159,16 @@ class TestGenCollectorPolicy { // depends on so many other configurable variables. These tests only try to // verify that there are some basic rules for NewSize honored by the policies. +// Tests require at least 128M of MaxHeap +// otherwise ergonomic is different and generation sizes might be changed. + // If NewSize has been ergonomically set, the collector policy // should use it for min but calculate the initial young size // using NewRatio. TEST_VM(CollectorPolicy, young_scaled_initial_ergo) { + if (MaxHeapSize < 128 * M) { + return; + } TestGenCollectorPolicy::SetNewSizeErgo setter(20 * M); TestGenCollectorPolicy::CheckScaledYoungInitial checker; @@ -175,6 +181,9 @@ TEST_VM(CollectorPolicy, young_scaled_initial_ergo) { // the rest of the VM lifetime. This is an irreversible change and // could impact other tests so we use TEST_OTHER_VM TEST_OTHER_VM(CollectorPolicy, young_cmd) { + if (MaxHeapSize < 128 * M) { + return; + } // If NewSize is set on the command line, it should be used // for both min and initial young size if less than min heap. TestGenCollectorPolicy::SetNewSizeCmd setter(20 * M); @@ -187,7 +196,7 @@ TEST_OTHER_VM(CollectorPolicy, young_cmd) { // If NewSize is set on command line, but is larger than the min // heap size, it should only be used for initial young size. - TestGenCollectorPolicy::SetNewSizeCmd setter_large(80 * M); - TestGenCollectorPolicy::CheckYoungInitial checker_large(80 * M); + TestGenCollectorPolicy::SetNewSizeCmd setter_large(40 * M); + TestGenCollectorPolicy::CheckYoungInitial checker_large(40 * M); TestGenCollectorPolicy::TestWrapper::test(&setter_large, &checker_large); } From a8ac28725bfc22867c76856ddce094588a97b84c Mon Sep 17 00:00:00 2001 From: Justin Lu Date: Wed, 28 Aug 2024 21:14:29 +0000 Subject: [PATCH 65/72] 8339126: JNI exception pending in Inflater.c Reviewed-by: lancea, vtewari, jpai, naoto --- src/java.base/share/native/libzip/Inflater.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/native/libzip/Inflater.c b/src/java.base/share/native/libzip/Inflater.c index a41e9775bfa02..1f43d3d1abf56 100644 --- a/src/java.base/share/native/libzip/Inflater.c +++ b/src/java.base/share/native/libzip/Inflater.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -49,8 +49,8 @@ JNIEXPORT void JNICALL Java_java_util_zip_Inflater_initIDs(JNIEnv *env, jclass cls) { inputConsumedID = (*env)->GetFieldID(env, cls, "inputConsumed", "I"); - outputConsumedID = (*env)->GetFieldID(env, cls, "outputConsumed", "I"); CHECK_NULL(inputConsumedID); + outputConsumedID = (*env)->GetFieldID(env, cls, "outputConsumed", "I"); CHECK_NULL(outputConsumedID); } From 72a49005ee8c4aeb6dcf3eff4c56576a2b4d0081 Mon Sep 17 00:00:00 2001 From: David Holmes Date: Wed, 28 Aug 2024 21:16:18 +0000 Subject: [PATCH 66/72] 8338888: SystemDictionary::class_name_symbol has incorrect length check Reviewed-by: stuefe, kbarrett, coleenp --- .../share/classfile/systemDictionary.cpp | 29 ++++++++--- .../NoClassDefFoundErrorTest.java | 48 +++++++++++++++---- .../libNoClassDefFoundErrorTest.c | 37 +++++++++++++- 3 files changed, 98 insertions(+), 16 deletions(-) diff --git a/src/hotspot/share/classfile/systemDictionary.cpp b/src/hotspot/share/classfile/systemDictionary.cpp index 7f9e6430cc651..e32d124013d8b 100644 --- a/src/hotspot/share/classfile/systemDictionary.cpp +++ b/src/hotspot/share/classfile/systemDictionary.cpp @@ -261,17 +261,32 @@ Symbol* SystemDictionary::class_name_symbol(const char* name, Symbol* exception, if (name == nullptr) { THROW_MSG_NULL(exception, "No class name given"); } - if ((int)strlen(name) > Symbol::max_length()) { + size_t name_len = strlen(name); + if (name_len > static_cast(Symbol::max_length())) { // It's impossible to create this class; the name cannot fit - // into the constant pool. - Exceptions::fthrow(THREAD_AND_LOCATION, exception, - "Class name exceeds maximum length of %d: %s", - Symbol::max_length(), - name); + // into the constant pool. If necessary report an abridged name + // in the exception message. + if (name_len > static_cast(MaxStringPrintSize)) { + Exceptions::fthrow(THREAD_AND_LOCATION, exception, + "Class name exceeds maximum length of %d: %.*s ... (%zu characters omitted) ... %.*s", + Symbol::max_length(), + MaxStringPrintSize / 2, + name, + name_len - 2 * (MaxStringPrintSize / 2), // allows for odd value + MaxStringPrintSize / 2, + name + name_len - MaxStringPrintSize / 2); + } + else { + Exceptions::fthrow(THREAD_AND_LOCATION, exception, + "Class name exceeds maximum length of %d: %s", + Symbol::max_length(), + name); + } return nullptr; } // Callers should ensure that the name is never an illegal UTF8 string. - assert(UTF8::is_legal_utf8((const unsigned char*)name, (int)strlen(name), false), + assert(UTF8::is_legal_utf8((const unsigned char*)name, + static_cast(name_len), false), "Class name is not a valid utf8 string."); // Make a new symbol for the class name. diff --git a/test/hotspot/jtreg/runtime/exceptionMsgs/NoClassDefFoundError/NoClassDefFoundErrorTest.java b/test/hotspot/jtreg/runtime/exceptionMsgs/NoClassDefFoundError/NoClassDefFoundErrorTest.java index d369f77f32433..b03d627485014 100644 --- a/test/hotspot/jtreg/runtime/exceptionMsgs/NoClassDefFoundError/NoClassDefFoundErrorTest.java +++ b/test/hotspot/jtreg/runtime/exceptionMsgs/NoClassDefFoundError/NoClassDefFoundErrorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,12 +23,12 @@ /* * @test - * @bug 8056900 + * @bug 8056900 8338888 * @summary Verifies message returned with NoClassDefFoundError exception. * @library /test/lib * @modules java.base/jdk.internal.misc * java.compiler - * @run main/native NoClassDefFoundErrorTest + * @run main/native/othervm -Xlog:exceptions=info NoClassDefFoundErrorTest */ import jdk.test.lib.compiler.InMemoryJavaCompiler; @@ -36,8 +36,14 @@ public class NoClassDefFoundErrorTest { + // Use the specified name static native void callDefineClass(String className); static native void callFindClass(String className); + // Use a name longer than a Java string - returns false + // if native allocation failed. + static native boolean tryCallDefineClass(); + static native boolean tryCallFindClass(); + static { System.loadLibrary("NoClassDefFoundErrorTest"); } @@ -54,7 +60,7 @@ public static void main(String args[]) throws Exception { tooBigClassName = tooBigClassName.append(tooBigClassName); } - // Test JVM_DefineClass() with long name. + System.out.println("Test JVM_DefineClass() with long name"); try { unsafe.defineClass(tooBigClassName.toString(), klassbuf, 4, klassbuf.length - 4, null, null); throw new RuntimeException("defineClass did not throw expected NoClassDefFoundError"); @@ -64,7 +70,7 @@ public static void main(String args[]) throws Exception { } } - // Test JNI_DefineClass() with long name. + System.out.println("Test JNI_DefineClass() with long name"); try { callDefineClass(tooBigClassName.toString()); throw new RuntimeException("DefineClass did not throw expected NoClassDefFoundError"); @@ -74,17 +80,17 @@ public static void main(String args[]) throws Exception { } } - // Test JNI_FindClass() with long name. + System.out.println("Test JNI_FindClass() with long name"); try { callFindClass(tooBigClassName.toString()); - throw new RuntimeException("DefineClass did not throw expected NoClassDefFoundError"); + throw new RuntimeException("FindClass did not throw expected NoClassDefFoundError"); } catch (NoClassDefFoundError e) { if (!e.getMessage().contains("Class name exceeds maximum length of ")) { throw new RuntimeException("Wrong NoClassDefFoundError: " + e.getMessage()); } } - // Test JNI_FindClass() with null name. + System.out.println("Test JNI_FindClass() with null name"); try { callFindClass(null); throw new RuntimeException("FindClass did not throw expected NoClassDefFoundError"); @@ -93,5 +99,31 @@ public static void main(String args[]) throws Exception { throw new RuntimeException("Wrong NoClassDefFoundError: " + e.getMessage()); } } + + System.out.println("Test JNI_DefineClass() with giant name"); + try { + if (tryCallDefineClass()) { + throw new RuntimeException("DefineClass did not throw expected NoClassDefFoundError"); + } else { + System.out.println("Test skipped due to native allocation failure"); + } + } catch (NoClassDefFoundError e) { + if (!e.getMessage().contains("Class name exceeds maximum length of ")) { + throw new RuntimeException("Wrong NoClassDefFoundError: " + e.getMessage()); + } + } + + System.out.println("Test JNI_FindClass() with giant name"); + try { + if (tryCallFindClass()) { + throw new RuntimeException("FindClass did not throw expected NoClassDefFoundError"); + } else { + System.out.println("Test skipped due to native allocation failure"); + } + } catch (NoClassDefFoundError e) { + if (!e.getMessage().contains("Class name exceeds maximum length of ")) { + throw new RuntimeException("Wrong NoClassDefFoundError: " + e.getMessage()); + } + } } } diff --git a/test/hotspot/jtreg/runtime/exceptionMsgs/NoClassDefFoundError/libNoClassDefFoundErrorTest.c b/test/hotspot/jtreg/runtime/exceptionMsgs/NoClassDefFoundError/libNoClassDefFoundErrorTest.c index 607d2541a8994..023f299a5d404 100644 --- a/test/hotspot/jtreg/runtime/exceptionMsgs/NoClassDefFoundError/libNoClassDefFoundErrorTest.c +++ b/test/hotspot/jtreg/runtime/exceptionMsgs/NoClassDefFoundError/libNoClassDefFoundErrorTest.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,6 +22,10 @@ */ #include +#include +#include +#include + JNIEXPORT void JNICALL Java_NoClassDefFoundErrorTest_callDefineClass(JNIEnv *env, jclass klass, jstring className) { @@ -42,3 +46,34 @@ Java_NoClassDefFoundErrorTest_callFindClass(JNIEnv *env, jclass klass, jstring c } +static char* giant_string() { + size_t len = ((size_t)INT_MAX) + 3; + char* c_name = malloc(len * sizeof(char)); + if (c_name != NULL) { + memset(c_name, 'Y', len - 1); + c_name[len - 1] = '\0'; + } + return c_name; +} + +JNIEXPORT jboolean JNICALL +Java_NoClassDefFoundErrorTest_tryCallDefineClass(JNIEnv *env, jclass klass) { + char* c_name = giant_string(); + if (c_name != NULL) { + (*env)->DefineClass(env, c_name, NULL, NULL, 0); + free(c_name); + return JNI_TRUE; + } + return JNI_FALSE; +} + +JNIEXPORT jboolean JNICALL +Java_NoClassDefFoundErrorTest_tryCallFindClass(JNIEnv *env, jclass klass) { + char* c_name = giant_string(); + if (c_name != NULL) { + jclass cls = (*env)->FindClass(env, c_name); + free(c_name); + return JNI_TRUE; + } + return JNI_FALSE; +} From 26e3d535ad4d6e5d78ca50941cfa39dd337892a9 Mon Sep 17 00:00:00 2001 From: Brent Christian Date: Wed, 28 Aug 2024 22:54:38 +0000 Subject: [PATCH 67/72] 8338716: Re-visit "interrupt handling" in jdk.internal.loader.Resource Reviewed-by: alanb --- .../classes/jdk/internal/loader/Resource.java | 36 ++----------------- 1 file changed, 3 insertions(+), 33 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/loader/Resource.java b/src/java.base/share/classes/jdk/internal/loader/Resource.java index d119c4b5eae9f..b72f4df7d52eb 100644 --- a/src/java.base/share/classes/jdk/internal/loader/Resource.java +++ b/src/java.base/share/classes/jdk/internal/loader/Resource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,7 +28,6 @@ import java.io.EOFException; import java.net.URL; import java.io.IOException; -import java.io.InterruptedIOException; import java.io.InputStream; import java.security.CodeSigner; import java.util.jar.Manifest; @@ -87,25 +86,8 @@ public byte[] getBytes() throws IOException { // Get stream before content length so that a FileNotFoundException // can propagate upwards without being caught too early InputStream in = cachedInputStream(); - - // This code has been uglified to protect against interrupts. - // Even if a thread has been interrupted when loading resources, - // the IO should not abort, so must carefully retry, failing only - // if the retry leads to some other IO exception. - - boolean isInterrupted = Thread.interrupted(); - int len; - for (;;) { - try { - len = getContentLength(); - break; - } catch (InterruptedIOException iioe) { - Thread.interrupted(); - isInterrupted = true; - } - } - try { + int len = getContentLength(); b = new byte[0]; if (len == -1) len = Integer.MAX_VALUE; int pos = 0; @@ -121,13 +103,7 @@ public byte[] getBytes() throws IOException { } else { bytesToRead = b.length - pos; } - int cc = 0; - try { - cc = in.read(b, pos, bytesToRead); - } catch (InterruptedIOException iioe) { - Thread.interrupted(); - isInterrupted = true; - } + int cc = in.read(b, pos, bytesToRead); if (cc < 0) { if (len != Integer.MAX_VALUE) { throw new EOFException("Detect premature EOF"); @@ -143,13 +119,7 @@ public byte[] getBytes() throws IOException { } finally { try { in.close(); - } catch (InterruptedIOException iioe) { - isInterrupted = true; } catch (IOException ignore) {} - - if (isInterrupted) { - Thread.currentThread().interrupt(); - } } return b; } From 0ddcd7017576a0f9c97a74b7d47624ae06ed06d6 Mon Sep 17 00:00:00 2001 From: Dean Long Date: Thu, 29 Aug 2024 00:34:11 +0000 Subject: [PATCH 68/72] 8335120: assert(!target->can_be_statically_bound() || target == cha_monomorphic_target) failed Reviewed-by: kvn, vlivanov --- src/hotspot/share/c1/c1_GraphBuilder.cpp | 2 +- src/hotspot/share/ci/ciMethod.cpp | 16 ++++++++++++++++ src/hotspot/share/ci/ciMethod.hpp | 2 ++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/hotspot/share/c1/c1_GraphBuilder.cpp b/src/hotspot/share/c1/c1_GraphBuilder.cpp index 780ced7f433b2..dc4475a4b8101 100644 --- a/src/hotspot/share/c1/c1_GraphBuilder.cpp +++ b/src/hotspot/share/c1/c1_GraphBuilder.cpp @@ -2116,7 +2116,7 @@ void GraphBuilder::invoke(Bytecodes::Code code) { } if (cha_monomorphic_target != nullptr) { - assert(!target->can_be_statically_bound() || target == cha_monomorphic_target, ""); + assert(!target->can_be_statically_bound() || target->equals(cha_monomorphic_target), ""); assert(!cha_monomorphic_target->is_abstract(), ""); if (!cha_monomorphic_target->can_be_statically_bound(actual_recv)) { // If we inlined because CHA revealed only a single target method, diff --git a/src/hotspot/share/ci/ciMethod.cpp b/src/hotspot/share/ci/ciMethod.cpp index b2ac3a8b2175c..94b405cdbfacd 100644 --- a/src/hotspot/share/ci/ciMethod.cpp +++ b/src/hotspot/share/ci/ciMethod.cpp @@ -781,6 +781,22 @@ bool ciMethod::can_omit_stack_trace() const { return _can_omit_stack_trace; } +// ------------------------------------------------------------------ +// ciMethod::equals +// +// Returns true if the methods are the same, taking redefined methods +// into account. +bool ciMethod::equals(const ciMethod* m) const { + if (this == m) return true; + VM_ENTRY_MARK; + Method* m1 = this->get_Method(); + Method* m2 = m->get_Method(); + if (m1->is_old()) m1 = m1->get_new_method(); + if (m2->is_old()) m2 = m2->get_new_method(); + return m1 == m2; +} + + // ------------------------------------------------------------------ // ciMethod::resolve_invoke // diff --git a/src/hotspot/share/ci/ciMethod.hpp b/src/hotspot/share/ci/ciMethod.hpp index 5a4a1dd1c7fc3..5cb63204d0b72 100644 --- a/src/hotspot/share/ci/ciMethod.hpp +++ b/src/hotspot/share/ci/ciMethod.hpp @@ -366,6 +366,8 @@ class ciMethod : public ciMetadata { bool can_omit_stack_trace() const; + bool equals(const ciMethod* m) const; + // Replay data methods static void dump_name_as_ascii(outputStream* st, Method* method); void dump_name_as_ascii(outputStream* st); From eb7ead58fd70822669d2aa1a0053814e58955f82 Mon Sep 17 00:00:00 2001 From: Prasanta Sadhukhan Date: Thu, 29 Aug 2024 05:03:15 +0000 Subject: [PATCH 69/72] 8336873: BasicSplitPaneDivider:oneTouchExpandableChanged() should mention that implementation depends on SplitPane.supportsOneTouchButtons property Reviewed-by: prr, abhiscxk --- .../javax/swing/plaf/basic/BasicSplitPaneDivider.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicSplitPaneDivider.java b/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicSplitPaneDivider.java index 188e675388776..119e12276c790 100644 --- a/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicSplitPaneDivider.java +++ b/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicSplitPaneDivider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -394,7 +394,10 @@ public void paint(Graphics g) { /** * Messaged when the oneTouchExpandable value of the JSplitPane the - * divider is contained in changes. Will create the + * divider is contained in changes. + * If a particular L&F supports this Swing + * "SplitPane.supportsOneTouchButtons" property + * it will create the * leftButton and rightButton if they are null * and corresponding JSplitPane supports oneTouchExpandable property. * Invalidates the corresponding JSplitPane as well. From 1383fec41756322bf2832c55633e46395b937b40 Mon Sep 17 00:00:00 2001 From: Kangcheng Xu Date: Thu, 29 Aug 2024 05:34:08 +0000 Subject: [PATCH 70/72] 8327381: Refactor type-improving transformations in BoolNode::Ideal to BoolNode::Value Reviewed-by: chagedorn, thartmann, jkarthikeyan, epeter --- src/hotspot/share/opto/subnode.cpp | 51 ++++--- src/hotspot/share/opto/subnode.hpp | 1 + .../compiler/c2/gvn/TestBoolNodeGVN.java | 135 ++++++++++++++++++ .../compiler/lib/ir_framework/IRNode.java | 2 +- 4 files changed, 169 insertions(+), 20 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/c2/gvn/TestBoolNodeGVN.java diff --git a/src/hotspot/share/opto/subnode.cpp b/src/hotspot/share/opto/subnode.cpp index 7eb7922c5fbd8..445eb16821443 100644 --- a/src/hotspot/share/opto/subnode.cpp +++ b/src/hotspot/share/opto/subnode.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -1623,25 +1623,8 @@ Node *BoolNode::Ideal(PhaseGVN *phase, bool can_reshape) { return new BoolNode( ncmp, _test.negate() ); } - // Change ((x & m) u<= m) or ((m & x) u<= m) to always true - // Same with ((x & m) u< m+1) and ((m & x) u< m+1) - if (cop == Op_CmpU && - cmp1_op == Op_AndI) { - Node* bound = nullptr; - if (_test._test == BoolTest::le) { - bound = cmp2; - } else if (_test._test == BoolTest::lt && - cmp2->Opcode() == Op_AddI && - cmp2->in(2)->find_int_con(0) == 1) { - bound = cmp2->in(1); - } - if (cmp1->in(2) == bound || cmp1->in(1) == bound) { - return ConINode::make(1); - } - } - // Change ((x & (m - 1)) u< m) into (m > 0) - // This is the off-by-one variant of the above + // This is the off-by-one variant of ((x & m) u<= m) if (cop == Op_CmpU && _test._test == BoolTest::lt && cmp1_op == Op_AndI) { @@ -1827,9 +1810,39 @@ Node *BoolNode::Ideal(PhaseGVN *phase, bool can_reshape) { } //------------------------------Value------------------------------------------ +// Change ((x & m) u<= m) or ((m & x) u<= m) to always true +// Same with ((x & m) u< m+1) and ((m & x) u< m+1) +const Type* BoolNode::Value_cmpu_and_mask(PhaseValues* phase) const { + Node* cmp = in(1); + if (cmp != nullptr && cmp->Opcode() == Op_CmpU) { + Node* cmp1 = cmp->in(1); + Node* cmp2 = cmp->in(2); + + if (cmp1->Opcode() == Op_AndI) { + Node* bound = nullptr; + if (_test._test == BoolTest::le) { + bound = cmp2; + } else if (_test._test == BoolTest::lt && cmp2->Opcode() == Op_AddI && cmp2->in(2)->find_int_con(0) == 1) { + bound = cmp2->in(1); + } + + if (cmp1->in(2) == bound || cmp1->in(1) == bound) { + return TypeInt::ONE; + } + } + } + + return nullptr; +} + // Simplify a Bool (convert condition codes to boolean (1 or 0)) node, // based on local information. If the input is constant, do it. const Type* BoolNode::Value(PhaseGVN* phase) const { + const Type* t = Value_cmpu_and_mask(phase); + if (t != nullptr) { + return t; + } + return _test.cc2logical( phase->type( in(1) ) ); } diff --git a/src/hotspot/share/opto/subnode.hpp b/src/hotspot/share/opto/subnode.hpp index a0c052645c69c..6ceaa851739d8 100644 --- a/src/hotspot/share/opto/subnode.hpp +++ b/src/hotspot/share/opto/subnode.hpp @@ -347,6 +347,7 @@ class BoolNode : public Node { BoolNode* negate(PhaseGVN* phase); virtual int Opcode() const; virtual Node *Ideal(PhaseGVN *phase, bool can_reshape); + const Type* Value_cmpu_and_mask(PhaseValues* phase) const; virtual const Type* Value(PhaseGVN* phase) const; virtual const Type *bottom_type() const { return TypeInt::BOOL; } uint match_edge(uint idx) const { return 0; } diff --git a/test/hotspot/jtreg/compiler/c2/gvn/TestBoolNodeGVN.java b/test/hotspot/jtreg/compiler/c2/gvn/TestBoolNodeGVN.java new file mode 100644 index 0000000000000..3f2fe7ecf053b --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/gvn/TestBoolNodeGVN.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2024 Red Hat and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.c2.gvn; + +import compiler.lib.ir_framework.*; + +import java.util.Random; + +/** + * @test + * @bug 8327381 + * @summary Refactor boolean node tautology transformations + * @library /test/lib / + * @run driver compiler.c2.gvn.TestBoolNodeGVN + */ +public class TestBoolNodeGVN { + public static void main(String[] args) { + TestFramework.run(); + testCorrectness(); + } + + /** + * Test changing ((x & m) u<= m) or ((m & x) u<= m) to always true, same with ((x & m) u< m+1) and ((m & x) u< m+1) + * The test is only applicable to x64, aarch64 and riscv64 for having Integer.compareUnsigned + * intrinsified. + */ + @Test + @Arguments(values = {Argument.DEFAULT, Argument.DEFAULT}) + @IR(failOn = IRNode.CMP_U, + phase = CompilePhase.AFTER_PARSING, + applyIfPlatformOr = {"x64", "true", "aarch64", "true", "riscv64", "true"}) + public static boolean testShouldReplaceCpmUCase1(int x, int m) { + return !(Integer.compareUnsigned((x & m), m) > 0); // assert in inversions to generates the pattern looking for + } + @Test + @Arguments(values = {Argument.DEFAULT, Argument.DEFAULT}) + @IR(failOn = IRNode.CMP_U, + phase = CompilePhase.AFTER_PARSING, + applyIfPlatformOr = {"x64", "true", "aarch64", "true", "riscv64", "true"}) + public static boolean testShouldReplaceCpmUCase2(int x, int m) { + return !(Integer.compareUnsigned((m & x), m) > 0); + } + + @Test + @Arguments(values = {Argument.DEFAULT, Argument.DEFAULT}) + @IR(failOn = IRNode.CMP_U, + phase = CompilePhase.AFTER_PARSING, + applyIfPlatformOr = {"x64", "true", "aarch64", "true", "riscv64", "true"}) + public static boolean testShouldReplaceCpmUCase3(int x, int m) { + return Integer.compareUnsigned((x & m), m + 1) < 0; + } + + @Test + @Arguments(values = {Argument.DEFAULT, Argument.DEFAULT}) + @IR(failOn = IRNode.CMP_U, + phase = CompilePhase.AFTER_PARSING, + applyIfPlatformOr = {"x64", "true", "aarch64", "true", "riscv64", "true"}) + public static boolean testShouldReplaceCpmUCase4(int x, int m) { + return Integer.compareUnsigned((m & x), m + 1) < 0; + } + + @Test + @Arguments(values = {Argument.DEFAULT, Argument.DEFAULT}) + @IR(counts = {IRNode.CMP_U, "1"}, + phase = CompilePhase.AFTER_PARSING, + applyIfPlatformOr = {"x64", "true", "aarch64", "true", "riscv64", "true"}) + public static boolean testShouldHaveCpmUCase1(int x, int m) { + return !(Integer.compareUnsigned((x & m), m - 1) > 0); + } + + @Test + @Arguments(values = {Argument.DEFAULT, Argument.DEFAULT}) + @IR(counts = {IRNode.CMP_U, "1"}, + phase = CompilePhase.AFTER_PARSING, + applyIfPlatformOr = {"x64", "true", "aarch64", "true", "riscv64", "true"}) + public static boolean testShouldHaveCpmUCase2(int x, int m) { + return !(Integer.compareUnsigned((m & x), m - 1) > 0); + } + + @Test + @Arguments(values = {Argument.DEFAULT, Argument.DEFAULT}) + @IR(counts = {IRNode.CMP_U, "1"}, + phase = CompilePhase.AFTER_PARSING, + applyIfPlatformOr = {"x64", "true", "aarch64", "true", "riscv64", "true"}) + public static boolean testShouldHaveCpmUCase3(int x, int m) { + return Integer.compareUnsigned((x & m), m + 2) < 0; + } + + @Test + @Arguments(values = {Argument.DEFAULT, Argument.DEFAULT}) + @IR(counts = {IRNode.CMP_U, "1"}, + phase = CompilePhase.AFTER_PARSING, + applyIfPlatformOr = {"x64", "true", "aarch64", "true", "riscv64", "true"}) + public static boolean testShouldHaveCpmUCase4(int x, int m) { + return Integer.compareUnsigned((m & x), m + 2) < 0; + } + + private static void testCorrectness() { + int[] values = { + 0, 1, 5, 8, 16, 42, 100, new Random().nextInt(0, Integer.MAX_VALUE), Integer.MAX_VALUE + }; + + for (int x : values) { + for (int m : values) { + if (!testShouldReplaceCpmUCase1(x, m) | + !testShouldReplaceCpmUCase2(x, m) | + !testShouldReplaceCpmUCase3(x, m) | + !testShouldReplaceCpmUCase4(x, m)) { + throw new RuntimeException("Bad result for x = " + x + " and m = " + m + ", expected always true"); + } + } + } + } +} diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java index efa7ccadfda1b..1b554df9e674d 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java @@ -439,7 +439,7 @@ public class IRNode { public static final String CMP_U = PREFIX + "CMP_U" + POSTFIX; static { - beforeMatchingNameRegex(CMP_U, "CmpU"); + beforeMatchingNameRegex(CMP_U, "CmpU\\b"); } public static final String CMP_U3 = PREFIX + "CMP_U3" + POSTFIX; From 0b4a7d534204b7b3b041f5117282dd13b1c7c62f Mon Sep 17 00:00:00 2001 From: Jan Lahoda Date: Thu, 29 Aug 2024 06:25:27 +0000 Subject: [PATCH 71/72] 8324859: Improve error recovery Reviewed-by: mcimadamore --- .../sun/tools/javac/parser/JavacParser.java | 93 ++- .../tools/javac/parser/JavacParserTest.java | 533 +++++++++++++++++- 2 files changed, 622 insertions(+), 4 deletions(-) diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java index 6fc93fdc591ab..27cb44bebba15 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java @@ -59,6 +59,7 @@ import static com.sun.tools.javac.parser.Tokens.TokenKind.GT; import static com.sun.tools.javac.parser.Tokens.TokenKind.IMPORT; import static com.sun.tools.javac.parser.Tokens.TokenKind.LT; +import com.sun.tools.javac.parser.VirtualParser.VirtualScanner; import static com.sun.tools.javac.tree.JCTree.Tag.*; import static com.sun.tools.javac.resources.CompilerProperties.Fragments.ImplicitAndExplicitNotAllowed; import static com.sun.tools.javac.resources.CompilerProperties.Fragments.VarAndExplicitNotAllowed; @@ -5019,13 +5020,17 @@ protected JCTree methodDeclaratorRest(int pos, // Parsing formalParameters sets the receiverParam, if present List params = List.nil(); List thrown = List.nil(); + boolean unclosedParameterList; if (!isRecord || name != names.init || token.kind == LPAREN) { params = formalParameters(); + unclosedParameterList = token.pos == endPosTable.errorEndPos; if (!isVoid) type = bracketsOpt(type); if (token.kind == THROWS) { nextToken(); thrown = qualidentList(true); } + } else { + unclosedParameterList = false; } saveDanglingDocComments(dc); @@ -5039,14 +5044,18 @@ protected JCTree methodDeclaratorRest(int pos, if (token.kind == DEFAULT) { accept(DEFAULT); defaultValue = annotationValue(); + accept(SEMI); } else { defaultValue = null; + accept(SEMI, tk -> Errors.Expected2(LBRACE, SEMI)); } - accept(SEMI); if (token.pos <= endPosTable.errorEndPos) { // error recovery - skip(false, true, false, false); - if (token.kind == LBRACE) { + // look if there is a probable missing opening brace, + // and if yes, parse as a block + boolean parseAsBlock = openingBraceMissing(unclosedParameterList); + + if (parseAsBlock) { body = block(); } } @@ -5063,6 +5072,84 @@ protected JCTree methodDeclaratorRest(int pos, } } + /** + * After seeing a method header, and not seeing an opening left brace, + * attempt to estimate if acting as if the left brace was present and + * parsing the upcoming code will get better results than not parsing + * the code as a block. + * + * The estimate is as follows: + * - tokens are skipped until member, statement or identifier is found, + * - then, if there is a left brace, parse as a block, + * - otherwise, if the head was broken, do not parse as a block, + * - otherwise, look at the next token and: + * - if it definitelly starts a statement, parse as a block, + * - otherwise, if it is a closing/right brace, count opening and closing + * braces in the rest of the file, to see if imaginarily "adding" an opening + * brace would lead to a balanced count - if yes, parse as a block, + * - otherwise, speculatively parse the following code as a block, and if + * it contains statements that cannot be members, parse as a block, + * - otherwise, don't parse as a block. + * + * @param unclosedParameterList whether there was a serious problem in the + * parameters list + * @return true if and only if the following code should be parsed as a block. + */ + private boolean openingBraceMissing(boolean unclosedParameterList) { + skip(false, true, !unclosedParameterList, !unclosedParameterList); + + if (token.kind == LBRACE) { + return true; + } else if (unclosedParameterList) { + return false; + } else { + return switch (token.kind) { + //definitelly sees a statement: + case CASE, DEFAULT, IF, FOR, WHILE, DO, TRY, SWITCH, + RETURN, THROW, BREAK, CONTINUE, ELSE, FINALLY, + CATCH, THIS, SUPER, NEW -> true; + case RBRACE -> { + //check if adding an opening brace would balance out + //the opening and closing braces: + int braceBalance = 1; + VirtualScanner virtualScanner = new VirtualScanner(S); + + virtualScanner.nextToken(); + + while (virtualScanner.token().kind != TokenKind.EOF) { + switch (virtualScanner.token().kind) { + case LBRACE -> braceBalance++; + case RBRACE -> braceBalance--; + } + virtualScanner.nextToken(); + } + + yield braceBalance == 0; + } + default -> { + //speculatively try to parse as a block, and check + //if the result would suggest there is a block + //e.g.: it contains a statement that is not + //a member declaration + JavacParser speculative = new VirtualParser(this); + JCBlock speculativeResult = + speculative.block(); + if (!speculativeResult.stats.isEmpty()) { + JCStatement last = speculativeResult.stats.last(); + yield !speculativeResult.stats.stream().allMatch(s -> s.hasTag(VARDEF) || + s.hasTag(CLASSDEF) || + s.hasTag(BLOCK) || + s == last) || + !(last instanceof JCExpressionStatement exprStatement && + exprStatement.expr.hasTag(ERRONEOUS)); + } else { + yield false; + } + } + }; + } + } + /** QualidentList = [Annotations] Qualident {"," [Annotations] Qualident} */ List qualidentList(boolean allowAnnos) { diff --git a/test/langtools/tools/javac/parser/JavacParserTest.java b/test/langtools/tools/javac/parser/JavacParserTest.java index 547de1088b460..40ab577a5d135 100644 --- a/test/langtools/tools/javac/parser/JavacParserTest.java +++ b/test/langtools/tools/javac/parser/JavacParserTest.java @@ -23,7 +23,7 @@ /* * @test - * @bug 7073631 7159445 7156633 8028235 8065753 8205418 8205913 8228451 8237041 8253584 8246774 8256411 8256149 8259050 8266436 8267221 8271928 8275097 8293897 8295401 8304671 8310326 8312093 8312204 8315452 8337976 + * @bug 7073631 7159445 7156633 8028235 8065753 8205418 8205913 8228451 8237041 8253584 8246774 8256411 8256149 8259050 8266436 8267221 8271928 8275097 8293897 8295401 8304671 8310326 8312093 8312204 8315452 8337976 8324859 * @summary tests error and diagnostics positions * @author Jan Lahoda * @modules jdk.compiler/com.sun.tools.javac.api @@ -2483,6 +2483,537 @@ public class Test { codes); } + @Test //JDK-8324859 + void testImplicitlyDeclaredClassesConfusion1() throws IOException { + String code = """ + package tests; + public class TestB { + public static boolean test() // missing open brace + return true; + } + public static boolean test2() { + return true; + } + }"""; + DiagnosticCollector coll = + new DiagnosticCollector<>(); + JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll, + List.of("--enable-preview", "--source", SOURCE_VERSION), + null, Arrays.asList(new MyFileObject(code))); + CompilationUnitTree cut = ct.parse().iterator().next(); + + List codes = new LinkedList<>(); + + for (Diagnostic d : coll.getDiagnostics()) { + codes.add(d.getLineNumber() + ":" + d.getColumnNumber() + ":" + d.getCode()); + } + + assertEquals("testImplicitlyDeclaredClassesConfusion1: " + codes, + List.of("3:33:compiler.err.expected2"), + codes); + String result = toStringWithErrors(cut).replaceAll("\\R", "\n"); + System.out.println("RESULT\n" + result); + assertEquals("incorrect AST", + result, + """ + package tests; + \n\ + public class TestB { + \n\ + public static boolean test() { + return true; + } + \n\ + public static boolean test2() { + return true; + } + }"""); + } + + @Test //JDK-8324859 + void testImplicitlyDeclaredClassesConfusion2() throws IOException { + String code = """ + package tests; + public class TestB { + public static boolean test() // missing open brace + + public static boolean test2() { + return true; + } + } """; + DiagnosticCollector coll = + new DiagnosticCollector<>(); + JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll, + List.of("--enable-preview", "--source", SOURCE_VERSION), + null, Arrays.asList(new MyFileObject(code))); + CompilationUnitTree cut = ct.parse().iterator().next(); + + List codes = new LinkedList<>(); + + for (Diagnostic d : coll.getDiagnostics()) { + codes.add(d.getLineNumber() + ":" + d.getColumnNumber() + ":" + d.getCode()); + } + + assertEquals("testImplicitlyDeclaredClassesConfusion2: " + codes, + List.of("3:33:compiler.err.expected2"), + codes); + String result = toStringWithErrors(cut).replaceAll("\\R", "\n"); + System.out.println("RESULT\n" + result); + assertEquals("incorrect AST", + result, + """ + package tests; + \n\ + public class TestB { + \n\ + public static boolean test(); + \n\ + public static boolean test2() { + return true; + } + }"""); + } + + @Test //JDK-8324859 + void testImplicitlyDeclaredClassesConfusion3() throws IOException { + String code = """ + package tests; + public class TestB { + public static boolean test() // missing open brace + } + """; + DiagnosticCollector coll = + new DiagnosticCollector<>(); + JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll, + List.of("--enable-preview", "--source", SOURCE_VERSION), + null, Arrays.asList(new MyFileObject(code))); + CompilationUnitTree cut = ct.parse().iterator().next(); + + List codes = new LinkedList<>(); + + for (Diagnostic d : coll.getDiagnostics()) { + codes.add(d.getLineNumber() + ":" + d.getColumnNumber() + ":" + d.getCode()); + } + + assertEquals("testImplicitlyDeclaredClassesConfusion3: " + codes, + List.of("3:33:compiler.err.expected2"), + codes); + String result = toStringWithErrors(cut).replaceAll("\\R", "\n"); + System.out.println("RESULT\n" + result); + assertEquals("incorrect AST", + result, + """ + package tests; + \n\ + public class TestB { + \n\ + public static boolean test(); + }"""); + } + + @Test //JDK-8324859 + void testImplicitlyDeclaredClassesConfusion4() throws IOException { + String code = """ + package tests; + public class TestB { + public static boolean test() // missing open brace + } + } + """; + DiagnosticCollector coll = + new DiagnosticCollector<>(); + JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll, + List.of("--enable-preview", "--source", SOURCE_VERSION), + null, Arrays.asList(new MyFileObject(code))); + CompilationUnitTree cut = ct.parse().iterator().next(); + + List codes = new LinkedList<>(); + + for (Diagnostic d : coll.getDiagnostics()) { + codes.add(d.getLineNumber() + ":" + d.getColumnNumber() + ":" + d.getCode()); + } + + assertEquals("testImplicitlyDeclaredClassesConfusion4: " + codes, + List.of("3:33:compiler.err.expected2"), + codes); + String result = toStringWithErrors(cut).replaceAll("\\R", "\n"); + System.out.println("RESULT\n" + result); + assertEquals("incorrect AST", + result, + """ + package tests; + \n\ + public class TestB { + \n\ + public static boolean test() { + } + }"""); + } + + @Test //JDK-8324859 + void testImplicitlyDeclaredClassesConfusion5() throws IOException { + String code = """ + package tests; + public class TestB { + public static boolean test(String, + } + class T {} + """; + DiagnosticCollector coll = + new DiagnosticCollector<>(); + JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll, + List.of("--enable-preview", "--source", SOURCE_VERSION), + null, Arrays.asList(new MyFileObject(code))); + CompilationUnitTree cut = ct.parse().iterator().next(); + + List codes = new LinkedList<>(); + + for (Diagnostic d : coll.getDiagnostics()) { + codes.add(d.getLineNumber() + ":" + d.getColumnNumber() + ":" + d.getCode()); + } + + assertEquals("testImplicitlyDeclaredClassesConfusion5: " + codes, + List.of("3:38:compiler.err.expected", + "4:1:compiler.err.illegal.start.of.type"), + codes); + String result = toStringWithErrors(cut).replaceAll("\\R", "\n"); + System.out.println("RESULT\n" + result); + assertEquals("incorrect AST", + result, + """ + package tests; + \n\ + public class TestB { + \n\ + public static boolean test(String , (ERROR: ) ); + } + class T { + }"""); + } + + @Test //JDK-8324859 + void testImplicitlyDeclaredClassesConfusion6() throws IOException { + String code = """ + package tests; + public class TestB { + private Object testMethod(final String arg1 final String arg2) { + return null; + } + } + """; + DiagnosticCollector coll = + new DiagnosticCollector<>(); + JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll, + List.of("--enable-preview", "--source", SOURCE_VERSION), + null, Arrays.asList(new MyFileObject(code))); + CompilationUnitTree cut = ct.parse().iterator().next(); + + List codes = new LinkedList<>(); + + for (Diagnostic d : coll.getDiagnostics()) { + codes.add(d.getLineNumber() + ":" + d.getColumnNumber() + ":" + d.getCode()); + } + + assertEquals("testImplicitlyDeclaredClassesConfusion5: " + codes, + List.of("3:48:compiler.err.expected3", + "3:66:compiler.err.expected"), + codes); + String result = toStringWithErrors(cut).replaceAll("\\R", "\n"); + System.out.println("RESULT\n" + result); + assertEquals("incorrect AST", + result, + """ + package tests; + \n\ + public class TestB { + \n\ + private Object testMethod(final String arg1); + final String arg2; + { + return null; + } + }"""); + } + + @Test //JDK-8324859 + void testImplicitlyDeclaredClassesConfusion7() throws IOException { + //after 'default' attribute value, only semicolon (';') is expected, + //not left brace ('{'): + String code = """ + package tests; + public @interface A { + public String value() default "" + } + """; + DiagnosticCollector coll = + new DiagnosticCollector<>(); + JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll, + List.of("--enable-preview", "--source", SOURCE_VERSION), + null, Arrays.asList(new MyFileObject(code))); + CompilationUnitTree cut = ct.parse().iterator().next(); + + List codes = new LinkedList<>(); + + for (Diagnostic d : coll.getDiagnostics()) { + codes.add(d.getLineNumber() + ":" + d.getColumnNumber() + ":" + d.getCode()); + } + + assertEquals("testImplicitlyDeclaredClassesConfusion5: " + codes, + List.of("3:37:compiler.err.expected"), + codes); + String result = toStringWithErrors(cut).replaceAll("\\R", "\n"); + System.out.println("RESULT\n" + result); + assertEquals("incorrect AST", + result, + """ + package tests; + \n\ + public @interface A { + \n\ + public String value() default ""; + }"""); + } + + @Test //JDK-8324859 + void testImplicitlyDeclaredClassesConfusion10() throws IOException { + String code = """ + package tests; + public class TestB { + public static boolean test() // missing open brace + String s = ""; + return s.isEmpty(); + } + public static boolean test2() { + return true; + } + }"""; + DiagnosticCollector coll = + new DiagnosticCollector<>(); + JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll, + List.of("--enable-preview", "--source", SOURCE_VERSION), + null, Arrays.asList(new MyFileObject(code))); + CompilationUnitTree cut = ct.parse().iterator().next(); + + List codes = new LinkedList<>(); + + for (Diagnostic d : coll.getDiagnostics()) { + codes.add(d.getLineNumber() + ":" + d.getColumnNumber() + ":" + d.getCode()); + } + + assertEquals("testImplicitlyDeclaredClassesConfusion1: " + codes, + List.of("3:33:compiler.err.expected2"), + codes); + String result = toStringWithErrors(cut).replaceAll("\\R", "\n"); + System.out.println("RESULT\n" + result); + assertEquals("incorrect AST", + result, + """ + package tests; + + public class TestB { + \n\ + public static boolean test() { + String s = ""; + return s.isEmpty(); + } + \n\ + public static boolean test2() { + return true; + } + }"""); + } + + @Test //JDK-8324859 + void testImplicitlyDeclaredClassesConfusion11() throws IOException { + String code = """ + package tests; + public class TestB { + public static boolean test() // missing open brace + String s = ""; //field declaration + public static boolean test2() { + return true; + } + }"""; + DiagnosticCollector coll = + new DiagnosticCollector<>(); + JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll, + List.of("--enable-preview", "--source", SOURCE_VERSION), + null, Arrays.asList(new MyFileObject(code))); + CompilationUnitTree cut = ct.parse().iterator().next(); + + List codes = new LinkedList<>(); + + for (Diagnostic d : coll.getDiagnostics()) { + codes.add(d.getLineNumber() + ":" + d.getColumnNumber() + ":" + d.getCode()); + } + + assertEquals("testImplicitlyDeclaredClassesConfusion1: " + codes, + List.of("3:33:compiler.err.expected2"), + codes); + String result = toStringWithErrors(cut).replaceAll("\\R", "\n"); + System.out.println("RESULT\n" + result); + assertEquals("incorrect AST", + result, + """ + package tests; + \n\ + public class TestB { + \n\ + public static boolean test(); + String s = ""; + \n\ + public static boolean test2() { + return true; + } + }"""); + } + + @Test //JDK-8324859 + void testImplicitlyDeclaredClassesConfusion12() throws IOException { + String code = """ + package tests; + public class TestB { + public static boolean test() // missing open brace + final String s = ""; + return s.isEmpty(); + } + public static boolean test2() { + return true; + } + }"""; + DiagnosticCollector coll = + new DiagnosticCollector<>(); + JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll, + List.of("--enable-preview", "--source", SOURCE_VERSION), + null, Arrays.asList(new MyFileObject(code))); + CompilationUnitTree cut = ct.parse().iterator().next(); + + List codes = new LinkedList<>(); + + for (Diagnostic d : coll.getDiagnostics()) { + codes.add(d.getLineNumber() + ":" + d.getColumnNumber() + ":" + d.getCode()); + } + + assertEquals("testImplicitlyDeclaredClassesConfusion1: " + codes, + List.of("3:33:compiler.err.expected2"), + codes); + String result = toStringWithErrors(cut).replaceAll("\\R", "\n"); + System.out.println("RESULT\n" + result); + assertEquals("incorrect AST", + result, + """ + package tests; + \n\ + public class TestB { + \n\ + public static boolean test() { + final String s = ""; + return s.isEmpty(); + } + \n\ + public static boolean test2() { + return true; + } + }"""); + } + + @Test //JDK-8324859 + void testImplicitlyDeclaredClassesConfusion13() throws IOException { + String code = """ + package tests; + public class TestB { + public static boolean test() // missing open brace + final String s = ""; //field declaration? + public static boolean test2() { + return true; + } + }"""; + DiagnosticCollector coll = + new DiagnosticCollector<>(); + JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll, + List.of("--enable-preview", "--source", SOURCE_VERSION), + null, Arrays.asList(new MyFileObject(code))); + CompilationUnitTree cut = ct.parse().iterator().next(); + + List codes = new LinkedList<>(); + + for (Diagnostic d : coll.getDiagnostics()) { + codes.add(d.getLineNumber() + ":" + d.getColumnNumber() + ":" + d.getCode()); + } + + assertEquals("testImplicitlyDeclaredClassesConfusion1: " + codes, + List.of("3:33:compiler.err.expected2"), + codes); + String result = toStringWithErrors(cut).replaceAll("\\R", "\n"); + System.out.println("RESULT\n" + result); + assertEquals("incorrect AST", + result, + """ + package tests; + \n\ + public class TestB { + \n\ + public static boolean test(); + final String s = ""; + \n\ + public static boolean test2() { + return true; + } + }"""); + } + + @Test //JDK-8324859 + void testImplicitlyDeclaredClassesConfusion14() throws IOException { + String code = """ + package tests; + public class TestB { + public static boolean test() // missing open brace + String s = ""; + s.length(); + if (true); //force parse as block + public static boolean test2() { + return true; + } + }"""; + DiagnosticCollector coll = + new DiagnosticCollector<>(); + JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll, + List.of("--enable-preview", "--source", SOURCE_VERSION), + null, Arrays.asList(new MyFileObject(code))); + CompilationUnitTree cut = ct.parse().iterator().next(); + + List codes = new LinkedList<>(); + + for (Diagnostic d : coll.getDiagnostics()) { + codes.add(d.getLineNumber() + ":" + d.getColumnNumber() + ":" + d.getCode()); + } + + assertEquals("testImplicitlyDeclaredClassesConfusion1: " + codes, + List.of("3:33:compiler.err.expected2", + "7:5:compiler.err.illegal.start.of.expr"), + codes); + String result = toStringWithErrors(cut).replaceAll("\\R", "\n"); + System.out.println("RESULT\n" + result); + assertEquals("incorrect AST", + result, + """ + package tests; + \n\ + public class TestB { + \n\ + public static boolean test() { + String s = ""; + s.length(); + if (true) ; + (ERROR: ); + } + \n\ + public static boolean test2() { + return true; + } + }"""); + } + void run(String[] args) throws Exception { int passed = 0, failed = 0; final Pattern p = (args != null && args.length > 0) From ff59532ddd3002df61e46d58b3f29d26c78295da Mon Sep 17 00:00:00 2001 From: Jan Lahoda Date: Thu, 29 Aug 2024 06:28:05 +0000 Subject: [PATCH 72/72] 8338678: Erroneous parameterized type represented as Reviewed-by: vromero --- .../com/sun/tools/javac/code/Type.java | 6 +- .../com/sun/tools/javac/comp/Attr.java | 8 ++ .../tools/javac/recovery/AttrRecovery.java | 92 ++++++++++++++++++- 3 files changed, 100 insertions(+), 6 deletions(-) diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java index 702b51d3b96b6..151c39fae40f0 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java @@ -2338,7 +2338,7 @@ public ErrorType(Type originalType, TypeSymbol tsym) { this.originalType = (originalType == null ? noType : originalType); } - private ErrorType(Type originalType, TypeSymbol tsym, + public ErrorType(Type originalType, TypeSymbol tsym, List metadata) { super(noType, List.nil(), null, metadata); this.tsym = tsym; @@ -2393,10 +2393,6 @@ public R accept(Type.Visitor v, S s) { public boolean isCompound() { return false; } public boolean isInterface() { return false; } - public List allparams() { return List.nil(); } - @DefinedBy(Api.LANGUAGE_MODEL) - public List getTypeArguments() { return List.nil(); } - @DefinedBy(Api.LANGUAGE_MODEL) public TypeKind getKind() { return TypeKind.ERROR; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java index 54edc19560bd1..44ccbade4a900 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java @@ -5081,6 +5081,14 @@ public void visitTypeApply(JCTypeApply tree) { } owntype = types.createErrorType(tree.type); } + } else if (clazztype.hasTag(ERROR)) { + ErrorType parameterizedErroneous = + new ErrorType(clazztype.getOriginalType(), + clazztype.tsym, + clazztype.getMetadata()); + + parameterizedErroneous.typarams_field = actuals; + owntype = parameterizedErroneous; } result = check(tree, owntype, KindSelector.TYP, resultInfo); } diff --git a/test/langtools/tools/javac/recovery/AttrRecovery.java b/test/langtools/tools/javac/recovery/AttrRecovery.java index 629cef45c6270..19325420af7f9 100644 --- a/test/langtools/tools/javac/recovery/AttrRecovery.java +++ b/test/langtools/tools/javac/recovery/AttrRecovery.java @@ -23,7 +23,7 @@ /* * @test - * @bug 8301580 8322159 8333107 8332230 + * @bug 8301580 8322159 8333107 8332230 8338678 * @summary Verify error recovery w.r.t. Attr * @library /tools/lib * @enablePreview @@ -34,9 +34,23 @@ * @run main AttrRecovery */ +import com.sun.source.tree.VariableTree; +import com.sun.source.util.TaskEvent; +import com.sun.source.util.TaskListener; +import com.sun.source.util.TreePathScanner; +import com.sun.source.util.Trees; import java.nio.file.Path; +import java.util.IdentityHashMap; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.stream.Collectors; +import javax.lang.model.element.Element; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.tools.Diagnostic; import toolbox.JavacTask; import toolbox.Task.Expect; @@ -234,4 +248,80 @@ public Undefined g(Undefined u) { } } + @Test + public void testParameterizedErroneousType() throws Exception { + String code = """ + public class C { + Undefined1 variable1; + } + """; + Path curPath = Path.of("."); + List actual = new JavacTask(tb) + .options("-XDrawDiagnostics") + .sources(code) + .outdir(curPath) + .callback(task -> { + task.addTaskListener(new TaskListener() { + @Override + public void finished(TaskEvent e) { + Trees trees = Trees.instance(task); + + if (e.getKind() == TaskEvent.Kind.ANALYZE) { + new TreePathScanner() { + @Override + public Void visitVariable(VariableTree tree, Void p) { + VariableElement var = (VariableElement) trees.getElement(getCurrentPath()); + + trees.printMessage(Diagnostic.Kind.NOTE, type2String(var.asType()), tree, e.getCompilationUnit()); + + return super.visitVariable(tree, p); + } + }.scan(e.getCompilationUnit(), null); + } + } + Map identityRename = new IdentityHashMap<>(); + String type2String(TypeMirror type) { + StringBuilder result = new StringBuilder(); + + result.append(type.getKind()); + result.append(":"); + result.append(type.toString()); + + if (type.getKind() == TypeKind.DECLARED || + type.getKind() == TypeKind.ERROR) { + DeclaredType dt = (DeclaredType) type; + Element el = task.getTypes().asElement(dt); + result.append(":"); + result.append(el.toString()); + if (!dt.getTypeArguments().isEmpty()) { + result.append(dt.getTypeArguments() + .stream() + .map(tm -> type2String(tm)) + .collect(Collectors.joining(", ", "<", ">"))); + } + } else { + throw new AssertionError(type.getKind().name()); + } + + return result.toString(); + } + }); + }) + .run(Expect.FAIL) + .writeAll() + .getOutputLines(OutputKind.DIRECT); + + List expected = List.of( + "C.java:2:5: compiler.err.cant.resolve.location: kindname.class, Undefined1, , , (compiler.misc.location: kindname.class, C, null)", + "C.java:2:16: compiler.err.cant.resolve.location: kindname.class, Undefined2, , , (compiler.misc.location: kindname.class, C, null)", + "C.java:2:28: compiler.err.cant.resolve.location: kindname.class, Undefined3, , , (compiler.misc.location: kindname.class, C, null)", + "C.java:2:40: compiler.note.proc.messager: ERROR:Undefined1:Undefined1", + "3 errors" + ); + + if (!Objects.equals(actual, expected)) { + error("Expected: " + expected + ", but got: " + actual); + } + } + }