diff --git a/make/ToolsJdk.gmk b/make/ToolsJdk.gmk index b04d7820c916..54d590799fd9 100644 --- a/make/ToolsJdk.gmk +++ b/make/ToolsJdk.gmk @@ -1,5 +1,5 @@ # -# Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -81,6 +81,9 @@ TOOL_GENERATEEXTRAPROPERTIES = $(JAVA_SMALL) -cp $(BUILDTOOLS_OUTPUTDIR)/jdk_too TOOL_GENERATECASEFOLDING = $(JAVA_SMALL) -cp $(BUILDTOOLS_OUTPUTDIR)/jdk_tools_classes \ build.tools.generatecharacter.GenerateCaseFolding +TOOL_GENERATESPECIALCASING = $(JAVA_SMALL) -cp $(BUILDTOOLS_OUTPUTDIR)/jdk_tools_classes \ + build.tools.generatespecialcasing.GenerateSpecialCasing + TOOL_MAKEZIPREPRODUCIBLE = $(JAVA_SMALL) -cp $(BUILDTOOLS_OUTPUTDIR)/jdk_tools_classes \ build.tools.makezipreproducible.MakeZipReproducible diff --git a/make/autoconf/libraries.m4 b/make/autoconf/libraries.m4 index 5daacdc1ced5..99851bfd3760 100644 --- a/make/autoconf/libraries.m4 +++ b/make/autoconf/libraries.m4 @@ -1,5 +1,5 @@ # -# Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -149,7 +149,7 @@ AC_DEFUN_ONCE([LIB_SETUP_LIBRARIES], if test "x$OPENJDK_TARGET_OS" = xwindows; then BASIC_JVM_LIBS="$BASIC_JVM_LIBS kernel32.lib user32.lib gdi32.lib winspool.lib \ comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib powrprof.lib uuid.lib \ - ws2_32.lib winmm.lib version.lib psapi.lib" + ws2_32.lib winmm.lib version.lib" fi LIB_SETUP_JVM_LIBS(BUILD) LIB_SETUP_JVM_LIBS(TARGET) diff --git a/make/jdk/src/classes/build/tools/generatespecialcasing/GenerateSpecialCasing.java b/make/jdk/src/classes/build/tools/generatespecialcasing/GenerateSpecialCasing.java new file mode 100644 index 000000000000..fccc6264092d --- /dev/null +++ b/make/jdk/src/classes/build/tools/generatespecialcasing/GenerateSpecialCasing.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package build.tools.generatespecialcasing; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Parses UCD's "SpecialCasing.txt" file and extract selected special case + * mappings, then reformats each entry into `Entry` instances in + * `java.lang.ConditionalSpecialCasing` class. + * + * Arguments to this utility: + * args[0]: Full path to the "ConditionalSpecialCasing" template file + * args[1]: Full path to the "SpecialCasing.txt" UCD file + * args[2]: Full path to the generated output file + */ +public class GenerateSpecialCasing { + // Represents a code point with selected special casing. + private record Entry(String codePoint, List lowerCase, + List upperCase, String language, + String condition) {}; + + public static void main(String[] args) throws IOException { + var templateFile = Paths.get(args[0]); + var specialCasingFile = Paths.get(args[1]); + var gensrcFile = Paths.get(args[2]); + + List entries = Files.lines(specialCasingFile) + .filter(Predicate.not(l -> l.startsWith("#") || l.isBlank())) + .map(l -> l.replaceFirst("#.*$", "").split(";")) + // "U+0130" (LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE) + // is needed even if it is non-conditional + .filter(l -> !l[4].isBlank() || l[0].equals("0130")) + .map(l -> new Entry(l[0], + Arrays.asList(l[1].trim().split(" ")), + Arrays.asList(l[3].trim().split(" ")), + l[4].replaceFirst("[A-Z].*$", "").trim(), + l[4].replaceFirst("^[ a-z]*", "").toUpperCase(Locale.ROOT).trim())) + .toList(); + + // Generate output file + Files.write(gensrcFile, + Files.lines(templateFile) + .flatMap(l -> { + if (l.trim().equals("%%%SpecialCasing%%%")) { + return entries.stream().map(GenerateSpecialCasing::entryToString); + } else { + return Stream.of(l); + } + }) + .collect(Collectors.toList()), + StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + } + + static String entryToString(Entry e) { + var codePoint = e.codePoint(); + if (codePoint == null || codePoint.isBlank()) { + throw new RuntimeException("Corrupt entry: " + e); + } + + return " new Entry(%s, new char[]{%s}, new char[]{%s}, %s, %s)," + .formatted( + "0x" + codePoint, + e.lowerCase().stream().map(cp -> cp.isEmpty() ? "" : "0x"+cp).collect(Collectors.joining(",")), + e.upperCase().stream().map(cp -> cp.isEmpty() ? "" : "0x"+cp).collect(Collectors.joining(",")), + "\"" + e.language() + "\"", + e.condition().isEmpty() ? "NONE" : e.condition()); + } +} diff --git a/make/modules/java.base/gensrc/GensrcCharacterData.gmk b/make/modules/java.base/gensrc/GensrcCharacterData.gmk index d7947d907e2f..c15acd9fa2a0 100644 --- a/make/modules/java.base/gensrc/GensrcCharacterData.gmk +++ b/make/modules/java.base/gensrc/GensrcCharacterData.gmk @@ -1,5 +1,5 @@ # -# Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -34,6 +34,9 @@ GENSRC_CHARACTERDATA := CHARACTERDATA_TEMPLATES = $(MODULE_SRC)/share/classes/java/lang UNICODEDATA = $(MODULE_SRC)/share/data/unicodedata +SPECIALCASING = $(UNICODEDATA)/SpecialCasing.txt +DERIVEDCOREPROPS = $(UNICODEDATA)/DerivedCoreProperties.txt +PROPLIST = $(UNICODEDATA)/PropList.txt ifneq ($(DEBUG_LEVEL), release) ifeq ($(ALLOW_ABSOLUTE_PATHS_IN_OUTPUT), true) @@ -49,9 +52,9 @@ define SetupCharacterData $(TOOL_GENERATECHARACTER) $2 $(DEBUG_OPTION) \ -template $(CHARACTERDATA_TEMPLATES)/$1.java.template \ -spec $(UNICODEDATA)/UnicodeData.txt \ - -specialcasing $(UNICODEDATA)/SpecialCasing.txt \ - -proplist $(UNICODEDATA)/PropList.txt \ - -derivedprops $(UNICODEDATA)/DerivedCoreProperties.txt \ + -specialcasing $(SPECIALCASING) \ + -proplist $(PROPLIST) \ + -derivedprops $(DERIVEDCOREPROPS) \ -emojidata $(UNICODEDATA)/emoji/emoji-data.txt \ -o $(SUPPORT_OUTPUTDIR)/gensrc/java.base/java/lang/$1.java \ -usecharforbyte $3 @@ -88,6 +91,40 @@ $(GENSRC_STRINGCASEFOLDING): $(BUILD_TOOLS_JDK) $(STRINGCASEFOLDING_TEMPLATE) $( TARGETS += $(GENSRC_STRINGCASEFOLDING) +################################################################################ +# Rule to create $(SUPPORT_OUTPUTDIR)/gensrc/java.base/java/lang/ConditionalSpecialCasing.java +################################################################################ + +GENSRC_CONDITIONALSPECIALCASING := $(SUPPORT_OUTPUTDIR)/gensrc/java.base/java/lang/ConditionalSpecialCasing.java +GENSRC_CONDITIONALSPECIALCASING_SPECIALCASING_TMP := $(GENSRC_CONDITIONALSPECIALCASING).specialcasing.tmp +GENSRC_CONDITIONALSPECIALCASING_PROPS_TMP := $(GENSRC_CONDITIONALSPECIALCASING).props.tmp + +CONDITIONALSPECIALCASINGTEMP := $(MODULE_SRC)/share/classes/java/lang/ConditionalSpecialCasing.java.template +FINALSIGMAPARAMS := Cased Case_Ignorable +SOFTDOTTEDPARAMS := Soft_Dotted + +$(GENSRC_CONDITIONALSPECIALCASING): $(BUILD_TOOLS_JDK) $(CONDITIONALSPECIALCASINGTEMP) \ + $(SPECIALCASING) $(DERIVEDCOREPROPS) $(PROPLIST) + $(call LogInfo, Generating $@) + $(call MakeTargetDir) + $(TOOL_GENERATESPECIALCASING) \ + $(CONDITIONALSPECIALCASINGTEMP) \ + $(SPECIALCASING) \ + $(GENSRC_CONDITIONALSPECIALCASING_SPECIALCASING_TMP) + $(TOOL_GENERATEEXTRAPROPERTIES) \ + $(GENSRC_CONDITIONALSPECIALCASING_SPECIALCASING_TMP) \ + $(DERIVEDCOREPROPS) \ + $(GENSRC_CONDITIONALSPECIALCASING_PROPS_TMP) \ + $(FINALSIGMAPARAMS) + $(TOOL_GENERATEEXTRAPROPERTIES) \ + $(GENSRC_CONDITIONALSPECIALCASING_PROPS_TMP) \ + $(PROPLIST) \ + $(GENSRC_CONDITIONALSPECIALCASING) \ + $(SOFTDOTTEDPARAMS) + $(RM) $(GENSRC_CONDITIONALSPECIALCASING_SPECIALCASING_TMP) + $(RM) $(GENSRC_CONDITIONALSPECIALCASING_PROPS_TMP) + +TARGETS += $(GENSRC_CONDITIONALSPECIALCASING) endif # include guard include MakeIncludeEnd.gmk diff --git a/make/modules/java.management/Lib.gmk b/make/modules/java.management/Lib.gmk index 89c99266fcca..cdc70c773fc6 100644 --- a/make/modules/java.management/Lib.gmk +++ b/make/modules/java.management/Lib.gmk @@ -1,5 +1,5 @@ # -# Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -36,7 +36,7 @@ $(eval $(call SetupJdkLibrary, BUILD_LIBMANAGEMENT, \ OPTIMIZATION := HIGH, \ JDK_LIBS := java.base:libjava java.base:libjvm, \ LIBS_aix := -lperfstat, \ - LIBS_windows := advapi32.lib psapi.lib, \ + LIBS_windows := advapi32.lib, \ )) TARGETS += $(BUILD_LIBMANAGEMENT) diff --git a/make/modules/jdk.attach/Lib.gmk b/make/modules/jdk.attach/Lib.gmk index 78437d761d2e..92fb58d73178 100644 --- a/make/modules/jdk.attach/Lib.gmk +++ b/make/modules/jdk.attach/Lib.gmk @@ -1,5 +1,5 @@ # -# Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -31,20 +31,12 @@ include LibCommon.gmk ## Build libattach ################################################################################ -ifeq ($(call isTargetOs, windows), true) - # In (at least) VS2013 and later, -DPSAPI_VERSION=1 is needed to generate - # a binary that is compatible with windows versions older than 7/2008R2. - # See MSDN documentation for GetProcessMemoryInfo for more information. - LIBATTACH_CFLAGS := -DPSAPI_VERSION=1 -endif - $(eval $(call SetupJdkLibrary, BUILD_LIBATTACH, \ NAME := attach, \ OPTIMIZATION := LOW, \ - CFLAGS := $(LIBATTACH_CFLAGS), \ CFLAGS_windows := -Gy, \ JDK_LIBS := java.base:libjava, \ - LIBS_windows := advapi32.lib psapi.lib, \ + LIBS_windows := advapi32.lib, \ )) TARGETS += $(BUILD_LIBATTACH) diff --git a/make/modules/jdk.hotspot.agent/Lib.gmk b/make/modules/jdk.hotspot.agent/Lib.gmk index da02e0dab394..5c4506550464 100644 --- a/make/modules/jdk.hotspot.agent/Lib.gmk +++ b/make/modules/jdk.hotspot.agent/Lib.gmk @@ -60,6 +60,9 @@ LIBSAPROC_EXCLUDE_FILES := ifneq ($(call And, $(call isTargetOs, linux) $(call isTargetCpu, x86_64 aarch64)), true) LIBSAPROC_EXCLUDE_FILES := DwarfParser.cpp dwarf.cpp endif +ifneq ($(call And, $(call isTargetOs, linux) $(call isTargetCpu, aarch64)), true) + LIBSAPROC_EXCLUDE_FILES += AARCH64DwarfParser.cpp aarch64Dwarf.cpp LinuxAARCH64DebuggerLocal.cpp +endif $(eval $(call SetupJdkLibrary, BUILD_LIBSAPROC, \ NAME := saproc, \ diff --git a/make/modules/jdk.management/Lib.gmk b/make/modules/jdk.management/Lib.gmk index f65348e9381a..927c2c3529fe 100644 --- a/make/modules/jdk.management/Lib.gmk +++ b/make/modules/jdk.management/Lib.gmk @@ -31,21 +31,13 @@ include LibCommon.gmk ## Build libmanagement_ext ################################################################################ -ifeq ($(call isTargetOs, windows), true) - # In (at least) VS2013 and later, -DPSAPI_VERSION=1 is needed to generate - # a binary that is compatible with windows versions older than 7/2008R2. - # See MSDN documentation for GetProcessMemoryInfo for more information. - LIBMANAGEMENT_EXT_CFLAGS := -DPSAPI_VERSION=1 -endif - $(eval $(call SetupJdkLibrary, BUILD_LIBMANAGEMENT_EXT, \ NAME := management_ext, \ OPTIMIZATION := HIGH, \ DISABLED_WARNINGS_clang_UnixOperatingSystem.c := format-nonliteral, \ - CFLAGS := $(LIBMANAGEMENT_EXT_CFLAGS), \ JDK_LIBS := java.base:libjava java.base:libjvm, \ LIBS_aix := -lperfstat, \ - LIBS_windows := advapi32.lib psapi.lib, \ + LIBS_windows := advapi32.lib, \ )) TARGETS += $(BUILD_LIBMANAGEMENT_EXT) diff --git a/src/hotspot/cpu/aarch64/aarch64.ad b/src/hotspot/cpu/aarch64/aarch64.ad index be9d79d03c7f..37c8e0ae011f 100644 --- a/src/hotspot/cpu/aarch64/aarch64.ad +++ b/src/hotspot/cpu/aarch64/aarch64.ad @@ -8228,7 +8228,7 @@ instruct encodeKlass_not_null(iRegNNoSp dst, iRegP src) %{ ins_encode %{ Register src_reg = as_Register($src$$reg); Register dst_reg = as_Register($dst$$reg); - __ encode_klass_not_null(dst_reg, src_reg); + __ encode_klass_not_null(dst_reg, src_reg, rscratch1); %} ins_pipe(ialu_reg); @@ -8243,11 +8243,7 @@ instruct decodeKlass_not_null(iRegPNoSp dst, iRegN src) %{ ins_encode %{ Register src_reg = as_Register($src$$reg); Register dst_reg = as_Register($dst$$reg); - if (dst_reg != src_reg) { - __ decode_klass_not_null(dst_reg, src_reg); - } else { - __ decode_klass_not_null(dst_reg); - } + __ decode_klass_not_null(dst_reg, src_reg, rscratch1); %} ins_pipe(ialu_reg); diff --git a/src/hotspot/cpu/aarch64/aarch64_vector.ad b/src/hotspot/cpu/aarch64/aarch64_vector.ad index c06c8b856b7f..b844ac561cc4 100644 --- a/src/hotspot/cpu/aarch64/aarch64_vector.ad +++ b/src/hotspot/cpu/aarch64/aarch64_vector.ad @@ -324,6 +324,17 @@ source %{ return false; } break; + case Op_DivVB: + case Op_DivVS: + case Op_DivVI: + case Op_DivVL: + // Integer vector divide is only available on SVE (SDIV for 32-bit and + // 64-bit elements). NEON has no integer vector divide instruction. + // BYTE/SHORT are widened to 32-bit, divided, and narrowed back. + if (UseSVE == 0) { + return false; + } + break; default: break; } @@ -348,6 +359,11 @@ source %{ case Op_CompressBitsV: case Op_ExpandBitsV: case Op_VectorBitwiseBlend: + // There is no native SVE divide for BYTE/SHORT elements (these are + // emulated by widening to 32-bit), so the masked variants are handled + // by an unpredicated divide combined with a VectorBlend. + case Op_DivVB: + case Op_DivVS: return false; case Op_SaturatingAddV: case Op_SaturatingSubV: @@ -1477,6 +1493,83 @@ instruct vdivD_masked(vReg dst_src1, vReg src2, pRegGov pg) %{ ins_pipe(pipe_slow); %} +// ------------------------------ Vector integer div --------------------------- + +// BYTE and SHORT have no native integer divide on SVE (SDIV only supports 32-bit +// and 64-bit elements), so they are emulated by widening each element to 32-bit, +// performing SDIV, and narrowing the result back. + +instruct vdivB_sve(vReg dst_src1, vReg src2, vReg vtmp1, vReg vtmp2, vReg vtmp3, vReg vtmp4) %{ + predicate(UseSVE > 0); + match(Set dst_src1 (DivVB dst_src1 src2)); + effect(TEMP_DEF dst_src1, TEMP vtmp1, TEMP vtmp2, TEMP vtmp3, TEMP vtmp4); + format %{ "vdivB_sve $dst_src1, $dst_src1, $src2" %} + ins_encode %{ + __ sve_sdiv_byte($dst_src1$$FloatRegister, $src2$$FloatRegister, + $vtmp1$$FloatRegister, $vtmp2$$FloatRegister, + $vtmp3$$FloatRegister, $vtmp4$$FloatRegister); + %} + ins_pipe(pipe_slow); +%} + +instruct vdivS_sve(vReg dst_src1, vReg src2, vReg vtmp1, vReg vtmp2) %{ + predicate(UseSVE > 0); + match(Set dst_src1 (DivVS dst_src1 src2)); + effect(TEMP_DEF dst_src1, TEMP vtmp1, TEMP vtmp2); + format %{ "vdivS_sve $dst_src1, $dst_src1, $src2" %} + ins_encode %{ + __ sve_sdiv_short($dst_src1$$FloatRegister, $src2$$FloatRegister, + $vtmp1$$FloatRegister, $vtmp2$$FloatRegister); + %} + ins_pipe(pipe_slow); +%} + +instruct vdivI_sve(vReg dst_src1, vReg src2) %{ + predicate(UseSVE > 0); + match(Set dst_src1 (DivVI dst_src1 src2)); + format %{ "vdivI_sve $dst_src1, $dst_src1, $src2" %} + ins_encode %{ + __ sve_sdiv($dst_src1$$FloatRegister, __ S, ptrue, $src2$$FloatRegister); + %} + ins_pipe(pipe_slow); +%} + +instruct vdivL_sve(vReg dst_src1, vReg src2) %{ + predicate(UseSVE > 0); + match(Set dst_src1 (DivVL dst_src1 src2)); + format %{ "vdivL_sve $dst_src1, $dst_src1, $src2" %} + ins_encode %{ + __ sve_sdiv($dst_src1$$FloatRegister, __ D, ptrue, $src2$$FloatRegister); + %} + ins_pipe(pipe_slow); +%} + +// Vector integer div - predicated +// +// There is no native SVE divide for BYTE/SHORT elements (these are emulated by +// widening to 32-bit), so the masked variants are handled by an unpredicated +// divide combined with a VectorBlend. + +instruct vdivI_masked(vReg dst_src1, vReg src2, pRegGov pg) %{ + predicate(UseSVE > 0); + match(Set dst_src1 (DivVI (Binary dst_src1 src2) pg)); + format %{ "vdivI_masked $dst_src1, $pg, $dst_src1, $src2" %} + ins_encode %{ + __ sve_sdiv($dst_src1$$FloatRegister, __ S, $pg$$PRegister, $src2$$FloatRegister); + %} + ins_pipe(pipe_slow); +%} + +instruct vdivL_masked(vReg dst_src1, vReg src2, pRegGov pg) %{ + predicate(UseSVE > 0); + match(Set dst_src1 (DivVL (Binary dst_src1 src2) pg)); + format %{ "vdivL_masked $dst_src1, $pg, $dst_src1, $src2" %} + ins_encode %{ + __ sve_sdiv($dst_src1$$FloatRegister, __ D, $pg$$PRegister, $src2$$FloatRegister); + %} + ins_pipe(pipe_slow); +%} + // ------------------------------ Vector and ----------------------------------- // vector and diff --git a/src/hotspot/cpu/aarch64/aarch64_vector_ad.m4 b/src/hotspot/cpu/aarch64/aarch64_vector_ad.m4 index b749647ae1e4..70d27e5248a2 100644 --- a/src/hotspot/cpu/aarch64/aarch64_vector_ad.m4 +++ b/src/hotspot/cpu/aarch64/aarch64_vector_ad.m4 @@ -314,6 +314,17 @@ source %{ return false; } break; + case Op_DivVB: + case Op_DivVS: + case Op_DivVI: + case Op_DivVL: + // Integer vector divide is only available on SVE (SDIV for 32-bit and + // 64-bit elements). NEON has no integer vector divide instruction. + // BYTE/SHORT are widened to 32-bit, divided, and narrowed back. + if (UseSVE == 0) { + return false; + } + break; default: break; } @@ -338,6 +349,11 @@ source %{ case Op_CompressBitsV: case Op_ExpandBitsV: case Op_VectorBitwiseBlend: + // There is no native SVE divide for BYTE/SHORT elements (these are + // emulated by widening to 32-bit), so the masked variants are handled + // by an unpredicated divide combined with a VectorBlend. + case Op_DivVB: + case Op_DivVS: return false; case Op_SaturatingAddV: case Op_SaturatingSubV: @@ -842,6 +858,61 @@ BINARY_OP_NEON_SVE_PAIRWISE(vdivD, DivVD, fdiv, sve_fdiv, D) // vector float div - predicated BINARY_OP_PREDICATE(vdivF, DivVF, sve_fdiv, S) BINARY_OP_PREDICATE(vdivD, DivVD, sve_fdiv, D) + +dnl +dnl BINARY_OP_SVE_ONLY($1, $2, $3, $4 ) +dnl BINARY_OP_SVE_ONLY(rule_name, op_name, insn, size) +define(`BINARY_OP_SVE_ONLY', ` +instruct $1_sve(vReg dst_src1, vReg src2) %{ + predicate(UseSVE > 0); + match(Set dst_src1 ($2 dst_src1 src2)); + format %{ "$1_sve $dst_src1, $dst_src1, $src2" %} + ins_encode %{ + __ $3($dst_src1$$FloatRegister, __ $4, ptrue, $src2$$FloatRegister); + %} + ins_pipe(pipe_slow); +%}')dnl +dnl +// ------------------------------ Vector integer div --------------------------- + +// BYTE and SHORT have no native integer divide on SVE (SDIV only supports 32-bit +// and 64-bit elements), so they are emulated by widening each element to 32-bit, +// performing SDIV, and narrowing the result back. + +instruct vdivB_sve(vReg dst_src1, vReg src2, vReg vtmp1, vReg vtmp2, vReg vtmp3, vReg vtmp4) %{ + predicate(UseSVE > 0); + match(Set dst_src1 (DivVB dst_src1 src2)); + effect(TEMP_DEF dst_src1, TEMP vtmp1, TEMP vtmp2, TEMP vtmp3, TEMP vtmp4); + format %{ "vdivB_sve $dst_src1, $dst_src1, $src2" %} + ins_encode %{ + __ sve_sdiv_byte($dst_src1$$FloatRegister, $src2$$FloatRegister, + $vtmp1$$FloatRegister, $vtmp2$$FloatRegister, + $vtmp3$$FloatRegister, $vtmp4$$FloatRegister); + %} + ins_pipe(pipe_slow); +%} + +instruct vdivS_sve(vReg dst_src1, vReg src2, vReg vtmp1, vReg vtmp2) %{ + predicate(UseSVE > 0); + match(Set dst_src1 (DivVS dst_src1 src2)); + effect(TEMP_DEF dst_src1, TEMP vtmp1, TEMP vtmp2); + format %{ "vdivS_sve $dst_src1, $dst_src1, $src2" %} + ins_encode %{ + __ sve_sdiv_short($dst_src1$$FloatRegister, $src2$$FloatRegister, + $vtmp1$$FloatRegister, $vtmp2$$FloatRegister); + %} + ins_pipe(pipe_slow); +%} +BINARY_OP_SVE_ONLY(vdivI, DivVI, sve_sdiv, S) +BINARY_OP_SVE_ONLY(vdivL, DivVL, sve_sdiv, D) + +// Vector integer div - predicated +// +// There is no native SVE divide for BYTE/SHORT elements (these are emulated by +// widening to 32-bit), so the masked variants are handled by an unpredicated +// divide combined with a VectorBlend. +BINARY_OP_PREDICATE(vdivI, DivVI, sve_sdiv, S) +BINARY_OP_PREDICATE(vdivL, DivVL, sve_sdiv, D) dnl dnl BITWISE_OP_IMM($1, $2, $3, $4, $5, $6 ) dnl BITWISE_OP_IMM(rule_name, type, op_name, insn, size, basic_type) diff --git a/src/hotspot/cpu/aarch64/assembler_aarch64.hpp b/src/hotspot/cpu/aarch64/assembler_aarch64.hpp index a81213c5ae4a..89f3dd63f426 100644 --- a/src/hotspot/cpu/aarch64/assembler_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/assembler_aarch64.hpp @@ -3475,13 +3475,15 @@ template public: -// SVE integer arithmetic - predicate +// SVE Arithmetic - Predicated #define INSN(NAME, op1, op2) \ void NAME(FloatRegister Zdn_or_Zd_or_Vd, SIMD_RegVariant T, PRegister Pg, FloatRegister Znm_or_Vn) { \ - assert(T != Q, "invalid register variant"); \ + assert(ALLOWED, "invalid register variant"); \ sve_predicate_reg_insn(op1, op2, Zdn_or_Zd_or_Vd, T, Pg, Znm_or_Vn); \ } +// SVE Integer Arithmetic - Predicated (B/H/S/D element sizes). +#define ALLOWED (T != Q) INSN(sve_abs, 0b00000100, 0b010110101); // vector abs, unary INSN(sve_add, 0b00000100, 0b000000000); // vector add INSN(sve_and, 0b00000100, 0b011010000); // vector and @@ -3511,15 +3513,16 @@ template INSN(sve_umaxv, 0b00000100, 0b001001001); // unsigned maximum reduction to scalar INSN(sve_umin, 0b00000100, 0b001011000); // unsigned minimum vectors INSN(sve_uminv, 0b00000100, 0b001011001); // unsigned minimum reduction to scalar -#undef INSN +#undef ALLOWED -// SVE floating-point arithmetic - predicate -#define INSN(NAME, op1, op2) \ - void NAME(FloatRegister Zd_or_Zdn_or_Vd, SIMD_RegVariant T, PRegister Pg, FloatRegister Zn_or_Zm) { \ - assert(T == H || T == S || T == D, "invalid register variant"); \ - sve_predicate_reg_insn(op1, op2, Zd_or_Zdn_or_Vd, T, Pg, Zn_or_Zm); \ - } +// SVE Integer Binary Arithmetic - Predicated (S/D element sizes). +#define ALLOWED (T == S || T == D) + INSN(sve_sdiv, 0b00000100, 0b010100000); // signed divide + INSN(sve_udiv, 0b00000100, 0b010101000); // unsigned divide +#undef ALLOWED +// SVE Floating-point Arithmetic - Predicated (H/S/D element sizes). +#define ALLOWED (T == H || T == S || T == D) INSN(sve_fabd, 0b01100101, 0b001000100); // floating-point absolute difference INSN(sve_fabs, 0b00000100, 0b011100101); INSN(sve_fadd, 0b01100101, 0b000000100); @@ -3537,9 +3540,18 @@ template INSN(sve_frintp, 0b01100101, 0b000001101); // floating-point round to integral value, toward plus infinity INSN(sve_fsqrt, 0b01100101, 0b001101101); INSN(sve_fsub, 0b01100101, 0b000001100); +#undef ALLOWED + +// SVE2 Signed/Unsigned Saturating Add/Sub - Predicated (B/H/S/D element sizes). +#define ALLOWED (T != Q) + INSN(sve_sqadd, 0b01000100, 0b011000100); // signed saturating add + INSN(sve_sqsub, 0b01000100, 0b011010100); // signed saturating sub + INSN(sve_uqadd, 0b01000100, 0b011001100); // unsigned saturating add + INSN(sve_uqsub, 0b01000100, 0b011011100); // unsigned saturating sub +#undef ALLOWED #undef INSN - // SVE multiple-add/sub - predicated +// SVE multiple-add/sub - predicated #define INSN(NAME, op0, op1, op2) \ void NAME(FloatRegister Zda, SIMD_RegVariant T, PRegister Pg, FloatRegister Zn, FloatRegister Zm) { \ starti; \ @@ -4347,20 +4359,6 @@ template INSN(sve_smullt, /* is_unsigned */ false, /* is_top */ true ); // Signed widening multiply of top elements #undef INSN -// SVE2 saturating operations - predicate -#define INSN(NAME, op1, op2) \ - void NAME(FloatRegister Zdn, SIMD_RegVariant T, PRegister Pg, FloatRegister Znm) { \ - assert(T != Q, "invalid register variant"); \ - sve_predicate_reg_insn(op1, op2, Zdn, T, Pg, Znm); \ - } - - INSN(sve_sqadd, 0b01000100, 0b011000100); // signed saturating add - INSN(sve_sqsub, 0b01000100, 0b011010100); // signed saturating sub - INSN(sve_uqadd, 0b01000100, 0b011001100); // unsigned saturating add - INSN(sve_uqsub, 0b01000100, 0b011011100); // unsigned saturating sub - -#undef INSN - Assembler(CodeBuffer* code) : AbstractAssembler(code) { MACOS_AARCH64_ONLY(os::thread_wx_enable_write()); } diff --git a/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp index 0290a200366d..5b77d15457fe 100644 --- a/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp @@ -1324,7 +1324,7 @@ void LIR_Assembler::emit_typecheck_helper(LIR_OpTypeCheck *op, Label* success, L __ bind(not_null); Register recv = k_RInfo; - __ load_klass(recv, obj); + __ load_klass(recv, obj, rscratch1); type_profile_helper(mdo, md, data, recv); } else { __ cbz(obj, *obj_is_null); @@ -1340,15 +1340,15 @@ void LIR_Assembler::emit_typecheck_helper(LIR_OpTypeCheck *op, Label* success, L if (op->fast_check()) { // get object class // not a safepoint as obj null check happens earlier - __ load_klass(rscratch1, obj); - __ cmp( rscratch1, k_RInfo); + __ load_klass(rscratch2, obj, rscratch1); + __ cmp( rscratch2, k_RInfo); __ br(Assembler::NE, *failure_target); // successful cast, fall through to profile or jump } else { // get object class // not a safepoint as obj null check happens earlier - __ load_klass(klass_RInfo, obj); + __ load_klass(klass_RInfo, obj, rscratch1); if (k->is_loaded()) { // See if we get an immediate positive hit __ ldr(rscratch1, Address(klass_RInfo, int64_t(k->super_check_offset()))); @@ -1433,15 +1433,15 @@ void LIR_Assembler::emit_opTypeCheck(LIR_OpTypeCheck* op) { __ bind(not_null); Register recv = k_RInfo; - __ load_klass(recv, value); + __ load_klass(recv, value, rscratch1); type_profile_helper(mdo, md, data, recv); } else { __ cbz(value, done); } add_debug_info_for_null_check_here(op->info_for_exception()); - __ load_klass(k_RInfo, array); - __ load_klass(klass_RInfo, value); + __ load_klass(k_RInfo, array, rscratch1); + __ load_klass(klass_RInfo, value, rscratch1); // get instance klass (it's already uncompressed) __ ldr(k_RInfo, Address(k_RInfo, ObjArrayKlass::element_klass_offset())); @@ -2258,14 +2258,14 @@ void LIR_Assembler::emit_arraycopy(LIR_OpArrayCopy* op) { // an instance type. if (flags & LIR_OpArrayCopy::type_check) { if (!(flags & LIR_OpArrayCopy::LIR_OpArrayCopy::dst_objarray)) { - __ load_klass(tmp, dst); + __ load_klass(tmp, dst, rscratch1); __ ldrw(rscratch1, Address(tmp, in_bytes(Klass::layout_helper_offset()))); __ cmpw(rscratch1, Klass::_lh_neutral_value); __ br(Assembler::GE, *stub->entry()); } if (!(flags & LIR_OpArrayCopy::LIR_OpArrayCopy::src_objarray)) { - __ load_klass(tmp, src); + __ load_klass(tmp, src, rscratch1); __ ldrw(rscratch1, Address(tmp, in_bytes(Klass::layout_helper_offset()))); __ cmpw(rscratch1, Klass::_lh_neutral_value); __ br(Assembler::GE, *stub->entry()); @@ -2319,8 +2319,8 @@ void LIR_Assembler::emit_arraycopy(LIR_OpArrayCopy* op) { __ PUSH(src, dst); - __ load_klass(src, src); - __ load_klass(dst, dst); + __ load_klass(src, src, rscratch1); + __ load_klass(dst, dst, rscratch1); __ check_klass_subtype_fast_path(src, dst, tmp, &cont, &slow, nullptr); @@ -2344,9 +2344,9 @@ void LIR_Assembler::emit_arraycopy(LIR_OpArrayCopy* op) { assert(flags & mask, "one of the two should be known to be an object array"); if (!(flags & LIR_OpArrayCopy::src_objarray)) { - __ load_klass(tmp, src); + __ load_klass(tmp, src, rscratch1); } else if (!(flags & LIR_OpArrayCopy::dst_objarray)) { - __ load_klass(tmp, dst); + __ load_klass(tmp, dst, rscratch1); } int lh_offset = in_bytes(Klass::layout_helper_offset()); Address klass_lh_addr(tmp, lh_offset); @@ -2372,7 +2372,7 @@ void LIR_Assembler::emit_arraycopy(LIR_OpArrayCopy* op) { __ uxtw(c_rarg2, length); assert_different_registers(c_rarg2, dst); - __ load_klass(c_rarg4, dst); + __ load_klass(c_rarg4, dst, rscratch1); __ ldr(c_rarg4, Address(c_rarg4, ObjArrayKlass::element_klass_offset())); __ ldrw(c_rarg3, Address(c_rarg4, Klass::super_check_offset_offset())); __ far_call(RuntimeAddress(copyfunc_addr)); @@ -2428,12 +2428,12 @@ void LIR_Assembler::emit_arraycopy(LIR_OpArrayCopy* op) { __ mov_metadata(tmp, default_type->constant_encoding()); if (basic_type != T_OBJECT) { - __ cmp_klass(dst, tmp, rscratch1); + __ cmp_klass(dst, tmp, rscratch1, rscratch2); __ br(Assembler::NE, halt); - __ cmp_klass(src, tmp, rscratch1); + __ cmp_klass(src, tmp, rscratch1, rscratch2); __ br(Assembler::EQ, known_ok); } else { - __ cmp_klass(dst, tmp, rscratch1); + __ cmp_klass(dst, tmp, rscratch1, rscratch2); __ br(Assembler::EQ, known_ok); __ cmp(src, dst); __ br(Assembler::EQ, known_ok); @@ -2508,7 +2508,7 @@ void LIR_Assembler::emit_load_klass(LIR_OpLoadKlass* op) { add_debug_info_for_null_check_here(info); } - __ load_klass(result, obj); + __ load_klass(result, obj, rscratch1); } void LIR_Assembler::emit_profile_call(LIR_OpProfileCall* op) { @@ -2550,7 +2550,7 @@ void LIR_Assembler::emit_profile_call(LIR_OpProfileCall* op) { // Fall back to runtime helper to handle the rest at runtime. __ mov_metadata(recv, known_klass->constant_encoding()); } else { - __ load_klass(recv, recv); + __ load_klass(recv, recv, rscratch1); } type_profile_helper(mdo, md, data, recv); } else { @@ -2636,7 +2636,7 @@ void LIR_Assembler::emit_profile_type(LIR_OpProfileType* op) { #ifdef ASSERT if (exact_klass != nullptr) { Label ok; - __ load_klass(tmp, tmp); + __ load_klass(tmp, tmp, rscratch1); __ mov_metadata(rscratch1, exact_klass->constant_encoding()); __ eor(rscratch1, tmp, rscratch1); __ cbz(rscratch1, ok); @@ -2649,7 +2649,7 @@ void LIR_Assembler::emit_profile_type(LIR_OpProfileType* op) { if (exact_klass != nullptr) { __ mov_metadata(tmp, exact_klass->constant_encoding()); } else { - __ load_klass(tmp, tmp); + __ load_klass(tmp, tmp, rscratch1); } __ ldr(rscratch2, mdo_addr); diff --git a/src/hotspot/cpu/aarch64/c1_MacroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/c1_MacroAssembler_aarch64.cpp index 89a9422ea488..f81c976d291e 100644 --- a/src/hotspot/cpu/aarch64/c1_MacroAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/c1_MacroAssembler_aarch64.cpp @@ -105,7 +105,7 @@ void C1_MacroAssembler::initialize_header(Register obj, Register klass, Register } else { mov(t1, checked_cast(markWord::prototype().value())); str(t1, Address(obj, oopDesc::mark_offset_in_bytes())); - encode_klass_not_null(t1, klass); // Take care not to kill klass + encode_klass_not_null(t1, klass, t1); // Take care not to kill klass strw(t1, Address(obj, oopDesc::klass_offset_in_bytes())); } diff --git a/src/hotspot/cpu/aarch64/c1_Runtime1_aarch64.cpp b/src/hotspot/cpu/aarch64/c1_Runtime1_aarch64.cpp index 449ad4f8a4c5..1745bb8aae98 100644 --- a/src/hotspot/cpu/aarch64/c1_Runtime1_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/c1_Runtime1_aarch64.cpp @@ -824,7 +824,7 @@ OopMapSet* Runtime1::generate_code_for(StubId id, StubAssembler* sasm) { // load the klass and check the has finalizer flag Label register_finalizer; Register t = r5; - __ load_klass(t, r0); + __ load_klass(t, r0, rscratch1); __ ldrb(t, Address(t, Klass::misc_flags_offset())); __ tbnz(t, exact_log2(KlassFlags::_misc_has_finalizer), register_finalizer); __ ret(lr); @@ -947,7 +947,7 @@ OopMapSet* Runtime1::generate_code_for(StubId id, StubAssembler* sasm) { __ br(Assembler::EQ, is_secondary); // Klass is a secondary superclass // Klass is a concrete class - __ load_klass(r5, obj); + __ load_klass(r5, obj, rscratch1); __ ldr(rscratch1, Address(r5, r3)); __ cmp(klass, rscratch1); __ cset(result, Assembler::EQ); @@ -955,7 +955,7 @@ OopMapSet* Runtime1::generate_code_for(StubId id, StubAssembler* sasm) { __ bind(is_secondary); - __ load_klass(obj, obj); + __ load_klass(obj, obj, rscratch1); // This is necessary because I am never in my own secondary_super list. __ cmp(obj, klass); diff --git a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp index fe9180bda5cc..7d490a99eaa9 100644 --- a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp @@ -167,7 +167,7 @@ void C2_MacroAssembler::fast_lock(Register obj, Register box, Register t1, } if (DiagnoseSyncOnValueBasedClasses != 0) { - load_klass(t1, obj); + load_klass(t1, obj, rscratch2); ldrb(t1, Address(t1, Klass::misc_flags_offset())); tst(t1, KlassFlags::_misc_is_value_based_class); br(Assembler::NE, slow_path); @@ -2970,3 +2970,42 @@ int C2_MacroAssembler::vector_iota_entry_index(BasicType bt) { ShouldNotReachHere(); } } + +// Vector integer division for BYTE elements. Each BYTE is widened to SHORT for +// the low and high halves of the register, divided using the SHORT helper +// (which widens further to INT), and the two SHORT result halves are narrowed +// back to BYTE. +void C2_MacroAssembler::sve_sdiv_byte(FloatRegister dst_src1, FloatRegister src2, + FloatRegister vtmp1, FloatRegister vtmp2, + FloatRegister vtmp3, FloatRegister vtmp4) { + assert_different_registers(dst_src1, src2, vtmp1, vtmp2, vtmp3, vtmp4); + FloatRegister src1 = dst_src1; + // Low half of the bytes -> SHORT, then divide (result SHORT in vtmp1). + sve_sunpklo(vtmp1, H, src1); + sve_sunpklo(vtmp2, H, src2); + sve_sdiv_short(vtmp1, vtmp2, vtmp3, vtmp4); + // High half of the bytes -> SHORT, then divide (result SHORT in src1). + sve_sunpkhi(src1, H, src1); + sve_sunpkhi(vtmp2, H, src2); + sve_sdiv_short(src1, vtmp2, vtmp3, vtmp4); + // Narrow the two SHORT result halves back to BYTE. + sve_uzp1(dst_src1, B, vtmp1, src1); +} + +// Vector integer division for SHORT elements, implemented by widening each +// element to 32 bits, performing SDIV, and narrowing back. +void C2_MacroAssembler::sve_sdiv_short(FloatRegister dst_src1, FloatRegister src2, + FloatRegister vtmp1, FloatRegister vtmp2) { + assert_different_registers(dst_src1, src2, vtmp1, vtmp2); + FloatRegister src1 = dst_src1; + // Low half: SHORT -> INT, then divide. + sve_sunpklo(vtmp1, S, src1); + sve_sunpklo(vtmp2, S, src2); + sve_sdiv(vtmp1, S, ptrue, vtmp2); + // High half: SHORT -> INT, then divide. + sve_sunpkhi(src1, S, src1); + sve_sunpkhi(vtmp2, S, src2); + sve_sdiv(src1, S, ptrue, vtmp2); + // Narrow the two INT result halves back to SHORT. + sve_uzp1(dst_src1, H, vtmp1, src1); +} \ No newline at end of file diff --git a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.hpp b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.hpp index f96d3ffb8635..778f5e465899 100644 --- a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.hpp @@ -253,4 +253,9 @@ void sve_cpy(FloatRegister dst, SIMD_RegVariant T, PRegister pg, int imm8, bool isMerge); int vector_iota_entry_index(BasicType bt); + + void sve_sdiv_byte(FloatRegister dst_src1, FloatRegister src2, FloatRegister vtmp1, + FloatRegister vtmp2, FloatRegister vtmp3, FloatRegister vtmp4); + void sve_sdiv_short(FloatRegister dst_src1, FloatRegister src2, + FloatRegister vtmp1, FloatRegister vtmp2); #endif // CPU_AARCH64_C2_MACROASSEMBLER_AARCH64_HPP diff --git a/src/hotspot/cpu/aarch64/compressedKlass_aarch64.cpp b/src/hotspot/cpu/aarch64/compressedKlass_aarch64.cpp index 3874c8cd54ef..7cc2a004c40d 100644 --- a/src/hotspot/cpu/aarch64/compressedKlass_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/compressedKlass_aarch64.cpp @@ -120,11 +120,7 @@ char* CompressedKlassPointers::reserve_address_space_for_compressed_classes(size return result; } -bool CompressedKlassPointers::check_klass_decode_mode(address base, int shift, const size_t range) { - return MacroAssembler::check_klass_decode_mode(base, shift, range); -} - -bool CompressedKlassPointers::set_klass_decode_mode() { +void CompressedKlassPointers::initialize_pd() { const size_t range = klass_range_end() - base(); - return MacroAssembler::set_klass_decode_mode(_base, _shift, range); + MacroAssembler::initialize_klass_decode_mode(_base, _shift, range); } diff --git a/src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp b/src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp index 22c2383816cd..0da237c133fc 100644 --- a/src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp @@ -1403,7 +1403,7 @@ void InterpreterMacroAssembler::profile_obj_type(Register obj, const Address& md b(next); bind(update); - load_klass(obj, obj); + load_klass(obj, obj, rscratch1); ldr(rscratch1, mdo_addr); eor(obj, obj, rscratch1); diff --git a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp index f2208aa0ad69..527e79459ec5 100644 --- a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp @@ -25,6 +25,7 @@ #include "asm/assembler.hpp" #include "asm/assembler.inline.hpp" +#include "cds/archiveBuilder.hpp" #include "ci/ciEnv.hpp" #include "code/compiledIC.hpp" #include "compiler/compileTask.hpp" @@ -5115,9 +5116,9 @@ void MacroAssembler::load_narrow_klass(Register dst, Register src) { } } -void MacroAssembler::load_klass(Register dst, Register src) { +void MacroAssembler::load_klass(Register dst, Register src, Register tmp) { load_narrow_klass(dst, src); - decode_klass_not_null(dst); + decode_klass_not_null(dst, dst, tmp); } void MacroAssembler::restore_cpu_control_state_after_jni(Register tmp1, Register tmp2) { @@ -5167,8 +5168,8 @@ void MacroAssembler::load_mirror(Register dst, Register method, Register tmp1, R resolve_oop_handle(dst, tmp1, tmp2); } -void MacroAssembler::cmp_klass(Register obj, Register klass, Register tmp) { - assert_different_registers(obj, klass, tmp); +void MacroAssembler::cmp_klass(Register obj, Register klass, Register tmp, Register tmp2) { + assert_different_registers(obj, klass, tmp, tmp2); if (UseCompactObjectHeaders) { load_narrow_klass_compact(tmp, obj); } else { @@ -5184,7 +5185,7 @@ void MacroAssembler::cmp_klass(Register obj, Register klass, Register tmp) { cmpw(klass, tmp); return; } - decode_klass_not_null(tmp); + decode_klass_not_null(tmp, tmp, tmp2); cmp(klass, tmp); } @@ -5199,11 +5200,11 @@ void MacroAssembler::cmp_klasses_from_objects(Register obj1, Register obj2, Regi cmpw(tmp1, tmp2); } -void MacroAssembler::store_klass(Register dst, Register src) { +void MacroAssembler::store_klass(Register dst, Register src, Register tmp) { // FIXME: Should this be a store release? concurrent gcs assumes // klass length is valid if klass field is not null. assert(!UseCompactObjectHeaders, "not with compact headers"); - encode_klass_not_null(src); + encode_klass_not_null(src, src, tmp); strw(src, Address(dst, oopDesc::klass_offset_in_bytes())); } @@ -5356,8 +5357,6 @@ MacroAssembler::KlassDecodeMode MacroAssembler::klass_decode_mode() { } MacroAssembler::KlassDecodeMode MacroAssembler::klass_decode_mode(address base, int shift, const size_t range) { - // KlassDecodeMode shouldn't be set already. - assert(_klass_decode_mode == KlassDecodeNone, "set once"); if (base == nullptr) { return KlassDecodeZero; @@ -5377,148 +5376,128 @@ MacroAssembler::KlassDecodeMode MacroAssembler::klass_decode_mode(address base, return KlassDecodeMovk; } - // No valid encoding. - return KlassDecodeNone; -} - -// Check if one of the above decoding modes will work for given base, shift and range. -bool MacroAssembler::check_klass_decode_mode(address base, int shift, const size_t range) { - return klass_decode_mode(base, shift, range) != KlassDecodeNone; + return KlassDecodeFallback; } -bool MacroAssembler::set_klass_decode_mode(address base, int shift, const size_t range) { +void MacroAssembler::initialize_klass_decode_mode(address base, int shift, const size_t range) { + // KlassDecodeMode shouldn't be set already. + assert(_klass_decode_mode == KlassDecodeNone, "set once"); _klass_decode_mode = klass_decode_mode(base, shift, range); - return _klass_decode_mode != KlassDecodeNone; + log_info(metaspace)("Klass Decode Mode: %d", (int)_klass_decode_mode); } -static Register pick_different_tmp(Register dst, Register src) { - auto tmps = RegSet::of(r0, r1, r2) - RegSet::of(src, dst); - return *tmps.begin(); +void MacroAssembler::encode_klass_not_null(Register dst, Register src, Register tmp) { + emit_encode_klass_not_null(dst, src, tmp, CompressedKlassPointers::base(), + CompressedKlassPointers::shift(), klass_decode_mode()); } -void MacroAssembler::encode_klass_not_null_for_aot(Register dst, Register src) { - // we have to load the klass base from the AOT constants area but - // not the shift because it is not allowed to change - int shift = CompressedKlassPointers::shift(); - assert(shift >= 0 && shift <= CompressedKlassPointers::max_shift(), "unexpected compressed klass shift!"); - if (dst != src) { - // we can load the base into dst, subtract it formthe src and shift down - lea(dst, ExternalAddress(CompressedKlassPointers::base_addr())); - ldr(dst, dst); - sub(dst, src, dst); - lsr(dst, dst, shift); - } else { - // we need an extra register in order to load the coop base - Register tmp = pick_different_tmp(dst, src); - RegSet regs = RegSet::of(tmp); - push(regs, sp); +void MacroAssembler::emit_encode_klass_not_null(Register dst, Register src, Register tmp, + address base, int shift, KlassDecodeMode decode_mode) { + + assert_different_registers(tmp, src); + assert(tmp != noreg, "valid tmp required"); + + if (AOTCodeCache::is_on_for_dump()) { + // We are generating code during AOT buildup that will run in *future* processes + // with likely different encoding settings. Therefore, we have to load the + // encoding base dynamically, we cannot just bake it in as immediate. + // Note that we only need to do this for base. The encoding shift would be the + // same between build time and runtime: the standard precomputed shift. + assert(shift == ArchiveBuilder::precomputed_narrow_klass_shift(), "unexpected compressed klass shift!"); lea(tmp, ExternalAddress(CompressedKlassPointers::base_addr())); ldr(tmp, tmp); sub(dst, src, tmp); lsr(dst, dst, shift); - pop(regs, sp); - } -} - -void MacroAssembler::encode_klass_not_null(Register dst, Register src) { - if (CompressedKlassPointers::base() != nullptr && AOTCodeCache::is_on_for_dump()) { - encode_klass_not_null_for_aot(dst, src); return; } - switch (klass_decode_mode()) { + switch (decode_mode) { case KlassDecodeZero: - if (CompressedKlassPointers::shift() != 0) { - lsr(dst, src, CompressedKlassPointers::shift()); - } else { - if (dst != src) mov(dst, src); - } + lsr(dst, src, shift); break; case KlassDecodeXor: - if (CompressedKlassPointers::shift() != 0) { - eor(dst, src, (uint64_t)CompressedKlassPointers::base()); - lsr(dst, dst, CompressedKlassPointers::shift()); - } else { - eor(dst, src, (uint64_t)CompressedKlassPointers::base()); - } + eor(dst, src, (uint64_t)base); + lsr(dst, dst, shift); break; case KlassDecodeMovk: - if (CompressedKlassPointers::shift() != 0) { - ubfx(dst, src, CompressedKlassPointers::shift(), 32); + if (shift != 0) { + ubfx(dst, src, shift, 32); } else { movw(dst, src); } break; + case KlassDecodeFallback: { + mov(tmp, base); + sub(dst, src, tmp); + lsr(dst, dst, shift); + break; + } + case KlassDecodeNone: ShouldNotReachHere(); break; } + +#ifdef ASSERT + if (tmp != dst) { + mov(tmp, 0xdead); + } +#endif // ASSERT + } -void MacroAssembler::encode_klass_not_null(Register r) { - encode_klass_not_null(r, r); +void MacroAssembler::decode_klass_not_null(Register dst, Register src, Register tmp) { + emit_decode_klass_not_null(dst, src, tmp, + CompressedKlassPointers::base(), + CompressedKlassPointers::shift(), + klass_decode_mode()); } -void MacroAssembler::decode_klass_not_null_for_aot(Register dst, Register src) { - // we have to load the klass base from the AOT constants area but - // not the shift because it is not allowed to change - int shift = CompressedKlassPointers::shift(); - assert(shift >= 0 && shift <= CompressedKlassPointers::max_shift(), "unexpected compressed klass shift!"); - if (dst != src) { - // we can load the base into dst then add the offset with a suitable shift - lea(dst, ExternalAddress(CompressedKlassPointers::base_addr())); - ldr(dst, dst); - add(dst, dst, src, LSL, shift); - } else { - // we need an extra register in order to load the coop base - Register tmp = pick_different_tmp(dst, src); - RegSet regs = RegSet::of(tmp); - push(regs, sp); +void MacroAssembler::emit_decode_klass_not_null(Register dst, Register src, Register tmp, + address base, int shift, KlassDecodeMode decode_mode) { + + assert_different_registers(tmp, src); + assert(tmp != noreg, "valid tmp required"); + + if (AOTCodeCache::is_on_for_dump()) { + // We are generating code during AOT buildup that will run in *future* processes + // with likely different encoding settings. Therefore, we have to load the + // encoding base dynamically, we cannot just bake it in as immediate. + // Note that we only need to do this for base. The encoding shift would be the + // same between build time and runtime: the standard precomputed shift. + assert(shift == ArchiveBuilder::precomputed_narrow_klass_shift(), "unexpected compressed klass shift!"); lea(tmp, ExternalAddress(CompressedKlassPointers::base_addr())); ldr(tmp, tmp); add(dst, tmp, src, LSL, shift); - pop(regs, sp); - } -} - -void MacroAssembler::decode_klass_not_null(Register dst, Register src) { - if (AOTCodeCache::is_on_for_dump()) { - decode_klass_not_null_for_aot(dst, src); return; } - switch (klass_decode_mode()) { - case KlassDecodeZero: - if (CompressedKlassPointers::shift() != 0) { - lsl(dst, src, CompressedKlassPointers::shift()); - } else { - if (dst != src) mov(dst, src); - } + switch (decode_mode) { + case KlassDecodeZero: // 0-1 instructions + lsl(dst, src, shift); break; - case KlassDecodeXor: - if (CompressedKlassPointers::shift() != 0) { - lsl(dst, src, CompressedKlassPointers::shift()); - eor(dst, dst, (uint64_t)CompressedKlassPointers::base()); - } else { - eor(dst, src, (uint64_t)CompressedKlassPointers::base()); - } + case KlassDecodeXor: // 1-2 instructions + lsl(dst, src, shift); + eor(dst, dst, (uint64_t)base); break; - case KlassDecodeMovk: { + case KlassDecodeMovk: { // 1-3 instructions const uint64_t shifted_base = - (uint64_t)CompressedKlassPointers::base() >> CompressedKlassPointers::shift(); + (uint64_t)base >> shift; if (dst != src) movw(dst, src); movk(dst, shifted_base >> 32, 32); + lsl(dst, dst, shift); + break; + } - if (CompressedKlassPointers::shift() != 0) { - lsl(dst, dst, CompressedKlassPointers::shift()); - } - + case KlassDecodeFallback: { // 3-4 instructions + mov(tmp, base); + add(dst, tmp, src, LSL, shift); break; } @@ -5526,10 +5505,14 @@ void MacroAssembler::decode_klass_not_null(Register dst, Register src) { ShouldNotReachHere(); break; } -} -void MacroAssembler::decode_klass_not_null(Register r) { - decode_klass_not_null(r, r); +#ifdef ASSERT + // Always clobber tmp + if (tmp != dst) { + mov(tmp, 0xdead); + } +#endif // ASSERT + } void MacroAssembler::set_narrow_oop(Register dst, jobject obj) { @@ -7181,7 +7164,7 @@ void MacroAssembler::fast_lock(Register basic_lock, Register obj, Register t1, R } if (DiagnoseSyncOnValueBasedClasses != 0) { - load_klass(t1, obj); + load_klass(t1, obj, rscratch1); ldrb(t1, Address(t1, Klass::misc_flags_offset())); tst(t1, KlassFlags::_misc_is_value_based_class); br(Assembler::NE, slow); diff --git a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp index b39596aab531..df331ed02d32 100644 --- a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp @@ -38,6 +38,7 @@ #include "utilities/powerOfTwo.hpp" class OopMap; +struct GtestFriendToMacroAssembler; // MacroAssembler extends Assembler by frequently used macros. // @@ -46,6 +47,7 @@ class OopMap; class MacroAssembler: public Assembler { friend class LIR_Assembler; + friend struct GtestFriendToMacroAssembler; public: using Assembler::mov; @@ -91,28 +93,31 @@ class MacroAssembler: public Assembler { void call_VM_helper(Register oop_result, address entry_point, int number_of_arguments, bool check_exceptions = true); + private: + enum KlassDecodeMode { KlassDecodeNone, KlassDecodeZero, KlassDecodeXor, - KlassDecodeMovk + KlassDecodeMovk, + KlassDecodeFallback }; - // Calculate decoding mode based on given parameters, used for checking then ultimately setting. - static KlassDecodeMode klass_decode_mode(address base, int shift, const size_t range); - - private: static KlassDecodeMode _klass_decode_mode; // Returns above setting with asserts static KlassDecodeMode klass_decode_mode(); - public: - // Checks the decode mode and returns false if not compatible with preferred decoding mode. - static bool check_klass_decode_mode(address base, int shift, const size_t range); + // Calculate decoding mode based on given parameters, used for checking then ultimately setting. + static KlassDecodeMode klass_decode_mode(address base, int shift, const size_t range); - // Sets the decode mode and returns false if cannot be set. - static bool set_klass_decode_mode(address base, int shift, const size_t range); + void emit_encode_klass_not_null(Register dst, Register src, Register tmp, + address base, int shift, KlassDecodeMode decode_mode); + void emit_decode_klass_not_null(Register dst, Register src, Register tmp, + address base, int shift, KlassDecodeMode decode_mode); + public: + // Determines the decode mode best suited for the given encoding parameters. + static void initialize_klass_decode_mode(address base, int shift, const size_t range); public: MacroAssembler(CodeBuffer* code) : Assembler(code) {} @@ -308,19 +313,27 @@ class MacroAssembler: public Assembler { } inline void lslw(Register Rd, Register Rn, unsigned imm) { - ubfmw(Rd, Rn, ((32 - imm) & 31), (31 - imm)); + if (imm > 0 || Rd != Rn) { + ubfmw(Rd, Rn, ((32 - imm) & 31), (31 - imm)); + } } inline void lsl(Register Rd, Register Rn, unsigned imm) { - ubfm(Rd, Rn, ((64 - imm) & 63), (63 - imm)); + if (imm > 0 || Rd != Rn) { + ubfm(Rd, Rn, ((64 - imm) & 63), (63 - imm)); + } } inline void lsrw(Register Rd, Register Rn, unsigned imm) { - ubfmw(Rd, Rn, imm, 31); + if (imm > 0 || Rd != Rn) { + ubfmw(Rd, Rn, imm, 31); + } } inline void lsr(Register Rd, Register Rn, unsigned imm) { - ubfm(Rd, Rn, imm, 63); + if (imm > 0 || Rd != Rn) { + ubfm(Rd, Rn, imm, 63); + } } inline void rorw(Register Rd, Register Rn, unsigned imm) { @@ -925,9 +938,9 @@ class MacroAssembler: public Assembler { // oop manipulations void load_narrow_klass_compact(Register dst, Register src); void load_narrow_klass(Register dst, Register src); - void load_klass(Register dst, Register src); - void store_klass(Register dst, Register src); - void cmp_klass(Register obj, Register klass, Register tmp); + void load_klass(Register dst, Register src, Register tmp); + void store_klass(Register dst, Register src, Register tmp); + void cmp_klass(Register obj, Register klass, Register tmp, Register tmp2); void cmp_klasses_from_objects(Register obj1, Register obj2, Register tmp1, Register tmp2); void resolve_weak_handle(Register result, Register tmp1, Register tmp2); @@ -972,12 +985,8 @@ class MacroAssembler: public Assembler { void set_narrow_oop(Register dst, jobject obj); - void decode_klass_not_null_for_aot(Register dst, Register src); - void encode_klass_not_null_for_aot(Register dst, Register src); - void encode_klass_not_null(Register r); - void decode_klass_not_null(Register r); - void encode_klass_not_null(Register dst, Register src); - void decode_klass_not_null(Register dst, Register src); + void encode_klass_not_null(Register dst, Register src, Register tmp); + void decode_klass_not_null(Register dst, Register src, Register tmp); void set_narrow_klass(Register dst, Klass* k); @@ -1798,6 +1807,8 @@ class MacroAssembler: public Assembler { SVE_DESTRUCTIVE_BINARY_5(sve_fmul, sve_fsub, sve_lsl, sve_lsr, sve_mul) SVE_DESTRUCTIVE_BINARY_5(sve_orr, sve_smax, sve_smin, sve_sqadd, sve_sqsub) SVE_DESTRUCTIVE_BINARY_5(sve_sub, sve_uqadd, sve_uqsub, sve_umax, sve_umin) + SVE_DESTRUCTIVE_BINARY_INS(sve_sdiv); + SVE_DESTRUCTIVE_BINARY_INS(sve_udiv); #undef SVE_DESTRUCTIVE_BINARY_INS #undef SVE_DESTRUCTIVE_BINARY_5 diff --git a/src/hotspot/cpu/aarch64/methodHandles_aarch64.cpp b/src/hotspot/cpu/aarch64/methodHandles_aarch64.cpp index cdf67e3423f6..7dc74f44cdc1 100644 --- a/src/hotspot/cpu/aarch64/methodHandles_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/methodHandles_aarch64.cpp @@ -76,7 +76,7 @@ void MethodHandles::verify_klass(MacroAssembler* _masm, __ verify_oop(obj); __ cbz(obj, L_bad); __ push(RegSet::of(temp, temp2), sp); - __ load_klass(temp, obj); + __ load_klass(temp, obj, temp2); __ cmpptr(temp, ExternalAddress((address) klass_addr)); __ br(Assembler::EQ, L_ok); intptr_t super_check_offset = klass->super_check_offset(); @@ -368,7 +368,7 @@ void MethodHandles::generate_method_handle_dispatch(MacroAssembler* _masm, __ null_check(receiver_reg); } else { // load receiver klass itself - __ load_klass(temp1_recv_klass, receiver_reg); + __ load_klass(temp1_recv_klass, receiver_reg, temp2); __ verify_klass_ptr(temp1_recv_klass); } BLOCK_COMMENT("check_receiver {"); @@ -376,7 +376,7 @@ void MethodHandles::generate_method_handle_dispatch(MacroAssembler* _masm, // Check the receiver against the MemberName.clazz if (VerifyMethodHandles && iid == vmIntrinsics::_linkToSpecial) { // Did not load it above... - __ load_klass(temp1_recv_klass, receiver_reg); + __ load_klass(temp1_recv_klass, receiver_reg, temp2); __ verify_klass_ptr(temp1_recv_klass); } if (VerifyMethodHandles && iid != vmIntrinsics::_linkToInterface) { diff --git a/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp b/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp index 5dfd41293fd7..03eb5084eb4c 100644 --- a/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp @@ -2258,7 +2258,7 @@ class StubGenerator: public StubCodeGenerator { // checked. assert_different_registers(from, to, count, ckoff, ckval, start_to, - copied_oop, r19_klass, count_save); + copied_oop, r19_klass, count_save, rscratch1); __ align(CodeEntryAlignment); StubCodeMark mark(this, stub_id); @@ -2342,7 +2342,7 @@ class StubGenerator: public StubCodeGenerator { gct1); __ cbz(copied_oop, L_store_element); - __ load_klass(r19_klass, copied_oop);// query the object klass + __ load_klass(r19_klass, copied_oop, rscratch1);// query the object klass BLOCK_COMMENT("type_check:"); generate_type_check(/*sub_klass*/r19_klass, @@ -2583,7 +2583,7 @@ class StubGenerator: public StubCodeGenerator { BLOCK_COMMENT("} assert klasses not null done"); } #endif - __ decode_klass_not_null(scratch_src_klass, scratch_src_klass); + __ decode_klass_not_null(scratch_src_klass, scratch_src_klass, rscratch1); // Load layout helper (32-bits) // @@ -2603,7 +2603,7 @@ class StubGenerator: public StubCodeGenerator { __ cbzw(rscratch2, L_objArray); // if (src->klass() != dst->klass()) return -1; - __ load_klass(rscratch2, dst); + __ load_klass(rscratch2, dst, rscratch1); __ eor(rscratch2, rscratch2, scratch_src_klass); __ cbnz(rscratch2, L_failed); @@ -2699,7 +2699,7 @@ class StubGenerator: public StubCodeGenerator { Label L_plain_copy, L_checkcast_copy; // test array classes for subtyping - __ load_klass(r15, dst); + __ load_klass(r15, dst, rscratch1); __ cmp(scratch_src_klass, r15); // usual case is exact equality __ br(Assembler::NE, L_checkcast_copy); @@ -2728,7 +2728,7 @@ class StubGenerator: public StubCodeGenerator { arraycopy_range_checks(src, src_pos, dst, dst_pos, scratch_length, r15, L_failed); - __ load_klass(dst_klass, dst); // reload + __ load_klass(dst_klass, dst, rscratch1); // reload // Marshal the base address arguments now, freeing registers. __ lea(from, Address(src, src_pos, Address::lsl(LogBytesPerHeapOop))); diff --git a/src/hotspot/cpu/aarch64/templateTable_aarch64.cpp b/src/hotspot/cpu/aarch64/templateTable_aarch64.cpp index b6cf58d6062f..a0ce1d043170 100644 --- a/src/hotspot/cpu/aarch64/templateTable_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/templateTable_aarch64.cpp @@ -1123,9 +1123,9 @@ void TemplateTable::aastore() { __ cbz(r0, is_null); // Move subklass into r1 - __ load_klass(r1, r0); + __ load_klass(r1, r0, rscratch1); // Move superklass into r0 - __ load_klass(r0, r3); + __ load_klass(r0, r3, rscratch1); __ ldr(r0, Address(r0, ObjArrayKlass::element_klass_offset())); // Compress array + index*oopSize + 12 into a single register. Frees r2. @@ -1173,7 +1173,7 @@ void TemplateTable::bastore() // Need to check whether array is boolean or byte // since both types share the bastore bytecode. - __ load_klass(r2, r3); + __ load_klass(r2, r3, rscratch1); __ ldrw(r2, Address(r2, Klass::layout_helper_offset())); int diffbit_index = exact_log2(Klass::layout_helper_boolean_diffbit()); Label L_skip; @@ -2194,7 +2194,7 @@ void TemplateTable::_return(TosState state) assert(state == vtos, "only valid state"); __ ldr(c_rarg1, aaddress(0)); - __ load_klass(r3, c_rarg1); + __ load_klass(r3, c_rarg1, rscratch1); __ ldrb(r3, Address(r3, Klass::misc_flags_offset())); Label skip_register_finalizer; __ tbz(r3, exact_log2(KlassFlags::_misc_has_finalizer), skip_register_finalizer); @@ -3338,8 +3338,8 @@ void TemplateTable::invokevirtual_helper(Register index, Register recv, Register flags) { - // Uses temporary registers r0, r3 - assert_different_registers(index, recv, r0, r3); + // Uses temporary registers r0, r3, rscratch1 + assert_different_registers(index, recv, r0, r3, rscratch1); // Test for an invoke of a final method Label notFinal; __ tbz(flags, ResolvedMethodEntry::is_vfinal_shift, notFinal); @@ -3363,7 +3363,7 @@ void TemplateTable::invokevirtual_helper(Register index, __ bind(notFinal); // get receiver klass - __ load_klass(r0, recv); + __ load_klass(r0, recv, rscratch1); // profile this call __ profile_virtual_call(r0, rlocals); @@ -3464,7 +3464,7 @@ void TemplateTable::invokeinterface(int byte_no) { __ tbz(r3, ResolvedMethodEntry::is_vfinal_shift, notVFinal); // Get receiver klass into r3 - __ load_klass(r3, r2); + __ load_klass(r3, r2, rscratch1); Label subtype; __ check_klass_subtype(r3, r0, r4, subtype); @@ -3479,7 +3479,7 @@ void TemplateTable::invokeinterface(int byte_no) { __ bind(notVFinal); // Get receiver klass into r3 - __ load_klass(r3, r2); + __ load_klass(r3, r2, rscratch1); Label no_such_method; @@ -3678,7 +3678,7 @@ void TemplateTable::_new() { __ mov(rscratch1, (intptr_t)markWord::prototype().value()); __ str(rscratch1, Address(r0, oopDesc::mark_offset_in_bytes())); __ store_klass_gap(r0, zr); // zero klass gap for compressed oops - __ store_klass(r0, r4); // store klass last + __ store_klass(r0, r4, rscratch1); // store klass last } if (DTraceAllocProbes) { @@ -3759,7 +3759,7 @@ void TemplateTable::checkcast() __ load_resolved_klass_at_offset(r2, r19, r0, rscratch1); // r0 = klass __ bind(resolved); - __ load_klass(r19, r3); + __ load_klass(r19, r3, rscratch1); // Generate subtype check. Blows r2, r5. Object in r3. // Superklass in r0. Subklass in r19. @@ -3805,12 +3805,12 @@ void TemplateTable::instanceof() { __ get_vm_result_metadata(r0, rthread); __ pop(r3); // restore receiver __ verify_oop(r3); - __ load_klass(r3, r3); + __ load_klass(r3, r3, rscratch1); __ b(resolved); // Get superklass in r0 and subklass in r3 __ bind(quicked); - __ load_klass(r3, r0); + __ load_klass(r3, r0, rscratch1); __ load_resolved_klass_at_offset(r2, r19, r0, rscratch1); __ bind(resolved); diff --git a/src/hotspot/cpu/aarch64/vm_version_aarch64.cpp b/src/hotspot/cpu/aarch64/vm_version_aarch64.cpp index 5462ccf2a764..10e70127ac80 100644 --- a/src/hotspot/cpu/aarch64/vm_version_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/vm_version_aarch64.cpp @@ -544,6 +544,13 @@ void VM_Version::initialize() { UsePopCountInstruction = true; } + if (supports_paca()) { + // Determine the mask of address bits used for PAC. Clear bit 55 of + // the input to make it look like a user address. + // This mask would be used in mixed jstack in SA. + _pac_mask = (uintptr_t)pauth_strip_pointer((address)~(UINT64_C(1) << 55)); + } + if (UseBranchProtection == nullptr || strcmp(UseBranchProtection, "none") == 0) { _rop_protection = false; } else if (strcmp(UseBranchProtection, "standard") == 0 || @@ -565,13 +572,6 @@ void VM_Version::initialize() { } else { vm_exit_during_initialization(err_msg("Unsupported UseBranchProtection: %s", UseBranchProtection)); } - - if (_rop_protection == true) { - // Determine the mask of address bits used for PAC. Clear bit 55 of - // the input to make it look like a user address. - _pac_mask = (uintptr_t)pauth_strip_pointer((address)~(UINT64_C(1) << 55)); - } - #ifdef COMPILER2 if (FLAG_IS_DEFAULT(UseMultiplyToLenIntrinsic)) { UseMultiplyToLenIntrinsic = true; diff --git a/src/hotspot/cpu/aarch64/vtableStubs_aarch64.cpp b/src/hotspot/cpu/aarch64/vtableStubs_aarch64.cpp index 714904ab3df4..1b7820fc3370 100644 --- a/src/hotspot/cpu/aarch64/vtableStubs_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/vtableStubs_aarch64.cpp @@ -79,7 +79,7 @@ VtableStub* VtableStubs::create_vtable_stub(int vtable_index) { // get receiver klass address npe_addr = __ pc(); - __ load_klass(r16, j_rarg0); + __ load_klass(r16, j_rarg0, rscratch1); #ifndef PRODUCT if (DebugVtables) { @@ -189,7 +189,7 @@ VtableStub* VtableStubs::create_itable_stub(int itable_index) { // get receiver klass (also an implicit null-check) address npe_addr = __ pc(); - __ load_klass(recv_klass_reg, j_rarg0); + __ load_klass(recv_klass_reg, j_rarg0, rscratch1); // Receiver subtype check against REFC. // Get selected method from declaring class and itable index diff --git a/src/hotspot/cpu/ppc/ppc.ad b/src/hotspot/cpu/ppc/ppc.ad index d3e08a216400..2c0904a6b583 100644 --- a/src/hotspot/cpu/ppc/ppc.ad +++ b/src/hotspot/cpu/ppc/ppc.ad @@ -6612,36 +6612,6 @@ instruct decodeN2I_unscaled(iRegIdst dst, iRegNsrc src) %{ // Convert klass pointer into compressed form. -// Nodes for postalloc expand. - -// Shift node for expand. -instruct encodePKlass_shift(iRegNdst dst, iRegNsrc src) %{ - // The match rule is needed to make it a 'MachTypeNode'! - match(Set dst (EncodePKlass src)); - predicate(false); - - format %{ "SRDI $dst, $src, 3 \t// encode" %} - size(4); - ins_encode %{ - __ srdi($dst$$Register, $src$$Register, CompressedKlassPointers::shift()); - %} - ins_pipe(pipe_class_default); -%} - -// Add node for expand. -instruct encodePKlass_sub_base(iRegPdst dst, iRegLsrc base, iRegPdst src) %{ - // The match rule is needed to make it a 'MachTypeNode'! - match(Set dst (EncodePKlass (Binary base src))); - predicate(false); - - format %{ "SUB $dst, $base, $src \t// encode" %} - size(4); - ins_encode %{ - __ subf($dst$$Register, $base$$Register, $src$$Register); - %} - ins_pipe(pipe_class_default); -%} - // Disjoint narrow oop base. instruct encodePKlass_Disjoint(iRegNdst dst, iRegPsrc src) %{ match(Set dst (EncodePKlass src)); @@ -6656,116 +6626,56 @@ instruct encodePKlass_Disjoint(iRegNdst dst, iRegPsrc src) %{ %} // shift != 0, base != 0 -instruct encodePKlass_not_null_Ex(iRegNdst dst, iRegLsrc base, iRegPsrc src) %{ +instruct encodePKlass_not_null(iRegNdst dst, iRegLsrc base, iRegPsrc src) %{ match(Set dst (EncodePKlass (Binary base src))); predicate(false); - format %{ "EncodePKlass $dst, $src\t// $src != Null, postalloc expanded" %} - postalloc_expand %{ - encodePKlass_sub_baseNode *n1 = new encodePKlass_sub_baseNode(); - n1->add_req(n_region, n_base, n_src); - n1->_opnds[0] = op_dst; - n1->_opnds[1] = op_base; - n1->_opnds[2] = op_src; - n1->_bottom_type = _bottom_type; - - encodePKlass_shiftNode *n2 = new encodePKlass_shiftNode(); - n2->add_req(n_region, n1); - n2->_opnds[0] = op_dst; - n2->_opnds[1] = op_dst; - n2->_bottom_type = _bottom_type; - ra_->set_pair(n1->_idx, ra_->get_reg_second(this), ra_->get_reg_first(this)); - ra_->set_pair(n2->_idx, ra_->get_reg_second(this), ra_->get_reg_first(this)); - - nodes->push(n1); - nodes->push(n2); + format %{ "EncodePKlass $dst = ($src - $base) >> 3\t// $src != nullptr" %} + size(8); + ins_encode %{ + __ subf($dst$$Register, $base$$Register, $src$$Register); + __ srdi($dst$$Register, $dst$$Register, CompressedKlassPointers::shift()); %} + ins_pipe(pipe_class_default); %} // shift != 0, base != 0 -instruct encodePKlass_not_null_ExEx(iRegNdst dst, iRegPsrc src) %{ +instruct encodePKlass_not_null_Ex(iRegNdst dst, iRegPsrc src) %{ match(Set dst (EncodePKlass src)); //predicate(CompressedKlassPointers::shift() != 0 && // true /* TODO: PPC port CompressedKlassPointers::base_overlaps()*/); - //format %{ "EncodePKlass $dst, $src\t// $src != Null, postalloc expanded" %} ins_cost(DEFAULT_COST*2); // Don't count constant. expand %{ immL baseImm %{ (jlong)(intptr_t)CompressedKlassPointers::base() %} iRegLdst base; loadConL_Ex(base, baseImm); - encodePKlass_not_null_Ex(dst, base, src); + encodePKlass_not_null(dst, base, src); %} %} // Decode nodes. -// Shift node for expand. -instruct decodeNKlass_shift(iRegPdst dst, iRegPsrc src) %{ - // The match rule is needed to make it a 'MachTypeNode'! - match(Set dst (DecodeNKlass src)); - predicate(false); - - format %{ "SLDI $dst, $src, #3 \t// DecodeNKlass" %} - size(4); - ins_encode %{ - __ sldi($dst$$Register, $src$$Register, CompressedKlassPointers::shift()); - %} - ins_pipe(pipe_class_default); -%} - -// Add node for expand. - -instruct decodeNKlass_add_base(iRegPdst dst, iRegLsrc base, iRegPdst src) %{ - // The match rule is needed to make it a 'MachTypeNode'! +// src != 0, shift != 0, base != 0 +instruct decodeNKlass_notNull(iRegPdst dst, iRegLsrc base, iRegNsrc src) %{ match(Set dst (DecodeNKlass (Binary base src))); predicate(false); - format %{ "ADD $dst, $base, $src \t// DecodeNKlass, add klass base" %} - size(4); + format %{ "DecodeNKlass $dst = ($base + $src) << 3\t// $src != nullptr, base pre-shifted" %} + size(8); ins_encode %{ __ add($dst$$Register, $base$$Register, $src$$Register); + __ sldi($dst$$Register, $dst$$Register, CompressedKlassPointers::shift()); %} ins_pipe(pipe_class_default); %} // src != 0, shift != 0, base != 0 -instruct decodeNKlass_notNull_addBase_Ex(iRegPdst dst, iRegLsrc base, iRegNsrc src) %{ - match(Set dst (DecodeNKlass (Binary base src))); - //effect(kill src); // We need a register for the immediate result after shifting. - predicate(false); - - format %{ "DecodeNKlass $dst = $base + ($src << 3) \t// $src != nullptr, postalloc expanded" %} - postalloc_expand %{ - decodeNKlass_add_baseNode *n1 = new decodeNKlass_add_baseNode(); - n1->add_req(n_region, n_base, n_src); - n1->_opnds[0] = op_dst; - n1->_opnds[1] = op_base; - n1->_opnds[2] = op_src; - n1->_bottom_type = _bottom_type; - - decodeNKlass_shiftNode *n2 = new decodeNKlass_shiftNode(); - n2->add_req(n_region, n1); - n2->_opnds[0] = op_dst; - n2->_opnds[1] = op_dst; - n2->_bottom_type = _bottom_type; - - ra_->set_pair(n1->_idx, ra_->get_reg_second(this), ra_->get_reg_first(this)); - ra_->set_pair(n2->_idx, ra_->get_reg_second(this), ra_->get_reg_first(this)); - - nodes->push(n1); - nodes->push(n2); - %} -%} - -// src != 0, shift != 0, base != 0 -instruct decodeNKlass_notNull_addBase_ExEx(iRegPdst dst, iRegNsrc src) %{ +instruct decodeNKlass_notNull_Ex(iRegPdst dst, iRegNsrc src) %{ match(Set dst (DecodeNKlass src)); // predicate(CompressedKlassPointers::shift() != 0 && // CompressedKlassPointers::base() != 0); - //format %{ "DecodeNKlass $dst, $src \t// $src != nullptr, expanded" %} - ins_cost(DEFAULT_COST*2); // Don't count constant. expand %{ // We add first, then we shift. Like this, we can get along with one register less. @@ -6773,7 +6683,7 @@ instruct decodeNKlass_notNull_addBase_ExEx(iRegPdst dst, iRegNsrc src) %{ immL baseImm %{ (jlong)((intptr_t)CompressedKlassPointers::base() >> CompressedKlassPointers::shift()) %} iRegLdst base; loadConL_Ex(base, baseImm); - decodeNKlass_notNull_addBase_Ex(dst, base, src); + decodeNKlass_notNull(dst, base, src); %} %} diff --git a/src/hotspot/cpu/riscv/globals_riscv.hpp b/src/hotspot/cpu/riscv/globals_riscv.hpp index d399bc13082f..bc312bfc52cf 100644 --- a/src/hotspot/cpu/riscv/globals_riscv.hpp +++ b/src/hotspot/cpu/riscv/globals_riscv.hpp @@ -118,8 +118,9 @@ define_pd_global(intx, InlineSmallCode, 1000); "Use Zihintpause instructions") \ product(bool, UseZtso, false, EXPERIMENTAL, "Assume Ztso memory model") \ product(bool, UseZvbb, false, DIAGNOSTIC, "Use Zvbb instructions") \ - product(bool, UseZvbc, false, EXPERIMENTAL, "Use Zvbc instructions") \ + product(bool, UseZvbc, false, DIAGNOSTIC, "Use Zvbc instructions") \ product(bool, UseZvfh, false, DIAGNOSTIC, "Use Zvfh instructions") \ + product(bool, UseZvfhmin, false, DIAGNOSTIC, "Use Zvfhmin instructions") \ product(bool, UseZvkg, false, DIAGNOSTIC, "Use Zvkg instructions") \ product(bool, UseZvkn, false, DIAGNOSTIC, \ "Use Zvkn group extension, Zvkned, Zvknhb, Zvkb, Zvkt") \ diff --git a/src/hotspot/cpu/riscv/riscv_v.ad b/src/hotspot/cpu/riscv/riscv_v.ad index a0af43364cb0..2a63221de04d 100644 --- a/src/hotspot/cpu/riscv/riscv_v.ad +++ b/src/hotspot/cpu/riscv/riscv_v.ad @@ -113,6 +113,7 @@ source %{ break; case Op_VectorCastHF2F: case Op_VectorCastF2HF: + return UseZvfh || UseZvfhmin; case Op_AddVHF: case Op_SubVHF: case Op_MulVHF: diff --git a/src/hotspot/cpu/riscv/vm_version_riscv.hpp b/src/hotspot/cpu/riscv/vm_version_riscv.hpp index 11a88dfedd7b..ab048719ad24 100644 --- a/src/hotspot/cpu/riscv/vm_version_riscv.hpp +++ b/src/hotspot/cpu/riscv/vm_version_riscv.hpp @@ -219,78 +219,80 @@ class VM_Version : public Abstract_VM_Version { // // Fields description in `decl`: // declaration name, extension name, bit value from linux, feature string?, mapped flag) - #define RV_EXT_FEATURE_FLAGS(decl) \ - /* A Atomic Instructions */ \ - decl(a , ('A' - 'A'), true , NO_UPDATE_DEFAULT) \ - /* C Compressed Instructions */ \ - decl(c , ('C' - 'A'), true , UPDATE_DEFAULT(UseRVC)) \ - /* D Single-Precision Floating-Point */ \ - decl(d , ('D' - 'A'), true , NO_UPDATE_DEFAULT) \ - /* F Single-Precision Floating-Point */ \ - decl(f , ('F' - 'A'), true , NO_UPDATE_DEFAULT) \ - /* H Hypervisor */ \ - decl(h , ('H' - 'A'), true , NO_UPDATE_DEFAULT) \ - /* I RV64I */ \ - decl(i , ('I' - 'A'), true , NO_UPDATE_DEFAULT) \ - /* M Integer Multiplication and Division */ \ - decl(m , ('M' - 'A'), true , NO_UPDATE_DEFAULT) \ - /* Q Quad-Precision Floating-Point */ \ - decl(q , ('Q' - 'A'), true , NO_UPDATE_DEFAULT) \ - /* V Vector */ \ - decl(v , ('V' - 'A'), true , UPDATE_DEFAULT(UseRVV)) \ - \ - /* ----------------------- Other extensions ----------------------- */ \ - \ - /* Atomic compare-and-swap (CAS) instructions */ \ - decl(Zacas , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZacas)) \ - /* Zba Address generation instructions */ \ - decl(Zba , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZba)) \ - /* Zbb Basic bit-manipulation */ \ - decl(Zbb , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZbb)) \ - /* Zbc Carry-less multiplication */ \ - decl(Zbc , RV_NO_FLAG_BIT, true , NO_UPDATE_DEFAULT) \ - /* Bitmanip instructions for Cryptography */ \ - decl(Zbkb , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZbkb)) \ - /* Zbs Single-bit instructions */ \ - decl(Zbs , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZbs)) \ - /* Zcb Simple code-size saving instructions */ \ - decl(Zcb , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZcb)) \ - /* Additional Floating-Point instructions */ \ - decl(Zfa , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZfa)) \ - /* Zfh Half-Precision Floating-Point instructions */ \ - decl(Zfh , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZfh)) \ - /* Zfhmin Minimal Half-Precision Floating-Point instructions */ \ - decl(Zfhmin , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZfhmin)) \ - /* Zicbom Cache Block Management Operations */ \ - decl(Zicbom , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZicbom)) \ - /* Zicbop Cache Block Prefetch Operations */ \ - decl(Zicbop , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZicbop)) \ - /* Zicboz Cache Block Zero Operations */ \ - decl(Zicboz , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZicboz)) \ - /* Base Counters and Timers */ \ - decl(Zicntr , RV_NO_FLAG_BIT, true , NO_UPDATE_DEFAULT) \ - /* Zicond Conditional operations */ \ - decl(Zicond , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZicond)) \ - /* Zicsr Control and Status Register (CSR) Instructions */ \ - decl(Zicsr , RV_NO_FLAG_BIT, true , NO_UPDATE_DEFAULT) \ - /* Zic64b Cache blocks must be 64 bytes in size, naturally aligned in the address space. */ \ - decl(Zic64b , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZic64b)) \ - /* Zifencei Instruction-Fetch Fence */ \ - decl(Zifencei , RV_NO_FLAG_BIT, true , NO_UPDATE_DEFAULT) \ - /* Zihintpause Pause instruction HINT */ \ - decl(Zihintpause , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZihintpause)) \ - /* Total Store Ordering */ \ - decl(Ztso , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZtso)) \ - /* Vector Basic Bit-manipulation */ \ - decl(Zvbb , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT_DEP(UseZvbb, &ext_v, nullptr)) \ - /* Vector Carryless Multiplication */ \ - decl(Zvbc , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT_DEP(UseZvbc, &ext_v, nullptr)) \ - /* Vector Extension for Half-Precision Floating-Point */ \ - decl(Zvfh , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT_DEP(UseZvfh, &ext_v, &ext_Zfh, nullptr)) \ - /* Shorthand for Zvkned + Zvknhb + Zvkb + Zvkt */ \ - decl(Zvkn , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT_DEP(UseZvkn, &ext_v, nullptr)) \ - /* Zvkg crypto extension for ghash and gcm */ \ - decl(Zvkg , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT_DEP(UseZvkg, &ext_v, nullptr)) \ + #define RV_EXT_FEATURE_FLAGS(decl) \ + /* A Atomic Instructions */ \ + decl(a , ('A' - 'A'), true , NO_UPDATE_DEFAULT) \ + /* C Compressed Instructions */ \ + decl(c , ('C' - 'A'), true , UPDATE_DEFAULT(UseRVC)) \ + /* D Single-Precision Floating-Point */ \ + decl(d , ('D' - 'A'), true , NO_UPDATE_DEFAULT) \ + /* F Single-Precision Floating-Point */ \ + decl(f , ('F' - 'A'), true , NO_UPDATE_DEFAULT) \ + /* H Hypervisor */ \ + decl(h , ('H' - 'A'), true , NO_UPDATE_DEFAULT) \ + /* I RV64I */ \ + decl(i , ('I' - 'A'), true , NO_UPDATE_DEFAULT) \ + /* M Integer Multiplication and Division */ \ + decl(m , ('M' - 'A'), true , NO_UPDATE_DEFAULT) \ + /* Q Quad-Precision Floating-Point */ \ + decl(q , ('Q' - 'A'), true , NO_UPDATE_DEFAULT) \ + /* V Vector */ \ + decl(v , ('V' - 'A'), true , UPDATE_DEFAULT(UseRVV)) \ + \ + /* ----------------------- Other extensions ----------------------- */ \ + \ + /* Atomic compare-and-swap (CAS) instructions */ \ + decl(Zacas , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZacas)) \ + /* Zba Address generation instructions */ \ + decl(Zba , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZba)) \ + /* Zbb Basic bit-manipulation */ \ + decl(Zbb , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZbb)) \ + /* Zbc Carry-less multiplication */ \ + decl(Zbc , RV_NO_FLAG_BIT, true , NO_UPDATE_DEFAULT) \ + /* Bitmanip instructions for Cryptography */ \ + decl(Zbkb , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZbkb)) \ + /* Zbs Single-bit instructions */ \ + decl(Zbs , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZbs)) \ + /* Zcb Simple code-size saving instructions */ \ + decl(Zcb , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZcb)) \ + /* Additional Floating-Point instructions */ \ + decl(Zfa , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZfa)) \ + /* Zfh Half-Precision Floating-Point instructions */ \ + decl(Zfh , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZfh)) \ + /* Zfhmin Minimal Half-Precision Floating-Point instructions */ \ + decl(Zfhmin , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZfhmin)) \ + /* Zicbom Cache Block Management Operations */ \ + decl(Zicbom , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZicbom)) \ + /* Zicbop Cache Block Prefetch Operations */ \ + decl(Zicbop , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZicbop)) \ + /* Zicboz Cache Block Zero Operations */ \ + decl(Zicboz , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZicboz)) \ + /* Base Counters and Timers */ \ + decl(Zicntr , RV_NO_FLAG_BIT, true , NO_UPDATE_DEFAULT) \ + /* Zicond Conditional operations */ \ + decl(Zicond , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZicond)) \ + /* Zicsr Control and Status Register (CSR) Instructions */ \ + decl(Zicsr , RV_NO_FLAG_BIT, true , NO_UPDATE_DEFAULT) \ + /* Zic64b Cache blocks must be 64 bytes in size, naturally aligned in the address space. */ \ + decl(Zic64b , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZic64b)) \ + /* Zifencei Instruction-Fetch Fence */ \ + decl(Zifencei , RV_NO_FLAG_BIT, true , NO_UPDATE_DEFAULT) \ + /* Zihintpause Pause instruction HINT */ \ + decl(Zihintpause , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZihintpause)) \ + /* Total Store Ordering */ \ + decl(Ztso , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZtso)) \ + /* Vector Basic Bit-manipulation */ \ + decl(Zvbb , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT_DEP(UseZvbb, &ext_v, nullptr)) \ + /* Vector Carryless Multiplication */ \ + decl(Zvbc , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT_DEP(UseZvbc, &ext_v, nullptr)) \ + /* Vector Extension for Half-Precision Floating-Point */ \ + decl(Zvfh , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT_DEP(UseZvfh, &ext_v, &ext_Zfhmin, nullptr)) \ + /* Vector Extension for Minimal Half-Precision Floating-Point */ \ + decl(Zvfhmin , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT_DEP(UseZvfhmin, &ext_v, nullptr)) \ + /* Shorthand for Zvkned + Zvknhb + Zvkb + Zvkt */ \ + decl(Zvkn , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT_DEP(UseZvkn, &ext_v, nullptr)) \ + /* Zvkg crypto extension for ghash and gcm */ \ + decl(Zvkg , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT_DEP(UseZvkg, &ext_v, nullptr)) \ #define DECLARE_RV_EXT_FEATURE(PRETTY, LINUX_BIT, FSTRING, FLAGF) \ struct ext_##PRETTY##RVExtFeatureValue : public RVExtFeatureValue { \ @@ -442,6 +444,8 @@ class VM_Version : public Abstract_VM_Version { RV_ENABLE_EXTENSION(UseZicboz) \ RV_ENABLE_EXTENSION(UseZicond) \ RV_ENABLE_EXTENSION(UseZihintpause) \ + RV_ENABLE_EXTENSION(UseZvfhmin) \ + RV_ENABLE_EXTENSION(UseZvbb) \ static void useRVA23U64Profile(); diff --git a/src/hotspot/cpu/s390/c2_MacroAssembler_s390.cpp b/src/hotspot/cpu/s390/c2_MacroAssembler_s390.cpp index 957c89af3fc0..3bf9ab8dd820 100644 --- a/src/hotspot/cpu/s390/c2_MacroAssembler_s390.cpp +++ b/src/hotspot/cpu/s390/c2_MacroAssembler_s390.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2017, 2024 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -42,10 +42,12 @@ void C2_MacroAssembler::fast_unlock(Register obj, Register box, Register temp1, } void C2_MacroAssembler::load_narrow_klass_compact_c2(Register dst, Address src) { + BLOCK_COMMENT("load_narrow_klass_compact_c2 {"); // The incoming address is pointing into obj-start + klass_offset_in_bytes. We need to extract // obj-start, so that we can load from the object's mark-word instead. z_lg(dst, src.plus_disp(-oopDesc::klass_offset_in_bytes())); - z_srlg(dst, dst, markWord::klass_shift); // TODO: could be z_sra + z_srlg(dst, dst, markWord::klass_shift); + BLOCK_COMMENT("} load_narrow_klass_compact_c2"); } //------------------------------------------------------ diff --git a/src/hotspot/cpu/s390/gc/shared/barrierSetAssembler_s390.cpp b/src/hotspot/cpu/s390/gc/shared/barrierSetAssembler_s390.cpp index 9a401766200a..d0f92cc129ac 100644 --- a/src/hotspot/cpu/s390/gc/shared/barrierSetAssembler_s390.cpp +++ b/src/hotspot/cpu/s390/gc/shared/barrierSetAssembler_s390.cpp @@ -116,7 +116,7 @@ void BarrierSetAssembler::resolve_jobject(MacroAssembler* masm, Register value, __ z_bre(done); // Use null result as-is. __ z_tmll(value, JNIHandles::tag_mask); - __ z_btrue(tagged); // not zero + __ branch_optimized(Assembler::bcondNotAllZero, tagged); // not zero // Resolve Local handle __ access_load_at(T_OBJECT, IN_NATIVE | AS_RAW, Address(value, 0), value, tmp1, tmp2); @@ -124,7 +124,7 @@ void BarrierSetAssembler::resolve_jobject(MacroAssembler* masm, Register value, __ bind(tagged); __ testbit(value, exact_log2(JNIHandles::TypeTag::weak_global)); // test for weak tag - __ z_btrue(weak_tag); + __ branch_optimized(Assembler::bcondNotAllZero, weak_tag); // resolve global handle __ access_load_at(T_OBJECT, IN_NATIVE, Address(value, -JNIHandles::TypeTag::global), value, tmp1, tmp2); diff --git a/src/hotspot/cpu/s390/s390.ad b/src/hotspot/cpu/s390/s390.ad index 256e39b03c2b..3d4abea75294 100644 --- a/src/hotspot/cpu/s390/s390.ad +++ b/src/hotspot/cpu/s390/s390.ad @@ -4816,17 +4816,15 @@ instruct loadNKlass(iRegN dst, memory mem) %{ ins_pipe(pipe_class_dummy); %} -instruct loadNKlassCompactHeaders(iRegN dst, memory mem, flagsReg cr) %{ +instruct loadNKlassCompactHeaders(iRegN dst, memory mem) %{ match(Set dst (LoadNKlass mem)); predicate(UseCompactObjectHeaders); - effect(KILL cr); ins_cost(MEMORY_REF_COST); format %{ "load_narrow_klass_compact $dst,$mem \t# compressed class ptr" %} - // TODO: size() + // z_lg (6 bytes) + z_srlg (6 bytes); neither instruction modifies the CC. + size(12); ins_encode %{ - __ block_comment("load_narrow_klass_compact_c2 {"); __ load_narrow_klass_compact_c2($dst$$Register, $mem$$Address); - __ block_comment("} load_narrow_klass_compact"); %} ins_pipe(pipe_class_dummy); %} @@ -8786,6 +8784,7 @@ instruct compP_decode_reg_imm0(flagsReg cr, iRegN op1, immP0 op2) %{ instruct compP_reg_mem(iRegP dst, memory src, flagsReg cr)%{ match(Set cr (CmpP dst (LoadP src))); + predicate(n->in(2)->as_Load()->barrier_data() == 0); ins_cost(MEMORY_REF_COST); size(Z_DISP3_SIZE); format %{ "CLG $dst, $src\t # ptr" %} diff --git a/src/hotspot/cpu/x86/c2_intelJccErratum_x86.cpp b/src/hotspot/cpu/x86/c2_intelJccErratum_x86.cpp index 909554cdf764..540991c5d0c5 100644 --- a/src/hotspot/cpu/x86/c2_intelJccErratum_x86.cpp +++ b/src/hotspot/cpu/x86/c2_intelJccErratum_x86.cpp @@ -103,7 +103,7 @@ int IntelJccErratum::compute_padding(uintptr_t current_offset, const MachNode* m } if (jcc_size > largest_jcc_size()) { // Let's not try fixing this for nodes that seem unreasonably large - return false; + return 0; } if (is_crossing_or_ending_at_32_byte_boundary(current_offset, current_offset + jcc_size)) { return int(align_up(current_offset, 32) - current_offset); diff --git a/src/hotspot/cpu/x86/methodHandles_x86.hpp b/src/hotspot/cpu/x86/methodHandles_x86.hpp index c4dde903d29f..8fdb6c1fb52f 100644 --- a/src/hotspot/cpu/x86/methodHandles_x86.hpp +++ b/src/hotspot/cpu/x86/methodHandles_x86.hpp @@ -27,7 +27,7 @@ // Adapters enum /* platform_dependent_constants */ { - adapter_code_size = 6000 DEBUG_ONLY(+ 6000) + adapter_code_size = 8000 DEBUG_ONLY(+ 6000) }; // Additional helper methods for MethodHandles code generation: diff --git a/src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp b/src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp index 8bb9982a8202..7fc105046ffc 100644 --- a/src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp +++ b/src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp @@ -3452,7 +3452,7 @@ RuntimeStub* SharedRuntime::generate_jfr_write_checkpoint() { }; const char* name = SharedRuntime::stub_name(StubId::shared_jfr_write_checkpoint_id); - CodeBuffer code(name, 1024, 64); + CodeBuffer code(name, 1024 + (UseAPX ? 1024 : 0), 64); MacroAssembler* masm = new MacroAssembler(&code); address start = __ pc(); diff --git a/src/hotspot/cpu/x86/vm_version_x86.cpp b/src/hotspot/cpu/x86/vm_version_x86.cpp index 6112c280a1dc..e395dd301f4f 100644 --- a/src/hotspot/cpu/x86/vm_version_x86.cpp +++ b/src/hotspot/cpu/x86/vm_version_x86.cpp @@ -1343,7 +1343,7 @@ void VM_Version::get_processor_features() { } if (UseSHA && ((supports_evex() && supports_avx512vlbw()) || - (EnableX86ECoreOpts && !supports_hybrid()))) { + (supports_avx2() && EnableX86ECoreOpts && !supports_hybrid()))) { if (FLAG_IS_DEFAULT(UseSHA3Intrinsics)) { FLAG_SET_DEFAULT(UseSHA3Intrinsics, true); } diff --git a/src/hotspot/cpu/x86/x86.ad b/src/hotspot/cpu/x86/x86.ad index 3f953dbe725b..883fb0c6300b 100644 --- a/src/hotspot/cpu/x86/x86.ad +++ b/src/hotspot/cpu/x86/x86.ad @@ -3179,10 +3179,7 @@ bool Matcher::match_rule_supported(int opcode) { break; case Op_VectorCmpMasked: - if (!UseCountTrailingZerosInstruction) { - return false; - } - if (UseAVX < 3 || !VM_Version::supports_bmi2()) { + if (UseAVX < 3 || !UseCountTrailingZerosInstruction) { return false; } break; diff --git a/src/hotspot/os/linux/globals_linux.hpp b/src/hotspot/os/linux/globals_linux.hpp index 90e1e5e5f3f0..fa7b5a63c6ce 100644 --- a/src/hotspot/os/linux/globals_linux.hpp +++ b/src/hotspot/os/linux/globals_linux.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -94,6 +94,10 @@ " 0 = no timeout (default)") \ range(0,1000000) \ \ + product(ccstr, AltTempDir, nullptr, \ + "Alternate temporary directory for JVM files.") \ + \ + // end of RUNTIME_OS_FLAGS // diff --git a/src/hotspot/os/linux/os_linux.cpp b/src/hotspot/os/linux/os_linux.cpp index aad18edf2a65..12a4ea2bda47 100644 --- a/src/hotspot/os/linux/os_linux.cpp +++ b/src/hotspot/os/linux/os_linux.cpp @@ -112,6 +112,7 @@ # include # include # include +# include # include # include # include @@ -1547,11 +1548,47 @@ int os::current_process_id() { return ::getpid(); } -// DLL functions +static bool is_writable_directory(const char* name) { + struct stat mystat; + int ret_val = stat(name, &mystat); + return (ret_val != -1 && S_ISDIR(mystat.st_mode) > 0 && access(name, R_OK|W_OK|X_OK) == 0); +} + +// Check that a given alternate temporary directory name specifies an absolute path and is an existing, writable +// directory. + +// If it is not an absolute path, revert back to hardcoded /tmp. If the directory is non existant or not +// writable give a warning but use AltTempDir. In the latter case, we may be connecting to a process that is +// inside a container. +// +// Since the attach mechanism uses the socket name length, this limits the length of the alternate +// temporary directory name. We don't check that here since the temporary directory is +// used for many things. The perfData and attach code will check it. + +void os::pd_check_temp_directory() { + if (AltTempDir != nullptr && AltTempDir[0] != '\0') { + if (AltTempDir[0] != '/') { + log_warning(os)("Warning: AltTempDir is ignored because it must be an absolute pathname"); + AltTempDir = nullptr; + } else { + if (!is_writable_directory(AltTempDir)) { + // This is only a warning and still uses AltTempDir, which is needed to attach to a + // containerized process from the host. + log_warning(os)("Warning: AltTempDir is not an existing or writable directory"); + } + } + } else { + if (!is_writable_directory("/tmp")) { + log_warning(os)("Warning: /tmp is not writable. Consider using -XX:AltTempDir=/ to set a writable temp directory"); + } + AltTempDir = nullptr; // avoid checking AltTempDir[0] again. + } +} -// This must be hard coded because it's the system's temporary -// directory not the java application's temp directory, ala java.io.tmpdir. -const char* os::get_temp_directory() { return "/tmp"; } +const char* os::get_temp_directory() { + // AltTempDir is already checked. + return AltTempDir != nullptr ? AltTempDir : "/tmp"; +} // check if addr is inside libjvm.so bool os::address_is_in_vm(address addr) { diff --git a/src/hotspot/os/posix/attachListener_posix.cpp b/src/hotspot/os/posix/attachListener_posix.cpp index a7cf17031280..152fd6140e08 100644 --- a/src/hotspot/os/posix/attachListener_posix.cpp +++ b/src/hotspot/os/posix/attachListener_posix.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -201,6 +201,8 @@ int PosixAttachListener::init() { n = os::snprintf(initial_path, UNIX_PATH_MAX, "%s.tmp", path); } if (n >= (int)UNIX_PATH_MAX) { + log_warning(attach)("Failed to create temporary file for attach %s/.java_pid%d: file name is too long", + os::get_temp_directory(), os::current_process_id()); return -1; } @@ -346,8 +348,11 @@ void AttachListener::vm_start() { struct stat st; int ret; - os::snprintf_checked(fn, UNIX_PATH_MAX, "%s/.java_pid%d", + int n = os::snprintf(fn, UNIX_PATH_MAX, "%s/.java_pid%d", os::get_temp_directory(), os::current_process_id()); + if (n >= (int)UNIX_PATH_MAX) { + return; + } RESTARTABLE(::stat(fn, &st), ret); if (ret == 0) { diff --git a/src/hotspot/os/posix/perfMemory_posix.cpp b/src/hotspot/os/posix/perfMemory_posix.cpp index 300c86ffc47a..aaeb33b6d9b4 100644 --- a/src/hotspot/os/posix/perfMemory_posix.cpp +++ b/src/hotspot/os/posix/perfMemory_posix.cpp @@ -135,23 +135,25 @@ static void save_memory_to_file(char* addr, size_t size) { // return the user specific temporary directory name. // the caller is expected to free the allocated memory. // -#define TMP_BUFFER_LEN (4+22) static char* get_user_tmp_dir(const char* user, int vmid, int nspid) { char* tmpdir = (char *)os::get_temp_directory(); + char buffer[PATH_MAX] = {0}; #if defined(LINUX) // On linux, if containerized process, get dirname of // /proc/{vmid}/root/tmp/{PERFDATA_NAME_user} // otherwise /tmp/{PERFDATA_NAME_user} - char buffer[TMP_BUFFER_LEN]; - assert(strlen(tmpdir) == 4, "No longer using /tmp - update buffer size"); + // The /tmp directory can be overridden with AltTempDir. if (nspid != -1) { - jio_snprintf(buffer, TMP_BUFFER_LEN, "/proc/%d/root%s", vmid, tmpdir); + int val = os::snprintf(buffer, PATH_MAX, "/proc/%d/root%s", vmid, tmpdir); + if (val >= (int)PATH_MAX) { + log_warning(perf)("The temporary directory for perf data /proc/%d/root%s name is truncated", + vmid, tmpdir); + } tmpdir = buffer; } #endif #ifdef __APPLE__ - char buffer[PATH_MAX] = {0}; // Check if the current user is root and the target VM is running as non-root. // Otherwise the output of os::get_temp_directory() is used. // @@ -524,7 +526,6 @@ static char* get_user_name_slow(int vmid, int nspid, TRAPS) { char* tmpdirname = (char *)os::get_temp_directory(); #if defined(LINUX) char buffer[MAXPATHLEN + 1]; - assert(strlen(tmpdirname) == 4, "No longer using /tmp - update buffer size"); // On Linux, if nspid != -1, look in /proc/{vmid}/root/tmp for directories // containing nspid, otherwise just look for vmid in /tmp. diff --git a/src/hotspot/os/windows/os_windows.cpp b/src/hotspot/os/windows/os_windows.cpp index 0fc636483f5d..f62e9c298e86 100644 --- a/src/hotspot/os/windows/os_windows.cpp +++ b/src/hotspot/os/windows/os_windows.cpp @@ -3507,6 +3507,152 @@ char* os::pd_reserve_memory(size_t bytes, bool exec) { return pd_attempt_reserve_memory_at(nullptr /* addr */, bytes, exec); } +// This allocates a placeholder via VirtualAlloc2(MEM_RESERVE_PLACEHOLDER). +os::win32::PlaceholderRegion os::win32::reserve_placeholder_memory(size_t bytes, char* addr) { + assert(bytes > 0, "Size must be a value greater than 0"); + assert(is_aligned(addr, os::vm_allocation_granularity()), "Requested address should be aligned to allocation granularity."); + assert(is_aligned(bytes, os::vm_page_size()), "Requested size, bytes, should be aligned to page size."); + + if (!is_VirtualAlloc2_supported()) { + return PlaceholderRegion(); + } + + char* res = (char*)os::win32::VirtualAlloc2( + GetCurrentProcess(), + addr, + bytes, + MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, + PAGE_NOACCESS, + nullptr, 0); + + if (res != nullptr) { + log_trace(os)("VirtualAlloc2 placeholder of size (%zu) returned " PTR_FORMAT ".", bytes, p2i(res)); + return PlaceholderRegion(res, bytes); + } else { + log_warning(os)("VirtualAlloc2 placeholder reservation of size (%zu) at " PTR_FORMAT ": error %lu.", bytes, p2i(addr), GetLastError()); + return PlaceholderRegion(); + } +} + +os::win32::PlaceholderRegionPair os::win32::split_memory(const PlaceholderRegion& orig, size_t offset) { + guarantee(is_VirtualAlloc2_supported(), "split_memory requires VirtualAlloc2."); + assert(!orig.is_empty(), "Region cannot be empty"); + assert(offset <= orig.size(), "Offset must be less than or equal to region size"); + + char* original_base = orig.base(); + size_t original_size = orig.size(); + + if (offset == 0) { + log_trace(os)("Split memory has offset 0: " RANGEFMT, RANGEFMTARGS(original_base, original_size)); + return { PlaceholderRegion(), orig }; + } else if (offset == original_size) { + log_trace(os)("Split memory consumed the whole region: " RANGEFMT, RANGEFMTARGS(original_base, original_size)); + return { orig, PlaceholderRegion() }; + } + + assert(is_aligned(offset, os::vm_allocation_granularity()), "If the split does not consume the entire original region, the offset should be aligned to allocation granularity since a new Placeholder is spawned the split point."); + + // VirtualFree with MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER splits the + // placeholder [original_base, original_base+original_size) in two: + // [original_base, original_base+offset) and [original_base+offset, original_base+original_size) + // + // With correct inputs, this should not fail. + // A failure indicates either a programming error (e.g., bad alignment, + // region not actually a placeholder) or a catastrophic system problem. + // Crashing with a diagnostic is more useful than attempting recovery. + BOOL result = virtualFree(original_base, offset, MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER); + guarantee(result != FALSE, + "Failed to split placeholder at " PTR_FORMAT " (offset %zu): error %lu.", + p2i(original_base), offset, GetLastError()); + + log_trace(os)("Split placeholder " RANGE_FORMAT " at offset %zu.", + RANGE_FORMAT_ARGS(original_base, original_size), offset); + + return {PlaceholderRegion(original_base, offset), PlaceholderRegion(original_base + offset, original_size - offset)}; +} + +char* os::win32::convert_to_reserved(PlaceholderRegion region, int numa_node) { + guarantee(is_VirtualAlloc2_supported(), "convert_to_reserved requires VirtualAlloc2"); + assert(!region.is_empty(), "Region cannot be empty"); + + char* base = region.base(); + size_t size = region.size(); + + assert(base != nullptr, "Region base cannot be null"); + assert(size > 0, "Region size must be positive"); + + MEM_EXTENDED_PARAMETER param = { 0 }; + MEM_EXTENDED_PARAMETER* param_ptr = nullptr; + ULONG param_count = 0; + + if (numa_node >= 0) { + param.Type = MemExtendedParameterNumaNode; + param.ULong = (DWORD)numa_node; + param_ptr = ¶m; + param_count = 1; + } + + // Similar to split_memory, with correct inputs, this should never fail. + char* reserved = (char*)os::win32::VirtualAlloc2( + GetCurrentProcess(), + base, + size, + MEM_RESERVE | MEM_REPLACE_PLACEHOLDER, + PAGE_READWRITE, + param_ptr, param_count); + guarantee(reserved != nullptr, + "Failed to convert placeholder to reservation at " PTR_FORMAT " (%zu, numa node %d): error %lu.", + p2i(base), size, numa_node, GetLastError()); + + if (numa_node >= 0) { + log_trace(os)("Converted placeholder " RANGE_FORMAT " to reservation on NUMA node %d.", RANGE_FORMAT_ARGS(reserved, size), numa_node); + } else { + log_trace(os)("Converted placeholder " RANGE_FORMAT " to reservation.", RANGE_FORMAT_ARGS(reserved, size)); + } + + return reserved; +} + +// Reserve a region split across NUMA nodes. +// Uses VirtualAlloc2 placeholders in order to avoid races when splitting up the initial reservation into +// chunks assigned to different nodes. Returns the base address of the reserved range, or nullptr on failure. +static char* reserve_with_numa_placeholder(char* addr, size_t bytes) { + assert(is_VirtualAlloc2_supported(), "requires VirtualAlloc2"); + + const size_t chunk_size = NUMAInterleaveGranularity; + + // Reserve the full range as a placeholder. + // If we requested an address, reserve_placeholder_memory will obtain it or fail. + os::win32::PlaceholderRegion whole_range = os::win32::reserve_placeholder_memory(bytes, addr); + if (whole_range.is_empty()) { + log_warning(os)("Failed to reserve placeholder for NUMA interleaving (" PTR_FORMAT ", %zu).", p2i(addr), bytes); + return nullptr; + } + + char* const whole_range_base = whole_range.base(); + log_trace(os)("Created VirtualAlloc2 NUMA placeholder at " RANGE_FORMAT " (%zu bytes).", RANGE_FORMAT_ARGS(whole_range_base, bytes), bytes); + + char* cur = whole_range_base; + size_t remaining_len = whole_range.size(); + + int count = 0; + const int node_count = numa_node_list_holder.get_count(); + + while (remaining_len > 0) { + const size_t bytes_to_rq = MIN2(remaining_len, chunk_size - ((uintptr_t)cur % chunk_size)); + os::win32::PlaceholderRegion remaining(cur, remaining_len); + os::win32::PlaceholderRegionPair split = os::win32::split_memory(remaining, bytes_to_rq); + // Assign 0 for testing on systems without NUMA interleaving + DWORD node = node_count > 0 ? numa_node_list_holder.get_node_list_entry(count % node_count) : 0; + os::win32::convert_to_reserved(split.left, (int)node); + cur = split.right.base(); + remaining_len = split.right.size(); + count++; + } + + return whole_range_base; +} + // Reserve memory at an arbitrary address, only if that area is // available (and not reserved for something else). char* os::pd_attempt_reserve_memory_at(char* addr, size_t bytes, bool exec) { @@ -3516,23 +3662,32 @@ char* os::pd_attempt_reserve_memory_at(char* addr, size_t bytes, bool exec) { char* res; // note that if UseLargePages is on, all the areas that require interleaving // will go thru reserve_memory_special rather than thru here. - bool use_individual = (UseNUMAInterleaving && !UseLargePages); - if (!use_individual) { - res = (char*)virtualAlloc(addr, bytes, MEM_RESERVE, PAGE_READWRITE); - } else { + bool use_numa_interleaving = (UseNUMAInterleaving && !UseLargePages); + if (use_numa_interleaving) { elapsedTimer reserveTimer; if (Verbose && PrintMiscellaneous) reserveTimer.start(); - // in numa interleaving, we have to allocate pages individually - // (well really chunks of NUMAInterleaveGranularity size) - res = allocate_pages_individually(bytes, addr, MEM_RESERVE, PAGE_READWRITE); - if (res == nullptr) { - warning("NUMA page allocation failed"); + if (is_VirtualAlloc2_supported()) { + // Splittable NUMA interleaving with VirtualAlloc2 placeholders. + res = reserve_with_numa_placeholder(addr, bytes); + if (res == nullptr) { + log_warning(os)("NUMA allocation using placeholders failed"); + } + } else { + // Non-splittable NUMA interleaving: allocate_pages_individually (possible races). + // (well really chunks of NUMAInterleaveGranularity size) + res = allocate_pages_individually(bytes, addr, MEM_RESERVE, PAGE_READWRITE); + if (res == nullptr) { + log_warning(os)("NUMA page allocation failed"); + } } if (Verbose && PrintMiscellaneous) { reserveTimer.stop(); tty->print_cr("reserve_memory of %zx bytes took " JLONG_FORMAT " ms (" JLONG_FORMAT " ticks)", bytes, - reserveTimer.milliseconds(), reserveTimer.ticks()); + reserveTimer.milliseconds(), reserveTimer.ticks()); } + } else { + // Standard reservation. + res = (char*)virtualAlloc(addr, bytes, MEM_RESERVE, PAGE_READWRITE); } assert(res == nullptr || addr == nullptr || addr == res, "Unexpected address from reserve."); diff --git a/src/hotspot/os/windows/os_windows.hpp b/src/hotspot/os/windows/os_windows.hpp index 5ebc80c817b7..68e77c9957fc 100644 --- a/src/hotspot/os/windows/os_windows.hpp +++ b/src/hotspot/os/windows/os_windows.hpp @@ -122,6 +122,57 @@ class os::win32 { typedef PVOID (WINAPI *MapViewOfFile3Fn)(HANDLE, HANDLE, PVOID, ULONG64, SIZE_T, ULONG, ULONG, MEM_EXTENDED_PARAMETER*, ULONG); static MapViewOfFile3Fn MapViewOfFile3; + // A "reserved" region of address space that can be split or converted to a + // normal reservation. Conceptually distinct from a reserved region: + // callers must NOT call commit_memory, map_memory, or other operations + // directly on the raw address. They must first convert it via + // convert_to_reserved(). + class PlaceholderRegion { + char* const _base; + size_t const _size; + public: + PlaceholderRegion() : _base(nullptr), _size(0) {} + PlaceholderRegion(char* base, size_t size) : _base(base), _size(size) { + if (base != nullptr) { + assert(size > 0, "Non-empty Placeholder must have positive size."); + assert(is_aligned(base, os::vm_allocation_granularity()), "New Placeholder base should be aligned to allocation granularity."); + assert(is_aligned(size, os::vm_page_size()), "New Placeholder size should be page-aligned"); + } else { + assert(size == 0, "Empty Placeholder must have zero size."); + } + } + PlaceholderRegion(const PlaceholderRegion& source) : PlaceholderRegion(source._base, source._size) {} + char* base() const { return _base; } + size_t size() const { return _size; } + bool is_empty() const { return _base == nullptr; } + }; + + struct PlaceholderRegionPair { + PlaceholderRegion left; + PlaceholderRegion right; + }; + + // Reserves a virtual memory region that can be split after allocation. + // The returned region must be converted via convert_to_reserved() before committing. + // If the returned PlaceholderRegion is empty, the reservation failed. + // This should only be called after os::init_2() has completed, otherwise the Windows API may not be initialized. + // Uses VirtualAlloc2, which requires the base address be null or aligned to allocation granularity. + static PlaceholderRegion reserve_placeholder_memory(size_t bytes, char* addr); + + // Split 'orig' at 'offset'. Returns left and right placeholder pieces as a PlaceholderRegionPair. + // The caller must not use 'orig' afterward. + // Offset must be aligned to allocation granularity. + // If offset == orig.size(), returns { orig, empty }. + // If offset == 0, returns { empty, orig }. + // This should not fail. If unsuccessful, this function fails fatally. + static PlaceholderRegionPair split_memory(const PlaceholderRegion& orig, size_t offset); + + // Convert a placeholder region into a regular reserved region via VirtualAlloc2(MEM_REPLACE_PLACEHOLDER). + // After conversion the Placeholder region should no longer be used. + // This should not fail. If unsuccessful, this function fails fatally. + // If numa_node >= 0, binds the reservation to that NUMA node. + static char* convert_to_reserved(PlaceholderRegion region, int numa_node = -1); + private: static void initialize_performance_counter(); diff --git a/src/hotspot/os_cpu/linux_riscv/riscv_hwprobe.cpp b/src/hotspot/os_cpu/linux_riscv/riscv_hwprobe.cpp index fe555ec5ffb3..24c65bd21e04 100644 --- a/src/hotspot/os_cpu/linux_riscv/riscv_hwprobe.cpp +++ b/src/hotspot/os_cpu/linux_riscv/riscv_hwprobe.cpp @@ -237,16 +237,19 @@ void RiscvHwprobe::add_features_from_query_result() { if (is_set(RISCV_HWPROBE_KEY_IMA_EXT_0, RISCV_HWPROBE_EXT_ZTSO)) { VM_Version::ext_Ztso.enable_feature(); } +#endif if (is_set(RISCV_HWPROBE_KEY_IMA_EXT_0, RISCV_HWPROBE_EXT_ZVBC)) { VM_Version::ext_Zvbc.enable_feature(); } -#endif if (is_set(RISCV_HWPROBE_KEY_IMA_EXT_0, RISCV_HWPROBE_EXT_ZVBB)) { VM_Version::ext_Zvbb.enable_feature(); } if (is_set(RISCV_HWPROBE_KEY_IMA_EXT_0, RISCV_HWPROBE_EXT_ZVFH)) { VM_Version::ext_Zvfh.enable_feature(); } + if (is_set(RISCV_HWPROBE_KEY_IMA_EXT_0, RISCV_HWPROBE_EXT_ZVFHMIN)) { + VM_Version::ext_Zvfhmin.enable_feature(); + } if (is_set(RISCV_HWPROBE_KEY_IMA_EXT_0, RISCV_HWPROBE_EXT_ZVKNED) && is_set(RISCV_HWPROBE_KEY_IMA_EXT_0, RISCV_HWPROBE_EXT_ZVKNHB) && is_set(RISCV_HWPROBE_KEY_IMA_EXT_0, RISCV_HWPROBE_EXT_ZVKB) && diff --git a/src/hotspot/share/cds/aotMetaspace.cpp b/src/hotspot/share/cds/aotMetaspace.cpp index fbd12038c94a..8106258c331f 100644 --- a/src/hotspot/share/cds/aotMetaspace.cpp +++ b/src/hotspot/share/cds/aotMetaspace.cpp @@ -165,22 +165,15 @@ size_t AOTMetaspace::protection_zone_size() { } bool AOTMetaspace::shared_base_valid(char* shared_base) { - // We check user input for SharedBaseAddress at dump time. - // At CDS runtime, "shared_base" will be the (attempted) mapping start. It will also // be the encoding base, since the headers of archived base objects (and with Lilliput, // the prototype mark words) carry pre-computed narrow Klass IDs that refer to the mapping // start as base. - // - // The "shared_base" may not be later usable as encoding base, depending on the - // total size of the reserved area and the precomputed_narrow_klass_shift. This is checked - // before reserving memory. Here we weed out values already known to be invalid later. - // Since we cannot predict the range, we use the full maximum encoding range - // (4G). - constexpr size_t range = 4 * G; - address addr = (address)shared_base; - const int shift = ArchiveBuilder::precomputed_narrow_klass_shift(); - return CompressedKlassPointers::check_klass_decode_mode(addr, shift, range); + // Note that all narrowKlass inside CDS/AOT archives will be precomputed with the + // shift that, at build time, will afford us the maximum encoding range of 4GB. We do this + // since we don't know how large the class space at runtime will actually be. + return CLASS_SPACE_ONLY(is_aligned(shared_base, Metaspace::reserve_alignment())) + NOT_CLASS_SPACE(true); } class DumpClassListCLDClosure : public CLDClosure { @@ -1976,16 +1969,11 @@ char* AOTMetaspace::reserve_address_space_for_archives(FileMapInfo* static_mapin const size_t total_range_size = archive_space_size + gap_size + class_space_size; - // The code for dumping the archive ensures that the base address is valid. - // Here we validate that the base address plus shift can be decoded when - // restored. - assert(shared_base_valid((char*)base_address), - "Cannot use SharedBaseAddress " PTR_FORMAT " with precomputed shift %d.", - p2i(base_address), ArchiveBuilder::precomputed_narrow_klass_shift()); - assert(total_range_size > ccs_begin_offset, "must be"); if (use_windows_memory_mapping() && use_archive_base_addr) { if (base_address != nullptr) { + // Note: We already checked the base address for validity at dump time. + // On Windows, we cannot safely split a reserved memory space into two (see JDK-8255917). // Hence, we optimistically reserve archive space and class space side-by-side. We only // do this for use_archive_base_addr=true since for use_archive_base_addr=false case diff --git a/src/hotspot/share/cds/aotReferenceObjSupport.cpp b/src/hotspot/share/cds/aotReferenceObjSupport.cpp index 2d5fc8c7f217..62ed3a56b620 100644 --- a/src/hotspot/share/cds/aotReferenceObjSupport.cpp +++ b/src/hotspot/share/cds/aotReferenceObjSupport.cpp @@ -142,6 +142,16 @@ void AOTReferenceObjSupport::stabilize_cached_reference_objects(TRAPS) { vmSymbols::void_method_signature(), CHECK); } + { + TempNewSymbol method_name = SymbolTable::new_symbol("assemblySetup"); + JavaValue result(T_VOID); + Symbol* baseLocale_name = vmSymbols::sun_util_locale_BaseLocale(); + Klass* baseLocale_klass = SystemDictionary::resolve_or_fail(baseLocale_name, true, CHECK); + JavaCalls::call_static(&result, baseLocale_klass, + method_name, + vmSymbols::void_method_signature(), + CHECK); + } { Symbol* cds_name = vmSymbols::jdk_internal_misc_CDS(); diff --git a/src/hotspot/share/classfile/verifier.cpp b/src/hotspot/share/classfile/verifier.cpp index 48be24c20dcd..8422a39827ac 100644 --- a/src/hotspot/share/classfile/verifier.cpp +++ b/src/hotspot/share/classfile/verifier.cpp @@ -222,9 +222,9 @@ bool Verifier::verify(InstanceKlass* klass, bool should_verify_class, TRAPS) { split_verifier.verify_class(THREAD); exception_name = split_verifier.result(); - // If dumping {classic, final} static archive, don't bother to run the old verifier, as + // If dumping classic static archive, don't bother to run the old verifier, as // the class will be excluded from the archive anyway. - bool can_failover = !(CDSConfig::is_dumping_classic_static_archive() || CDSConfig::is_dumping_final_static_archive()) && + bool can_failover = !(CDSConfig::is_dumping_classic_static_archive()) && klass->major_version() < NOFAILOVER_MAJOR_VERSION; if (can_failover && !HAS_PENDING_EXCEPTION && // Split verifier doesn't set PENDING_EXCEPTION for failure @@ -233,9 +233,9 @@ bool Verifier::verify(InstanceKlass* klass, bool should_verify_class, TRAPS) { log_info(verification)("Fail over class verification to old verifier for: %s", klass->external_name()); log_info(class, init)("Fail over class verification to old verifier for: %s", klass->external_name()); #if INCLUDE_CDS - // Exclude any classes that are verified with the old verifier, as the old verifier - // doesn't call SystemDictionaryShared::add_verification_constraint() - if (CDSConfig::is_dumping_archive()) { + // Exclude any classes that are verified with the old verifier when the verification constraints + // cannot be preserved. + if (CDSConfig::is_dumping_archive() && !CDSConfig::is_preserving_verification_constraints()) { SystemDictionaryShared::log_exclusion(klass, "Verified with old verifier"); SystemDictionaryShared::set_excluded(klass); } @@ -244,6 +244,10 @@ bool Verifier::verify(InstanceKlass* klass, bool should_verify_class, TRAPS) { exception_message = message_buffer; exception_name = inference_verify( klass, message_buffer, message_buffer_len, THREAD); + + if (exception_name == nullptr && !HAS_PENDING_EXCEPTION) { + klass->set_fail_over_verified(); + } } if (exception_name != nullptr) { exception_message = split_verifier.exception_message(); diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp index 0348fae28b01..4337020846f3 100644 --- a/src/hotspot/share/classfile/vmSymbols.hpp +++ b/src/hotspot/share/classfile/vmSymbols.hpp @@ -731,6 +731,7 @@ class SerializeClosure; template(runtimeSetup, "runtimeSetup") \ template(toFileURL_name, "toFileURL") \ template(toFileURL_signature, "(Ljava/lang/String;)Ljava/net/URL;") \ + template(sun_util_locale_BaseLocale, "sun/util/locale/BaseLocale") \ \ /* jcmd Thread.dump_to_file */ \ template(jdk_internal_vm_ThreadDumper, "jdk/internal/vm/ThreadDumper") \ diff --git a/src/hotspot/share/code/aotCodeCache.hpp b/src/hotspot/share/code/aotCodeCache.hpp index c65b9cb23d1d..88f65013938f 100644 --- a/src/hotspot/share/code/aotCodeCache.hpp +++ b/src/hotspot/share/code/aotCodeCache.hpp @@ -302,6 +302,7 @@ class AOTStubData : public StackObj { do_var(bool, UseSHA512Intrinsics) \ do_var(bool, UseIntPolyIntrinsics) \ do_var(bool, UseVectorizedMismatchIntrinsic) \ + do_var(bool, VMContinuations) \ do_fun(int, CompressedKlassPointers_shift, CompressedKlassPointers::shift()) \ do_fun(bool, JavaAssertions_systemClassDefault, JavaAssertions::systemClassDefault()) \ do_fun(bool, JavaAssertions_userClassDefault, JavaAssertions::userClassDefault()) \ diff --git a/src/hotspot/share/compiler/compilerDefinitions.cpp b/src/hotspot/share/compiler/compilerDefinitions.cpp index 5bb96f8d0315..1ad667e51f1e 100644 --- a/src/hotspot/share/compiler/compilerDefinitions.cpp +++ b/src/hotspot/share/compiler/compilerDefinitions.cpp @@ -272,7 +272,11 @@ void CompilerConfig::set_compilation_policy_flags() { } #ifdef COMPILER2 - if (HotCodeHeap) { + if (HotCodeHeap && !is_c2_enabled()) { + warning("HotCodeHeap disabled because C2 is disabled."); + FLAG_SET_ERGO(HotCodeHeap, false); + FLAG_SET_ERGO(HotCodeHeapSize, 0); + } else if (HotCodeHeap) { if (FLAG_IS_DEFAULT(SegmentedCodeCache)) { FLAG_SET_ERGO(SegmentedCodeCache, true); } else if (!SegmentedCodeCache) { @@ -285,10 +289,6 @@ void CompilerConfig::set_compilation_policy_flags() { vm_exit_during_initialization("HotCodeHeap requires NMethodRelocation enabled"); } - if (!is_c2_enabled()) { - vm_exit_during_initialization("HotCodeHeap requires C2 enabled"); - } - if (HotCodeMinSamplingMs > HotCodeMaxSamplingMs) { vm_exit_during_initialization("HotCodeMinSamplingMs cannot be larger than HotCodeMaxSamplingMs"); } diff --git a/src/hotspot/share/gc/g1/g1HeapRegionRemSet.hpp b/src/hotspot/share/gc/g1/g1HeapRegionRemSet.hpp index 20f7b785f458..b185aa3151c2 100644 --- a/src/hotspot/share/gc/g1/g1HeapRegionRemSet.hpp +++ b/src/hotspot/share/gc/g1/g1HeapRegionRemSet.hpp @@ -168,7 +168,7 @@ class G1HeapRegionRemSet : public CHeapObj { // Returns the memory occupancy of all static data structures associated // with remembered sets. static size_t static_mem_size() { - return G1CardSet::static_mem_size(); + return G1CardSet::static_mem_size() + G1FromCardCache::static_mem_size(); } static void print_static_mem_size(outputStream* out); diff --git a/src/hotspot/share/gc/shared/barrierSet.cpp b/src/hotspot/share/gc/shared/barrierSet.cpp index a30b23ce2d99..1fd0317e8f1a 100644 --- a/src/hotspot/share/gc/shared/barrierSet.cpp +++ b/src/hotspot/share/gc/shared/barrierSet.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -86,7 +86,7 @@ BarrierSet::BarrierSet(BarrierSetAssembler* barrier_set_assembler, void BarrierSet::on_thread_attach(Thread* thread) { BarrierSetNMethod* bs_nm = barrier_set_nmethod(); - thread->set_nmethod_disarmed_guard_value(bs_nm->disarmed_guard_value()); + bs_nm->set_thread_disarmed_guard_value(thread); } // Called from init.cpp diff --git a/src/hotspot/share/gc/shared/barrierSetNMethod.cpp b/src/hotspot/share/gc/shared/barrierSetNMethod.cpp index 2f7b79beab0f..c36deb3446ad 100644 --- a/src/hotspot/share/gc/shared/barrierSetNMethod.cpp +++ b/src/hotspot/share/gc/shared/barrierSetNMethod.cpp @@ -135,6 +135,10 @@ ByteSize BarrierSetNMethod::thread_disarmed_guard_value_offset() const { return Thread::nmethod_disarmed_guard_value_offset(); } +void BarrierSetNMethod::set_thread_disarmed_guard_value(Thread* thread) { + thread->set_nmethod_disarmed_guard_value(disarmed_guard_value()); +} + class BarrierSetNMethodArmClosure : public ThreadClosure { private: int _disarmed_guard_value; diff --git a/src/hotspot/share/gc/shared/barrierSetNMethod.hpp b/src/hotspot/share/gc/shared/barrierSetNMethod.hpp index 812763e429d2..cd01ddda09c5 100644 --- a/src/hotspot/share/gc/shared/barrierSetNMethod.hpp +++ b/src/hotspot/share/gc/shared/barrierSetNMethod.hpp @@ -54,9 +54,11 @@ class BarrierSetNMethod: public CHeapObj { bool supports_entry_barrier(nmethod* nm); virtual bool nmethod_entry_barrier(nmethod* nm); - virtual ByteSize thread_disarmed_guard_value_offset() const; virtual int* disarmed_guard_value_address() const; + ByteSize thread_disarmed_guard_value_offset() const; + void set_thread_disarmed_guard_value(Thread* thread); + int disarmed_guard_value() const; static int nmethod_stub_entry_barrier(address* return_address_ptr); diff --git a/src/hotspot/share/gc/shared/gcThreadLocalData.hpp b/src/hotspot/share/gc/shared/gcThreadLocalData.hpp index 2847cd8bf333..b0659c583906 100644 --- a/src/hotspot/share/gc/shared/gcThreadLocalData.hpp +++ b/src/hotspot/share/gc/shared/gcThreadLocalData.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -40,6 +40,6 @@ // should consider placing frequently accessed fields first in // T, so that field offsets relative to Thread are small, which // often allows for a more compact instruction encoding. -typedef uint64_t GCThreadLocalData[40]; // 320 bytes +typedef uint64_t GCThreadLocalData[39]; // 312 bytes #endif // SHARE_GC_SHARED_GCTHREADLOCALDATA_HPP diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp index ac1feacd74d8..c01d82acbe24 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp @@ -580,20 +580,21 @@ bool ShenandoahGenerationalControlThread::check_cancellation_or_degen(Shenandoah return false; } - if (_heap->cancelled_cause() == GCCause::_shenandoah_stop_vm - || _heap->cancelled_cause() == GCCause::_shenandoah_concurrent_gc) { - log_debug(gc, thread)("Cancellation detected, reason: %s", GCCause::to_string(_heap->cancelled_cause())); + const GCCause::Cause cancelled_cause = _heap->cancelled_cause(); + if (cancelled_cause == GCCause::_shenandoah_stop_vm + || cancelled_cause == GCCause::_shenandoah_concurrent_gc) { + log_debug(gc, thread)("Cancellation detected, reason: %s", GCCause::to_string(cancelled_cause)); return true; } - if (ShenandoahCollectorPolicy::is_allocation_failure(_heap->cancelled_cause())) { + if (ShenandoahCollectorPolicy::is_allocation_failure(cancelled_cause)) { assert(_degen_point == ShenandoahGC::_degenerated_unset, "Should not be set yet: %s", ShenandoahGC::degen_point_to_string(_degen_point)); MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag); - _requested_gc_cause = _heap->cancelled_cause(); + _requested_gc_cause = cancelled_cause; _degen_point = point; log_debug(gc, thread)("Cancellation detected:, reason: %s, degen point: %s", - GCCause::to_string(_heap->cancelled_cause()), + GCCause::to_string(cancelled_cause), ShenandoahGC::degen_point_to_string(_degen_point)); return true; } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp index 7731ad911c5c..84add03f1bac 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp @@ -2244,8 +2244,18 @@ size_t ShenandoahHeap::tlab_used() const { } bool ShenandoahHeap::try_cancel_gc(GCCause::Cause cause) { - const GCCause::Cause prev = _cancelled_gc.xchg(cause); - return prev == GCCause::_no_gc || prev == GCCause::_shenandoah_concurrent_gc; + while (true) { + const GCCause::Cause prev = _cancelled_gc.get(); + if (prev != GCCause::_no_gc && prev != GCCause::_shenandoah_concurrent_gc && cause != GCCause::_shenandoah_stop_vm) { + // Only when the gc has not been cancelled, or it has been cancelled to interrupt an old marking cycle + // do we allow the new cancellation request to happen. We make an exception for stopping the VM. + return false; + } + + if (_cancelled_gc.cmpxchg(cause, prev) == prev) { + return true; + } + } } void ShenandoahHeap::cancel_concurrent_mark() { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMark.cpp b/src/hotspot/share/gc/shenandoah/shenandoahMark.cpp index fc508dddd840..9354ef25f3de 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahMark.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahMark.cpp @@ -73,11 +73,19 @@ void ShenandoahMark::mark_loop_prework(uint w, TaskTerminator *t, StringDedup::R if (update_refs) { using Closure = ShenandoahMarkUpdateRefsClosure; Closure cl(q, rp, old_q); - mark_loop_work(&cl, ld, w, t, req); + if (UseCompressedOops) { + mark_loop_work(&cl, ld, w, t, req); + } else { + mark_loop_work(&cl, ld, w, t, req); + } } else { using Closure = ShenandoahMarkRefsClosure; Closure cl(q, rp, old_q); - mark_loop_work(&cl, ld, w, t, req); + if (UseCompressedOops) { + mark_loop_work(&cl, ld, w, t, req); + } else { + mark_loop_work(&cl, ld, w, t, req); + } } heap->flush_liveness_cache(w); @@ -154,7 +162,7 @@ void ShenandoahMark::mark_drain_extra_queues(ShenandoahObjToScanQueueSet* queues } } -template +template void ShenandoahMark::mark_loop_work(T* cl, ShenandoahLiveData* live_data, uint worker_id, TaskTerminator *terminator, StringDedup::Requests* const req) { uintx stride = ShenandoahMarkLoopStride; @@ -182,7 +190,7 @@ void ShenandoahMark::mark_loop_work(T* cl, ShenandoahLiveData* live_data, uint w for (uint i = 0; i < stride; i++) { if (q->pop(t) || queues->steal(worker_id, t)) { - do_task(q, cl, live_data, req, &t, worker_id); + do_task(q, cl, live_data, req, &t, worker_id); work++; } else { break; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMark.hpp b/src/hotspot/share/gc/shenandoah/shenandoahMark.hpp index 69d792d0277e..a2c363b21299 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahMark.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahMark.hpp @@ -72,17 +72,17 @@ class ShenandoahMark: public StackObj { private: // ---------- Marking loop and tasks - template + template ALWAYSINLINE static void do_task(ShenandoahObjToScanQueue* q, T* cl, ShenandoahLiveData* live_data, StringDedup::Requests* const req, ShenandoahMarkTask* task, uint worker_id); - template + template ALWAYSINLINE static void do_chunked_array_start(ShenandoahObjToScanQueue* q, T* cl, oop array, Klass* klass, bool weak); - template + template ALWAYSINLINE - static void do_chunked_array(ShenandoahObjToScanQueue* q, T* cl, oop array, int chunk, int pow, bool weak); + static void do_chunked_array(ShenandoahObjToScanQueue* q, T* cl, oop array, Klass* klass, int chunk, int pow, bool weak); template ALWAYSINLINE @@ -105,7 +105,7 @@ class ShenandoahMark: public StackObj { template void mark_loop_prework(uint worker_id, TaskTerminator *terminator, StringDedup::Requests* const req, bool update_refs); - template + template NOINLINE // Main hot loop, start inlining from here void mark_loop_work(T* cl, ShenandoahLiveData* live_data, uint worker_id, TaskTerminator *t, StringDedup::Requests* const req); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp index 8a7ce7ea8315..45cec71935bd 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp @@ -47,7 +47,7 @@ #include "utilities/devirtualizer.inline.hpp" #include "utilities/powerOfTwo.hpp" -template +template void ShenandoahMark::do_task(ShenandoahObjToScanQueue* q, T* cl, ShenandoahLiveData* live_data, StringDedup::Requests* const req, ShenandoahMarkTask* task, uint worker_id) { oop obj = task->obj(); @@ -55,32 +55,58 @@ void ShenandoahMark::do_task(ShenandoahObjToScanQueue* q, T* cl, ShenandoahLiveD shenandoah_assert_marked(nullptr, obj); shenandoah_assert_not_in_cset_except(nullptr, obj, ShenandoahHeap::heap()->cancelled_gc()); + Klass* klass = obj->klass(); + // Are we in weak subgraph scan? bool weak = task->is_weak(); cl->set_weak(weak); if (task->is_not_chunked()) { - Klass* klass = obj->klass(); - if (klass->is_instance_klass()) { - // Case 1: Normal oop, process as usual. - if (STRING_DEDUP && (klass == vmClasses::String_klass())) { - dedup_string(obj, req); + // Dispatch based on object type. The case order does not seem to affect performance, + // so it matches the enum order for consistency. + switch (klass->kind()) { + case Klass::InstanceKlassKind: { + // Regular instance. + if (STRING_DEDUP && (klass == vmClasses::String_klass())) { + dedup_string(obj, req); + } + InstanceKlass::cast(klass)->oop_oop_iterate(obj, cl); + break; + } + case Klass::InstanceRefKlassKind: { + // (Weak) reference instance. + InstanceRefKlass::cast(klass)->oop_oop_iterate(obj, cl); + break; } - if (klass->is_stack_chunk_instance_klass()) { - // Loom doesn't support mixing of weak marking and strong marking of stack chunks. + case Klass::InstanceMirrorKlassKind: + case Klass::InstanceClassLoaderKlassKind: { + // Remaining rare classes, dispatch generically. + obj->oop_iterate(cl); + break; + } + case Klass::InstanceStackChunkKlassKind: { + // Stack chunk. Loom doesn't support mixing of weak marking and strong marking + // of stack chunks, upgrade to strong right away. cl->set_weak(false); + InstanceStackChunkKlass::cast(klass)->oop_oop_iterate(obj, cl); + break; + } + case Klass::TypeArrayKlassKind: { + // Primitive array. Do nothing, no oops there. We use the same + // performance tweak TypeArrayKlass::oop_oop_iterate_impl is using: + // We skip iterating over the klass pointer since we know that + // Universe::TypeArrayKlass never moves. + break; + } + case Klass::ObjArrayKlassKind: { + // Object array and no chunk is set. Must be the first + // time we visit it, start the chunked processing. + do_chunked_array_start(q, cl, obj, klass, weak); + break; + } + default: { + fatal("Unknown klass kind: %d", klass->kind()); } - obj->oop_iterate(cl); - } else if (klass->is_objArray_klass()) { - // Case 2: Object array instance and no chunk is set. Must be the first - // time we visit it, start the chunked processing. - do_chunked_array_start(q, cl, obj, klass, weak); - } else { - // Case 3: Primitive array. Do nothing, no oops there. We use the same - // performance tweak TypeArrayKlass::oop_oop_iterate_impl is using: - // We skip iterating over the klass pointer since we know that - // Universe::TypeArrayKlass never moves. - assert(klass->is_typeArray_klass(), "should be type array"); } // Count liveness the last: push the outstanding work to the queues first // Avoid double-counting objects that are visited twice due to upgrade @@ -89,8 +115,8 @@ void ShenandoahMark::do_task(ShenandoahObjToScanQueue* q, T* cl, ShenandoahLiveD count_liveness(live_data, obj, klass, worker_id); } } else { - // Case 4: Array chunk, has sensible chunk id. Process it. - do_chunked_array(q, cl, obj, task->chunk(), task->pow(), weak); + // Object array chunk. Process it. + do_chunked_array(q, cl, obj, klass, task->chunk(), task->pow(), weak); } } @@ -154,7 +180,7 @@ void ShenandoahMark::count_liveness(ShenandoahLiveData* live_data, oop obj, Klas } } -template +template void ShenandoahMark::do_chunked_array_start(ShenandoahObjToScanQueue* q, T* cl, oop obj, Klass* klass, bool weak) { assert(obj->is_objArray(), "expect object array"); objArrayOop array = objArrayOop(obj); @@ -167,7 +193,7 @@ void ShenandoahMark::do_chunked_array_start(ShenandoahObjToScanQueue* q, T* cl, if (len <= (int) ObjArrayMarkingStride*2) { // A few slices only, process directly - array->oop_iterate_elements_range(cl, 0, len); + ObjArrayKlass::cast(klass)->oop_oop_iterate_elements_range(array, cl, 0, len); } else { int bits = log2i_graceful(len); // Compensate for non-power-of-two arrays, cover the array in excess: @@ -216,13 +242,13 @@ void ShenandoahMark::do_chunked_array_start(ShenandoahObjToScanQueue* q, T* cl, // Process the irregular tail, if present int from = last_idx; if (from < len) { - array->oop_iterate_elements_range(cl, from, len); + ObjArrayKlass::cast(klass)->oop_oop_iterate_elements_range(array, cl, from, len); } } } -template -void ShenandoahMark::do_chunked_array(ShenandoahObjToScanQueue* q, T* cl, oop obj, int chunk, int pow, bool weak) { +template +void ShenandoahMark::do_chunked_array(ShenandoahObjToScanQueue* q, T* cl, oop obj, Klass* klass, int chunk, int pow, bool weak) { assert(obj->is_objArray(), "expect object array"); objArrayOop array = objArrayOop(obj); @@ -246,7 +272,7 @@ void ShenandoahMark::do_chunked_array(ShenandoahObjToScanQueue* q, T* cl, oop ob assert (0 < to && to <= len, "to is sane: %d/%d", to, len); #endif - array->oop_iterate_elements_range(cl, from, to); + ObjArrayKlass::cast(klass)->oop_oop_iterate_elements_range(array, cl, from, to); } template diff --git a/src/hotspot/share/gc/shenandoah/shenandoahStackWatermark.cpp b/src/hotspot/share/gc/shenandoah/shenandoahStackWatermark.cpp index 8df2449f8b6b..dac64629bce6 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahStackWatermark.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahStackWatermark.cpp @@ -33,7 +33,7 @@ #include "gc/shenandoah/shenandoahUtils.hpp" #include "runtime/safepointVerifiers.hpp" -uint32_t ShenandoahStackWatermark::_epoch_id = 1; +uint32_t ShenandoahStackWatermark::_epoch_id = MIN_EPOCH_ID; ShenandoahOnStackNMethodClosure::ShenandoahOnStackNMethodClosure() : _bs_nm(BarrierSet::barrier_set()->barrier_set_nmethod()) {} @@ -55,6 +55,9 @@ uint32_t ShenandoahStackWatermark::epoch_id() const { void ShenandoahStackWatermark::change_epoch_id() { shenandoah_assert_safepoint(); _epoch_id++; + if (_epoch_id > MAX_EPOCH_ID) { + _epoch_id = MIN_EPOCH_ID; + } } ShenandoahStackWatermark::ShenandoahStackWatermark(JavaThread* jt) : @@ -63,7 +66,10 @@ ShenandoahStackWatermark::ShenandoahStackWatermark(JavaThread* jt) : _stats(), _keep_alive_cl(), _evac_update_oop_cl(), - _nm_cl() {} + _nm_cl() { + assert(StackWatermarkState::epoch(_state) == _epoch_id, + "Should be the same: %u != %u", StackWatermarkState::epoch(_state), _epoch_id); +} OopClosure* ShenandoahStackWatermark::closure_from_context(void* context) { if (context != nullptr) { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahStackWatermark.hpp b/src/hotspot/share/gc/shenandoah/shenandoahStackWatermark.hpp index 7a7291abed2a..78aba0b569e2 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahStackWatermark.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahStackWatermark.hpp @@ -49,6 +49,12 @@ class ShenandoahOnStackNMethodClosure : public NMethodClosure { class ShenandoahStackWatermark : public StackWatermark { private: + // Start epochs from 1 to catch uninitialized paths. + // Wrap the epoch around smaller range to avoid truncation + // in StackWatermark encoding and verify the wraparound in tests. + static constexpr uint32_t MIN_EPOCH_ID = 1; + static constexpr uint32_t MAX_EPOCH_ID = (1 << 10); + static uint32_t _epoch_id; ShenandoahHeap* const _heap; ThreadLocalAllocStats _stats; diff --git a/src/hotspot/share/gc/z/zBarrierSet.cpp b/src/hotspot/share/gc/z/zBarrierSet.cpp index f6f996728867..c5d6fc5a9b10 100644 --- a/src/hotspot/share/gc/z/zBarrierSet.cpp +++ b/src/hotspot/share/gc/z/zBarrierSet.cpp @@ -251,13 +251,14 @@ void ZBarrierSet::on_thread_destroy(Thread* thread) { } void ZBarrierSet::on_thread_attach(Thread* thread) { + BarrierSet::on_thread_attach(thread); + // Set thread local masks ZThreadLocalData::set_load_bad_mask(thread, ZPointerLoadBadMask); ZThreadLocalData::set_load_good_mask(thread, ZPointerLoadGoodMask); ZThreadLocalData::set_mark_bad_mask(thread, ZPointerMarkBadMask); ZThreadLocalData::set_store_bad_mask(thread, ZPointerStoreBadMask); ZThreadLocalData::set_store_good_mask(thread, ZPointerStoreGoodMask); - ZThreadLocalData::set_nmethod_disarmed(thread, ZPointerStoreGoodMask); if (thread->is_Java_thread()) { JavaThread* const jt = JavaThread::cast(thread); StackWatermark* const watermark = new ZStackWatermark(jt); diff --git a/src/hotspot/share/gc/z/zBarrierSetNMethod.cpp b/src/hotspot/share/gc/z/zBarrierSetNMethod.cpp index a439b3a167bd..6e89c5a1032a 100644 --- a/src/hotspot/share/gc/z/zBarrierSetNMethod.cpp +++ b/src/hotspot/share/gc/z/zBarrierSetNMethod.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -30,7 +30,6 @@ #include "gc/z/zLock.inline.hpp" #include "gc/z/zNMethod.hpp" #include "gc/z/zResurrection.inline.hpp" -#include "gc/z/zThreadLocalData.hpp" #include "gc/z/zUncoloredRoot.inline.hpp" #include "logging/log.hpp" #include "runtime/icache.hpp" @@ -98,10 +97,6 @@ int* ZBarrierSetNMethod::disarmed_guard_value_address() const { return (int*)ZPointerStoreGoodMaskLowOrderBitsAddr; } -ByteSize ZBarrierSetNMethod::thread_disarmed_guard_value_offset() const { - return ZThreadLocalData::nmethod_disarmed_offset(); -} - oop ZBarrierSetNMethod::oop_load_no_keepalive(const nmethod* nm, int index) { return ZNMethod::oop_load_no_keepalive(nm, index); } diff --git a/src/hotspot/share/gc/z/zBarrierSetNMethod.hpp b/src/hotspot/share/gc/z/zBarrierSetNMethod.hpp index c7bbe35e17da..304be1f0a88d 100644 --- a/src/hotspot/share/gc/z/zBarrierSetNMethod.hpp +++ b/src/hotspot/share/gc/z/zBarrierSetNMethod.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -36,7 +36,6 @@ class ZBarrierSetNMethod : public BarrierSetNMethod { public: uintptr_t color(nmethod* nm); - virtual ByteSize thread_disarmed_guard_value_offset() const; virtual int* disarmed_guard_value_address() const; virtual oop oop_load_no_keepalive(const nmethod* nm, int index); diff --git a/src/hotspot/share/gc/z/zStackWatermark.cpp b/src/hotspot/share/gc/z/zStackWatermark.cpp index 4a50dea0cec0..de57ea974f36 100644 --- a/src/hotspot/share/gc/z/zStackWatermark.cpp +++ b/src/hotspot/share/gc/z/zStackWatermark.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,6 +23,7 @@ #include "gc/z/zAddress.hpp" #include "gc/z/zBarrier.inline.hpp" +#include "gc/z/zBarrierSet.hpp" #include "gc/z/zGeneration.inline.hpp" #include "gc/z/zStackWatermark.hpp" #include "gc/z/zStoreBarrierBuffer.hpp" @@ -189,7 +190,9 @@ void ZStackWatermark::start_processing_impl(void* context) { ZThreadLocalData::set_mark_bad_mask(_jt, ZPointerMarkBadMask); ZThreadLocalData::set_store_bad_mask(_jt, ZPointerStoreBadMask); ZThreadLocalData::set_store_good_mask(_jt, ZPointerStoreGoodMask); - ZThreadLocalData::set_nmethod_disarmed(_jt, ZPointerStoreGoodMask); + + // Update thread-local nmethod disarmed guard value + BarrierSet::barrier_set()->barrier_set_nmethod()->set_thread_disarmed_guard_value(_jt); // Retire TLAB if (ZGeneration::young()->is_phase_mark() || ZGeneration::old()->is_phase_mark()) { diff --git a/src/hotspot/share/gc/z/zThreadLocalData.hpp b/src/hotspot/share/gc/z/zThreadLocalData.hpp index a141fd8f83a3..297d57c2cfe7 100644 --- a/src/hotspot/share/gc/z/zThreadLocalData.hpp +++ b/src/hotspot/share/gc/z/zThreadLocalData.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -39,7 +39,6 @@ class ZThreadLocalData { uintptr_t _mark_bad_mask; uintptr_t _store_good_mask; uintptr_t _store_bad_mask; - uintptr_t _nmethod_disarmed; ZStoreBarrierBuffer* _store_barrier_buffer; ZMarkThreadLocalStacks _mark_stacks[2]; zaddress_unsafe* _invisible_root; @@ -50,7 +49,6 @@ class ZThreadLocalData { _mark_bad_mask(0), _store_good_mask(0), _store_bad_mask(0), - _nmethod_disarmed(0), _store_barrier_buffer(new ZStoreBarrierBuffer()), _mark_stacks(), _invisible_root(nullptr) {} @@ -92,10 +90,6 @@ class ZThreadLocalData { data(thread)->_store_good_mask = mask; } - static void set_nmethod_disarmed(Thread* thread, uintptr_t value) { - data(thread)->_nmethod_disarmed = value; - } - static ZMarkThreadLocalStacks* mark_stacks(Thread* thread, ZGenerationId id) { return &data(thread)->_mark_stacks[(int)id]; } @@ -134,10 +128,6 @@ class ZThreadLocalData { return Thread::gc_data_offset() + byte_offset_of(ZThreadLocalData, _store_good_mask); } - static ByteSize nmethod_disarmed_offset() { - return Thread::gc_data_offset() + byte_offset_of(ZThreadLocalData, _nmethod_disarmed); - } - static ByteSize store_barrier_buffer_offset() { return Thread::gc_data_offset() + byte_offset_of(ZThreadLocalData, _store_barrier_buffer); } diff --git a/src/hotspot/share/gc/z/z_globals.hpp b/src/hotspot/share/gc/z/z_globals.hpp index b1a4ae4c8980..ceffd490ba00 100644 --- a/src/hotspot/share/gc/z/z_globals.hpp +++ b/src/hotspot/share/gc/z/z_globals.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -96,10 +96,11 @@ product(uint, ZOldGCThreads, 0, DIAGNOSTIC, \ "Number of GC threads for the old generation") \ \ - product(uintx, ZIndexDistributorStrategy, 0, DIAGNOSTIC, \ + product(uint, ZIndexDistributorStrategy, 0, DIAGNOSTIC, \ "Strategy used to distribute indices to parallel workers " \ "0: Claim tree " \ "1: Simple Striped ") \ + range(0, 1) \ \ product(bool, ZVerifyRemembered, trueInDebug, DIAGNOSTIC, \ "Verify remembered sets") \ diff --git a/src/hotspot/share/jfr/dcmd/jfrDcmds.cpp b/src/hotspot/share/jfr/dcmd/jfrDcmds.cpp index a41515edfbbf..58d6c029bc16 100644 --- a/src/hotspot/share/jfr/dcmd/jfrDcmds.cpp +++ b/src/hotspot/share/jfr/dcmd/jfrDcmds.cpp @@ -486,8 +486,9 @@ void JfrConfigureFlightRecorderDCmd::print_help(outputStream* out, bool startup) out->print_cr(" The option redact-argument is best-effort and applies only to"); out->print_cr(" command-line arguments in the jdk.JVMInformation event and to"); out->print_cr(" the java.command system property in the jdk.InitialSystemProperty"); - out->print_cr(" event. Other events, such as jdk.ProcessStart (child processes),"); - out->print_cr(" are not redacted."); + out->print_cr(" event, and to matching command-line argument text in the values"); + out->print_cr(" of jdk.InitialEnvironmentVariable events. Other events, such as"); + out->print_cr(" jdk.ProcessStart (child processes), are not redacted."); out->print_cr(""); out->print_cr(" If the redact-argument option is not specified, the following"); out->print_cr(" filters are used by default:"); diff --git a/src/hotspot/share/jfr/periodic/jfrRedactedEvents.cpp b/src/hotspot/share/jfr/periodic/jfrRedactedEvents.cpp index 331c28cffa20..652320c99046 100644 --- a/src/hotspot/share/jfr/periodic/jfrRedactedEvents.cpp +++ b/src/hotspot/share/jfr/periodic/jfrRedactedEvents.cpp @@ -29,6 +29,7 @@ #include "logging/logMessage.hpp" #include "runtime/arguments.hpp" #include "runtime/flags/jvmFlag.hpp" +#include "runtime/javaThread.hpp" #include "runtime/os.hpp" #include "runtime/vm_version.hpp" #include "services/diagnosticArgument.hpp" @@ -46,17 +47,20 @@ using StringFlag = JfrRedactedEvents::StringFlag; using StringKeyValueArray = GrowableArray*; static const char REDACTED[] = "[REDACTED]"; +static const char REDACTED_MARKER = (char)0xFF; static const char DELIMITER[] = " "; +static const char REDACT_ARGUMENT[] = "redact-argument"; static const char REDACT_ARGUMENT_EQUAL[] = "redact-argument="; static const size_t REDACTED_LENGTH = sizeof(REDACTED) -1; static const size_t DELIMITER_LENGTH = sizeof(DELIMITER) -1; -static const size_t REDACT_ARGUMENT_EQUAL_LENGTH = sizeof(REDACT_ARGUMENT_EQUAL) -1; +static const size_t REDACT_ARGUMENT_LENGTH = sizeof(REDACT_ARGUMENT) -1; String* JfrRedactedEvents::_redacted_java_command_line = nullptr; String* JfrRedactedEvents::_redacted_jvm_command_line = nullptr; String* JfrRedactedEvents::_redacted_flags_command_line = nullptr; String* JfrRedactedEvents::_redacted_flight_recorder_options = nullptr; +String* JfrRedactedEvents::_redacted_flight_recorder_options_with_marker = nullptr; StringKeyValueArray JfrRedactedEvents::_initial_environment_variables = nullptr; StringKeyValueArray JfrRedactedEvents::_initial_system_properties = nullptr; @@ -71,6 +75,10 @@ bool JfrRedactedEvents::_initialized = false; bool JfrRedactedEvents::set_argument_filter(const char* filters) { assert (_argument_filters == nullptr, "invariant"); assert (filters != nullptr, "invariant"); + if (strcmp(filters, "*") != 0 && strcmp(filters, "none") != 0) { + _redacted_arguments = new StringArray(); + _redacted_arguments->add(filters); + } _argument_filters = new StringArray(); return append_filters(_argument_filters, true, filters); } @@ -139,9 +147,8 @@ bool JfrRedactedEvents::append_filters(StringArray* target, bool argument, const } if (filters[0] == '\0') { LogMessage(jfr, redact) msg; - msg.warning("Default redaction filters are replaced. Specify:"); - msg.warning("-XX:FlightRecorderOptions:%s=none to disable filters without a warning.", option_name); - return true; + msg.error("Specify -XX:FlightRecorderOptions:%s=none to disable filters completely.", option_name); + return false; } if (strcmp(filters, "none") == 0) { return true; @@ -187,6 +194,67 @@ char* JfrRedactedEvents::new_redacted_text() { return result; } +void JfrRedactedEvents::redact(String* scratch_string, const char* target, const String* redaction) { + if (strchr(redaction->text(), REDACTED_MARKER)) { + return; + } + const char* position = target; + while (true) { + const char* sensitive = strstr(position, redaction->text()); + if (sensitive == nullptr) { + return; + } + size_t index = (size_t)(sensitive - target); + for (size_t i = 0; i < redaction->length(); i++) { + scratch_string->set(index + i, REDACTED_MARKER); + } + position = sensitive + 1; + } +} + +String* JfrRedactedEvents::redact_environment_variable_value(const char* value) { + if (strchr(value, REDACTED_MARKER)) { + return new String(REDACTED); + } + bool changed = false; + String* input = new String(value); + if (_redacted_flight_recorder_options_with_marker != nullptr) { + size_t length = strlen(FlightRecorderOptions); + while (const char* start = strstr(input->text(), FlightRecorderOptions)) { + changed = true; + const char* end = start + length; + stringStream s; + s.write(input->text(), start - input->text()); + s.write(_redacted_flight_recorder_options_with_marker->text(), _redacted_flight_recorder_options_with_marker->length()); + s.write(end, strlen(end)); + String* result = new String(s.base()); + delete input; + input = result; + } + } + String* scratch_string = new String(input->text()); + for (int i = 0; i < _redacted_arguments->length(); i++) { + redact(scratch_string, input->text(), _redacted_arguments->at(i)); + } + stringStream result; + bool inside_redaction = false; + for (size_t i = 0; i < scratch_string->length(); i++) { + if (scratch_string->at(i) == REDACTED_MARKER) { + changed = true; + if (!inside_redaction) { + result.print(REDACTED); + } + inside_redaction = true; + } else { + result.put(scratch_string->at(i)); + inside_redaction = false; + } + } + delete scratch_string; + delete input; + return changed ? new String(result.base()) : nullptr; +} + bool JfrRedactedEvents::emit_initial_environment_variables(bool log) { if (_initial_environment_variables == nullptr) { ensure_initialized(); @@ -207,6 +275,16 @@ bool JfrRedactedEvents::emit_initial_environment_variables(bool log) { if (log) { log_debug(jfr, redact)("Redacted initial environment variable named '%s'", key->text()); } + } else { + String* redacted_value = redact_environment_variable_value(value); + if (redacted_value != nullptr) { + if (log) { + log_debug(jfr, redact)("Redacted argument in initial environment variable value named '%s'", key->text()); + } + _initial_environment_variables->append(new StringKeyValue(key, redacted_value->text())); + delete redacted_value; + continue; + } } _initial_environment_variables->append(new StringKeyValue(key, value)); } @@ -262,6 +340,9 @@ bool JfrRedactedEvents::match_flag(const char* flag_name, const char* arg) { if (flag_name == nullptr || arg == nullptr) { return false; } + if (strncmp(arg, "-XX:", 4) == 0) { + arg += 4; + } while (*flag_name) { if (*arg != *flag_name) { return false; @@ -346,6 +427,34 @@ void JfrRedactedEvents::emit_jvm_information(bool log) { } } +// Method assumes that FlightRecorderOptions has been successfully parsed during startup +String* JfrRedactedEvents::redact_flight_recorder_options(const char* option, bool marker) { + JavaThread* THREAD = JavaThread::current(); + const size_t length = strlen(option); + DCmdArgIter iterator(option, length, ','); + while (iterator.next(THREAD)) { + if (strncmp(iterator.key_addr(), REDACT_ARGUMENT, REDACT_ARGUMENT_LENGTH) == 0) { + const char* start = iterator.value_addr(); + const char* end = start + iterator.value_length(); + stringStream result; + result.write(option, start - option); + if (marker) { + result.put(REDACTED_MARKER); + } else { + result.write(REDACTED, REDACTED_LENGTH); + } + result.write(end, option + length - end); + return new String(result.base()); + } + } + if (HAS_PENDING_EXCEPTION) { + DEBUG_ONLY(ShouldNotReachHere();) + CLEAR_PENDING_EXCEPTION; + return new String(REDACTED); + } + return nullptr; +} + void JfrRedactedEvents::ensure_initialized() { if (_initialized) { return; @@ -359,31 +468,12 @@ void JfrRedactedEvents::ensure_initialized() { add_default_filters(_argument_filters, true); } if (FlightRecorderOptions != nullptr) { - if (strstr(FlightRecorderOptions, REDACT_ARGUMENT_EQUAL) != nullptr) { - DCmdIter iterator(FlightRecorderOptions, ','); - stringStream result; - size_t pos = 0; - while(iterator.has_next()) { - CmdLine line = iterator.next(); - const char* start = line.cmd_addr(); - if (strncmp(start, REDACT_ARGUMENT_EQUAL, REDACT_ARGUMENT_EQUAL_LENGTH) == 0) { - result.print(REDACT_ARGUMENT_EQUAL); - result.print(REDACTED); - // Preserve ',' if there are more tokens - pos = iterator.has_next() ? iterator.cursor() - 1 : iterator.cursor(); - } - while (pos < iterator.cursor()) { - result.write(FlightRecorderOptions + pos, 1); - pos++; - } - } - _redacted_flight_recorder_options = new String(result.base()); - } else { - _redacted_flight_recorder_options = new String(FlightRecorderOptions); - } + _redacted_flight_recorder_options = redact_flight_recorder_options(FlightRecorderOptions, false); + _redacted_flight_recorder_options_with_marker = redact_flight_recorder_options(FlightRecorderOptions, true); + } + if (_redacted_arguments == nullptr) { + _redacted_arguments = new StringArray(); } - - _redacted_arguments = new StringArray(); StringArray* java_args = make_java_args_array(); _redacted_java_command_line = redact_command_line(java_args); @@ -396,7 +486,6 @@ void JfrRedactedEvents::ensure_initialized() { StringArray* flags_args = make_jvm_args_array(Arguments::jvm_flags_array(), Arguments::num_jvm_flags()); _redacted_flags_command_line = redact_command_line(flags_args); delete flags_args; - _initialized = true; } @@ -422,8 +511,8 @@ String* JfrRedactedEvents::redact_command_line(StringArray* arguments) { for (int j = arg_index; j < next_index; j++) { result->add(REDACTED); const char* arg = arguments->at(j)->text(); - if (arg != nullptr && strncmp(arg, "-XX:", 4) == 0) { - _redacted_arguments->add(arg + 4); + if (arg != nullptr) { + _redacted_arguments->add(arg); } } arg_index = next_index; @@ -504,15 +593,23 @@ StringArray* JfrRedactedEvents::make_jvm_args_array(char** jvm_args_array, int a return nullptr; } StringArray* result = new StringArray(array_length); - for(int i = 0; i < array_length; i++) { + for (int i = 0; i < array_length; i++) { char* argument = jvm_args_array[i]; - if (_redacted_flight_recorder_options != nullptr && - strncmp(argument, "-XX:FlightRecorderOptions", 25) == 0) { - const char* text = _redacted_flight_recorder_options->text(); - size_t length = _redacted_flight_recorder_options->length(); - // Length must be at least 26 or the JVM will not start. - result->add(new String(argument, 26, text, length)); - continue; + if (strncmp(argument, "-XX:FlightRecorderOptions", 25) == 0) { + size_t length = strlen(argument); + if (length > 25 && + _redacted_flight_recorder_options != nullptr && + strcmp(argument + 26, FlightRecorderOptions) == 0) { + const char* text = _redacted_flight_recorder_options->text(); + // Length must be at least 26 or the JVM will not start. + result->add(new String(argument, 26, text, _redacted_flight_recorder_options->length())); + continue; + } + if (strstr(argument, REDACT_ARGUMENT_EQUAL) != nullptr) { + _redacted_arguments->add(argument); + result->add("-XX:FlightRecorderOptions:[REDACTED]"); + continue; + } } if (strncmp(argument, "-D", 2) == 0) { const char* key_start = argument + 2; @@ -523,6 +620,7 @@ StringArray* JfrRedactedEvents::make_jvm_args_array(char** jvm_args_array, int a bool redact = match_key(_key_filters, key_tmp->text()); delete key_tmp; if (redact) { + _redacted_arguments->add(argument); size_t unsensitive_length = (size_t)(eq - argument) + 1; result->add(new String(argument, unsensitive_length, REDACTED, REDACTED_LENGTH)); continue; diff --git a/src/hotspot/share/jfr/periodic/jfrRedactedEvents.hpp b/src/hotspot/share/jfr/periodic/jfrRedactedEvents.hpp index dc972190b6ce..c3d6fd32cbc1 100644 --- a/src/hotspot/share/jfr/periodic/jfrRedactedEvents.hpp +++ b/src/hotspot/share/jfr/periodic/jfrRedactedEvents.hpp @@ -195,6 +195,7 @@ class JfrRedactedEvents: public AllStatic { static String* _redacted_jvm_command_line; static String* _redacted_flags_command_line; static String* _redacted_flight_recorder_options; + static String* _redacted_flight_recorder_options_with_marker; static GrowableArray* _initial_system_properties; static GrowableArray* _initial_environment_variables; static GrowableArray* _string_flags; @@ -217,7 +218,10 @@ class JfrRedactedEvents: public AllStatic { static int match_arguments(StringArray* filter_array, StringArray* arguments, int arg_index); static bool match_key(StringArray* array, const char* text); static bool read_file(StringArray* target, const char* filename); + static void redact(String* scratch_string, const char* target, const String* redaction); + static String* redact_flight_recorder_options(const char* option, bool marker); static String* redact_command_line(StringArray* arguments); + static String* redact_environment_variable_value(const char* value); static StringArray* split(const char* text, char separator); }; diff --git a/src/hotspot/share/memory/metaspace.cpp b/src/hotspot/share/memory/metaspace.cpp index 8b8b80cd893a..43bd5e452c8d 100644 --- a/src/hotspot/share/memory/metaspace.cpp +++ b/src/hotspot/share/memory/metaspace.cpp @@ -593,7 +593,8 @@ ReservedSpace Metaspace::reserve_address_space_for_compressed_classes(size_t siz optimize_for_zero_base)); if (result == nullptr) { - // Fallback: reserve anywhere + // Fallback: we let the OS decide where to place the area, but align (overallocation-and-cut) + // to metaspace reserve alignment (16MB). log_debug(metaspace, map)("Trying anywhere..."); result = os::reserve_memory_aligned(size, Metaspace::reserve_alignment(), mtClass); } diff --git a/src/hotspot/share/oops/bsmAttribute.hpp b/src/hotspot/share/oops/bsmAttribute.hpp index 535821d539e0..25a5293119fa 100644 --- a/src/hotspot/share/oops/bsmAttribute.hpp +++ b/src/hotspot/share/oops/bsmAttribute.hpp @@ -30,6 +30,7 @@ #include "utilities/globalDefinitions.hpp" class ClassLoaderData; +class MetaspaceClosure; class BSMAttributeEntry { friend class ConstantPool; @@ -130,10 +131,8 @@ class BSMAttributeEntries { return _offsets == nullptr && _bootstrap_methods == nullptr; } - Array*& offsets() { return _offsets; } - const Array* const& offsets() const { return _offsets; } - Array*& bootstrap_methods() { return _bootstrap_methods; } - const Array* const& bootstrap_methods() const { return _bootstrap_methods; } + Array* offsets() const { return _offsets; } + Array* bootstrap_methods() const { return _bootstrap_methods; } BSMAttributeEntry* entry(int bsms_attribute_index) { return reinterpret_cast(_bootstrap_methods->adr_at(_offsets->at(bsms_attribute_index))); @@ -164,6 +163,8 @@ class BSMAttributeEntries { void end_extension(InsertionIterator& iter, ClassLoaderData* loader_data, TRAPS); // Append all of the BSMAEs in other into this. void append(const BSMAttributeEntries& other, ClassLoaderData* loader_data, TRAPS); + + void metaspace_pointers_do(MetaspaceClosure* it); }; #endif // SHARE_OOPS_BSMATTRIBUTE_HPP diff --git a/src/hotspot/share/oops/compressedKlass.cpp b/src/hotspot/share/oops/compressedKlass.cpp index ca1c46d40958..134f5a933658 100644 --- a/src/hotspot/share/oops/compressedKlass.cpp +++ b/src/hotspot/share/oops/compressedKlass.cpp @@ -188,11 +188,7 @@ void CompressedKlassPointers::initialize_for_given_encoding(address addr, size_t calc_lowest_highest_narrow_klass_id(); - // This has already been checked for SharedBaseAddress and if this fails, it's a bug in the allocation code. - if (!set_klass_decode_mode()) { - fatal("base=" PTR_FORMAT " given with shift %d, cannot be used to encode class pointers", - p2i(_base), _shift); - } + initialize_pd(); DEBUG_ONLY(sanity_check_after_initialization();) } @@ -299,20 +295,7 @@ void CompressedKlassPointers::initialize(address addr, size_t len) { calc_lowest_highest_narrow_klass_id(); - // Initialize JIT-specific decoding settings - if (!set_klass_decode_mode()) { - - // Give fatal error if this is a specified address - if (CompressedClassSpaceBaseAddress == (size_t)_base) { - vm_exit_during_initialization( - err_msg("CompressedClassSpaceBaseAddress=" PTR_FORMAT " given with shift %d, cannot be used to encode class pointers", - CompressedClassSpaceBaseAddress, _shift)); - } else { - // If this fails, it's a bug in the allocation code. - fatal("CompressedClassSpaceBaseAddress=" PTR_FORMAT " given with shift %d, cannot be used to encode class pointers", - p2i(_base), _shift); - } - } + initialize_pd(); DEBUG_ONLY(sanity_check_after_initialization();) } diff --git a/src/hotspot/share/oops/compressedKlass.hpp b/src/hotspot/share/oops/compressedKlass.hpp index fe1ce9e07ae7..949a9b6a705f 100644 --- a/src/hotspot/share/oops/compressedKlass.hpp +++ b/src/hotspot/share/oops/compressedKlass.hpp @@ -270,15 +270,8 @@ class CompressedKlassPointers : public AllStatic { // Returns true if address points into protection zone (for error reporting) static bool is_in_protection_zone(address addr); -#if defined(AARCH64) && !defined(ZERO) - // Check that with the given base, shift and range, aarch64 code can encode and decode the klass pointer. - static bool check_klass_decode_mode(address base, int shift, const size_t range); - // Called after initialization. - static bool set_klass_decode_mode(); -#else - static bool check_klass_decode_mode(address base, int shift, const size_t range) { return true; } - static bool set_klass_decode_mode() { return true; } -#endif + // platform-specific initializations (needed only on non-zero aarch64 builds) + static void initialize_pd() ZERO_ONLY({}) NOT_ZERO(NOT_AARCH64({})); }; #endif // SHARE_OOPS_COMPRESSEDKLASS_HPP diff --git a/src/hotspot/share/oops/constantPool.cpp b/src/hotspot/share/oops/constantPool.cpp index f3beb15aaee1..b3ff0eb1b76d 100644 --- a/src/hotspot/share/oops/constantPool.cpp +++ b/src/hotspot/share/oops/constantPool.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -152,9 +152,8 @@ void ConstantPool::metaspace_pointers_do(MetaspaceClosure* it) { it->push(&_tags, MetaspaceClosure::_writable); it->push(&_cache); it->push(&_pool_holder); - it->push(&bsm_entries().offsets()); - it->push(&bsm_entries().bootstrap_methods()); it->push(&_resolved_klasses, MetaspaceClosure::_writable); + bsm_entries().metaspace_pointers_do(it); for (int i = 0; i < length(); i++) { // The only MSO's embedded in the CP entries are Symbols: @@ -2424,3 +2423,8 @@ void BSMAttributeEntries::end_extension(InsertionIterator& iter, ClassLoaderData _offsets = new_offsets; _bootstrap_methods = new_array; } + +void BSMAttributeEntries::metaspace_pointers_do(MetaspaceClosure* it) { + it->push(&_offsets); + it->push(&_bootstrap_methods); +} diff --git a/src/hotspot/share/oops/generateOopMap.cpp b/src/hotspot/share/oops/generateOopMap.cpp index 56f3e7e03259..f7c933b0ea70 100644 --- a/src/hotspot/share/oops/generateOopMap.cpp +++ b/src/hotspot/share/oops/generateOopMap.cpp @@ -2242,9 +2242,9 @@ void GenerateOopMap::rewrite_refval_conflicts() // Tracing flag _did_rewriting = true; - if (log_is_enabled(Trace, generateoopmap)) { + if (log_is_enabled(Debug, generateoopmap)) { ResourceMark rm; - LogStream st(Log(generateoopmap)::trace()); + LogStream st(Log(generateoopmap)::debug()); st.print_cr("ref/value conflict for method %s - bytecodes are getting rewritten", method()->name()->as_C_string()); method()->print_on(&st); method()->print_codes_on(&st); @@ -2502,32 +2502,9 @@ void GenerateOopMap::update_ret_adr_at_TOS(int bci, int delta) { // =================================================================== -#ifndef PRODUCT -int ResolveOopMapConflicts::_nof_invocations = 0; -int ResolveOopMapConflicts::_nof_rewrites = 0; -int ResolveOopMapConflicts::_nof_relocations = 0; -#endif - methodHandle ResolveOopMapConflicts::do_potential_rewrite(TRAPS) { if (!compute_map(THREAD)) { THROW_HANDLE_(exception(), methodHandle()); } - -#ifndef PRODUCT - // Tracking and statistics - if (PrintRewrites) { - _nof_invocations++; - if (did_rewriting()) { - _nof_rewrites++; - if (did_relocation()) _nof_relocations++; - tty->print("Method was rewritten %s: ", (did_relocation()) ? "and relocated" : ""); - method()->print_value(); tty->cr(); - tty->print_cr("Cand.: %d rewrts: %d (%d%%) reloc.: %d (%d%%)", - _nof_invocations, - _nof_rewrites, (_nof_rewrites * 100) / _nof_invocations, - _nof_relocations, (_nof_relocations * 100) / _nof_invocations); - } - } -#endif return methodHandle(THREAD, method()); } diff --git a/src/hotspot/share/oops/instanceKlass.cpp b/src/hotspot/share/oops/instanceKlass.cpp index fd1cf1b1457d..8161516421e3 100644 --- a/src/hotspot/share/oops/instanceKlass.cpp +++ b/src/hotspot/share/oops/instanceKlass.cpp @@ -2911,6 +2911,16 @@ bool InstanceKlass::can_be_verified_at_dumptime() const { // SystemDictionaryShared::check_verification_constraints() will not work for this class. return false; } + + if (CDSConfig::is_dumping_final_static_archive() && fail_over_verified()) { + // This is a class with version >50 but was verified with the old verifier in the training run, + // which had -XX:+AOTClassLinking. However, we are now in the assembly run with -XX:-AOTClassLinking. + // As SystemDictionaryShared::check_verification_constraints() does not support this case, + // we must exclude this class. + assert(!CDSConfig::is_dumping_aot_linked_classes(), "must be"); + return false; + } + if (super() != nullptr && !super()->can_be_verified_at_dumptime()) { return false; } diff --git a/src/hotspot/share/oops/instanceKlass.hpp b/src/hotspot/share/oops/instanceKlass.hpp index 41f176330fa1..721a50c73c6e 100644 --- a/src/hotspot/share/oops/instanceKlass.hpp +++ b/src/hotspot/share/oops/instanceKlass.hpp @@ -339,6 +339,9 @@ class InstanceKlass: public Klass { bool has_localvariable_table() const { return _misc_flags.has_localvariable_table(); } void set_has_localvariable_table(bool b) { _misc_flags.set_has_localvariable_table(b); } + bool fail_over_verified() const { return _misc_flags.fail_over_verified(); } + void set_fail_over_verified() { _misc_flags.set_fail_over_verified(true); } + // field sizes int nonstatic_field_size() const { return _nonstatic_field_size; } void set_nonstatic_field_size(int size) { _nonstatic_field_size = size; } diff --git a/src/hotspot/share/oops/instanceKlassFlags.hpp b/src/hotspot/share/oops/instanceKlassFlags.hpp index 1709c02a1712..84041a3a0e76 100644 --- a/src/hotspot/share/oops/instanceKlassFlags.hpp +++ b/src/hotspot/share/oops/instanceKlassFlags.hpp @@ -54,6 +54,7 @@ class InstanceKlassFlags { flag(has_miranda_methods , 1 << 12) /* True if this class has miranda methods in it's vtable */ \ flag(has_final_method , 1 << 13) /* True if klass has final method */ \ flag(trust_final_fields , 1 << 14) /* All instance final fields in this class should be trusted */ \ + flag(fail_over_verified , 1 << 15) /* class failed split verification but passed inference verification */ \ /* end of list */ #define IK_FLAGS_ENUM_NAME(name, value) _misc_##name = value, diff --git a/src/hotspot/share/oops/instanceRefKlass.hpp b/src/hotspot/share/oops/instanceRefKlass.hpp index fc219d067397..de7ec6fce9a8 100644 --- a/src/hotspot/share/oops/instanceRefKlass.hpp +++ b/src/hotspot/share/oops/instanceRefKlass.hpp @@ -58,6 +58,16 @@ class InstanceRefKlass: public InstanceKlass { public: InstanceRefKlass(); + static InstanceRefKlass* cast(Klass* k) { + return const_cast(cast(const_cast(k))); + } + + static const InstanceRefKlass* cast(const Klass* k) { + assert(k != nullptr, "k should not be null"); + assert(k->is_reference_instance_klass(), "cast to InstanceRefKlass"); + return static_cast(k); + } + // Oop fields (and metadata) iterators // // The InstanceRefKlass iterators also support reference processing. diff --git a/src/hotspot/share/opto/c2_globals.hpp b/src/hotspot/share/opto/c2_globals.hpp index dd9288f76179..9ff88e8c310f 100644 --- a/src/hotspot/share/opto/c2_globals.hpp +++ b/src/hotspot/share/opto/c2_globals.hpp @@ -922,8 +922,8 @@ "Use StoreStore barrier instead of Release barrier at the end " \ "of constructors") \ \ - develop(bool, KillPathsReachableByDeadDataNode, true, \ - "When a data node becomes top, make paths where the node is " \ + develop(bool, KillPathsReachableByDeadTypeNode, true, \ + "When a Type node becomes top, make paths where the node is " \ "used dead by replacing them with a Halt node. Turning this off " \ "could corrupt the graph in rare cases and should be used with " \ "care.") \ diff --git a/src/hotspot/share/opto/castnode.cpp b/src/hotspot/share/opto/castnode.cpp index 076a95acfd88..befa208a5e25 100644 --- a/src/hotspot/share/opto/castnode.cpp +++ b/src/hotspot/share/opto/castnode.cpp @@ -111,6 +111,9 @@ Node* ConstraintCastNode::Ideal(PhaseGVN* phase, bool can_reshape) { if (in(0) != nullptr && remove_dead_region(phase, can_reshape)) { return this; } + if (in(1) != nullptr && phase->type(in(1)) != Type::TOP) { + return TypeNode::Ideal(phase, can_reshape); + } return nullptr; } @@ -465,6 +468,11 @@ const Type* CheckCastPPNode::Value(PhaseGVN* phase) const { if (in_type != nullptr && my_type != nullptr) { TypePtr::PTR in_ptr = in_type->ptr(); if (in_ptr == TypePtr::Null) { + // A null input cast to a type that cannot be null (e.g. NotNull) describes + // an impossible value: the join is empty, so the result must be TOP. + if (my_type->join_ptr(TypePtr::Null) == TypePtr::TopPTR) { + return Type::TOP; + } result = in_type; } else if (in_ptr != TypePtr::Constant) { result = my_type->cast_to_ptr_type(my_type->join_ptr(in_ptr)); diff --git a/src/hotspot/share/opto/cfgnode.cpp b/src/hotspot/share/opto/cfgnode.cpp index ed5da0466089..828e5bf299f7 100644 --- a/src/hotspot/share/opto/cfgnode.cpp +++ b/src/hotspot/share/opto/cfgnode.cpp @@ -693,13 +693,14 @@ Node *RegionNode::Ideal(PhaseGVN *phase, bool can_reshape) { if (add_to_worklist) { igvn->add_users_to_worklist(this); // Check for further allowed opts } - uint edges_removed; - for (DUIterator_Last imin, i = last_outs(imin); i >= imin; i -= edges_removed) { - edges_removed = 1; + for (DUIterator_Last imin, i = last_outs(imin); i >= imin; --i) { Node* n = last_out(i); igvn->hash_delete(n); // Remove from worklist before modifying edges if (n->outcnt() == 0) { - edges_removed = n->replace_edge(this, phase->C->top(), igvn); + int uses_found = n->replace_edge(this, phase->C->top(), igvn); + if (uses_found > 1) { // (--i) done at the end of the loop. + i -= (uses_found - 1); + } continue; } if( n->is_Phi() ) { // Collapse all Phis @@ -718,7 +719,10 @@ Node *RegionNode::Ideal(PhaseGVN *phase, bool can_reshape) { } else if( n->is_Region() ) { // Update all incoming edges assert(n != this, "Must be removed from DefUse edges"); - edges_removed = n->replace_edge(this, parent_ctrl, igvn); + int uses_found = n->replace_edge(this, parent_ctrl, igvn); + if (uses_found > 1) { // (--i) done at the end of the loop. + i -= (uses_found - 1); + } } else { assert(n->in(0) == this, "Expect RegionNode to be control parent"); diff --git a/src/hotspot/share/opto/classes.hpp b/src/hotspot/share/opto/classes.hpp index c296237de37b..3527c6dad371 100644 --- a/src/hotspot/share/opto/classes.hpp +++ b/src/hotspot/share/opto/classes.hpp @@ -121,7 +121,6 @@ macro(CompareAndExchangeI) macro(CompareAndExchangeL) macro(CompareAndExchangeP) macro(CompareAndExchangeN) -macro(DeadPath) macro(GetAndAddB) macro(GetAndAddS) macro(GetAndAddI) @@ -415,6 +414,10 @@ macro(MulAddVS2VI) macro(FmaVD) macro(FmaVF) macro(FmaVHF) +macro(DivVB) +macro(DivVS) +macro(DivVI) +macro(DivVL) macro(DivVHF) macro(DivVF) macro(DivVD) diff --git a/src/hotspot/share/opto/compile.cpp b/src/hotspot/share/opto/compile.cpp index db43c6fb1c43..93d8e4c425d5 100644 --- a/src/hotspot/share/opto/compile.cpp +++ b/src/hotspot/share/opto/compile.cpp @@ -312,8 +312,6 @@ void Compile::identify_useful_nodes(Unique_Node_List &useful) { // If 'top' is cached, declare it useful to preserve cached node if (cached_top_node()) { useful.push(cached_top_node()); } - if (dead_path()) { useful.push(dead_path()); } - // Push all useful nodes onto the list, breadthfirst for( uint next = 0; next < useful.size(); ++next ) { assert( next < unique(), "Unique useful nodes < total nodes"); @@ -390,7 +388,7 @@ void Compile::remove_useless_node(Node* dead) { // it reachable by adding use edges. So, we will NOT count Con nodes // as dead to be conservative about the dead node count at any // given time. - if (!dead->is_Con() && dead != dead_path()) { + if (!dead->is_Con()) { record_dead_node(dead->_idx); } if (dead->is_macro()) { @@ -686,7 +684,6 @@ Compile::Compile(ciEnv* ci_env, ciMethod* target, int osr_bci, _node_arena_one(mtCompiler, Arena::Tag::tag_node), _node_arena_two(mtCompiler, Arena::Tag::tag_node), _node_arena(&_node_arena_one), - _dead_path(nullptr), _mach_constant_base_node(nullptr), _Compile_types(mtCompiler, Arena::Tag::tag_type), _initial_gvn(nullptr), @@ -757,7 +754,6 @@ Compile::Compile(ciEnv* ci_env, ciMethod* target, int osr_bci, } Init(/*do_aliasing=*/ true); - set_dead_path(new DeadPathNode()); print_compile_messages(); @@ -967,7 +963,6 @@ Compile::Compile(ciEnv* ci_env, _node_arena_one(mtCompiler, Arena::Tag::tag_node), _node_arena_two(mtCompiler, Arena::Tag::tag_node), _node_arena(&_node_arena_one), - _dead_path(nullptr), _mach_constant_base_node(nullptr), _Compile_types(mtCompiler, Arena::Tag::tag_type), _initial_gvn(nullptr), @@ -2635,9 +2630,6 @@ void Compile::Optimize() { } } - // Unique DeadPath node should not be used anymore - _dead_path = nullptr; - print_method(PHASE_OPTIMIZE_FINISHED, 2); DEBUG_ONLY(set_phase_optimize_finished();) } @@ -3946,21 +3938,6 @@ void Compile::final_graph_reshaping_main_switch(Node* n, Final_Reshape_Counts& f break; } #endif - case Op_DeadPath: { - // The CFG inputs are dead paths. Replace the DeadPath with a Region and insert a Halt node. - assert(n->req() > 1, "why not removed if no input other than itself?"); - RegionNode* r = new RegionNode(n->req()); - for (uint i = 1; i < n->req(); ++i) { - r->set_req(i, n->in(i)); - } - n->disconnect_inputs(this); - Node* frame = start()->proj_out(TypeFunc::FramePtr); - stringStream ss; - ss.print("dead path discovered by data nodes during igvn"); - Node* halt = new HaltNode(r, frame, ss.as_string(comp_arena())); - root()->set_req(root()->find_edge(n), halt); - break; - } default: assert(!n->is_Call(), ""); assert(!n->is_Mem(), ""); diff --git a/src/hotspot/share/opto/compile.hpp b/src/hotspot/share/opto/compile.hpp index 73e136787f82..ab36f59a28fc 100644 --- a/src/hotspot/share/opto/compile.hpp +++ b/src/hotspot/share/opto/compile.hpp @@ -57,7 +57,6 @@ class CallStaticJavaNode; class CloneMap; class CompilationFailureInfo; class ConnectionGraph; -class DeadPathNode; class IdealGraphPrinter; class InlineTree; class Matcher; @@ -428,7 +427,7 @@ class Compile : public Phase { private: RootNode* _root; // Unique root of compilation, or null after bail-out. Node* _top; // Unique top node. (Reset by various phases.) - DeadPathNode* _dead_path; // Unique DeadPath node + Node* _immutable_memory; // Initial memory state Node* _recent_alloc_obj; @@ -898,12 +897,6 @@ class Compile : public Phase { Arena* old_arena() { return (&_node_arena_one == _node_arena) ? &_node_arena_two : &_node_arena_one; } RootNode* root() const { return _root; } void set_root(RootNode* r) { _root = r; } - DeadPathNode* dead_path() const { return _dead_path; } - - void set_dead_path(DeadPathNode* dead_path) { - assert(_dead_path == nullptr, "can only set once"); - _dead_path = dead_path; - } StartNode* start() const; // (Derived from root.) void verify_start(StartNode* s) const NOT_DEBUG_RETURN; Node* immutable_memory(); diff --git a/src/hotspot/share/opto/convertnode.cpp b/src/hotspot/share/opto/convertnode.cpp index d706a13feb3a..a495814da615 100644 --- a/src/hotspot/share/opto/convertnode.cpp +++ b/src/hotspot/share/opto/convertnode.cpp @@ -755,6 +755,13 @@ bool Compile::push_thru_add(PhaseGVN* phase, Node* z, const TypeInteger* tz, con //------------------------------Ideal------------------------------------------ Node* ConvI2LNode::Ideal(PhaseGVN* phase, bool can_reshape) { + if (in(1) != nullptr && phase->type(in(1)) != Type::TOP) { + Node* progress = TypeNode::Ideal(phase, can_reshape); + if (progress != nullptr) { + return progress; + } + } + const TypeLong* this_type = this->type()->is_long(); if (can_reshape && !phase->C->post_loop_opts_phase()) { // makes sure we run ::Value to potentially remove type assertion after loop opts @@ -857,6 +864,13 @@ const Type* ConvL2INode::Value(PhaseGVN* phase) const { // Return a node which is more "ideal" than the current node. // Blow off prior masking to int Node* ConvL2INode::Ideal(PhaseGVN* phase, bool can_reshape) { + if (in(1) != nullptr && phase->type(in(1)) != Type::TOP) { + Node* progress = TypeNode::Ideal(phase, can_reshape); + if (progress != nullptr) { + return progress; + } + } + Node *andl = in(1); uint andl_op = andl->Opcode(); if( andl_op == Op_AndL ) { diff --git a/src/hotspot/share/opto/divnode.cpp b/src/hotspot/share/opto/divnode.cpp index 3b51491294e3..1687ff2cade4 100644 --- a/src/hotspot/share/opto/divnode.cpp +++ b/src/hotspot/share/opto/divnode.cpp @@ -1031,10 +1031,6 @@ const Type* UDivINode::Value(PhaseGVN* phase) const { if( t1 == Type::TOP ) return Type::TOP; if( t2 == Type::TOP ) return Type::TOP; - if (t2 == TypeInt::ZERO) { - return Type::TOP; - } - // x/x == 1 since we always generate the dynamic divisor check for 0. if (in(1) == in(2)) { return TypeInt::ONE; @@ -1071,10 +1067,6 @@ const Type* UDivLNode::Value(PhaseGVN* phase) const { if( t1 == Type::TOP ) return Type::TOP; if( t2 == Type::TOP ) return Type::TOP; - if (t2 == TypeLong::ZERO) { - return Type::TOP; - } - // x/x == 1 since we always generate the dynamic divisor check for 0. if (in(1) == in(2)) { return TypeLong::ONE; @@ -1388,9 +1380,6 @@ Node* UModINode::Ideal(PhaseGVN* phase, bool can_reshape) { } const Type* UModINode::Value(PhaseGVN* phase) const { - if (phase->type(in(2)) == TypeInt::ZERO) { - return Type::TOP; - } return unsigned_mod_value(phase, this); } @@ -1531,9 +1520,6 @@ Node *UModLNode::Ideal(PhaseGVN *phase, bool can_reshape) { } const Type* UModLNode::Value(PhaseGVN* phase) const { - if (phase->type(in(2)) == TypeLong::ZERO) { - return Type::TOP; - } return unsigned_mod_value(phase, this); } diff --git a/src/hotspot/share/opto/divnode.hpp b/src/hotspot/share/opto/divnode.hpp index de89dcaad06e..366e3fb882db 100644 --- a/src/hotspot/share/opto/divnode.hpp +++ b/src/hotspot/share/opto/divnode.hpp @@ -40,9 +40,7 @@ class DivModIntegerNode : public Node { bool _pinned; protected: - DivModIntegerNode(Node* c, Node* dividend, Node* divisor) : Node(c, dividend, divisor), _pinned(false) { - init_class_id(Class_DivModInteger); - } + DivModIntegerNode(Node* c, Node* dividend, Node* divisor) : Node(c, dividend, divisor), _pinned(false) {} private: virtual uint size_of() const override { return sizeof(DivModIntegerNode); } @@ -54,15 +52,6 @@ class DivModIntegerNode : public Node { res->_pinned = true; return res; } - -public: - const TypeInteger* zero() const { - if (bottom_type() == TypeInt::INT) { - return TypeInt::ZERO; - } - assert(bottom_type() == TypeLong::LONG, "should be int or long"); - return TypeLong::ZERO; - } }; //------------------------------DivINode--------------------------------------- diff --git a/src/hotspot/share/opto/loopopts.cpp b/src/hotspot/share/opto/loopopts.cpp index d525c274ef63..ccd53129a87c 100644 --- a/src/hotspot/share/opto/loopopts.cpp +++ b/src/hotspot/share/opto/loopopts.cpp @@ -1725,7 +1725,7 @@ void PhaseIdealLoop::try_sink_out_of_loop(Node* n) { !n->is_OpaqueTemplateAssertionPredicate() && !is_raw_to_oop_cast && // don't extend live ranges of raw oops n->Opcode() != Op_CreateEx && - (KillPathsReachableByDeadDataNode || !n->is_Type()) + (KillPathsReachableByDeadTypeNode || !n->is_Type()) ) { Node *n_ctrl = get_ctrl(n); IdealLoopTree *n_loop = get_loop(n_ctrl); diff --git a/src/hotspot/share/opto/memnode.cpp b/src/hotspot/share/opto/memnode.cpp index 165eb9e430c5..2e689311761c 100644 --- a/src/hotspot/share/opto/memnode.cpp +++ b/src/hotspot/share/opto/memnode.cpp @@ -3525,8 +3525,9 @@ Node *StoreNode::Ideal(PhaseGVN *phase, bool can_reshape) { Node* address = in(MemNode::Address); Node* value = in(MemNode::ValueIn); // Back-to-back stores to same address? Fold em up. Generally - // unsafe if I have intervening uses. - { + // unsafe if I have intervening uses. Also unsafe for masked or + // scatter vector stores as the wider store. + if (!this->is_StoreVector() || this->Opcode() == Op_StoreVector) { Node* st = mem; // If Store 'st' has more than one use, we cannot fold 'st' away. // For example, 'st' might be the final state at a conditional @@ -3534,15 +3535,14 @@ Node *StoreNode::Ideal(PhaseGVN *phase, bool can_reshape) { // the same time 'st' is live, which might be unschedulable. So, // require exactly ONE user until such time as we clone 'mem' for // each of 'mem's uses (thus making the exactly-1-user-rule hold - // true). - while (st->is_Store() && st->outcnt() == 1) { + // true). Further, 'st' must be a contiguous store, otherwise + // memory_size does not make sense for measuring overlap. + while (st->is_Store() && st->outcnt() == 1 && (!st->is_StoreVector() || st->Opcode() == Op_StoreVector)) { // Looking at a dead closed cycle of memory? assert(st != st->in(MemNode::Memory), "dead loop in StoreNode::Ideal"); assert(Opcode() == st->Opcode() || st->Opcode() == Op_StoreVector || Opcode() == Op_StoreVector || - st->Opcode() == Op_StoreVectorScatter || - Opcode() == Op_StoreVectorScatter || phase->C->get_alias_index(adr_type()) == Compile::AliasIdxRaw || (Opcode() == Op_StoreL && st->Opcode() == Op_StoreI) || // expanded ClearArrayNode (Opcode() == Op_StoreI && st->Opcode() == Op_StoreL) || // initialization by arraycopy @@ -3551,6 +3551,11 @@ Node *StoreNode::Ideal(PhaseGVN *phase, bool can_reshape) { if (st->in(MemNode::Address)->eqv_uncast(address) && st->as_Store()->memory_size() <= this->memory_size()) { + assert(!is_predicated_vector() && !is_StoreVectorMasked() && + !is_StoreVectorScatter() && !is_StoreVectorScatterMasked() && + !st->is_predicated_vector() && !st->is_StoreVectorMasked() && + !st->is_StoreVectorScatter() && !st->is_StoreVectorScatterMasked(), + "optimization only correct for full-width stores without holes"); Node* use = st->raw_out(0); if (phase->is_IterGVN()) { phase->is_IterGVN()->rehash_node_delayed(use); diff --git a/src/hotspot/share/opto/movenode.cpp b/src/hotspot/share/opto/movenode.cpp index 7d38238da2f2..6b6becb434f0 100644 --- a/src/hotspot/share/opto/movenode.cpp +++ b/src/hotspot/share/opto/movenode.cpp @@ -90,6 +90,11 @@ Node *CMoveNode::Ideal(PhaseGVN *phase, bool can_reshape) { phase->type(in(IfTrue)) == Type::TOP) { return nullptr; } + Node* progress = TypeNode::Ideal(phase, can_reshape); + if (progress != nullptr) { + return progress; + } + // Check for Min/Max patterns. This is called before constants are pushed to the right input, as that transform can // make BoolTests non-canonical. Node* minmax = Ideal_minmax(phase, this); diff --git a/src/hotspot/share/opto/node.cpp b/src/hotspot/share/opto/node.cpp index 264216ddc6de..726a3ea1b555 100644 --- a/src/hotspot/share/opto/node.cpp +++ b/src/hotspot/share/opto/node.cpp @@ -597,7 +597,6 @@ void Node::setup_is_top() { //------------------------------~Node------------------------------------------ // Fancy destructor; eagerly attempt to reclaim Node numberings and storage void Node::destruct(PhaseValues* phase) { - assert(this != Compile::current()->dead_path(), "we want to keep the unique DeadPath node around"); Compile* compile = (phase != nullptr) ? phase->C : Compile::current(); if (phase != nullptr && phase->is_IterGVN()) { phase->is_IterGVN()->_worklist.remove(this); @@ -736,14 +735,11 @@ void Node::out_grow(uint len) { //------------------------------is_dead---------------------------------------- bool Node::is_dead() const { // Mach and pinch point nodes may look like dead. - if (is_top() || is_Mach() || (Opcode() == Op_Node && _outcnt > 0) || this == Compile::current()->dead_path()) { + if( is_top() || is_Mach() || (Opcode() == Op_Node && _outcnt > 0) ) return false; - } - for (uint i = 0; i < _max; i++) { - if (_in[i] != nullptr) { + for( uint i = 0; i < _max; i++ ) + if( _in[i] != nullptr ) return false; - } - } return true; } @@ -3182,11 +3178,10 @@ uint TypeNode::ideal_reg() const { return _type->ideal_reg(); } -void Node::make_path_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, Node* ctrl_use, uint j, const char* phase_str) { +void TypeNode::make_path_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, Node* ctrl_use, uint j, const char* phase_str) { Node* c = ctrl_use->in(j); - Node* top = igvn->C->top(); - if (c != top) { - igvn->replace_input_of(ctrl_use, j, top); + if (igvn->type(c) != Type::TOP) { + igvn->replace_input_of(ctrl_use, j, igvn->C->top()); create_halt_path(igvn, c, loop, phase_str); } } @@ -3198,18 +3193,14 @@ void Node::make_path_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, Node* ctrl_u // constant folds and the control flow that leads to the Type node becomes unreachable. There are cases where that // doesn't happen, however. They are handled here by following uses of the Type node until a CFG or a Phi to find dead // paths. The dead paths are then replaced by a Halt node. -void Node::make_paths_from_here_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, const char* phase_str) { +void TypeNode::make_paths_from_here_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, const char* phase_str) { Unique_Node_List wq; wq.push(this); for (uint i = 0; i < wq.size(); ++i) { Node* n = wq.at(i); - if (n->is_CFG()) { - n->remove_dead_region(igvn, true); - } for (DUIterator_Fast kmax, k = n->fast_outs(kmax); k < kmax; k++) { Node* u = n->fast_out(k); if (u->is_CFG()) { - wq.push(u); assert(!u->is_Region(), "Can't reach a Region without going through a Phi"); make_path_dead(igvn, loop, u, 0, phase_str); } else if (u->is_Phi()) { @@ -3229,7 +3220,7 @@ void Node::make_paths_from_here_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, c } } -void Node::create_halt_path(PhaseIterGVN* igvn, Node* c, PhaseIdealLoop* loop, const char* phase_str) { +void TypeNode::create_halt_path(PhaseIterGVN* igvn, Node* c, PhaseIdealLoop* loop, const char* phase_str) const { Node* frame = new ParmNode(igvn->C->start(), TypeFunc::FramePtr); if (loop == nullptr) { igvn->register_new_node_with_optimizer(frame); @@ -3248,3 +3239,15 @@ void Node::create_halt_path(PhaseIterGVN* igvn, Node* c, PhaseIdealLoop* loop, c } igvn->add_input_to(igvn->C->root(), halt); } + +Node* TypeNode::Ideal(PhaseGVN* phase, bool can_reshape) { + if (KillPathsReachableByDeadTypeNode && can_reshape && Value(phase) == Type::TOP) { + PhaseIterGVN* igvn = phase->is_IterGVN(); + Node* top = igvn->C->top(); + ResourceMark rm; + make_paths_from_here_dead(igvn, nullptr, "igvn"); + return top; + } + + return Node::Ideal(phase, can_reshape); +} diff --git a/src/hotspot/share/opto/node.hpp b/src/hotspot/share/opto/node.hpp index e593822c3130..b3de7498e50c 100644 --- a/src/hotspot/share/opto/node.hpp +++ b/src/hotspot/share/opto/node.hpp @@ -82,7 +82,6 @@ class CountedLoopEndNode; class DecodeNarrowPtrNode; class DecodeNNode; class DecodeNKlassNode; -class DivModIntegerNode; class EncodeNarrowPtrNode; class EncodePNode; class EncodePKlassNode; @@ -830,9 +829,8 @@ class Node { DEFINE_CLASS_ID(LShift, Node, 21) DEFINE_CLASS_ID(Neg, Node, 22) DEFINE_CLASS_ID(ReachabilityFence, Node, 23) - DEFINE_CLASS_ID(DivModInteger, Node, 24) - _max_classes = ClassMask_DivModInteger + _max_classes = ClassMask_Neg }; #undef DEFINE_CLASS_ID @@ -949,7 +947,6 @@ class Node { DEFINE_CLASS_QUERY(DecodeNarrowPtr) DEFINE_CLASS_QUERY(DecodeN) DEFINE_CLASS_QUERY(DecodeNKlass) - DEFINE_CLASS_QUERY(DivModInteger) DEFINE_CLASS_QUERY(EncodeNarrowPtr) DEFINE_CLASS_QUERY(EncodeP) DEFINE_CLASS_QUERY(EncodePKlass) @@ -1504,10 +1501,6 @@ class Node { uint _del_tick; // Bumped when a deletion happens.. #endif #endif - void make_paths_from_here_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, const char* phase_str); - - static void create_halt_path(PhaseIterGVN* igvn, Node* c, PhaseIdealLoop* loop, const char* phase_str); - void make_path_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, Node* ctrl_use, uint j, const char* phase_str); }; inline bool not_a_node(const Node* n) { @@ -2205,13 +2198,17 @@ class TypeNode : public Node { init_class_id(Class_Type); } virtual const Type* Value(PhaseGVN* phase) const; + virtual Node* Ideal(PhaseGVN* phase, bool can_reshape); virtual const Type *bottom_type() const; virtual uint ideal_reg() const; + void make_path_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, Node* ctrl_use, uint j, const char* phase_str); #ifndef PRODUCT virtual void dump_spec(outputStream *st) const; virtual void dump_compact_spec(outputStream *st) const; #endif + void make_paths_from_here_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, const char* phase_str); + void create_halt_path(PhaseIterGVN* igvn, Node* c, PhaseIdealLoop* loop, const char* phase_str) const; }; #include "opto/opcodes.hpp" diff --git a/src/hotspot/share/opto/parse2.cpp b/src/hotspot/share/opto/parse2.cpp index 6e58fae51e1f..9cb20cfcd006 100644 --- a/src/hotspot/share/opto/parse2.cpp +++ b/src/hotspot/share/opto/parse2.cpp @@ -1843,7 +1843,7 @@ void Parse::sharpen_type_after_if(BoolTest::mask btest, const Type* obj_type = _gvn.type(obj); const Type* tboth = obj_type->filter_speculative(cast_type); assert(tboth->higher_equal(obj_type) && tboth->higher_equal(cast_type), "sanity"); - if (tboth == Type::TOP && KillPathsReachableByDeadDataNode) { + if (tboth == Type::TOP && KillPathsReachableByDeadTypeNode) { // Let dead type node cleaning logic prune effectively dead path for us. // CheckCastPP::Value() == TOP and it will trigger the cleanup during GVN. // Don't materialize the cast when cleanup is disabled, because diff --git a/src/hotspot/share/opto/phaseX.cpp b/src/hotspot/share/opto/phaseX.cpp index c124f940a27b..a4d6a6c33d08 100644 --- a/src/hotspot/share/opto/phaseX.cpp +++ b/src/hotspot/share/opto/phaseX.cpp @@ -32,7 +32,6 @@ #include "opto/castnode.hpp" #include "opto/cfgnode.hpp" #include "opto/convertnode.hpp" -#include "opto/divnode.hpp" #include "opto/idealGraphPrinter.hpp" #include "opto/loopnode.hpp" #include "opto/machnode.hpp" @@ -2198,107 +2197,6 @@ Node *PhaseIterGVN::transform( Node *n ) { return transform_old(n); } -DeadPathNode* PhaseIterGVN::dead_path() { - DeadPathNode* dead_path_node = C->dead_path(); - if (!dead_path_node->is_active()) { - dead_path_node->activate(this); - } - assert(C->root()->find_edge(dead_path_node) > 0, "should be reachable from root"); - return dead_path_node; -} - - -// If dead_node is a data node, all CFG nodes reachable from dead_node are dead cfg paths. This method follows uses from -// dead_node until it encounters a cfg node or a phi and eagerly kills these dead cfg paths. This is needed because, in -// some corner cases, a data node dies but some data paths that use it (and are unreachable at runtime) are not proven -// dead by igvn, possibly leading to incorrect IR graphs. -// Also see comment at DeadPathNode declaration. -void PhaseIterGVN::make_dependent_paths_dead_if_top(Node* dead_node, const Type* t) { - if (t != Type::TOP) { - return; - } - if (!KillPathsReachableByDeadDataNode) { - return; - } - // dead_node is going dead, follow uses - ResourceMark rm; - Unique_Node_List wq; - wq.push(dead_node); - for (uint i = 0; i < wq.size(); i++) { - Node* n = wq.at(i); - if (n != dead_node && (n->is_Phi() || n->is_CFG())) { - continue; - } - for (DUIterator_Fast kmax, k = n->fast_outs(kmax); k < kmax; k++) { - Node* u = n->fast_out(k); - wq.push(u); - } - } - for (uint i = 0; i < wq.size(); i++) { - Node* n = wq.at(i); - if (n->is_Phi()) { - Node* region = n->in(0); - // Find out through which of the Phi's input, we reached that Phi and mark the corresponding CFG path dead - for (uint j = 1; j < n->req(); j++) { - Node* in = n->in(j); - // We don't follow uses beyond Phis so if 'in' is a Phi (unless it's dead_node), we couldn't reach this Phi through it - if (in == dead_node || (in != nullptr && !in->is_Phi() && wq.member(in))) { - if (!region->is_top() && region->in(j) != nullptr && !region->in(j)->is_top()) { - // We reached this CFG path through data nodes, record it in dead path to later insert a Halt node, if it - // doesn't die in the meantime - dead_path()->add_req(region->in(j)); - _worklist.push(dead_path()); - replace_input_of(region, j, C->top()); - } - replace_input_of(n, j, C->top()); - if (in->outcnt() == 0) { - remove_dead_node(in, NodeOrigin::Graph); - } - } - } - continue; - } - if (n == dead_node) { - continue; - } - // We don't want to follow CFG nodes but is_CFG() can return false for a cfg projection if its input is top. So - // there's no foolproof way of telling if dead_node is a cfg or not and as a consequence we can reach a Region. - if (n->is_Region()) { - // Find out through which of the Region's input, we reached that Region and mark it dead - for (uint j = 1; j < n->req(); j++) { - Node* in = n->in(j); - // We don't follow uses beyond Regions so if 'in' is a Region, we couldn't reach this Region through it - if (in != nullptr && !in->is_Region() && wq.member(in)) { - replace_input_of(n, j, C->top()); - in->remove_dead_region(this, true); - } - } - continue; - } - // If we reached this CFG node through a data input... - if (n->is_CFG()) { - Node* control_input = n->in(0); - if (control_input != nullptr && !control_input->is_top()) { - // record it in dead path to later insert a Halt node, if it doesn't die in the meantime - dead_path()->add_req(control_input); - _worklist.push(dead_path()); - replace_input_of(n, 0, C->top()); - } - n->remove_dead_region(this, true); - continue; - } - if (n->outcnt() == 0) { - remove_dead_node(n, NodeOrigin::Graph); - } - } -#ifdef ASSERT - for (uint i = 0; i < wq.size(); i++) { - Node* n = wq.at(i); - assert(n->is_Region() || n->is_Phi() || n->is_CFG() || n->outcnt() == 0, "node should be dead now"); - } -#endif -} - Node *PhaseIterGVN::transform_old(Node* n) { NOT_PRODUCT(set_transforms()); // Remove 'n' from hash table in case it gets modified @@ -2390,7 +2288,6 @@ Node *PhaseIterGVN::transform_old(Node* n) { } // If 'k' computes a constant, replace it with a constant if (t->singleton() && !k->is_Con()) { - make_dependent_paths_dead_if_top(k, t); set_progress(); Node* con = makecon(t); // Make a constant add_users_to_worklist(k); @@ -3060,14 +2957,10 @@ void PhaseCCP::analyze_step(Unique_Node_List& worklist, Node* n) { set_type(n, new_type); push_child_nodes_to_worklist(worklist, n); } - if (KillPathsReachableByDeadDataNode && n->is_Type() && new_type == Type::TOP) { + if (KillPathsReachableByDeadTypeNode && n->is_Type() && new_type == Type::TOP) { // Keep track of Type nodes to kill CFG paths that use Type // nodes that become dead. - _maybe_top_type_or_div_mod_nodes.push(n); - } - if (KillPathsReachableByDeadDataNode && new_type == Type::TOP && n->is_DivModInteger() && - type(n->in(2)) == n->as_DivModInteger()->zero()) { - _maybe_top_type_or_div_mod_nodes.push(n); + _maybe_top_type_nodes.push(n); } } @@ -3363,16 +3256,16 @@ Node *PhaseCCP::transform( Node *n ) { // track all visited nodes, so that we can remove the complement Unique_Node_List useful; - if (KillPathsReachableByDeadDataNode) { - for (uint i = 0; i < _maybe_top_type_or_div_mod_nodes.size(); ++i) { - Node* data_node = _maybe_top_type_or_div_mod_nodes.at(i); - if (type(data_node) == Type::TOP) { + if (KillPathsReachableByDeadTypeNode) { + for (uint i = 0; i < _maybe_top_type_nodes.size(); ++i) { + Node* type_node = _maybe_top_type_nodes.at(i); + if (type(type_node) == Type::TOP) { ResourceMark rm; - data_node->make_paths_from_here_dead(this, nullptr, "ccp"); + type_node->as_Type()->make_paths_from_here_dead(this, nullptr, "ccp"); } } } else { - assert(_maybe_top_type_or_div_mod_nodes.size() == 0, "we don't need type nodes"); + assert(_maybe_top_type_nodes.size() == 0, "we don't need type nodes"); } // Initialize the traversal. diff --git a/src/hotspot/share/opto/phaseX.hpp b/src/hotspot/share/opto/phaseX.hpp index 7ea7aa991426..014d16f92f6d 100644 --- a/src/hotspot/share/opto/phaseX.hpp +++ b/src/hotspot/share/opto/phaseX.hpp @@ -501,10 +501,6 @@ class PhaseIterGVN : public PhaseGVN { // Usually returns new_type. Returns old_type if new_type is only a slight // improvement, such that it would take many (>>10) steps to reach 2**32. - DeadPathNode* dead_path(); - - void make_dependent_paths_dead_if_top(Node* dead_node, const Type* t); - public: PhaseIterGVN(PhaseIterGVN* igvn); // Used by CCP constructor @@ -699,7 +695,7 @@ class PhaseIterGVN : public PhaseGVN { // Should be replaced with combined CCP & GVN someday. class PhaseCCP : public PhaseIterGVN { Unique_Node_List _root_and_safepoints; - Unique_Node_List _maybe_top_type_or_div_mod_nodes; + Unique_Node_List _maybe_top_type_nodes; // Non-recursive. Use analysis to transform single Node. virtual Node* transform_once(Node* n); diff --git a/src/hotspot/share/opto/rootnode.cpp b/src/hotspot/share/opto/rootnode.cpp index 1e5ef29e79c7..60167c5436a1 100644 --- a/src/hotspot/share/opto/rootnode.cpp +++ b/src/hotspot/share/opto/rootnode.cpp @@ -90,48 +90,3 @@ const Type* HaltNode::Value(PhaseGVN* phase) const { const RegMask &HaltNode::out_RegMask() const { return RegMask::EMPTY; } - -Node* DeadPathNode::Ideal(PhaseGVN* phase, bool can_reshape) { - assert(unique_ctrl_out() == phase->C->root(), "only referenced from root"); - assert(can_reshape, "only used once igvn executes"); - bool modified = false; - for (uint i = 1; i < req(); i++) { // For all inputs - // Check for and remove dead inputs - if (phase->type(in(i)) == Type::TOP) { - del_req(i--); // Delete TOP inputs - modified = true; - } - } - if (req() == 1 && is_active()) { - assert(modified, "only if some inputs were removed"); - deactivate(); - } - return modified ? this : nullptr; -} - -const Type* DeadPathNode::Value(PhaseGVN* phase) const { - if (req() == 1) { - return Type::TOP; - } - return bottom_type(); -} - -void DeadPathNode::activate(PhaseIterGVN* igvn) { - assert(Compile::current()->root()->find_edge(this) < 0, "should be disconnected from root"); - set_req(0, this); - // If an entire subgraph died such as with Node::remove_dead_region(), some dead inputs to the DeadPath node will have - // been left behind - while (req() > 1) { - uint last = req() - 1; - assert(in(last) == nullptr || in(last)->is_top(), "only dead inputs should remain"); - del_req(last); - } - Node* root_node = Compile::current()->root(); - root_node->add_req(this); - igvn->_worklist.push(root_node); - igvn->set_type(this, bottom_type()); -} - -void DeadPathNode::deactivate() { - set_req(0, nullptr); -} diff --git a/src/hotspot/share/opto/rootnode.hpp b/src/hotspot/share/opto/rootnode.hpp index 61ad317d455f..76f0ec440a9b 100644 --- a/src/hotspot/share/opto/rootnode.hpp +++ b/src/hotspot/share/opto/rootnode.hpp @@ -69,35 +69,4 @@ class HaltNode : public Node { virtual uint match_edge(uint idx) const { return 0; } }; - -// This node collects paths that are found dead by PhaseIterGVN::make_dependent_paths_dead_if_top() - -// There is a single DeadPath node for the lifetime of optimizations. It's initially not active (i.e. unreachable from -// the IR graph). When a cfg path becomes dead it's added as an input to the unique DeadPath node. If after some -// optimizations run, the DeadPath node gets disconnected, it's not destroyed. It becomes inactive and can possibly be -// activated again on a subsequent igvn. When optimizations are over, the DeadPath node, if it is active, is expanded to -// a Region and Halt node in Compile::final_graph_reshaping(). - -// Rather than having this dedicated node, igvn could add a Halt node everytime it finds a dead cfg path from a data -// node. What's likely, however, is that as igvn progresses, that same cfg path is found dead by following cfg edges. -// The Halt node then becomes dead. To avoid this unnecessary cycle of creation of a Halt node only to have it be found -// dead shortly after, dead cfg paths are added to the unique DeadPath node. -class DeadPathNode : public RegionNode { -public: - DeadPathNode() : RegionNode(1) { - deactivate(); - assert(Compile::current()->dead_path() == nullptr, "only one"); - } - virtual int Opcode() const; - virtual const Type* bottom_type() const { return Type::BOTTOM; } - virtual Node* Identity(PhaseGVN* phase) { return this; } - virtual Node* Ideal(PhaseGVN* phase, bool can_reshape); - virtual const Type* Value(PhaseGVN* phase) const; - bool is_active() const { - return in(0) == this; - } - void activate(PhaseIterGVN* igvn); - void deactivate(); -}; - #endif // SHARE_OPTO_ROOTNODE_HPP diff --git a/src/hotspot/share/opto/vectornode.cpp b/src/hotspot/share/opto/vectornode.cpp index 60eda1204b73..b1251562ca84 100644 --- a/src/hotspot/share/opto/vectornode.cpp +++ b/src/hotspot/share/opto/vectornode.cpp @@ -96,6 +96,15 @@ int VectorNode::opcode(int sopc, BasicType bt) { return (bt == T_DOUBLE ? Op_VectorBlend : 0); case Op_Bool: return Op_VectorMaskCmp; + case Op_DivI: + switch (bt) { + case T_BYTE: return Op_DivVB; + case T_SHORT: return Op_DivVS; + case T_INT: return Op_DivVI; + default: return 0; + } + case Op_DivL: + return (bt == T_LONG ? Op_DivVL : 0); case Op_DivHF: return (bt == T_SHORT ? Op_DivVHF : 0); case Op_DivF: @@ -333,6 +342,12 @@ int VectorNode::scalar_opcode(int vopc, BasicType bt) { case Op_MulVD: return Op_MulD; + case Op_DivVB: + case Op_DivVS: + case Op_DivVI: + return Op_DivI; + case Op_DivVL: + return Op_DivL; case Op_DivVF: return Op_DivF; case Op_DivVD: @@ -680,7 +695,7 @@ void VectorNode::vector_operands(Node* n, uint* start, uint* end) { case Op_AddI: case Op_AddL: case Op_AddHF: case Op_AddF: case Op_AddD: case Op_SubI: case Op_SubL: case Op_SubHF: case Op_SubF: case Op_SubD: case Op_MulI: case Op_MulL: case Op_MulHF: case Op_MulF: case Op_MulD: - case Op_DivHF: case Op_DivF: case Op_DivD: + case Op_DivI: case Op_DivL: case Op_DivHF: case Op_DivF: case Op_DivD: case Op_AndI: case Op_AndL: case Op_OrI: case Op_OrL: case Op_XorI: case Op_XorL: @@ -759,6 +774,10 @@ VectorNode* VectorNode::make(int vopc, Node* n1, Node* n2, const TypeVect* vt, b case Op_MulVF: return new MulVFNode(n1, n2, vt); case Op_MulVD: return new MulVDNode(n1, n2, vt); + case Op_DivVB: return new DivVBNode(n1, n2, vt); + case Op_DivVS: return new DivVSNode(n1, n2, vt); + case Op_DivVI: return new DivVINode(n1, n2, vt); + case Op_DivVL: return new DivVLNode(n1, n2, vt); case Op_DivVHF: return new DivVHFNode(n1, n2, vt); case Op_DivVF: return new DivVFNode(n1, n2, vt); case Op_DivVD: return new DivVDNode(n1, n2, vt); @@ -2391,7 +2410,7 @@ Node* VectorMaskOpNode::Ideal(PhaseGVN* phase, bool can_reshape) { if (n != nullptr) { return n; } - return nullptr; + return TypeNode::Ideal(phase, can_reshape); } Node* VectorMaskCastNode::Identity(PhaseGVN* phase) { diff --git a/src/hotspot/share/opto/vectornode.hpp b/src/hotspot/share/opto/vectornode.hpp index d013bbc25d6c..31faad5826ec 100644 --- a/src/hotspot/share/opto/vectornode.hpp +++ b/src/hotspot/share/opto/vectornode.hpp @@ -696,6 +696,34 @@ class MulReductionVDNode : public ReductionNode { virtual uint size_of() const { return sizeof(*this); } }; +// Vector divide byte +class DivVBNode : public VectorNode { +public: + DivVBNode(Node* in1, Node* in2, const TypeVect* vt) : VectorNode(in1, in2, vt) {} + virtual int Opcode() const; +}; + +// Vector divide short +class DivVSNode : public VectorNode { +public: + DivVSNode(Node* in1, Node* in2, const TypeVect* vt) : VectorNode(in1, in2, vt) {} + virtual int Opcode() const; +}; + +// Vector divide int +class DivVINode : public VectorNode { +public: + DivVINode(Node* in1, Node* in2, const TypeVect* vt) : VectorNode(in1, in2, vt) {} + virtual int Opcode() const; +}; + +// Vector divide long +class DivVLNode : public VectorNode { +public: + DivVLNode(Node* in1, Node* in2, const TypeVect* vt) : VectorNode(in1, in2, vt) {} + virtual int Opcode() const; +}; + // Vector divide half float class DivVHFNode : public VectorNode { public: diff --git a/src/hotspot/share/prims/downcallLinker.cpp b/src/hotspot/share/prims/downcallLinker.cpp index cbef98416523..795e34ac04ab 100644 --- a/src/hotspot/share/prims/downcallLinker.cpp +++ b/src/hotspot/share/prims/downcallLinker.cpp @@ -73,6 +73,10 @@ JVM_LEAF(void, DowncallLinker::capture_state_post(int32_t* value_ptr, int captur } JVM_END +bool DowncallLinker::is_downcall_stub(const CodeBlob* cb) { + return cb != nullptr && cb->is_runtime_stub() && (strcmp(cb->name(), "nep_invoker_blob") == 0); +} + void DowncallLinker::StubGenerator::add_offsets_to_oops(GrowableArray& java_regs, VMStorage tmp1, VMStorage tmp2) const { int reg_idx = 0; for (int sig_idx = 0; sig_idx < _num_args; sig_idx++) { diff --git a/src/hotspot/share/prims/downcallLinker.hpp b/src/hotspot/share/prims/downcallLinker.hpp index 519e84281cef..9ab86a72de42 100644 --- a/src/hotspot/share/prims/downcallLinker.hpp +++ b/src/hotspot/share/prims/downcallLinker.hpp @@ -28,6 +28,7 @@ #include "runtime/stubCodeGenerator.hpp" class RuntimeStub; +class CodeBlob; class DowncallLinker: AllStatic { public: @@ -45,6 +46,8 @@ class DowncallLinker: AllStatic { static void JNICALL capture_state_pre(int32_t* value_ptr, int captured_state_mask); static void JNICALL capture_state_post(int32_t* value_ptr, int captured_state_mask); + static bool is_downcall_stub(const CodeBlob* cb); + class StubGenerator : public StubCodeGenerator { BasicType* _signature; int _num_args; diff --git a/src/hotspot/share/prims/jvmtiEnv.cpp b/src/hotspot/share/prims/jvmtiEnv.cpp index 9c5abdf790b0..bd0552e45b14 100644 --- a/src/hotspot/share/prims/jvmtiEnv.cpp +++ b/src/hotspot/share/prims/jvmtiEnv.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -1205,35 +1205,13 @@ jvmtiError JvmtiEnv::StopThread(jthread thread, jobject exception) { JavaThread* current_thread = JavaThread::current(); - MountUnmountDisabler disabler(thread); - ThreadsListHandle tlh(current_thread); - JavaThread* java_thread = nullptr; - oop thread_oop = nullptr; - NULL_CHECK(thread, JVMTI_ERROR_INVALID_THREAD); - - jvmtiError err = get_threadOop_and_JavaThread(tlh.list(), thread, current_thread, &java_thread, &thread_oop); - - bool is_virtual = thread_oop != nullptr && thread_oop->is_a(vmClasses::BaseVirtualThread_klass()); - - if (is_virtual && !is_JavaThread_current(java_thread, thread_oop)) { - if (!is_vthread_suspended(thread_oop, java_thread)) { - return JVMTI_ERROR_THREAD_NOT_SUSPENDED; - } - if (java_thread == nullptr) { // unmounted virtual thread - return JVMTI_ERROR_OPAQUE_FRAME; - } - } - if (err != JVMTI_ERROR_NONE) { - return err; - } oop e = JNIHandles::resolve_external_guard(exception); NULL_CHECK(e, JVMTI_ERROR_NULL_POINTER); - JavaThread::send_async_exception(java_thread, e); - - return JVMTI_ERROR_NONE; - + StopThreadClosure op(current_thread, e); + JvmtiHandshake::execute(&op, thread); + return op.result(); } /* end StopThread */ diff --git a/src/hotspot/share/prims/jvmtiEnvBase.cpp b/src/hotspot/share/prims/jvmtiEnvBase.cpp index 4c9e3d67d5f6..beb7bac2a491 100644 --- a/src/hotspot/share/prims/jvmtiEnvBase.cpp +++ b/src/hotspot/share/prims/jvmtiEnvBase.cpp @@ -35,6 +35,7 @@ #include "oops/objArrayOop.hpp" #include "oops/oop.inline.hpp" #include "oops/oopHandle.inline.hpp" +#include "prims/downcallLinker.hpp" #include "prims/jvmtiEnvBase.hpp" #include "prims/jvmtiEventController.inline.hpp" #include "prims/jvmtiExtensions.hpp" @@ -2397,6 +2398,101 @@ JvmtiModuleClosure::get_all_modules(JvmtiEnv* env, jint* module_count_ptr, jobje return JVMTI_ERROR_NONE; } + +static bool is_async_unsafe_method(Method* m) { + return m != nullptr && + (m->method_holder() == vmClasses::VirtualThread_klass() || + // Checks for VirtualThread$VThreadContinuation$1.run + (m->name() == vmSymbols::run_method_name() && m->jvmti_hide_events())); +} + +class StopThreadAsyncClosure : public AsyncExceptionHandshakeClosure { + public: + StopThreadAsyncClosure(OopHandle& exception) + : AsyncExceptionHandshakeClosure(exception, "StopThreadAsyncClosure") {} + + virtual void do_thread(Thread* thread) { + JavaThread* java_thread = JavaThread::cast(thread); + DEBUG_ONLY(vframeStream vfst(java_thread);) + assert(vfst.at_end() || !is_async_unsafe_method(vfst.method()), "missing transition to Java with no async processing"); + assert(!java_thread->is_in_vthread_transition(), "should not throw inside transition"); + + AsyncExceptionHandshakeClosure::do_thread(java_thread); + } +}; + +StopThreadClosure::StopThreadClosure(JavaThread* current_thread, oop exception) + : JvmtiUnitedHandshakeClosure("StopThread"), _exception(current_thread, exception) {} + +void +StopThreadClosure::doit(JavaThread* target) { + OopHandle e(Universe::vm_global(), _exception()); + target->install_async_exception(new StopThreadAsyncClosure(e)); + _result = JVMTI_ERROR_NONE; +} + +void +StopThreadClosure::do_thread(Thread* target) { + assert(_target_jt == JavaThread::cast(target), "sanity check"); + + oop target_oop = _target_jt->threadObj(); + if (target_oop->is_a(vmClasses::BaseVirtualThread_klass())) { + if (!_self && !JvmtiEnvBase::is_vthread_suspended(target_oop, _target_jt)) { + _result = JVMTI_ERROR_THREAD_NOT_SUSPENDED; + return; + } + } + doit(_target_jt); +} + +void +StopThreadClosure::do_vthread(Handle target_h) { + if (!_self && !JvmtiVTSuspender::is_vthread_suspended(target_h())) { + _result = JVMTI_ERROR_THREAD_NOT_SUSPENDED; + return; + } + + if (_target_jt == nullptr) { + _result = JVMTI_ERROR_OPAQUE_FRAME; + return; + } + + if (_target_jt->on_monitor_waited_event()) { + // The exception could end up being thrown in the + // carrier so skip this case. + _result = JVMTI_ERROR_OPAQUE_FRAME; + return; + } + + vframeStream vfst(_target_jt); + Method* m = vfst.method(); + if (is_async_unsafe_method(m)) { + // Throwing from a VirtualThread method might leave the + // virtual thread in an inconsistent state. The current + // implementation simply checks the top method, which is + // enough to fix known issues where transition bits could + // be left in an invalid state and trigger assertion failures. + // A more thorough approach would be to walk the stack and + // check for VirtualThread methods further up (excluding + // VirtualThread.run and VirtualThread$VThreadContinuation$1.run + // which are always present). + _result = JVMTI_ERROR_OPAQUE_FRAME; + return; + } + + if (_target_jt->at_no_async_entry_count() > 0 + || _target_jt->is_at_poll_safepoint() + || DowncallLinker::is_downcall_stub(_target_jt->last_frame().cb())) { + // The target will defer processing of the async exception to + // some later safepoint poll. We cannot guarantee where that + // will be so we conservatively skip this case. + _result = JVMTI_ERROR_OPAQUE_FRAME; + return; + } + + doit(_target_jt); +} + void UpdateForPopTopFrameClosure::doit(Thread *target) { Thread* current_thread = Thread::current(); diff --git a/src/hotspot/share/prims/jvmtiEnvBase.hpp b/src/hotspot/share/prims/jvmtiEnvBase.hpp index d7cc12f2fbd0..4602c1003718 100644 --- a/src/hotspot/share/prims/jvmtiEnvBase.hpp +++ b/src/hotspot/share/prims/jvmtiEnvBase.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -532,6 +532,17 @@ class SetForceEarlyReturn : public JvmtiUnitedHandshakeClosure { } }; +// HandshakeClosure to send an asynchronous exception to target. +class StopThreadClosure : public JvmtiUnitedHandshakeClosure { + private: + Handle _exception; + public: + StopThreadClosure(JavaThread* current_thread, oop exception); + void doit(JavaThread* target); + void do_thread(Thread* target); + void do_vthread(Handle target_h); +}; + // HandshakeClosure to update for pop top frame. class UpdateForPopTopFrameClosure : public JvmtiUnitedHandshakeClosure { private: diff --git a/src/hotspot/share/prims/jvmtiImpl.cpp b/src/hotspot/share/prims/jvmtiImpl.cpp index c0a4ca949c99..96366cceaff5 100644 --- a/src/hotspot/share/prims/jvmtiImpl.cpp +++ b/src/hotspot/share/prims/jvmtiImpl.cpp @@ -379,7 +379,7 @@ bool VM_BaseGetOrSetLocal::check_slot_type_lvt(javaVFrame* jvf) { if (!method->has_localvariable_table()) { // Just to check index boundaries. jint extra_slot = (_type == T_LONG || _type == T_DOUBLE) ? 1 : 0; - if (_index < 0 || _index + extra_slot >= method->max_locals()) { + if (_index < 0 || _index >= method->max_locals() - extra_slot) { _result = JVMTI_ERROR_INVALID_SLOT; return false; } @@ -451,7 +451,7 @@ bool VM_BaseGetOrSetLocal::check_slot_type_no_lvt(javaVFrame* jvf) { Method* method = jvf->method(); jint extra_slot = (_type == T_LONG || _type == T_DOUBLE) ? 1 : 0; - if (_index < 0 || _index + extra_slot >= method->max_locals()) { + if (_index < 0 || _index >= method->max_locals() - extra_slot) { _result = JVMTI_ERROR_INVALID_SLOT; return false; } diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index 269a8b39e6b6..b08e71f559ae 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -1703,6 +1703,7 @@ jint Arguments::parse_vm_init_args(GrowableArrayCHeapinstall_async_exception(_aehc); - _aehc = nullptr; - } -}; - -void JavaThread::send_async_exception(JavaThread* target, oop java_throwable) { - OopHandle e(Universe::vm_global(), java_throwable); - InstallAsyncExceptionHandshakeClosure iaeh(new AsyncExceptionHandshakeClosure(e)); - Handshake::execute(&iaeh, target); -} - bool JavaThread::is_in_vthread_transition() const { DEBUG_ONLY(Thread* current = Thread::current();) assert(is_handshake_safe_for(current) || SafepointSynchronize::is_at_safepoint() diff --git a/src/hotspot/share/runtime/javaThread.hpp b/src/hotspot/share/runtime/javaThread.hpp index 9e3c629ad785..cd757ed8ec12 100644 --- a/src/hotspot/share/runtime/javaThread.hpp +++ b/src/hotspot/share/runtime/javaThread.hpp @@ -230,16 +230,16 @@ class JavaThread: public Thread { // Asynchronous exception support private: - friend class InstallAsyncExceptionHandshakeClosure; friend class AsyncExceptionHandshakeClosure; friend class HandshakeState; + int _at_no_async_entry_count; // Tracks JRT_xxx_NO_ASYNC entry points + void handle_async_exception(oop java_throwable); public: - void install_async_exception(AsyncExceptionHandshakeClosure* aec = nullptr); + void install_async_exception(AsyncExceptionHandshakeClosure* aec); bool has_async_exception_condition(); inline void set_pending_unsafe_access_error(); - static void send_async_exception(JavaThread* jt, oop java_throwable); class NoAsyncExceptionDeliveryMark : public StackObj { friend JavaThread; @@ -248,6 +248,20 @@ class JavaThread: public Thread { inline ~NoAsyncExceptionDeliveryMark(); }; + int at_no_async_entry_count() const { + assert(is_handshake_safe_for(Thread::current()), "Should only be invoked from within a handshake!"); + assert(_at_no_async_entry_count >= 0, ""); + return _at_no_async_entry_count; + } + void inc_at_no_async_entry_count() { + assert(_at_no_async_entry_count >= 0, ""); + _at_no_async_entry_count++; + } + void dec_at_no_async_entry_count() { + _at_no_async_entry_count--; + assert(_at_no_async_entry_count >= 0, ""); + } + // Safepoint support public: // Expose _thread_state for SafeFetchInt() volatile JavaThreadState _thread_state; @@ -1338,4 +1352,25 @@ class ThrowingUnsafeAccessError : public StackObj { } }; +// Tracks Java->VM entry points that defer async exception +// processing on the transition back to Java (except the +// safepoint poll from compiled code which is already +// tracked by JavaThread::is_at_poll_safepoint()). +class AtNoAsyncEntryMark : public StackObj { + JavaThread* _target; + bool _do_count; + public: + AtNoAsyncEntryMark(JavaThread* jt, bool do_count) + : _target(jt), _do_count(do_count) { + if (_do_count) { + _target->inc_at_no_async_entry_count(); + } + } + ~AtNoAsyncEntryMark() { + if (_do_count) { + _target->dec_at_no_async_entry_count(); + } + } +}; + #endif // SHARE_RUNTIME_JAVATHREAD_HPP diff --git a/src/hotspot/share/runtime/os.hpp b/src/hotspot/share/runtime/os.hpp index b35f5e8bc4bb..50e087dcc94c 100644 --- a/src/hotspot/share/runtime/os.hpp +++ b/src/hotspot/share/runtime/os.hpp @@ -257,6 +257,7 @@ class os: AllStatic { static void initialize_initial_active_processor_count(); LINUX_ONLY(static void pd_init_container_support();) + LINUX_ONLY(static void pd_check_temp_directory();) public: static void init(void); // Called before command line parsing @@ -265,6 +266,11 @@ class os: AllStatic { LINUX_ONLY(pd_init_container_support();) } + static void check_temp_directory() { + // Only applicable on linux. + LINUX_ONLY(pd_check_temp_directory();) + } + static void init_before_ergo(void); // Called after command line parsing // before VM ergonomics processing. static jint init_2(void); // Called after command line parsing diff --git a/src/hotspot/share/runtime/sharedRuntime.cpp b/src/hotspot/share/runtime/sharedRuntime.cpp index bcb7f5488f5e..919161dde2f0 100644 --- a/src/hotspot/share/runtime/sharedRuntime.cpp +++ b/src/hotspot/share/runtime/sharedRuntime.cpp @@ -108,14 +108,6 @@ nmethod* SharedRuntime::_cont_doYield_stub; -#if 0 -// TODO tweak global stub name generation to match this -#define SHARED_STUB_NAME_DECLARE(name, type) "Shared Runtime " # name "_blob", -const char *SharedRuntime::_stub_names[] = { - SHARED_STUBS_DO(SHARED_STUB_NAME_DECLARE) -}; -#endif - //----------------------------generate_stubs----------------------------------- void SharedRuntime::generate_initial_stubs() { // Build this early so it's available for the interpreter. diff --git a/src/hotspot/share/runtime/stubRoutines.cpp b/src/hotspot/share/runtime/stubRoutines.cpp index f5509b9d9964..db513a3771e9 100644 --- a/src/hotspot/share/runtime/stubRoutines.cpp +++ b/src/hotspot/share/runtime/stubRoutines.cpp @@ -167,6 +167,10 @@ static BufferBlob* initialize_stubs(BlobId blob_id, const char* buffer_name, const char* assert_msg) { assert(StubInfo::is_stubgen(blob_id), "not a stubgen blob %s", StubInfo::name(blob_id)); + if (blob_id == BlobId::stubgen_continuation_id && !VMContinuations) { + log_info(stubs)("%s\t not generated:\t VMContinuations is disabled", buffer_name); + return nullptr; + } ResourceMark rm; TraceTime timer(timer_msg, TRACETIME_LOG(Info, startuptime)); // If we are loading stubs we need to check if we can retrieve a diff --git a/src/hotspot/share/utilities/parseInteger.hpp b/src/hotspot/share/utilities/parseInteger.hpp index 8840275a8cbb..3fc2f62a19fd 100644 --- a/src/hotspot/share/utilities/parseInteger.hpp +++ b/src/hotspot/share/utilities/parseInteger.hpp @@ -124,7 +124,7 @@ static bool parse_integer(const char *s, char **endptr, T* result) { T n = 0; bool is_hex = (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) || - (s[0] == '-' && s[1] == '0' && (s[2] == 'x' || s[3] == 'X')); + (s[0] == '-' && s[1] == '0' && (s[2] == 'x' || s[2] == 'X')); char* remainder; if (!parse_integer_impl(s, &remainder, (is_hex ? 16 : 10), &n)) { diff --git a/src/java.base/macosx/classes/sun/nio/fs/BsdFileSystem.java b/src/java.base/macosx/classes/sun/nio/fs/BsdFileSystem.java index 69a5d775b0b0..1772b295fed3 100644 --- a/src/java.base/macosx/classes/sun/nio/fs/BsdFileSystem.java +++ b/src/java.base/macosx/classes/sun/nio/fs/BsdFileSystem.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -132,8 +132,6 @@ protected void copyFile(UnixPath source, try { chown(target, attrs.uid(), attrs.gid()); } catch (UnixException x) { - if (flags.failIfUnableToCopyPosix) - x.rethrowAsIOException(target); } return; } diff --git a/src/java.base/share/classes/java/lang/ConditionalSpecialCasing.java b/src/java.base/share/classes/java/lang/ConditionalSpecialCasing.java.template similarity index 53% rename from src/java.base/share/classes/java/lang/ConditionalSpecialCasing.java rename to src/java.base/share/classes/java/lang/ConditionalSpecialCasing.java.template index 17ec3601ec61..e169a43a6b8d 100644 --- a/src/java.base/share/classes/java/lang/ConditionalSpecialCasing.java +++ b/src/java.base/share/classes/java/lang/ConditionalSpecialCasing.java.template @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,7 +25,6 @@ package java.lang; -import java.text.BreakIterator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -47,7 +46,8 @@ final class ConditionalSpecialCasing { // context conditions. - static final int FINAL_CASED = 1; + static final int NONE = 0; + static final int FINAL_SIGMA = 1; static final int AFTER_SOFT_DOTTED = 2; static final int MORE_ABOVE = 3; static final int AFTER_I = 4; @@ -58,34 +58,7 @@ final class ConditionalSpecialCasing { // Special case mapping entries static Entry[] entry = { - //# ================================================================================ - //# Conditional mappings - //# ================================================================================ - new Entry(0x03A3, new char[]{0x03C2}, new char[]{0x03A3}, null, FINAL_CASED), // # GREEK CAPITAL LETTER SIGMA - new Entry(0x0130, new char[]{0x0069, 0x0307}, new char[]{0x0130}, null, 0), // # LATIN CAPITAL LETTER I WITH DOT ABOVE - - //# ================================================================================ - //# Locale-sensitive mappings - //# ================================================================================ - //# Lithuanian - new Entry(0x0307, new char[]{0x0307}, new char[]{}, "lt", AFTER_SOFT_DOTTED), // # COMBINING DOT ABOVE - new Entry(0x0049, new char[]{0x0069, 0x0307}, new char[]{0x0049}, "lt", MORE_ABOVE), // # LATIN CAPITAL LETTER I - new Entry(0x004A, new char[]{0x006A, 0x0307}, new char[]{0x004A}, "lt", MORE_ABOVE), // # LATIN CAPITAL LETTER J - new Entry(0x012E, new char[]{0x012F, 0x0307}, new char[]{0x012E}, "lt", MORE_ABOVE), // # LATIN CAPITAL LETTER I WITH OGONEK - new Entry(0x00CC, new char[]{0x0069, 0x0307, 0x0300}, new char[]{0x00CC}, "lt", 0), // # LATIN CAPITAL LETTER I WITH GRAVE - new Entry(0x00CD, new char[]{0x0069, 0x0307, 0x0301}, new char[]{0x00CD}, "lt", 0), // # LATIN CAPITAL LETTER I WITH ACUTE - new Entry(0x0128, new char[]{0x0069, 0x0307, 0x0303}, new char[]{0x0128}, "lt", 0), // # LATIN CAPITAL LETTER I WITH TILDE - - //# ================================================================================ - //# Turkish and Azeri - new Entry(0x0130, new char[]{0x0069}, new char[]{0x0130}, "tr", 0), // # LATIN CAPITAL LETTER I WITH DOT ABOVE - new Entry(0x0130, new char[]{0x0069}, new char[]{0x0130}, "az", 0), // # LATIN CAPITAL LETTER I WITH DOT ABOVE - new Entry(0x0307, new char[]{}, new char[]{0x0307}, "tr", AFTER_I), // # COMBINING DOT ABOVE - new Entry(0x0307, new char[]{}, new char[]{0x0307}, "az", AFTER_I), // # COMBINING DOT ABOVE - new Entry(0x0049, new char[]{0x0131}, new char[]{0x0049}, "tr", NOT_BEFORE_DOT), // # LATIN CAPITAL LETTER I - new Entry(0x0049, new char[]{0x0131}, new char[]{0x0049}, "az", NOT_BEFORE_DOT), // # LATIN CAPITAL LETTER I - new Entry(0x0069, new char[]{0x0069}, new char[]{0x0130}, "tr", 0), // # LATIN SMALL LETTER I - new Entry(0x0069, new char[]{0x0069}, new char[]{0x0130}, "az", 0) // # LATIN SMALL LETTER I +%%%SpecialCasing%%% }; // A hash table that contains the above entries @@ -93,7 +66,7 @@ final class ConditionalSpecialCasing { static { // create hashtable from the entry for (Entry cur : entry) { - Integer cp = cur.getCodePoint(); + Integer cp = cur.codePoint(); HashSet set = entryTable.get(cp); if (set == null) { set = new HashSet<>(); @@ -155,11 +128,11 @@ private static char[] lookUpTable(String src, int index, Locale locale, boolean String currentLang = locale.getLanguage(); while (iter.hasNext()) { Entry entry = iter.next(); - String conditionLang = entry.getLanguage(); - if (((conditionLang == null) || (conditionLang.equals(currentLang))) && - isConditionMet(src, index, locale, entry.getCondition())) { - ret = bLowerCasing ? entry.getLowerCase() : entry.getUpperCase(); - if (conditionLang != null) { + String conditionLang = entry.language(); + if ((conditionLang.isEmpty() || (conditionLang.equals(currentLang))) && + isConditionMet(src, index, entry.condition())) { + ret = bLowerCasing ? entry.lowerCase() : entry.upperCase(); + if (!conditionLang.isEmpty()) { break; } } @@ -169,52 +142,53 @@ private static char[] lookUpTable(String src, int index, Locale locale, boolean return ret; } - private static boolean isConditionMet(String src, int index, Locale locale, int condition) { + private static boolean isConditionMet(String src, int index, int condition) { return switch (condition) { - case FINAL_CASED -> isFinalCased(src, index, locale); + case NONE -> true; + case FINAL_SIGMA -> isFinalSigma(src, index); case AFTER_SOFT_DOTTED -> isAfterSoftDotted(src, index); case MORE_ABOVE -> isMoreAbove(src, index); case AFTER_I -> isAfterI(src, index); case NOT_BEFORE_DOT -> !isBeforeDot(src, index); - default -> true; + default -> throw new InternalError("Special casing condition is not recognized."); }; } /** - * Implements the "Final_Cased" condition + * Implements the "Final_Sigma" condition * - * Specification: Within the closest word boundaries containing C, there is a cased - * letter before C, and there is no cased letter after C. + * Specification: C is preceded by a sequence consisting of a cased letter + * and then zero or more case-ignorable characters, and C is not followed + * by a sequence consisting of zero or more case-ignorable characters and + * then a cased letter. * * Regular Expression: - * Before C: [{cased==true}][{wordBoundary!=true}]* - * After C: !([{wordBoundary!=true}]*[{cased}]) + * Before C: \p{cased} (\p{Case_Ignorable})* + * After C: !((\p{Case_Ignorable})* \p{cased}) */ - private static boolean isFinalCased(String src, int index, Locale locale) { - BreakIterator wordBoundary = BreakIterator.getWordInstance(locale); - wordBoundary.setText(src); - int ch; - - // Look for a preceding 'cased' letter - for (int i = index; (i >= 0) && !wordBoundary.isBoundary(i); - i -= Character.charCount(ch)) { - - ch = src.codePointBefore(i); - if (isCased(ch)) { + private static boolean isFinalSigma(String src, int index) { + int cp; + // Look for a preceding 'cased' letter before 'case-ignorable's + for (int i = index; i > 0; i -= Character.charCount(cp)) { + cp = src.codePointBefore(i); + if (isCased(cp)) { int len = src.length(); - // Check that there is no 'cased' letter after the index + // Check that there are no 'case-ignorable'*'cased' letters after the index for (i = index + Character.charCount(src.codePointAt(index)); - (i < len) && !wordBoundary.isBoundary(i); - i += Character.charCount(ch)) { - - ch = src.codePointAt(i); - if (isCased(ch)) { + i < len; + i += Character.charCount(cp)) { + cp = src.codePointAt(i); + if (isCased(cp)) { return false; + } else if (!isCaseIgnorable(cp)) { + break; } } return true; + } else if (!isCaseIgnorable(cp)) { + return false; } } @@ -353,108 +327,40 @@ private static boolean isBeforeDot(String src, int index) { /** * Examines whether a character is 'cased'. * - * A character C is defined to be 'cased' if and only if at least one of - * following are true for C: uppercase==true, or lowercase==true, or - * general_category==titlecase_letter. - * - * The uppercase and lowercase property values are specified in the data + * The 'cased' property values are specified in the data * file DerivedCoreProperties.txt in the Unicode Character Database. */ - private static boolean isCased(int ch) { - int type = Character.getType(ch); - if (type == Character.LOWERCASE_LETTER || - type == Character.UPPERCASE_LETTER || - type == Character.TITLECASE_LETTER) { - return true; - } else { - // Check for Other_Lowercase and Other_Uppercase - // - if ((ch >= 0x02B0) && (ch <= 0x02B8)) { - // MODIFIER LETTER SMALL H..MODIFIER LETTER SMALL Y - return true; - } else if ((ch >= 0x02C0) && (ch <= 0x02C1)) { - // MODIFIER LETTER GLOTTAL STOP..MODIFIER LETTER REVERSED GLOTTAL STOP - return true; - } else if ((ch >= 0x02E0) && (ch <= 0x02E4)) { - // MODIFIER LETTER SMALL GAMMA..MODIFIER LETTER SMALL REVERSED GLOTTAL STOP - return true; - } else if (ch == 0x0345) { - // COMBINING GREEK YPOGEGRAMMENI - return true; - } else if (ch == 0x037A) { - // GREEK YPOGEGRAMMENI - return true; - } else if ((ch >= 0x1D2C) && (ch <= 0x1D61)) { - // MODIFIER LETTER CAPITAL A..MODIFIER LETTER SMALL CHI - return true; - } else if ((ch >= 0x2160) && (ch <= 0x217F)) { - // ROMAN NUMERAL ONE..ROMAN NUMERAL ONE THOUSAND - // SMALL ROMAN NUMERAL ONE..SMALL ROMAN NUMERAL ONE THOUSAND - return true; - } else if ((ch >= 0x24B6) && (ch <= 0x24E9)) { - // CIRCLED LATIN CAPITAL LETTER A..CIRCLED LATIN CAPITAL LETTER Z - // CIRCLED LATIN SMALL LETTER A..CIRCLED LATIN SMALL LETTER Z - return true; - } else { - return false; - } - } + private static boolean isCased(int cp) { + return +%%%Cased%%% } - private static boolean isSoftDotted(int ch) { - switch (ch) { - case 0x0069: // Soft_Dotted # L& LATIN SMALL LETTER I - case 0x006A: // Soft_Dotted # L& LATIN SMALL LETTER J - case 0x012F: // Soft_Dotted # L& LATIN SMALL LETTER I WITH OGONEK - case 0x0268: // Soft_Dotted # L& LATIN SMALL LETTER I WITH STROKE - case 0x0456: // Soft_Dotted # L& CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I - case 0x0458: // Soft_Dotted # L& CYRILLIC SMALL LETTER JE - case 0x1D62: // Soft_Dotted # L& LATIN SUBSCRIPT SMALL LETTER I - case 0x1E2D: // Soft_Dotted # L& LATIN SMALL LETTER I WITH TILDE BELOW - case 0x1ECB: // Soft_Dotted # L& LATIN SMALL LETTER I WITH DOT BELOW - case 0x2071: // Soft_Dotted # L& SUPERSCRIPT LATIN SMALL LETTER I - return true; - default: - return false; - } + /** + * Examines whether a character is 'Case_Ignorable'. + * + * The 'Case_Ignorable' property values are specified in the data + * file DerivedCoreProperties.txt in the Unicode Character Database. + */ + private static boolean isCaseIgnorable(int cp) { + return +%%%Case_Ignorable%%% } /** - * An internal class that represents an entry in the Special Casing Properties. + * Examines whether a character is 'Soft_Dotted'. + * + * The 'Soft_Dotted' property values are specified in the data + * file PropList.txt in the Unicode Character Database. */ - static class Entry { - int ch; - char [] lower; - char [] upper; - String lang; - int condition; - - Entry(int ch, char[] lower, char[] upper, String lang, int condition) { - this.ch = ch; - this.lower = lower; - this.upper = upper; - this.lang = lang; - this.condition = condition; - } - - int getCodePoint() { - return ch; - } - - char[] getLowerCase() { - return lower; - } - - char[] getUpperCase() { - return upper; - } - - String getLanguage() { - return lang; - } - - int getCondition() { - return condition; - } + private static boolean isSoftDotted(int cp) { + return +%%%Soft_Dotted%%% } + + /** + * Represents a code point with conditional special casing. + * If `language` is empty, the casing rule applies across all languages. + */ + private record Entry(int codePoint, char [] lowerCase, char [] upperCase, + String language, int condition) {}; } diff --git a/src/java.base/share/classes/java/lang/String.java b/src/java.base/share/classes/java/lang/String.java index 9f56ceb445af..e3d120c23f62 100644 --- a/src/java.base/share/classes/java/lang/String.java +++ b/src/java.base/share/classes/java/lang/String.java @@ -4054,7 +4054,7 @@ public static String join(CharSequence delimiter, * (all) * * ΙΧΘΥΣ - * ιχθυσ + * ιχθυς * lowercased all chars in String * * diff --git a/src/java.base/share/classes/java/lang/reflect/ProxyGenerator.java b/src/java.base/share/classes/java/lang/reflect/ProxyGenerator.java index 0d90cedd5c58..9e645d5d9f6f 100644 --- a/src/java.base/share/classes/java/lang/reflect/ProxyGenerator.java +++ b/src/java.base/share/classes/java/lang/reflect/ProxyGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -222,7 +222,6 @@ static byte[] generateProxyClass(ClassLoader loader, path = Path.of(name + ".class"); } Files.write(path, classFile); - return null; } catch (IOException e) { throw new InternalError("I/O exception saving generated file: " + e); } diff --git a/src/java.base/share/classes/java/text/SimpleDateFormat.java b/src/java.base/share/classes/java/text/SimpleDateFormat.java index 4c57214dbba6..ba1ae827776d 100644 --- a/src/java.base/share/classes/java/text/SimpleDateFormat.java +++ b/src/java.base/share/classes/java/text/SimpleDateFormat.java @@ -920,10 +920,15 @@ private void parseAmbiguousDatesAsAfter(Date startDate) { } /** - * Sets the 100-year period 2-digit years will be interpreted as being in - * to begin on the date the user specifies. + * Sets the start date of the 100-year period used to interpret 2-digit years. + *

+ * For example, given a {@code SimpleDateFormat} with a {@code GregorianCalendar}, + * if the start date is set to January 1, 1950, 2-digit years are + * interpreted as falling within the 100-year range from 1950 through 2049. + * In that case, 50 is interpreted as 1950, 99 as 1999, 00 as 2000, and 49 + * as 2049. * - * @param startDate During parsing, two digit years will be placed in the range + * @param startDate During parsing, 2-digit years will be placed in the range * {@code startDate} to {@code startDate + 100 years}. * @see #get2DigitYearStart * @throws NullPointerException if {@code startDate} is {@code null}. @@ -934,11 +939,8 @@ public void set2DigitYearStart(Date startDate) { } /** - * Returns the beginning date of the 100-year period 2-digit years are interpreted - * as being within. + * {@return the start date of the 100-year period used to interpret 2-digit years} * - * @return the start of the 100-year period into which two digit years are - * parsed * @see #set2DigitYearStart * @since 1.2 */ diff --git a/src/java.base/share/classes/java/util/Locale.java b/src/java.base/share/classes/java/util/Locale.java index f727d3019547..9cb1521ac622 100644 --- a/src/java.base/share/classes/java/util/Locale.java +++ b/src/java.base/share/classes/java/util/Locale.java @@ -3214,18 +3214,20 @@ public LanguageRange(String range) { * * @param range a language range * @param weight a weight value between {@code MIN_WEIGHT} and - * {@code MAX_WEIGHT} + * {@code MAX_WEIGHT}, inclusive * @throws NullPointerException if the given {@code range} is * {@code null} * @throws IllegalArgumentException if the given {@code range} does not - * comply with the syntax of the language range mentioned in RFC 4647 - * or if the given {@code weight} is less than {@code MIN_WEIGHT} - * or greater than {@code MAX_WEIGHT} + * comply with the syntax of the language range mentioned in RFC 4647, + * or if the given {@code weight} is {@code Double.NaN}, less than {@code + * MIN_WEIGHT} or greater than {@code MAX_WEIGHT} */ public LanguageRange(String range, double weight) { Objects.requireNonNull(range); - if (weight < MIN_WEIGHT || weight > MAX_WEIGHT) { - throw new IllegalArgumentException("weight=" + weight); + if (weight < MIN_WEIGHT || weight > MAX_WEIGHT || Double.isNaN(weight)) { + throw new IllegalArgumentException( + "The weight " + weight + " must be between " + + MIN_WEIGHT + " and " + MAX_WEIGHT + ", inclusive."); } range = range.toLowerCase(Locale.ROOT); @@ -3311,9 +3313,9 @@ public double getWeight() { * * * In a weighted list, each language range is given a weight value. - * The weight value is identical to the "quality value" in + * The weight value has the same numeric bounds as the "quality value" * RFC 2616, and it - * expresses how much the user prefers the language. A weight value is + * expresses how much the user prefers the language. A weight value is * specified after a corresponding language range followed by * {@code ";q="}, and the default weight value is {@code MAX_WEIGHT} * when it is omitted. @@ -3356,8 +3358,9 @@ public double getWeight() { * included in the given {@code ranges} and their equivalent * language ranges if available. The list is modifiable. * @throws NullPointerException if {@code ranges} is null - * @throws IllegalArgumentException if a language range or a weight - * found in the given {@code ranges} is ill-formed + * @throws IllegalArgumentException if, in the given {@code ranges}, a + * language range is ill-formed, or a weight is out of range after + * string to double conversion by {@link Double#parseDouble(String)} * @spec https://www.rfc-editor.org/info/rfc2616 RFC 2616: Hypertext Transfer Protocol -- HTTP/1.1 */ public static List parse(String ranges) { @@ -3378,8 +3381,9 @@ public static List parse(String ranges) { * @return a Language Priority List with customization. The list is * modifiable. * @throws NullPointerException if {@code ranges} is null - * @throws IllegalArgumentException if a language range or a weight - * found in the given {@code ranges} is ill-formed + * @throws IllegalArgumentException if, in the given {@code ranges}, a + * language range is ill-formed, or a weight is out of range after + * string to double conversion by {@link Double#parseDouble(String)} * @spec https://www.rfc-editor.org/info/rfc2616 RFC 2616: Hypertext Transfer Protocol -- HTTP/1.1 * @see #parse(String) * @see #mapEquivalents(List, Map) diff --git a/src/java.base/share/classes/java/util/jar/JarVerifier.java b/src/java.base/share/classes/java/util/jar/JarVerifier.java index d73231a4c613..e3bdb0307b9c 100644 --- a/src/java.base/share/classes/java/util/jar/JarVerifier.java +++ b/src/java.base/share/classes/java/util/jar/JarVerifier.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -68,7 +68,7 @@ class JarVerifier { private ArrayList pendingBlocks; /* cache of CodeSigner objects */ - private ArrayList signerCache; + private List signerCache; /* Are we parsing a block? */ private boolean parsingBlockOrSF = false; @@ -288,7 +288,7 @@ private void processEntry(ManifestEntryVerifier mev) String key = uname.substring(0, uname.lastIndexOf('.')); if (signerCache == null) - signerCache = new ArrayList<>(); + signerCache = new LinkedList<>(); if (manDig == null) { synchronized(manifestRawBytes) { diff --git a/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java b/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java index 480553e9a627..45e641f11eee 100644 --- a/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java +++ b/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java @@ -571,8 +571,8 @@ public void setRequestMethod(String method) throws ProtocolException { lock(); try { - if (connecting) { - throw new IllegalStateException("connect in progress"); + if (connected || connecting) { + throw new IllegalStateException("Already connected"); } super.setRequestMethod(method); } finally { diff --git a/src/java.base/share/classes/sun/net/www/protocol/https/AbstractDelegateHttpsURLConnection.java b/src/java.base/share/classes/sun/net/www/protocol/https/AbstractDelegateHttpsURLConnection.java index 1415658e34d6..88449caaf096 100644 --- a/src/java.base/share/classes/sun/net/www/protocol/https/AbstractDelegateHttpsURLConnection.java +++ b/src/java.base/share/classes/sun/net/www/protocol/https/AbstractDelegateHttpsURLConnection.java @@ -178,7 +178,7 @@ public void setConnected(boolean conn) { public void connect() throws IOException { if (connected) return; - plainConnect(); + super.connect(); if (cachedResponse != null) { // using cached response return; diff --git a/src/java.base/share/classes/sun/security/pkcs/SignerInfo.java b/src/java.base/share/classes/sun/security/pkcs/SignerInfo.java index 36139d4f4a09..5b2c67500ac9 100644 --- a/src/java.base/share/classes/sun/security/pkcs/SignerInfo.java +++ b/src/java.base/share/classes/sun/security/pkcs/SignerInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -341,6 +341,21 @@ SignerInfo verify(PKCS7 block, byte[] data, X509Certificate cert) // if there are authenticated attributes, get the message // digest and compare it with the digest of data if (authenticatedAttributes == null) { + // RFC 5652 Section 5.3. "[signedAttrs] MUST be present if the + // content type of the EncapsulatedContentInfo value being + // signed is not id-data." + if (!content.getContentType().equals(ContentInfo.DATA_OID)) { + throw new SignatureException("Missing authenticatedAttributes"); + } else { + try { + var c = new DerValue(data); + if (c.tag == DerValue.tag_Set) { + throw new SignatureException("Not a .SF file content"); + } + } catch (IOException e) { + // Expected or ignored + } + } dataSigned = data; } else { @@ -688,6 +703,12 @@ public Timestamp getTimestamp() return null; } + // RFC 3161 Section 2.4.2. id-ct-TSTInfo. + if (!tsToken.getContentInfo().getContentType() + .equals(ContentInfo.TIMESTAMP_TOKEN_INFO_OID)) { + throw new SignatureException("Not using id-ct-TSTInfo"); + } + // Extract the content (an encoded timestamp token info) byte[] encTsTokenInfo = tsToken.getContentInfo().getData(); // Extract the signer (the Timestamping Authority) diff --git a/src/java.base/share/classes/sun/security/provider/certpath/URICertStore.java b/src/java.base/share/classes/sun/security/provider/certpath/URICertStore.java index 3e1fc8db1644..6eb95f92246d 100644 --- a/src/java.base/share/classes/sun/security/provider/certpath/URICertStore.java +++ b/src/java.base/share/classes/sun/security/provider/certpath/URICertStore.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2006, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,6 +25,7 @@ package sun.security.provider.certpath; +import java.io.FilterInputStream; import java.io.InputStream; import java.io.IOException; import java.net.HttpURLConnection; @@ -188,6 +189,16 @@ private static int initializeTimeout(String prop, int def) { return timeoutVal; } + /** + * Maximum size for a CRL downloaded through a URICertStore + * in bytes. This can be controlled by the com.sun.security.crl.maxSize + * Security or System property. The System property, if set, overrides + * the Security property. The default size is 20MiB. + */ + private static final long MAX_CRL_DOWNLOAD_SIZE = + SecurityProperties.getOverridableLongProp( + "com.sun.security.crl.maxSize", 20971520, debug); + /** * Enumeration for the allowed schemes we support when following a * URI from an authorityInfoAccess extension on a certificate. @@ -228,6 +239,13 @@ static AllowedScheme nameOf(String name) { private static final boolean CA_ISS_ALLOW_ANY; static { + // Add a debug message for the configured CRL download limit + if (debug != null) { + debug.println("Maximum downloadable CRL size: " + + MAX_CRL_DOWNLOAD_SIZE + + ((MAX_CRL_DOWNLOAD_SIZE < 0) ? " (DISABLED)" : "")); + } + boolean allowAny = false; try { if (Builder.USE_AIA) { @@ -623,7 +641,19 @@ public synchronized Collection engineGetCRLs(CRLSelector selector) if (debug != null) { debug.println("Downloading new CRL..."); } - crl = (X509CRL) factory.generateCRL(in); + InputStream crlIn = (MAX_CRL_DOWNLOAD_SIZE > -1) ? + new SizeLimitedInputStream(in, MAX_CRL_DOWNLOAD_SIZE) : + in; + try { + crl = (X509CRL) factory.generateCRL(crlIn); + } catch (IllegalArgumentException iae) { + // IAE should only be thrown when the CRL exceeds a + // configured maximum length. + if (debug != null) { + debug.println("Discarding CRL: " + iae.getMessage()); + crl = null; + } + } } return getMatchingCRLs(crl, selector); } catch (IOException | CRLException e) { @@ -816,4 +846,59 @@ boolean matchRule(URI filterRule, URI caIssuer) { return true; } } + + /** + * Stream wrapper used when an InputStream passed into a CertificateFactory + * needs to be size limited. It will throw IllegalArgumentException when + * the downloaded resource via the underlying stream exceeds the maximum + * limit. + */ + private static class SizeLimitedInputStream extends FilterInputStream { + + private final long maxBytes; + private long bytesRead = 0; + + private SizeLimitedInputStream(InputStream in, long maxBytes) { + super(in); + this.maxBytes = maxBytes; + } + + @Override + public int read() throws IOException { + if (bytesRead >= maxBytes) { + // We will use IAE here to differentiate this special case + // from other IOEs that the underlying input stream might + // legitimately throw. + throw new IllegalArgumentException("InputStream exceeded max " + + "size of " + maxBytes); + } + + int b = super.read(); + if (b != -1) { + bytesRead++; + } + return b; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + + if (bytesRead >= maxBytes) { + // We will use IAE here to differentiate this special case + // from other IOEs that the underlying input stream might + // legitimately throw. + throw new IllegalArgumentException("InputStream exceeded max " + + "size of " + maxBytes); + } + + long remaining = maxBytes - bytesRead; + int toRead = (int) Math.min(len, remaining); + + int n = super.read(b, off, toRead); + if (n != -1) { + bytesRead += n; + } + return n; + } + } } diff --git a/src/java.base/share/classes/sun/security/ssl/Alert.java b/src/java.base/share/classes/sun/security/ssl/Alert.java index e9588a09b3d8..c081ec5f747e 100644 --- a/src/java.base/share/classes/sun/security/ssl/Alert.java +++ b/src/java.base/share/classes/sun/security/ssl/Alert.java @@ -181,11 +181,13 @@ private static final class AlertMessage { AlertMessage(TransportContext context, ByteBuffer m) throws IOException { - // From RFC 8446 "Implementations - // MUST NOT send Handshake and Alert records that have a zero-length - // TLSInnerPlaintext.content; if such a message is received, the - // receiving implementation MUST terminate the connection with an - // "unexpected_message" alert." + + // From RFC 8446: TLSv1.3 + // + // Implementations MUST NOT send Handshake and Alert records that + // have a zero-length TLSInnerPlaintext.content; if such a message + // is received, the receiving implementation MUST terminate the + // connection with an "unexpected_message" alert. if (m.remaining() == 0) { throw context.fatal(Alert.UNEXPECTED_MESSAGE, "Alert fragments must not be zero length."); @@ -264,27 +266,39 @@ public void consume(ConnectionContext context, } else if ((level == Level.WARNING) && (alert != null)) { // Terminate the connection if an alert with a level of warning // is received during handshaking, except the no_certificate - // warning. - if (alert.handshakeOnly && (tc.handshakeContext != null)) { - // It's OK to get a no_certificate alert from a client of - // which we requested client authentication. However, - // if we required it, then this is not acceptable. - if (tc.sslConfig.isClientMode || - alert != Alert.NO_CERTIFICATE || - (tc.sslConfig.clientAuthType != + // warning for SSLv3. + HandshakeContext hc = tc.handshakeContext; + if (alert.handshakeOnly && (hc != null)) { + // In SSLv3, it's OK to get a no_certificate alert from a + // client where we requested (want) client authentication. + // If we required it (need), this is not acceptable + // and must fail. + // + // no_certificate alerts are not acceptable in TLSv1.*. + // + if (!tc.sslConfig.isClientMode && + (hc.negotiatedProtocol == ProtocolVersion.SSL30) && + (alert == Alert.NO_CERTIFICATE) && + (tc.sslConfig.clientAuthType == ClientAuthType.CLIENT_AUTH_REQUESTED)) { - throw tc.fatal(Alert.HANDSHAKE_FAILURE, - "received handshake warning: " + alert.description); - } else { - // Otherwise, ignore the warning but remove the - // Certificate and CertificateVerify handshake - // consumer so the state machine doesn't expect it. - tc.handshakeContext.handshakeConsumers.remove( - SSLHandshake.CERTIFICATE.id); - tc.handshakeContext.handshakeConsumers.remove( + + // We'll ignore the warning and remove the Certificate, + // CompressedCertificate and CertificateVerify handshake + // consumers so the state machine isn't expecting them. + if (hc.handshakeConsumers.remove( + SSLHandshake.CERTIFICATE.id) != null) { + hc.handshakeConsumers.remove( SSLHandshake.COMPRESSED_CERTIFICATE.id); - tc.handshakeContext.handshakeConsumers.remove( + hc.handshakeConsumers.remove( SSLHandshake.CERTIFICATE_VERIFY.id); + } else { + throw tc.fatal(Alert.HANDSHAKE_FAILURE, + "NO_CERTIFICATE alert received when certs" + + " were not expected or already received"); + } + } else { + throw tc.fatal(Alert.HANDSHAKE_FAILURE, + "Received handshake warning: " + alert.description); } } // Otherwise, ignore the warning } else { // fatal or unknown diff --git a/src/java.base/share/classes/sun/security/ssl/HelloCookieManager.java b/src/java.base/share/classes/sun/security/ssl/HelloCookieManager.java index b3155f5170a8..4268b9779bd8 100644 --- a/src/java.base/share/classes/sun/security/ssl/HelloCookieManager.java +++ b/src/java.base/share/classes/sun/security/ssl/HelloCookieManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,6 +26,7 @@ package sun.security.ssl; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; @@ -121,6 +122,7 @@ abstract boolean isCookieValid(ServerHandshakeContext context, private static final class D10HelloCookieManager extends HelloCookieManager { + private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; final SecureRandom secureRandom; private int cookieVersion; // allow to wrap, version + sequence private final byte[] cookieSecret; @@ -170,6 +172,7 @@ byte[] createCookie(ServerHandshakeContext context, } byte[] helloBytes = clientHello.getHelloCookieBytes(); md.update(helloBytes); + md.update(getHostPortBytes(context)); byte[] cookie = md.digest(secret); // 32 bytes cookie[0] = (byte)((version >> 24) & 0xFF); @@ -205,11 +208,30 @@ boolean isCookieValid(ServerHandshakeContext context, } byte[] helloBytes = clientHello.getHelloCookieBytes(); md.update(helloBytes); + md.update(getHostPortBytes(context)); byte[] target = md.digest(secret); // 32 bytes target[0] = cookie[0]; return MessageDigest.isEqual(target, cookie); } + + /** + * Returns host and port bytes if those are set. + * Using ASCII unit separator character to separate host and port so we + * can differentiate between otherwise identical host and port string + * concatenations, for example host 172.0.0.1 with port 25 and host + * 172.0.0.12 with port 5. + */ + private static byte[] getHostPortBytes(ServerHandshakeContext context) { + final String host = context.conContext.transport.getPeerHost(); + final int port = context.conContext.transport.getPeerPort(); + final String hostStr = host != null ? host : ""; + final String portStr = port > -1 ? Integer.toString(port) : ""; + return hostStr.isEmpty() && portStr.isEmpty() ? + EMPTY_BYTE_ARRAY : + (hostStr + '\u001F' + portStr).getBytes( + StandardCharsets.UTF_8); + } } private static final diff --git a/src/java.base/share/classes/sun/security/timestamp/TSResponse.java b/src/java.base/share/classes/sun/security/timestamp/TSResponse.java index 16ba761bff04..82287540b631 100644 --- a/src/java.base/share/classes/sun/security/timestamp/TSResponse.java +++ b/src/java.base/share/classes/sun/security/timestamp/TSResponse.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,6 +26,8 @@ package sun.security.timestamp; import java.io.IOException; + +import sun.security.pkcs.ContentInfo; import sun.security.pkcs.PKCS7; import sun.security.util.Debug; import sun.security.util.DerValue; @@ -357,6 +359,11 @@ private void parse(byte[] tsReply) throws IOException { DerValue timestampToken = derValue.data.getDerValue(); encodedTsToken = timestampToken.toByteArray(); tsToken = new PKCS7(encodedTsToken); + // RFC 3161 Section 2.4.2. id-ct-TSTInfo. + if (!tsToken.getContentInfo().getContentType() + .equals(ContentInfo.TIMESTAMP_TOKEN_INFO_OID)) { + throw new TimestampException("Not using id-ct-TSTInfo"); + } tstInfo = new TimestampToken(tsToken.getContentInfo().getData()); } diff --git a/src/java.base/share/classes/sun/security/util/DerValue.java b/src/java.base/share/classes/sun/security/util/DerValue.java index ec8b482b07dc..8d86c8dd1439 100644 --- a/src/java.base/share/classes/sun/security/util/DerValue.java +++ b/src/java.base/share/classes/sun/security/util/DerValue.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -157,6 +157,9 @@ public class DerValue { */ public static final byte tag_SetOf = 0x31; + // Max nested depth for constructed data + private static final int MAX_CONSTRUCTED_NEST = 30; + // This class is mostly immutable except that: // // 1. resetTag() modifies the tag @@ -564,6 +567,14 @@ public ObjectIdentifier getOID() throws IOException { * @return the octet string held in this DER value */ public byte[] getOctetString() throws IOException { + return getOctetString(0); + } + + private byte[] getOctetString(int limit) throws IOException { + if (++limit > MAX_CONSTRUCTED_NEST) { + throw new IOException("Nested OctetString limit reached (" + + MAX_CONSTRUCTED_NEST + ")."); + } if (tag != tag_OctetString && !isConstructed(tag_OctetString)) { throw new IOException( @@ -582,7 +593,7 @@ public byte[] getOctetString() throws IOException { ByteArrayOutputStream bout = new ByteArrayOutputStream(); DerInputStream dis = data(); while (dis.available() > 0) { - bout.write(dis.getDerValue().getOctetString()); + bout.write(dis.getDerValue().getOctetString(limit)); } return bout.toByteArray(); } diff --git a/src/java.base/share/classes/sun/security/util/HostnameChecker.java b/src/java.base/share/classes/sun/security/util/HostnameChecker.java index 65115c9aeaf9..b5a6e48e5707 100644 --- a/src/java.base/share/classes/sun/security/util/HostnameChecker.java +++ b/src/java.base/share/classes/sun/security/util/HostnameChecker.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -263,7 +263,7 @@ public static X500Name getSubjectX500Name(X509Certificate cert) * The name parameter should represent a DNS name. The * template parameter may contain the wildcard character '*'. */ - private boolean isMatched(String name, String template, + public boolean isMatched(String name, String template, boolean chainsToPublicCA) { // Normalize to Unicode, because PSL is in Unicode. diff --git a/src/java.base/share/classes/sun/security/util/LocalizedMessage.java b/src/java.base/share/classes/sun/security/util/LocalizedMessage.java index 61062bf6e1a2..8d79e85c8a50 100644 --- a/src/java.base/share/classes/sun/security/util/LocalizedMessage.java +++ b/src/java.base/share/classes/sun/security/util/LocalizedMessage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -106,32 +106,33 @@ public static String getNonlocalized(String key, // Classes like StringTokenizer may not be loaded, so parsing // is performed with String methods StringBuilder sb = new StringBuilder(); - int nextBraceIndex; - while ((nextBraceIndex = value.indexOf('{')) >= 0) { + int pos = 0; + int leftBraceIndex; + while ((leftBraceIndex = value.indexOf('{', pos)) >= 0) { - String firstPart = value.substring(0, nextBraceIndex); - sb.append(firstPart); - value = value.substring(nextBraceIndex + 1); + sb.append(value, pos, leftBraceIndex); // look for closing brace and argument index - nextBraceIndex = value.indexOf('}'); - if (nextBraceIndex < 0) { + int rightBraceIndex = value.indexOf('}', leftBraceIndex + 1); + if (rightBraceIndex < 0) { // no closing brace // MessageFormat would throw IllegalArgumentException, but // that exception class may not be loaded yet throw new RuntimeException("Unmatched braces"); } - String indexStr = value.substring(0, nextBraceIndex); try { - int index = Integer.parseInt(indexStr); + int index = Integer.parseInt(value, leftBraceIndex + 1, + rightBraceIndex, 10); sb.append(arguments[index]); } catch (NumberFormatException e) { // argument index is not an integer - throw new RuntimeException("not an integer: " + indexStr); + throw new RuntimeException("not an integer: " + + value.substring(leftBraceIndex + 1, rightBraceIndex)); } - value = value.substring(nextBraceIndex + 1); + + pos = rightBraceIndex + 1; } - sb.append(value); + sb.append(value, pos, value.length()); return sb.toString(); } diff --git a/src/java.base/share/classes/sun/security/util/SecurityProperties.java b/src/java.base/share/classes/sun/security/util/SecurityProperties.java index 98bc71d829be..da69ecbf5d66 100644 --- a/src/java.base/share/classes/sun/security/util/SecurityProperties.java +++ b/src/java.base/share/classes/sun/security/util/SecurityProperties.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2018 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -139,6 +139,36 @@ public static int getTimeoutSystemProp(String prop, int def, Debug dbg) { } } + /** + * A convenience routine for fetching a numeric value from a Security + * or System property and returning it as a long. The value from the + * property is obtained according to the logic in + * {@link SecurityProperties#getOverridableProperty(String)} + * + * @param prop the property to query + * @param defaultValue the default value + * @param dbg a Debug object, if null no debug messages will be sent + * @return the value of the property as a {@code long}. If a non-numeric + * value is supplied, the default value will be returned. + */ + public static long getOverridableLongProp(String prop, long defaultValue, + Debug dbg) { + long longVal = defaultValue; + try { + String propVal = SecurityProperties.getOverridableProperty(prop); + if (propVal != null) { + longVal = Long.parseLong(propVal); + } + } catch (NumberFormatException nfe) { + // We will use the default, but add a warning debug message + if (dbg != null) { + dbg.println("Warning: Non-numeric value found in property " + + prop + ", using default value of " + defaultValue); + } + } + return longVal; + } + /** * Convenience method for fetching System property values that are booleans. * diff --git a/src/java.base/share/classes/sun/security/util/SignatureFileVerifier.java b/src/java.base/share/classes/sun/security/util/SignatureFileVerifier.java index d7e65b6aef0f..0b21ccbd294c 100644 --- a/src/java.base/share/classes/sun/security/util/SignatureFileVerifier.java +++ b/src/java.base/share/classes/sun/security/util/SignatureFileVerifier.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -46,7 +46,12 @@ public class SignatureFileVerifier { /* Are we debugging ? */ private static final Debug debug = Debug.getInstance("jar"); - private final ArrayList signerCache; + private final List signerCache; + + // The maximum size of the signerCache. This is for debug only + // and not intended to be adjusted by users. + private static int SIGNER_CACHE_SIZE + = Integer.getInteger("sun.security.util.jar.signer.cache.size", 5); private static final String ATTR_DIGEST = "-DIGEST-" + ManifestDigester.MF_MAIN_ATTRS.toUpperCase(Locale.ENGLISH); @@ -97,7 +102,7 @@ public class SignatureFileVerifier { * * @param rawBytes the raw bytes of the signature block file */ - public SignatureFileVerifier(ArrayList signerCache, + public SignatureFileVerifier(List signerCache, ManifestDigester md, String name, byte[] rawBytes) @@ -282,7 +287,6 @@ public void process(Hashtable signers, } finally { Providers.stopJarVerification(obj); } - } private void processImpl(Hashtable signers, @@ -850,6 +854,9 @@ void updateSigners(CodeSigner[] newSigners, newSigners.length); } signerCache.add(cachedSigners); + if (signerCache.size() > SIGNER_CACHE_SIZE) { + signerCache.remove(0); + } signers.put(name, cachedSigners); } diff --git a/src/java.base/share/classes/sun/security/x509/DNSName.java b/src/java.base/share/classes/sun/security/x509/DNSName.java index ce903a3d16cf..02948c0c04fe 100644 --- a/src/java.base/share/classes/sun/security/x509/DNSName.java +++ b/src/java.base/share/classes/sun/security/x509/DNSName.java @@ -52,6 +52,8 @@ public class DNSName implements GeneralNameInterface { private final String name; + private static final HostnameChecker HOSTNAME_CHECKER = + HostnameChecker.getInstance(HostnameChecker.TYPE_TLS); private static final String DNS_ALLOWED = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-"; @@ -218,17 +220,22 @@ public int hashCode() { * For example, www.host.example.com would satisfy the constraint but * host1.example.com would not. *

+ * RFC6125: Match wildcard pattern in the input name being constrained, + * any wildcard in this name will be matched as a literal character. + *

* RFC1034: By convention, domain names can be stored with arbitrary case, but * domain name comparisons for all present domain functions are done in a * case-insensitive manner, assuming an ASCII character set, and a high * order zero bit. * * @param inputName to be checked for being constrained + * @param matchWildcard whether to match a wildcard in inputName * @return constraint type above * @throws UnsupportedOperationException if name is not exact match, but narrowing and widening are * not supported for this name type. */ - public int constrains(GeneralNameInterface inputName) throws UnsupportedOperationException { + public int constrains(GeneralNameInterface inputName, boolean matchWildcard) + throws UnsupportedOperationException { int constraintType; if (inputName == null) constraintType = NAME_DIFF_TYPE; @@ -238,7 +245,10 @@ else if (inputName.getType() != NAME_DNS) String inName = (((DNSName)inputName).getName()).toLowerCase(Locale.ENGLISH); String thisName = name.toLowerCase(Locale.ENGLISH); - if (inName.equals(thisName)) + + if (inName.equals(thisName) || (matchWildcard + && inName.contains("*") + && HOSTNAME_CHECKER.isMatched(thisName, inName, false))) constraintType = NAME_MATCH; else if (thisName.endsWith(inName)) { int inNdx = thisName.lastIndexOf(inName); @@ -259,6 +269,10 @@ else if (thisName.endsWith(inName)) { return constraintType; } + public int constrains(GeneralNameInterface inputName) { + return constrains(inputName, false); + } + /** * Return subtree depth of this name for purposes of determining * NameConstraints minimum and maximum bounds and for calculating diff --git a/src/java.base/share/classes/sun/security/x509/NameConstraintsExtension.java b/src/java.base/share/classes/sun/security/x509/NameConstraintsExtension.java index ce6d4721ad72..5b34a7267967 100644 --- a/src/java.base/share/classes/sun/security/x509/NameConstraintsExtension.java +++ b/src/java.base/share/classes/sun/security/x509/NameConstraintsExtension.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -506,9 +506,15 @@ public boolean verify(GeneralNameInterface name) throws IOException { if (exName == null) continue; + // Match a wildcard in DNSName only against the excluded subtree + int matchResult = + exName.getType() == GeneralNameInterface.NAME_DNS + ? ((DNSName) exName).constrains(name, true) + : exName.constrains(name); + // if name matches or narrows any excluded subtree, // return false - switch (exName.constrains(name)) { + switch (matchResult) { case GeneralNameInterface.NAME_DIFF_TYPE: case GeneralNameInterface.NAME_WIDENS: // name widens excluded case GeneralNameInterface.NAME_SAME_TYPE: 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 31078720ddca..295952e7896f 100644 --- a/src/java.base/share/classes/sun/util/locale/BaseLocale.java +++ b/src/java.base/share/classes/sun/util/locale/BaseLocale.java @@ -275,4 +275,10 @@ public int hashCode() { } return h; } + + // This is called from C code, at the very end of Java code execution + // during the AOT cache assembly phase. + private static void assemblySetup() { + CACHE.get().prepareForAOTCache(); + } } diff --git a/src/java.base/share/classes/sun/util/locale/LocaleMatcher.java b/src/java.base/share/classes/sun/util/locale/LocaleMatcher.java index bc5115e1ff17..5385a5598b68 100644 --- a/src/java.base/share/classes/sun/util/locale/LocaleMatcher.java +++ b/src/java.base/share/classes/sun/util/locale/LocaleMatcher.java @@ -467,17 +467,18 @@ public static List parse(String ranges) { try { w = Double.parseDouble(range.substring(index)); } - catch (Exception e) { - throw new IllegalArgumentException("weight=\"" + catch (NumberFormatException _) { + throw new IllegalArgumentException("The weight \"" + range.substring(index) - + "\" for language range \"" + r + "\""); + + "\" for language range \"" + r + "\"" + + " must be between " + MIN_WEIGHT + + " and " + MAX_WEIGHT + ", inclusive."); } - if (w < MIN_WEIGHT || w > MAX_WEIGHT) { - throw new IllegalArgumentException("weight=" + w - + " for language range \"" + r - + "\". It must be between " + MIN_WEIGHT - + " and " + MAX_WEIGHT + "."); + throw new IllegalArgumentException("The weight \"" + w + + "\" for language range \"" + r + "\"" + + " must be between " + MIN_WEIGHT + + " and " + MAX_WEIGHT + ", inclusive."); } } diff --git a/src/java.base/share/conf/security/java.security b/src/java.base/share/conf/security/java.security index 26842d0c8452..74797ed663b0 100644 --- a/src/java.base/share/conf/security/java.security +++ b/src/java.base/share/conf/security/java.security @@ -741,6 +741,22 @@ http.auth.digest.disabledAlgorithms = MD5, SHA-1 # suites that start with "TLS_RSA_". Only cipher suites starting with # "TLS_" are allowed to have wildcard characters. # +# - TLS cipher suites can also be disabled using components of the cipher +# suite, which include the key exchange algorithm, the bulk encryption +# algorithm, the MAC algorithm, and the PRF. For example, the key exchange +# algorithm "ECDH_ECDSA" will disable all cipher suites that contain +# ECDH_ECDSA as the key exchange algorithm. Similarly, the bulk encryption +# algorithm "AES_128_CBC" will disable all cipher suites that contain +# AES_128_CBC as the bulk encryption algorithm. The components also abide +# by the sub-element matching rules specified in the +# jdk.certpath.disabledAlgorithms property. For example, "ECDH" will +# disable all cipher suites with key exchange algorithms that contain ECDH +# in their name, but not ECDHE since that is a different algorithm. To +# disable cipher suites with a specific MAC or PRF algorithm, you can use +# the standard Hmac algorithm name (ex: "HmacSHA256" and not "SHA256") to +# avoid disabling other algorithms using the hash algorithm. To disable +# cipher suites ending with "_SHA" use "SHA-1", "SHA1", or "HmacSHA1". +# # - TLS protocol specific usage constraints are supported by this property: # # UsageConstraint: @@ -1714,6 +1730,22 @@ jdk.epkcs8.defaultAlgorithm=PBEWithHmacSHA256AndAES_128 # ldap://ldap.company.com/dc=company,dc=com?caCertificate;binary com.sun.security.allowedAIALocations= +# +# Certificate Revocation List (CRL) Download Size Limitation +# +# This property sets a size limit for CRLs downloaded via URIs provided +# in the CRL Distribution Points certificate extension. This property +# must be a numeric value that is the size in bytes of the DER-encoded CRL. +# For protocols that can return multi-value responses, such as LDAP, the +# size threshold is the sum of all CRLs downloaded from a single search +# query. CRLs that exceed this length will not be processed during certificate +# path validation. This size limit does not apply to CRLs that are imported +# through non-network-based means. A negative value will disable this size +# limitation. A non-numeric value will be ignored and the default size will +# be used instead. The default size limit is 20MiB. +# This property may be overridden by a System property of the same name. +com.sun.security.crl.maxSize = 20971520 + # # PKCS #8 encoding format for newly created ML-KEM and ML-DSA private keys # diff --git a/src/java.base/share/man/java.md b/src/java.base/share/man/java.md index 89166ae39e1b..a9b219362172 100644 --- a/src/java.base/share/man/java.md +++ b/src/java.base/share/man/java.md @@ -90,30 +90,39 @@ To launch a source-file program: ## Description -The `java` command starts a Java application. It does this by starting the Java -Virtual Machine (JVM), loading the specified class, and calling that -class's `main()` method. The method must be declared `public` and `static`, it -must not return any value, and it must accept a `String` array as a parameter. -The method declaration has the following form: - -> `public static void main(String[] args)` - -In source-file mode, the `java` command can launch a class declared in a source -file. See [Using Source-File Mode to Launch Source-Code Programs] -for a description of using the source-file mode. - -> **Note:** You can use the `JDK_JAVA_OPTIONS` launcher environment variable to prepend its -content to the actual command line of the `java` launcher. See [Using the -JDK\_JAVA\_OPTIONS Launcher Environment Variable]. +The `java` command launches a Java application. It does this by starting the Java +Virtual Machine (JVM), loading the main class of the application, and calling +that class's `main()` method. + +By default, the first argument that isn't an option of the `java` command indicates +the fully qualified name of the main class. If `-jar` is specified, then its +argument is the name of the JAR file containing class and resource files for the +application, and the main class is indicated by the `Main-Class` attribute in +the manifest of the JAR file. + +The `main()` method may be a static method or an instance method. It may +declare a `String` array parameter for arguments passed to the `java` +command after the main class name or the JAR file name; alternatively, +it may declare no parameters. It must not return any value, and must +have `public`, `protected`, or package access. The method declaration +typically has one of the following forms: + +> `public static void main(String[] args)` +> +> `public static void main()` +> +> `void main(String[] args)` +> +> `void main()` -By default, the first argument that isn't an option of the `java` command is -the fully qualified name of the class to be called. If `-jar` is specified, -then its argument is the name of the JAR file containing class and resource -files for the application. The startup class must be indicated by the -`Main-Class` manifest header in its manifest file. +In source-file mode, the `java` command can launch an application whose main class +is provided as source code instead of a class file. +See [Using Source-File Mode to Launch Source-Code Programs] for a description of +using the source-file mode. -Arguments after the class file name or the JAR file name are passed to the -`main()` method. +> **Note:** You can use the `JDK_JAVA_OPTIONS` launcher environment variable to +prepend its content to the actual command line of the java launcher. +See [Using the JDK\_JAVA\_OPTIONS Launcher Environment Variable]. ### `javaw` @@ -911,10 +920,12 @@ the Java HotSpot Virtual Machine. : Do not attempt to use shared class data. [`-XshowSettings`]{#-XshowSettings} -: Shows all settings and then continues. +: Shows all settings and continues. It exits normally if there is no Java + application to launch. [`-XshowSettings:`]{#-XshowSettings_}*category* -: Shows settings and continues. Possible *category* arguments for this option +: Shows settings and continues. It exits normally if there is no Java + application to launch. Possible *category* arguments for this option include the following: `all` @@ -1215,9 +1226,11 @@ These `java` options control the runtime behavior of the Java HotSpot VM. be replaced with `[REDACTED]`. The option `redact-argument` is best-effort and applies only to command-line arguments in the `jdk.JVMInformation` event and to the `java.command` system property in the - `jdk.InitialSystemProperty` event. Other events, such as `jdk.ProcessStart` - (child processes), are not redacted. Use `-XX:FlightRecorderOptions:help` - to see the default filters used by the `redact-argument` option. + `jdk.InitialSystemProperty` event, and to matching command-line argument + text in the values of `jdk.InitialEnvironmentVariable` events. Other + events, such as `jdk.ProcessStart` (child processes), are not redacted. + Use `-XX:FlightRecorderOptions:help` to see the default filters used by + the `redact-argument` option. `redact-key=`key-filter : Replace the value of environment variables and system properties @@ -2268,6 +2281,23 @@ performed by the Java HotSpot VM. These `java` options provide the ability to gather system information and perform extensive debugging. +[`-XX:AltTempDir=`]{#-XX_AltTempDir}*/path* +: **Linux-only:** On Linux, the usual directory to use for temporary files is `/tmp`. In some secure container + environments however, `/tmp` is made read-only and so is unusable by the VM for its temporary files. To accommodate + this uncommon circumstance the `-XX:AltTempDir` flag can be used to tell the VM to use a different temporary directory. + + It is important to note that this setting controls not only where the VM places its own temporary files, but also the location + it will look for the special files used by other VMs as part of the attach protocol for tools like `jcmd` and `jstack`. That + means that both VMs must use the same setting of this flag. For example, if you start a target VM with + `java -XX:AltTempDir=/scratch/vmTmp` then you must run e.g. `jcmd -J-XX:AltTempDir=/scratch/vmTmp` to interact with that target VM. + + The directory path must of course be writable and accessible to both the target and tool VM, so the simplest arrangement + is to always run both in the same container. + + The value for `AltTempDir` must be an absolute directory path starting with `/`. The length of the `AltTempDir` path should be + fairly small (less than approximately 80 characters) if it is to be used with the attach protocol due to path length limits + for socket files. + [`-XX:+DisableAttachMechanism`]{#-XX__DisableAttachMechanism} : Disables the mechanism that lets tools attach to the JVM. By default, this option is disabled, meaning that the attach mechanism is enabled and you diff --git a/src/java.base/share/man/keytool.md b/src/java.base/share/man/keytool.md index 1d70bd2f5f8e..faa2ff563a17 100644 --- a/src/java.base/share/man/keytool.md +++ b/src/java.base/share/man/keytool.md @@ -1191,14 +1191,14 @@ These options can appear for all commands operating on a keystore: [`-keystore`]{#option-keystore} *keystore* : The keystore location. - If the JKS `storetype` is used and a keystore file doesn't yet exist, then - certain `keytool` commands can result in a new keystore file being created. - For example, if `keytool -genkeypair` is called and the `-keystore` option - isn't specified, the default keystore file named `.keystore` is created in - the user's home directory if it doesn't already exist. Similarly, if the - `-keystore ks_file` option is specified but `ks_file` doesn't exist, then - it is created. For more information on the JKS `storetype`, see the - **KeyStore Implementation** section in **KeyStore aliases**. + If a keystore file doesn't yet exist, then certain `keytool` commands can + result in a new keystore file being created. For example, if + `keytool -genkeypair` is called and the `-keystore` option isn't specified, + the default keystore file named `.keystore` is created in the user's home + directory if it doesn't already exist. Similarly, if the `-keystore ks_file` + option is specified but `ks_file` doesn't exist, then it is created. For + more information on keystore types and implementations, see the + **KeyStore implementation** section in [Terms]. Note that the input stream from the `-keystore` option is passed to the `KeyStore.load` method. If `NONE` is specified as the URL, then a null @@ -1766,11 +1766,11 @@ keystore, then it prompts you for a password. If it detects alias duplication, then it asks you for a new alias, and you can specify a new alias or simply allow the `keytool` command to overwrite the existing one. -For example, import entries from a typical JKS type keystore `key.jks` into a -PKCS \#11 type hardware-based keystore, by entering the following command: +For example, import entries from a typical PKCS12 type keystore `key.p12` into +a PKCS \#11 type hardware-based keystore, by entering the following command: -> `keytool -importkeystore -srckeystore key.jks -destkeystore NONE - -srcstoretype JKS -deststoretype PKCS11 -srcstorepass` *password* +> `keytool -importkeystore -srckeystore key.p12 -destkeystore NONE + -srcstoretype PKCS12 -deststoretype PKCS11 -srcstorepass` *password* `-deststorepass` *password* The `importkeystore` command can also be used to import a single entry from a @@ -1780,8 +1780,8 @@ import. With the `-srcalias` option specified, you can also specify the destination alias name, protection password for a secret or private key, and the destination protection password you want as follows: -> `keytool -importkeystore -srckeystore key.jks -destkeystore NONE - -srcstoretype JKS -deststoretype PKCS11 -srcstorepass` *password* +> `keytool -importkeystore -srckeystore key.p12 -destkeystore NONE + -srcstoretype PKCS12 -deststoretype PKCS11 -srcstorepass` *password* `-deststorepass` *password* `-srcalias myprivatekey -destalias myoldprivatekey -srckeypass` *password* `-destkeypass` *password* `-noprompt` @@ -1800,22 +1800,22 @@ certificates for three entities: Ensure that you store all the certificates in the same keystore. ``` -keytool -genkeypair -keystore root.jks -alias root -ext bc:c -keyalg rsa -keytool -genkeypair -keystore ca.jks -alias ca -ext bc:c -keyalg rsa -keytool -genkeypair -keystore server.jks -alias server -keyalg rsa +keytool -genkeypair -keystore root.p12 -alias root -ext bc:c -keyalg rsa +keytool -genkeypair -keystore ca.p12 -alias ca -ext bc:c -keyalg rsa +keytool -genkeypair -keystore server.p12 -alias server -keyalg rsa -keytool -keystore root.jks -alias root -exportcert -rfc > root.pem +keytool -keystore root.p12 -alias root -exportcert -rfc > root.pem -keytool -storepass password -keystore ca.jks -certreq -alias ca | - keytool -storepass password -keystore root.jks +keytool -storepass password -keystore ca.p12 -certreq -alias ca | + keytool -storepass password -keystore root.p12 -gencert -alias root -ext BC=0 -rfc > ca.pem -keytool -keystore ca.jks -importcert -alias ca -file ca.pem +keytool -keystore ca.p12 -importcert -alias ca -file ca.pem -keytool -storepass password -keystore server.jks -certreq -alias server | - keytool -storepass password -keystore ca.jks -gencert -alias ca +keytool -storepass password -keystore server.p12 -certreq -alias server | + keytool -storepass password -keystore ca.p12 -gencert -alias ca -ext ku:c=dig,kE -rfc > server.pem cat root.pem ca.pem server.pem | - keytool -keystore server.jks -importcert -alias server + keytool -keystore server.p12 -importcert -alias server ``` @@ -1886,11 +1886,7 @@ Keystore implementation is a cross platform keystore based on the RSA PKCS12 Personal Information Exchange Syntax Standard. This standard is primarily meant for storing or transporting a user's private keys, certificates, and miscellaneous - secrets. There is another built-in implementation, provided by Oracle. It - implements the keystore as a file with a proprietary keystore type (format) - named `JKS`. It protects each private key with its individual password, and - also protects the integrity of the entire keystore with a (possibly - different) password. + secrets. Keystore implementations are provider-based. More specifically, the application interfaces supplied by `KeyStore` are implemented in terms of a @@ -1946,16 +1942,12 @@ Keystore implementation > `keystore.type=pkcs12` To have the tools utilize a keystore implementation other than the default, - you can change that line to specify a different keystore type. For example, - if you want to use the Oracle's `jks` keystore implementation, then change - the line to the following: - - > `keystore.type=jks` + you can change that line to specify a different keystore type. **Note:** - Case doesn't matter in keystore type designations. For example, `JKS` would - be considered the same as `jks`. + Case doesn't matter in keystore type designations. For example, `PKCS12` + would be considered the same as `pkcs12`. Certificate : A certificate (or public-key certificate) is a digitally signed statement @@ -2157,9 +2149,9 @@ cacerts Certificates File The `cacerts` file represents a system-wide keystore with CA certificates. System administrators can configure and manage that file with the `keytool` - command by specifying `jks` as the keystore type. The `cacerts` keystore - file ships with a default set of root CA certificates. For Linux, macOS, and - Windows, you can list the default certificates with the following command: + command. The `cacerts` keystore file ships with a default set of root CA + certificates. For Linux, macOS, and Windows, you can list the default + certificates with the following command: > `keytool -list -cacerts` diff --git a/src/java.base/share/native/libjli/java.c b/src/java.base/share/native/libjli/java.c index 4c3b503b08a0..bf2d309e8e8f 100644 --- a/src/java.base/share/native/libjli/java.c +++ b/src/java.base/share/native/libjli/java.c @@ -549,6 +549,11 @@ JavaMain(void* _args) LEAVE(); } + /* Exit normally after showing the settings if no application was specified. */ + if (showSettings != NULL && what == NULL) { + LEAVE(); + } + FreeKnownVMs(); /* after last possible PrintUsage */ if (JLI_IsTraceLauncher()) { @@ -1351,7 +1356,11 @@ ParseArguments(int *pargc, char ***pargv, if (*pwhat == NULL) { /* LM_UNKNOWN okay for options that exit */ - if (!listModules && !describeModule && !validateModules && !dumpSharedSpaces) { + if (!listModules && + !describeModule && + !validateModules && + !dumpSharedSpaces && + !showSettings) { *pret = 1; printUsageKind = HELP_CONCISE; } diff --git a/src/java.base/unix/classes/sun/nio/fs/UnixFileSystem.java b/src/java.base/unix/classes/sun/nio/fs/UnixFileSystem.java index 8a2c2bb27e82..c6cdb274ccd1 100644 --- a/src/java.base/unix/classes/sun/nio/fs/UnixFileSystem.java +++ b/src/java.base/unix/classes/sun/nio/fs/UnixFileSystem.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -389,10 +389,8 @@ protected static class Flags { boolean copyPosixAttributes; boolean copyNonPosixAttributes; - // flags that indicate if we should fail if attributes cannot be copied + // flag that indicates if we should fail if basic attributes cannot be copied boolean failIfUnableToCopyBasic; - boolean failIfUnableToCopyPosix; - boolean failIfUnableToCopyNonPosix; static Flags fromCopyOptions(CopyOption... options) { Flags flags = new Flags(); @@ -483,10 +481,6 @@ private void copyDirectory(UnixPath source, dfd = open(target, O_RDONLY, 0); } catch (UnixException x) { // access to target directory required to copy named attributes - if (flags.copyNonPosixAttributes && flags.failIfUnableToCopyNonPosix) { - try { rmdir(target); } catch (UnixException ignore) { } - x.rethrowAsIOException(target); - } } boolean done = false; @@ -503,8 +497,6 @@ private void copyDirectory(UnixPath source, } } catch (UnixException x) { // unable to set owner/group - if (flags.failIfUnableToCopyPosix) - x.rethrowAsIOException(target); } } // copy other attributes @@ -513,8 +505,6 @@ private void copyDirectory(UnixPath source, try { sfd = open(source, O_RDONLY, 0); } catch (UnixException x) { - if (flags.failIfUnableToCopyNonPosix) - x.rethrowAsIOException(source); } if (sfd >= 0) { source.getFileSystem().copyNonPosixAttributes(sfd, dfd); @@ -663,8 +653,6 @@ void copyFile(UnixPath source, fchown(fo, attrs.uid(), attrs.gid()); fchmod(fo, attrs.mode()); } catch (UnixException x) { - if (flags.failIfUnableToCopyPosix) - x.rethrowAsIOException(target); } } // copy non POSIX attributes (depends on file system) @@ -749,8 +737,6 @@ private void copySpecial(UnixPath source, chown(target, attrs.uid(), attrs.gid()); chmod(target, attrs.mode()); } catch (UnixException x) { - if (flags.failIfUnableToCopyPosix) - x.rethrowAsIOException(target); } } if (flags.copyBasicAttributes) { diff --git a/src/java.desktop/macosx/classes/sun/font/CFont.java b/src/java.desktop/macosx/classes/sun/font/CFont.java index 76409fb0b6aa..8340c692ffa1 100644 --- a/src/java.desktop/macosx/classes/sun/font/CFont.java +++ b/src/java.desktop/macosx/classes/sun/font/CFont.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -306,8 +306,8 @@ public boolean equals(Object o) { if (!super.equals(o)) { return false; } - - return ((Font2D)o).getStyle() == this.getStyle(); + return o instanceof Font2D other && + other.getStyle() == this.getStyle(); } @Override diff --git a/src/java.desktop/macosx/classes/sun/font/CFontManager.java b/src/java.desktop/macosx/classes/sun/font/CFontManager.java index 6504b71a27f8..0ad0562a8a16 100644 --- a/src/java.desktop/macosx/classes/sun/font/CFontManager.java +++ b/src/java.desktop/macosx/classes/sun/font/CFontManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -288,8 +288,8 @@ protected boolean cloneStyledFont(FontFamily realFamily, String logicalFamilyNam public String getFontPath(boolean noType1Fonts) { // In the case of the Cocoa toolkit, since we go through NSFont, we don't need to register /Library/Fonts Toolkit tk = Toolkit.getDefaultToolkit(); - if (tk instanceof HeadlessToolkit) { - tk = ((HeadlessToolkit)tk).getUnderlyingToolkit(); + if (tk instanceof HeadlessToolkit htk) { + tk = htk.getUnderlyingToolkit(); } if (tk instanceof LWCToolkit) { return ""; diff --git a/src/java.desktop/macosx/classes/sun/font/CStrike.java b/src/java.desktop/macosx/classes/sun/font/CStrike.java index f7fa00070ffc..2003b9e3e684 100644 --- a/src/java.desktop/macosx/classes/sun/font/CStrike.java +++ b/src/java.desktop/macosx/classes/sun/font/CStrike.java @@ -194,8 +194,8 @@ Rectangle2D.Float getGlyphOutlineBounds(int glyphCode) { GeneralPath gp = getGlyphOutline(glyphCode, 0f, 0f); Rectangle2D r2d = gp.getBounds2D(); Rectangle2D.Float r2df; - if (r2d instanceof Rectangle2D.Float) { - r2df = (Rectangle2D.Float)r2d; + if (r2d instanceof Rectangle2D.Float rf) { + r2df = rf; } else { float x = (float)r2d.getX(); float y = (float)r2d.getY(); diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLRenderQueue.m b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLRenderQueue.m index d6edd56a5abc..6981a2345df7 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLRenderQueue.m +++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLRenderQueue.m @@ -174,35 +174,21 @@ void MTLRenderQueue_CheckPreviousOp(jint op) { case sun_java2d_pipe_BufferedOpCodes_DRAW_POLY: { CHECK_PREVIOUS_OP(MTL_OP_OTHER); - jint nPoints = NEXT_INT(b); - jboolean isClosed = NEXT_BOOLEAN(b); - jint transX = NEXT_INT(b); - jint transY = NEXT_INT(b); - jint *xPoints = (jint *)b; - jint *yPoints = ((jint *)b) + nPoints; if ([mtlc useXORComposite]) { commitEncodedCommands(); J2dTraceLn(J2D_TRACE_VERBOSE, "DRAW_POLY in XOR mode - Force commit earlier draw calls before DRAW_POLY."); - // draw separate (N-1) lines using N points - for(int point = 0; point < nPoints-1; point++) { - jint x1 = xPoints[point] + transX; - jint y1 = yPoints[point] + transY; - jint x2 = xPoints[point + 1] + transX; - jint y2 = yPoints[point + 1] + transY; - MTLRenderer_DrawLine(mtlc, dstOps, x1, y1, x2, y2); - } - - if (isClosed) { - MTLRenderer_DrawLine(mtlc, dstOps, xPoints[0] + transX, yPoints[0] + transY, - xPoints[nPoints-1] + transX, yPoints[nPoints-1] + transY); - } - } else { - MTLRenderer_DrawPoly(mtlc, dstOps, nPoints, isClosed, transX, transY, xPoints, yPoints); } + jint nPoints = NEXT_INT(b); + jboolean isClosed = NEXT_BOOLEAN(b); + jint transX = NEXT_INT(b); + jint transY = NEXT_INT(b); + jint *xPoints = (jint *)b; + jint *yPoints = ((jint *)b) + nPoints; + MTLRenderer_DrawPoly(mtlc, dstOps, nPoints, isClosed, transX, transY, xPoints, yPoints); SKIP_BYTES(b, nPoints * BYTES_PER_POLY_POINT); break; } diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/shaders.metal b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/shaders.metal index 722506ab2c33..8718e5cac2ff 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/shaders.metal +++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/shaders.metal @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -84,13 +84,11 @@ struct GradShaderInOut { struct ColShaderInOut_XOR { float4 position [[position]]; float ptSize [[point_size]]; - float2 orig_pos; half4 color; }; struct TxtShaderInOut_XOR { float4 position [[position]]; - float2 orig_pos; float2 texCoords; float2 tpCoords; }; @@ -677,7 +675,6 @@ vertex ColShaderInOut_XOR vert_col_xorMode(VertexInput in [[stage_in]], float4 pos4 = float4(in.position, 0.0, 1.0); out.position = transform.transformMatrix*pos4; out.ptSize = 1.0; - out.orig_pos = in.position; out.color = half4(uniforms.color.r, uniforms.color.g, uniforms.color.b, uniforms.color.a); return out; } @@ -685,7 +682,7 @@ vertex ColShaderInOut_XOR vert_col_xorMode(VertexInput in [[stage_in]], fragment half4 frag_col_xorMode(ColShaderInOut_XOR in [[stage_in]], texture2d renderTexture [[texture(0)]]) { - uint2 texCoord = {(unsigned int)(in.orig_pos.x), (unsigned int)(in.orig_pos.y)}; + uint2 texCoord = {(unsigned int)(in.position.x), (unsigned int)(in.position.y)}; float4 pixelColor = renderTexture.read(texCoord); half4 color = in.color; @@ -707,7 +704,6 @@ vertex TxtShaderInOut_XOR vert_txt_xorMode( TxtShaderInOut_XOR out; float4 pos4 = float4(in.position, 0.0, 1.0); out.position = transform.transformMatrix*pos4; - out.orig_pos = in.position; out.texCoords = in.texCoords; return out; } @@ -719,7 +715,7 @@ fragment half4 frag_txt_xorMode( constant TxtFrameUniforms& uniforms [[buffer(1)]], sampler textureSampler [[sampler(0)]]) { - uint2 texCoord = {(unsigned int)(vert.orig_pos.x), (unsigned int)(vert.orig_pos.y)}; + uint2 texCoord = {(unsigned int)(vert.position.x), (unsigned int)(vert.position.y)}; float4 bgColor = backgroundTexture.read(texCoord); float4 pixelColor = renderTexture.sample(textureSampler, vert.texCoords); diff --git a/src/java.desktop/share/classes/javax/swing/JEditorPane.java b/src/java.desktop/share/classes/javax/swing/JEditorPane.java index 34528f9426bb..0a7e7edb1d34 100644 --- a/src/java.desktop/share/classes/javax/swing/JEditorPane.java +++ b/src/java.desktop/share/classes/javax/swing/JEditorPane.java @@ -1092,6 +1092,8 @@ private void setCharsetFromContentTypeParameters(String paramlist) { = "the currently installed kit for handling content") public void setEditorKit(EditorKit kit) { EditorKit old = this.kit; + AccessibleContext oldAccessibleContext = accessibleContext; + isUserSetEditorKit = true; if (old != null) { old.deinstall(this); @@ -1102,6 +1104,14 @@ public void setEditorKit(EditorKit kit) { setDocument(this.kit.createDefaultDocument()); } firePropertyChange("editorKit", old, kit); + + if (oldAccessibleContext != null) { + AccessibleContext newAccessibleContext = getAccessibleContext(); + if (oldAccessibleContext != newAccessibleContext) { + getDocument().removeDocumentListener( + (AccessibleJTextComponent) oldAccessibleContext ); + } + } } /** diff --git a/src/java.desktop/share/classes/sun/awt/image/XbmImageDecoder.java b/src/java.desktop/share/classes/sun/awt/image/XbmImageDecoder.java index 1b7cd41daa93..4e79c6a63547 100644 --- a/src/java.desktop/share/classes/sun/awt/image/XbmImageDecoder.java +++ b/src/java.desktop/share/classes/sun/awt/image/XbmImageDecoder.java @@ -31,12 +31,8 @@ import java.awt.image.ImageConsumer; import java.awt.image.IndexColorModel; import java.io.BufferedInputStream; -import java.io.BufferedReader; -import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import java.io.IOException; import static java.lang.Math.multiplyExact; @@ -62,9 +58,8 @@ public class XbmImageDecoder extends ImageDecoder { ImageConsumer.SINGLEPASS | ImageConsumer.SINGLEFRAME); + private static final int MAX_CHAR_LIMIT = 128000; private static final int MAX_XBM_SIZE = 16384; - private static final int HEADER_SCAN_LIMIT = 100; - public XbmImageDecoder(InputStreamImageSource src, InputStream is) { super(src, is); if (!(input instanceof BufferedInputStream)) { @@ -86,150 +81,229 @@ private static void error(String s1) throws ImageFormatException { * produce an image from the stream. */ public void produceImage() throws IOException, ImageFormatException { + char[] nm = new char[80]; + int c; + int i = 0; + int state = 0; int H = 0; int W = 0; int x = 0; int y = 0; - int n = 0; - int state = 0; + boolean consumeWidthValue = false; + boolean consumeHeightValue = false; + boolean validWidthConsumed = false; + boolean validHeightConsumed = false; + // number of tokens seen as part of this define statement + int defineTokenCount = 0; byte[] raster = null; IndexColorModel model = null; + int charCount = 0; - String matchRegex = "\\s*(0[xX])?((?:(?!,|\\};).)+)(,|\\};)"; - String replaceRegex = "0[xX]|,|\\s+|\\};"; - - String line; - int lineNum = 0; - - try (BufferedReader br = new BufferedReader(new InputStreamReader(input))) { - // loop to process XBM header - width, height and create raster - while (!aborted && (line = br.readLine()) != null - && lineNum <= HEADER_SCAN_LIMIT) { - lineNum++; - // process #define stmts - if (line.trim().startsWith("#define")) { - String[] token = line.split("\\s+"); - if (token.length != 3) { - error("Error while parsing define statement"); + //read header info + while (!aborted && (c = input.read()) != -1) { + charCount++; + if (charCount > MAX_CHAR_LIMIT) { + error("Incomplete image after reading " + + "the maximum allowed number of characters: " + + MAX_CHAR_LIMIT); + } + if ('a' <= c && c <= 'z' || + 'A' <= c && c <= 'Z' || + '0' <= c && c <= '9' || c == '#' || c == '_') { + if (i < nm.length) { + nm[i++] = (char) c; + } else { + error("XBM header contains literal greater than size 80"); + } + } else if (i > 0) { + int nc = i; + i = 0; + if (defineTokenCount >= 1) { + // we are inside a #define line + defineTokenCount++; + } + if (defineTokenCount == 0) { + if (nc == 7 && + nm[0] == '#' && + nm[1] == 'd' && + nm[2] == 'e' && + nm[3] == 'f' && + nm[4] == 'i' && + nm[5] == 'n' && + nm[6] == 'e') + { + defineTokenCount++; + continue; + } + } else if (defineTokenCount == 2) { + // consume second token in #define line + if (state < 2) { + if (nm[nc - 1] == 'h' && + !validWidthConsumed) { + consumeWidthValue = true; + } else if ((nm[nc - 1] == 't' && nc > 1 && + nm[nc - 2] == 'h') && + !validHeightConsumed) { + consumeHeightValue = true; + } } - try { - if (state < 2) { - if (token[1].endsWith("h")) { - W = Integer.parseInt(token[2]); - } else if (token[1].endsWith("ht")) { - H = Integer.parseInt(token[2]); + } else if (defineTokenCount == 3) { + defineTokenCount = 0; + // consume third token in #define line + int n = 0; + for (int p = 0; p < nc; p++) { + if ('0' <= (c = nm[p]) && c <= '9') { + n = n * 10 + c - '0'; + if (n > MAX_XBM_SIZE) { + error("Width/Height cannot be more than: " + + MAX_XBM_SIZE); } - // After the 1st dimension is set, state becomes 1; - // after the 2nd dimension is set, state becomes 2 - ++state; + } else { + error("Invalid width/height value"); } - } catch (NumberFormatException nfe) { - // parseInt() can throw NFE - error("Error while parsing width or height."); } - } - if (state == 2) { - if (W <= 0 || H <= 0) { - error("Invalid values for width or height."); + if (n > 0 && (consumeWidthValue || consumeHeightValue)) { + if (consumeWidthValue) { + if (!validWidthConsumed) { + W = n; + validWidthConsumed = true; + state++; + } + consumeWidthValue = false; + } else if (consumeHeightValue) { + if (!validHeightConsumed) { + H = n; + validHeightConsumed = true; + state++; + } + consumeHeightValue = false; + } } - if (multiplyExact(W, H) > MAX_XBM_SIZE) { - error("Large XBM file size." + // verify the consumed width & height value and initialize + // required constructs + if (state == 2) { + if (multiplyExact(W, H) > MAX_XBM_SIZE) { + error("Large XBM file size." + " Maximum allowed size: " + MAX_XBM_SIZE); - } - model = new IndexColorModel(8, 2, XbmColormap, + } + model = new IndexColorModel(8, 2, XbmColormap, 0, false, 0); - setDimensions(W, H); - setColorModel(model); - setHints(XbmHints); - headerComplete(); - raster = new byte[W]; - state = 3; - break; + setDimensions(W, H); + setColorModel(model); + setHints(XbmHints); + headerComplete(); + raster = new byte[W]; + state = 3; + break; + } } } + } - if (state != 3) { - error("Width or Height of XBM file not defined"); - } - - boolean contFlag = false; - StringBuilder sb = new StringBuilder(); - - // loop to process image data - while (!aborted && (line = br.readLine()) != null) { - lineNum++; - - if (!contFlag) { - if (line.contains("[]")) { - contFlag = true; - } else { - continue; - } - } + if (state != 3) { + error("Width or Height of XBM file not defined"); + } - int end = line.indexOf(';'); - if (end >= 0) { - sb.append(line, 0, end + 1); - break; - } else { - sb.append(line).append(System.lineSeparator()); - } + // skip until we find '{' + boolean imageDataStarted = false; + while (!aborted && (c = input.read()) != -1) { + charCount++; + if (charCount > MAX_CHAR_LIMIT) { + error("Incomplete image after reading " + + "the maximum allowed number of characters: " + + MAX_CHAR_LIMIT); } + if (c == '{') { + imageDataStarted = true; + break; + } + } - String resultLine = sb.toString(); - int cutOffIndex = resultLine.indexOf('{'); - resultLine = resultLine.substring(cutOffIndex + 1); - - Matcher matcher = Pattern.compile(matchRegex).matcher(resultLine); - while (matcher.find()) { - if (y >= H) { - error("Scan size of XBM file exceeds" - + " the defined width x height"); - } + if (!imageDataStarted) { + error("Missing '{' at the start of image data"); + } - int startIndex = matcher.start(); - int endIndex = matcher.end(); - String hexByte = resultLine.substring(startIndex, endIndex); - hexByte = hexByte.replaceAll("^\\s+", ""); + // used to make sure that we have the final delimiter '};', + // while parsing the image data + int previousChar = '{'; + // parse image data + boolean imageDataTerminated = false; + while (!aborted && (c = input.read()) != -1) { + charCount++; + if (charCount > MAX_CHAR_LIMIT) { + error("Incomplete image after reading " + + "the maximum allowed number of characters: " + + MAX_CHAR_LIMIT); + } - if (!(hexByte.startsWith("0x") - || hexByte.startsWith("0X"))) { - error("Invalid hexadecimal number at Ln#:" + lineNum - + " Col#:" + (startIndex + 1)); - } - hexByte = hexByte.replaceAll(replaceRegex, ""); - if (hexByte.length() != 2) { - error("Invalid hexadecimal number at Ln#:" + lineNum - + " Col#:" + (startIndex + 1)); + if (c == ';') { + if (previousChar != '}') { + error("Abrupt end of image data without '};' delimiter"); } + imageDataTerminated = true; + break; + } + if (!Character.isWhitespace(c)) { + previousChar = c; + } - try { - n = Integer.parseInt(hexByte, 16); - } catch (NumberFormatException nfe) { - error("Error parsing hexadecimal at Ln#:" + lineNum - + " Col#:" + (startIndex + 1)); + if (',' != c && '}' != c && + !Character.isWhitespace(c)) { + nm[i++] = (char) c; + if (i > 4) { + error("Image hex data should be 3 or 4 characters long"); } - for (int mask = 1; mask <= 0x80; mask <<= 1) { - if (x < W) { - if ((n & mask) != 0) - raster[x] = 1; + } else if (i == 3 || i == 4) { + // consume valid hex image data + int n = 0; + int nc = i; + i = 0; + if (nm[0] == '0' && + (nm[1] == 'x' || nm[1] == 'X')) { + for (int p = 2; p < nc; p++) { + c = nm[p]; + if ('0' <= c && c <= '9') + c = c - '0'; + else if ('A' <= c && c <= 'F') + c = c - 'A' + 10; + else if ('a' <= c && c <= 'f') + c = c - 'a' + 10; else - raster[x] = 0; + error("Corrupt hex image data"); + n = n * 16 + c; } - x++; - } - - if (x >= W) { - int result = setPixels(0, y, W, 1, model, raster, 0, W); - if (result <= 0) { - error("Unexpected error occurred during setPixel()"); + for (int mask = 1; mask <= 0x80; mask <<= 1) { + if (x < W) { + if ((n & mask) != 0) + raster[x] = 1; + else + raster[x] = 0; + } + x++; } - x = 0; - y++; + if (x >= W) { + if ((y + 1) > H) { + error("Scan size of XBM file exceeds" + + " the defined width x height"); + } + if (setPixels(0, y, W, 1, model, raster, 0, W) == 0) { + error("Unexpected error occurred during setPixel()"); + } + x = 0; + y++; + } + } else { + error("Corrupt hex image data"); } + } else if (i == 1 || i == 2) { + error("Image hex data should be 3 or 4 characters long"); } - imageComplete(ImageConsumer.STATICIMAGEDONE, true); } + if (!imageDataTerminated) { + error("Missing terminator ';'"); + } + input.close(); + imageComplete(ImageConsumer.STATICIMAGEDONE, true); } } diff --git a/src/java.desktop/share/classes/sun/font/AttributeValues.java b/src/java.desktop/share/classes/sun/font/AttributeValues.java index 47acd035e4b9..cc00b9917367 100644 --- a/src/java.desktop/share/classes/sun/font/AttributeValues.java +++ b/src/java.desktop/share/classes/sun/font/AttributeValues.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -56,6 +56,7 @@ import java.util.Map; import java.util.HashMap; import java.util.Hashtable; +import java.util.Objects; public final class AttributeValues implements Cloneable { private int defined; @@ -309,11 +310,9 @@ public AttributeValues merge(Mapmap) { return merge(map, MASK_ALL); } - public AttributeValues merge(Mapmap, - int mask) { - if (map instanceof AttributeMap && - ((AttributeMap) map).getValues() != null) { - merge(((AttributeMap)map).getValues(), mask); + public AttributeValues merge(Map map, int mask) { + if (map instanceof AttributeMap am && am.getValues() != null) { + merge(am.getValues(), mask); } else if (map != null && !map.isEmpty()) { for (Map.Entry e: map.entrySet()) { try { @@ -393,15 +392,10 @@ public static boolean is16Hashtable(Hashtable ht) { Object val = e.getValue(); if (key.equals(DEFINED_KEY)) { result.defineAll(((Integer)val).intValue()); - } else { - try { - EAttribute ea = - EAttribute.forAttribute((Attribute)key); - if (ea != null) { - result.set(ea, val); - } - } - catch (ClassCastException ex) { + } else if (key instanceof Attribute attr) { + EAttribute ea = EAttribute.forAttribute(attr); + if (ea != null) { + result.set(ea, val); } } } @@ -436,24 +430,15 @@ public int hashCode() { return defined << 8 ^ nondefault; } - public boolean equals(Object rhs) { - try { - return equals((AttributeValues)rhs); - } - catch (ClassCastException e) { - } - return false; - } - - public boolean equals(AttributeValues rhs) { + public boolean equals(Object o) { // test in order of most likely to differ and easiest to compare // also assumes we're generally calling this only if family, // size, weight, posture are the same - - if (rhs == null) return false; - if (rhs == this) return true; - - return defined == rhs.defined + if (o == this) { + return true; + } + return o instanceof AttributeValues rhs + && defined == rhs.defined && nondefault == rhs.nondefault && underline == rhs.underline && strikethrough == rhs.strikethrough @@ -465,19 +450,19 @@ public boolean equals(AttributeValues rhs) { && runDirection == rhs.runDirection && bidiEmbedding == rhs.bidiEmbedding && swapColors == rhs.swapColors - && equals(transform, rhs.transform) - && equals(foreground, rhs.foreground) - && equals(background, rhs.background) - && equals(numericShaping, rhs.numericShaping) - && equals(justification, rhs.justification) - && equals(charReplacement, rhs.charReplacement) + && Objects.equals(transform, rhs.transform) + && Objects.equals(foreground, rhs.foreground) + && Objects.equals(background, rhs.background) + && Objects.equals(numericShaping, rhs.numericShaping) + && Objects.equals(justification, rhs.justification) + && Objects.equals(charReplacement, rhs.charReplacement) && size == rhs.size && weight == rhs.weight && posture == rhs.posture - && equals(family, rhs.family) - && equals(font, rhs.font) + && Objects.equals(family, rhs.family) + && Objects.equals(font, rhs.font) && imUnderline == rhs.imUnderline - && equals(imHighlight, rhs.imHighlight); + && Objects.equals(imHighlight, rhs.imHighlight); } public AttributeValues clone() { @@ -549,10 +534,6 @@ public String toString() { // internal utilities - private static boolean equals(Object lhs, Object rhs) { - return lhs == null ? rhs == null : lhs.equals(rhs); - } - private void update(EAttribute a) { defined |= a.mask; if (i_validate(a)) { @@ -599,26 +580,26 @@ private void i_set(EAttribute a, AttributeValues src) { private boolean i_equals(EAttribute a, AttributeValues src) { switch (a) { - case EFAMILY: return equals(family, src.family); + case EFAMILY: return Objects.equals(family, src.family); case EWEIGHT: return weight == src.weight; case EWIDTH: return width == src.width; case EPOSTURE: return posture == src.posture; case ESIZE: return size == src.size; - case ETRANSFORM: return equals(transform, src.transform); + case ETRANSFORM: return Objects.equals(transform, src.transform); case ESUPERSCRIPT: return superscript == src.superscript; - case EFONT: return equals(font, src.font); - case ECHAR_REPLACEMENT: return equals(charReplacement, src.charReplacement); - case EFOREGROUND: return equals(foreground, src.foreground); - case EBACKGROUND: return equals(background, src.background); + case EFONT: return Objects.equals(font, src.font); + case ECHAR_REPLACEMENT: return Objects.equals(charReplacement, src.charReplacement); + case EFOREGROUND: return Objects.equals(foreground, src.foreground); + case EBACKGROUND: return Objects.equals(background, src.background); case EUNDERLINE: return underline == src.underline; case ESTRIKETHROUGH: return strikethrough == src.strikethrough; case ERUN_DIRECTION: return runDirection == src.runDirection; case EBIDI_EMBEDDING: return bidiEmbedding == src.bidiEmbedding; case EJUSTIFICATION: return justification == src.justification; - case EINPUT_METHOD_HIGHLIGHT: return equals(imHighlight, src.imHighlight); + case EINPUT_METHOD_HIGHLIGHT: return Objects.equals(imHighlight, src.imHighlight); case EINPUT_METHOD_UNDERLINE: return imUnderline == src.imUnderline; case ESWAP_COLORS: return swapColors == src.swapColors; - case ENUMERIC_SHAPING: return equals(numericShaping, src.numericShaping); + case ENUMERIC_SHAPING: return Objects.equals(numericShaping, src.numericShaping); case EKERNING: return kerning == src.kerning; case ELIGATURES: return ligatures == src.ligatures; case ETRACKING: return tracking == src.tracking; @@ -634,8 +615,7 @@ private void i_set(EAttribute a, Object o) { case EPOSTURE: posture = ((Number)o).floatValue(); break; case ESIZE: size = ((Number)o).floatValue(); break; case ETRANSFORM: { - if (o instanceof TransformAttribute) { - TransformAttribute ta = (TransformAttribute)o; + if (o instanceof TransformAttribute ta) { if (ta.isIdentity()) { transform = null; } else { @@ -663,8 +643,7 @@ private void i_set(EAttribute a, Object o) { case EBIDI_EMBEDDING: bidiEmbedding = (byte)((Integer)o).intValue(); break; case EJUSTIFICATION: justification = ((Number)o).floatValue(); break; case EINPUT_METHOD_HIGHLIGHT: { - if (o instanceof Annotation) { - Annotation at = (Annotation)o; + if (o instanceof Annotation at) { imHighlight = (InputMethodHighlight)at.getValue(); } else { imHighlight = (InputMethodHighlight)o; @@ -759,9 +738,8 @@ private boolean i_validate(EAttribute a) { // Plan to remove these. public static float getJustification(Map map) { if (map != null) { - if (map instanceof AttributeMap && - ((AttributeMap) map).getValues() != null) { - return ((AttributeMap)map).getValues().justification; + if (map instanceof AttributeMap am && am.getValues() != null) { + return am.getValues().justification; } Object obj = map.get(TextAttribute.JUSTIFICATION); if (obj instanceof Number number) { @@ -773,9 +751,8 @@ public static float getJustification(Map map) { public static NumericShaper getNumericShaping(Map map) { if (map != null) { - if (map instanceof AttributeMap && - ((AttributeMap) map).getValues() != null) { - return ((AttributeMap)map).getValues().numericShaping; + if (map instanceof AttributeMap am && am.getValues() != null) { + return am.getValues().numericShaping; } Object obj = map.get(TextAttribute.NUMERIC_SHAPING); if (obj instanceof NumericShaper shaper) { @@ -792,8 +769,8 @@ public static NumericShaper getNumericShaping(Map map) { public AttributeValues applyIMHighlight() { if (imHighlight != null) { InputMethodHighlight hl = null; - if (imHighlight instanceof InputMethodHighlight) { - hl = (InputMethodHighlight)imHighlight; + if (imHighlight instanceof InputMethodHighlight imh) { + hl = imh; } else { hl = (InputMethodHighlight)((Annotation)imHighlight).getValue(); } @@ -816,9 +793,8 @@ public AttributeValues applyIMHighlight() { public static AffineTransform getBaselineTransform(Map map) { if (map != null) { AttributeValues av = null; - if (map instanceof AttributeMap && - ((AttributeMap) map).getValues() != null) { - av = ((AttributeMap)map).getValues(); + if (map instanceof AttributeMap am && am.getValues() != null) { + av = am.getValues(); } else if (map.get(TextAttribute.TRANSFORM) != null) { av = AttributeValues.fromMap((Map)map); // yuck } @@ -833,9 +809,8 @@ public static AffineTransform getBaselineTransform(Map map) { public static AffineTransform getCharTransform(Map map) { if (map != null) { AttributeValues av = null; - if (map instanceof AttributeMap && - ((AttributeMap) map).getValues() != null) { - av = ((AttributeMap)map).getValues(); + if (map instanceof AttributeMap am && am.getValues() != null) { + av = am.getValues(); } else if (map.get(TextAttribute.TRANSFORM) != null) { av = AttributeValues.fromMap((Map)map); // yuck } @@ -850,9 +825,8 @@ public static AffineTransform getCharTransform(Map map) { public static float getTracking(Map map) { if (map != null) { AttributeValues av = null; - if (map instanceof AttributeMap && - ((AttributeMap) map).getValues() != null) { - av = ((AttributeMap)map).getValues(); + if (map instanceof AttributeMap am && am.getValues() != null) { + av = am.getValues(); } else if (map.get(TextAttribute.TRACKING) != null) { av = AttributeValues.fromMap((Map)map); } diff --git a/src/java.desktop/share/classes/sun/font/CompositeFont.java b/src/java.desktop/share/classes/sun/font/CompositeFont.java index 8feb96a29482..713cbe3daa52 100644 --- a/src/java.desktop/share/classes/sun/font/CompositeFont.java +++ b/src/java.desktop/share/classes/sun/font/CompositeFont.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -297,15 +297,15 @@ private void doDeferredInitialisation(int slot) { /* If a component specifies the file with a bad font, * the corresponding slot will be initialized by * default physical font. In such case findFont2D may - * return composite font which cannot be casted to + * return composite font which cannot be cast to * physical font. */ - try { - components[slot] = - (PhysicalFont) fm.findFont2D(componentNames[slot], - style, - FontManager.PHYSICAL_FALLBACK); - } catch (ClassCastException cce) { + Font2D f2d = fm.findFont2D(componentNames[slot], + style, + FontManager.PHYSICAL_FALLBACK); + if (f2d instanceof PhysicalFont pf) { + components[slot] = pf; + } else { /* Assign default physical font to the slot */ components[slot] = fm.getDefaultPhysicalFont(); } @@ -379,12 +379,13 @@ public PhysicalFont getSlotFont(int slot) { try { PhysicalFont font = components[slot]; if (font == null) { - try { - font = (PhysicalFont) fm. - findFont2D(componentNames[slot], style, - FontManager.PHYSICAL_FALLBACK); + Font2D f2d = fm.findFont2D(componentNames[slot], + style, + FontManager.PHYSICAL_FALLBACK); + if (f2d instanceof PhysicalFont pf) { + font = pf; components[slot] = font; - } catch (ClassCastException cce) { + } else { font = fm.getDefaultPhysicalFont(); } } diff --git a/src/java.desktop/share/classes/sun/font/CoreMetrics.java b/src/java.desktop/share/classes/sun/font/CoreMetrics.java index e6219dca46b0..cc8d397b8674 100644 --- a/src/java.desktop/share/classes/sun/font/CoreMetrics.java +++ b/src/java.desktop/share/classes/sun/font/CoreMetrics.java @@ -70,36 +70,24 @@ public int hashCode() { return Float.floatToIntBits(ascent + ssOffset); } - public boolean equals(Object rhs) { - try { - return equals((CoreMetrics)rhs); + public boolean equals(Object o) { + if (o == this) { + return true; } - catch(ClassCastException e) { - return false; - } - } - - public boolean equals(CoreMetrics rhs) { - if (rhs != null) { - if (this == rhs) { - return true; - } - - return ascent == rhs.ascent - && descent == rhs.descent - && leading == rhs.leading - && baselineIndex == rhs.baselineIndex - && baselineOffsets[0] == rhs.baselineOffsets[0] - && baselineOffsets[1] == rhs.baselineOffsets[1] - && baselineOffsets[2] == rhs.baselineOffsets[2] - && strikethroughOffset == rhs.strikethroughOffset - && strikethroughThickness == rhs.strikethroughThickness - && underlineOffset == rhs.underlineOffset - && underlineThickness == rhs.underlineThickness - && ssOffset == rhs.ssOffset - && italicAngle == rhs.italicAngle; - } - return false; + return o instanceof CoreMetrics rhs + && ascent == rhs.ascent + && descent == rhs.descent + && leading == rhs.leading + && baselineIndex == rhs.baselineIndex + && baselineOffsets[0] == rhs.baselineOffsets[0] + && baselineOffsets[1] == rhs.baselineOffsets[1] + && baselineOffsets[2] == rhs.baselineOffsets[2] + && strikethroughOffset == rhs.strikethroughOffset + && strikethroughThickness == rhs.strikethroughThickness + && underlineOffset == rhs.underlineOffset + && underlineThickness == rhs.underlineThickness + && ssOffset == rhs.ssOffset + && italicAngle == rhs.italicAngle; } // fullOffsets is an array of 5 baseline offsets, diff --git a/src/java.desktop/share/classes/sun/font/Decoration.java b/src/java.desktop/share/classes/sun/font/Decoration.java index f131914134f5..640df2e081cf 100644 --- a/src/java.desktop/share/classes/sun/font/Decoration.java +++ b/src/java.desktop/share/classes/sun/font/Decoration.java @@ -47,6 +47,7 @@ import java.awt.geom.Rectangle2D; import java.awt.geom.GeneralPath; import java.text.AttributedCharacterIterator.Attribute; +import java.util.Objects; import static sun.font.AttributeValues.*; import static sun.font.EAttribute.*; @@ -169,48 +170,17 @@ private static final class DecorationImpl extends Decoration { this.imUnderline = imUnderline; } - private static boolean areEqual(Object lhs, Object rhs) { - - if (lhs == null) { - return rhs == null; - } - else { - return lhs.equals(rhs); - } - } - - public boolean equals(Object rhs) { - - if (rhs == this) { + public boolean equals(Object o) { + if (o == this) { return true; } - if (rhs == null) { - return false; - } - - DecorationImpl other = null; - try { - other = (DecorationImpl) rhs; - } - catch(ClassCastException e) { - return false; - } - - if (!(swapColors == other.swapColors && - strikethrough == other.strikethrough)) { - return false; - } - - if (!areEqual(stdUnderline, other.stdUnderline)) { - return false; - } - if (!areEqual(fgPaint, other.fgPaint)) { - return false; - } - if (!areEqual(bgPaint, other.bgPaint)) { - return false; - } - return areEqual(imUnderline, other.imUnderline); + return o instanceof DecorationImpl other && + swapColors == other.swapColors && + strikethrough == other.strikethrough && + Objects.equals(stdUnderline, other.stdUnderline) && + Objects.equals(fgPaint, other.fgPaint) && + Objects.equals(bgPaint, other.bgPaint) && + Objects.equals(imUnderline, other.imUnderline); } public int hashCode() { @@ -304,8 +274,7 @@ public void drawTextAndDecorations(Label label, if (swapColors) { background = fgPaint==null? savedPaint : fgPaint; if (bgPaint == null) { - if (background instanceof Color) { - Color bg = (Color)background; + if (background instanceof Color bg) { // 30/59/11 is standard weights, tweaked a bit int brightness = 33 * bg.getRed() + 53 * bg.getGreen() diff --git a/src/java.desktop/share/classes/sun/font/FontDesignMetrics.java b/src/java.desktop/share/classes/sun/font/FontDesignMetrics.java index c5f020991d4d..66ccf7499c54 100644 --- a/src/java.desktop/share/classes/sun/font/FontDesignMetrics.java +++ b/src/java.desktop/share/classes/sun/font/FontDesignMetrics.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -214,12 +214,12 @@ void init(Font font, FontRenderContext frc) { } public boolean equals(Object key) { - if (!(key instanceof MetricsKey)) { - return false; + if (key == this) { + return true; } - return - font.equals(((MetricsKey)key).font) && - frc.equals(((MetricsKey)key).frc); + return key instanceof MetricsKey other && + font.equals(other.font) && + frc.equals(other.frc); } public int hashCode() { diff --git a/src/java.desktop/share/classes/sun/font/FontFamily.java b/src/java.desktop/share/classes/sun/font/FontFamily.java index dcec9545dfe6..45c8cbac2c58 100644 --- a/src/java.desktop/share/classes/sun/font/FontFamily.java +++ b/src/java.desktop/share/classes/sun/font/FontFamily.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -114,14 +114,14 @@ private boolean isFromSameSource(Font2D font) { } FileFont existingFont = null; - if (plain instanceof FileFont) { - existingFont = (FileFont)plain; - } else if (bold instanceof FileFont) { - existingFont = (FileFont)bold; - } else if (italic instanceof FileFont) { - existingFont = (FileFont)italic; - } else if (bolditalic instanceof FileFont) { - existingFont = (FileFont)bolditalic; + if (plain instanceof FileFont plainFF) { + existingFont = plainFF; + } else if (bold instanceof FileFont boldFF) { + existingFont = boldFF; + } else if (italic instanceof FileFont italicFF) { + existingFont = italicFF; + } else if (bolditalic instanceof FileFont boldItalicFF) { + existingFont = boldItalicFF; } // A family isn't created until there's a font. // So if we didn't find a file font it means this diff --git a/src/java.desktop/share/classes/sun/font/FontLineMetrics.java b/src/java.desktop/share/classes/sun/font/FontLineMetrics.java index 031f5cf68efa..0254a9dc0237 100644 --- a/src/java.desktop/share/classes/sun/font/FontLineMetrics.java +++ b/src/java.desktop/share/classes/sun/font/FontLineMetrics.java @@ -99,12 +99,10 @@ public int hashCode() { } public boolean equals(Object rhs) { - try { - return cm.equals(((FontLineMetrics)rhs).cm); - } - catch (ClassCastException e) { - return false; + if (rhs == this) { + return true; } + return rhs instanceof FontLineMetrics other && cm.equals(other.cm); } public Object clone() { diff --git a/src/java.desktop/share/classes/sun/font/FontStrikeDesc.java b/src/java.desktop/share/classes/sun/font/FontStrikeDesc.java index 6c1e471fbd0e..9c2461e708fc 100644 --- a/src/java.desktop/share/classes/sun/font/FontStrikeDesc.java +++ b/src/java.desktop/share/classes/sun/font/FontStrikeDesc.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2005, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -85,24 +85,19 @@ public int hashCode() { } public boolean equals(Object obj) { - try { - FontStrikeDesc desc = (FontStrikeDesc)obj; - return (desc.valuemask == this.valuemask && - desc.glyphTx.equals(this.glyphTx) && - desc.devTx.equals(this.devTx)); - } catch (Exception e) { - /* class cast or NP exceptions should not happen often, if ever, - * and I am hoping that this is faster than an instanceof check. - */ - return false; + if (obj == this) { + return true; } + return obj instanceof FontStrikeDesc desc && + desc.valuemask == this.valuemask && + desc.glyphTx.equals(this.glyphTx) && + desc.devTx.equals(this.devTx); } FontStrikeDesc() { // used with init } - /* This maps a public text AA hint value into one of the subset of values * used to index strikes. For the purpose of the strike cache there are * only 4 values : OFF, ON, LCD_HRGB, LCD_VRGB. diff --git a/src/java.desktop/share/classes/sun/font/FontUtilities.java b/src/java.desktop/share/classes/sun/font/FontUtilities.java index a14214ab751e..d4c9a6c66a5b 100644 --- a/src/java.desktop/share/classes/sun/font/FontUtilities.java +++ b/src/java.desktop/share/classes/sun/font/FontUtilities.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -481,7 +481,7 @@ public static FontUIResource getCompositeFontUIResource(Font font) { FontUIResource fuir = new FontUIResource(font); Font2D font2D = FontUtilities.getFont2D(font); - if (!(font2D instanceof PhysicalFont)) { + if (!(font2D instanceof PhysicalFont physicalFont)) { /* Swing should only be calling this when a font is obtained * from desktop properties, so should generally be a physical font, * an exception might be for names like "MS Serif" which are @@ -499,7 +499,6 @@ public static FontUIResource getCompositeFontUIResource(Font font) { if (!(dialog instanceof CompositeFont dialog2D)) { return fuir; } - PhysicalFont physicalFont = (PhysicalFont)font2D; ConcurrentHashMap compMap = compMapRef.get(); if (compMap == null) { // Its been collected. compMap = new ConcurrentHashMap(); @@ -565,8 +564,7 @@ public static FontUIResource getFontConfigFUIR(String fcFamily, FontUIResource fuir; FontManager fm = FontManagerFactory.getInstance(); - if (fm instanceof SunFontManager) { - SunFontManager sfm = (SunFontManager) fm; + if (fm instanceof SunFontManager sfm) { fuir = sfm.getFontConfigFUIR(mapped, style, size); } else { fuir = new FontUIResource(mapped, style, size); @@ -574,7 +572,6 @@ public static FontUIResource getFontConfigFUIR(String fcFamily, return fuir; } - /** * Used by windows printing to assess if a font is likely to * be layout compatible with JDK @@ -583,10 +580,8 @@ public static FontUIResource getFontConfigFUIR(String fcFamily, * fonts GDI handles differently. */ public static boolean textLayoutIsCompatible(Font font) { - Font2D font2D = getFont2D(font); - if (font2D instanceof TrueTypeFont) { - TrueTypeFont ttf = (TrueTypeFont) font2D; + if (font2D instanceof TrueTypeFont ttf) { return ttf.getDirectoryEntry(TrueTypeFont.GSUBTag) == null || ttf.getDirectoryEntry(TrueTypeFont.GPOSTag) != null; diff --git a/src/java.desktop/share/classes/sun/font/FreetypeFontScaler.java b/src/java.desktop/share/classes/sun/font/FreetypeFontScaler.java index 6d8d5512bae4..8713cb61cef5 100644 --- a/src/java.desktop/share/classes/sun/font/FreetypeFontScaler.java +++ b/src/java.desktop/share/classes/sun/font/FreetypeFontScaler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2014, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -60,12 +60,9 @@ private void invalidateScaler() throws FontScalerException { public FreetypeFontScaler(Font2D font, int indexInCollection, boolean supportsCJK, int filesize) { - int fonttype = TRUETYPE_FONT; - if (font instanceof Type1Font) { - fonttype = TYPE1_FONT; - } + int type = font instanceof Type1Font ? TYPE1_FONT : TRUETYPE_FONT; nativeScaler = initNativeScaler(font, - fonttype, + type, indexInCollection, supportsCJK, filesize); diff --git a/src/java.desktop/share/classes/sun/font/GlyphLayout.java b/src/java.desktop/share/classes/sun/font/GlyphLayout.java index 5af383911e72..7cebb4c2ff8c 100644 --- a/src/java.desktop/share/classes/sun/font/GlyphLayout.java +++ b/src/java.desktop/share/classes/sun/font/GlyphLayout.java @@ -178,16 +178,13 @@ public int hashCode() { } public boolean equals(Object o) { - try { - SDKey rhs = (SDKey)o; - return - hash == rhs.hash && - font.equals(rhs.font) && - frc.equals(rhs.frc); - } - catch (ClassCastException e) { + if (o == this) { + return true; } - return false; + return o instanceof SDKey rhs && + hash == rhs.hash && + font.equals(rhs.font) && + frc.equals(rhs.frc); } } @@ -302,15 +299,15 @@ public StandardGlyphVector layout(Font font, FontRenderContext frc, } Font2D font2D = FontUtilities.getFont2D(font); - if (font2D instanceof FontSubstitution) { - font2D = ((FontSubstitution)font2D).getCompositeFont2D(); + if (font2D instanceof FontSubstitution sub) { + font2D = sub.getCompositeFont2D(); } _textRecord.init(text, offset, lim, min, max); int start = offset; - if (font2D instanceof CompositeFont) { + if (font2D instanceof CompositeFont composite) { _scriptRuns.init(text, offset, count); // ??? how to handle 'common' chars - _fontRuns.init((CompositeFont)font2D, text, offset, lim); + _fontRuns.init(composite, text, offset, lim); while (_scriptRuns.next()) { int limit = _scriptRuns.getScriptLimit(); int script = _scriptRuns.getScriptCode(); @@ -322,8 +319,8 @@ public StandardGlyphVector layout(Font font, FontRenderContext frc, * its consistent with the way NativeFonts delegate * in other cases too. */ - if (pfont instanceof NativeFont) { - pfont = ((NativeFont)pfont).getDelegateFont(); + if (pfont instanceof NativeFont nf) { + pfont = nf.getDelegateFont(); } int gmask = _fontRuns.getGlyphMask(); int pos = _fontRuns.getPos(); diff --git a/src/java.desktop/share/classes/sun/font/PhysicalFont.java b/src/java.desktop/share/classes/sun/font/PhysicalFont.java index dc05c0e0a5f3..aa08727713e8 100644 --- a/src/java.desktop/share/classes/sun/font/PhysicalFont.java +++ b/src/java.desktop/share/classes/sun/font/PhysicalFont.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -33,6 +33,7 @@ import java.lang.ref.WeakReference; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; +import java.util.Objects; public abstract class PhysicalFont extends Font2D { @@ -41,14 +42,13 @@ public abstract class PhysicalFont extends Font2D { protected Object nativeNames; public boolean equals(Object o) { - if (o == null || o.getClass() != this.getClass()) { - return false; + if (o == this) { + return true; } - PhysicalFont other = (PhysicalFont)o; - return - (this.fullName.equals(other.fullName)) && - ((this.platName == null && other.platName == null) || - (this.platName != null && this.platName.equals(other.platName))); + return o instanceof PhysicalFont other && + other.getClass() == this.getClass() && + fullName.equals(other.fullName) && + Objects.equals(platName, other.platName); } public int hashCode() { diff --git a/src/java.desktop/share/classes/sun/font/StandardGlyphVector.java b/src/java.desktop/share/classes/sun/font/StandardGlyphVector.java index d44bd9a00e51..1c3ad7db47fd 100644 --- a/src/java.desktop/share/classes/sun/font/StandardGlyphVector.java +++ b/src/java.desktop/share/classes/sun/font/StandardGlyphVector.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -278,8 +278,8 @@ public static StandardGlyphVector getStandardGV(GlyphVector gv, return new StandardGlyphVector(gv, frc); } } - if (gv instanceof StandardGlyphVector) { - return (StandardGlyphVector)gv; + if (gv instanceof StandardGlyphVector sgv) { + return sgv; } return new StandardGlyphVector(gv, gv.getFontRenderContext()); } @@ -636,61 +636,54 @@ public GlyphJustificationInfo getGlyphJustificationInfo(int ix) { return null; } - public boolean equals(GlyphVector rhs) { - if (this == rhs) { + public boolean equals(GlyphVector gv) { + + if (gv == this) { return true; } - if (rhs == null) { + + if (!(gv instanceof StandardGlyphVector other)) { return false; } - try { - StandardGlyphVector other = (StandardGlyphVector)rhs; + if (glyphs.length != other.glyphs.length) { + return false; + } - if (glyphs.length != other.glyphs.length) { + for (int i = 0; i < glyphs.length; ++i) { + if (glyphs[i] != other.glyphs[i]) { return false; } + } - for (int i = 0; i < glyphs.length; ++i) { - if (glyphs[i] != other.glyphs[i]) { - return false; - } - } - - if (!font.equals(other.font)) { - return false; - } + if (!font.equals(other.font)) { + return false; + } - if (!frc.equals(other.frc)) { - return false; - } + if (!frc.equals(other.frc)) { + return false; + } - if ((other.positions == null) != (positions == null)) { - if (positions == null) { - initPositions(); - } else { - other.initPositions(); - } + if ((other.positions == null) != (positions == null)) { + if (positions == null) { + initPositions(); + } else { + other.initPositions(); } + } - if (positions != null) { - for (int i = 0; i < positions.length; ++i) { - if (positions[i] != other.positions[i]) { - return false; - } + if (positions != null) { + for (int i = 0; i < positions.length; ++i) { + if (positions[i] != other.positions[i]) { + return false; } } - - if (gti == null) { - return other.gti == null; - } else { - return gti.equals(other.gti); - } } - catch (ClassCastException e) { - // assume they are different simply by virtue of the class difference - return false; + if (gti == null) { + return other.gti == null; + } else { + return gti.equals(other.gti); } } @@ -707,13 +700,11 @@ public int hashCode() { * the inherited Object.equals(Object) as well. GlyphVector should do * this, and define two glyphvectors as not equal if the classes differ. */ - public boolean equals(Object rhs) { - try { - return equals((GlyphVector)rhs); - } - catch (ClassCastException e) { - return false; + public boolean equals(Object o) { + if (this == o) { + return true; } + return o instanceof GlyphVector other && this.equals(other); } /** @@ -1110,8 +1101,8 @@ private void init(Font font, char[] text, int start, int count, private void initFontData() { font2D = FontUtilities.getFont2D(font); - if (font2D instanceof FontSubstitution) { - font2D = ((FontSubstitution)font2D).getCompositeFont2D(); + if (font2D instanceof FontSubstitution sub) { + font2D = sub.getCompositeFont2D(); } float s = font.getSize2D(); if (font.isTransformed()) { @@ -1731,11 +1722,11 @@ static GlyphStrike create(StandardGlyphVector sgv, AffineTransform dtx, AffineTr aa, fm); // Get the strike via the handle. Shouldn't matter // if we've invalidated the font but its an extra precaution. - // do we want the CompFont from CFont here ? - Font2D f2d = sgv.font2D; - if (f2d instanceof FontSubstitution) { - f2d = ((FontSubstitution)f2d).getCompositeFont2D(); - } + // do we want the CompFont from CFont here ? + Font2D f2d = sgv.font2D; + if (f2d instanceof FontSubstitution sub) { + f2d = sub.getCompositeFont2D(); + } FontStrike strike = f2d.handle.font2D.getStrike(desc); // !!! getStrike(desc, false) return new GlyphStrike(sgv, strike, dx, dy); diff --git a/src/java.desktop/share/classes/sun/font/StrikeCache.java b/src/java.desktop/share/classes/sun/font/StrikeCache.java index 2266746e17ae..9a13d7f59705 100644 --- a/src/java.desktop/share/classes/sun/font/StrikeCache.java +++ b/src/java.desktop/share/classes/sun/font/StrikeCache.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -386,8 +386,7 @@ static void disposeStrike(final FontStrikeDisposer disposer) { if (!GraphicsEnvironment.isHeadless()) { GraphicsConfiguration gc = ge.getDefaultScreenDevice().getDefaultConfiguration(); - if (gc instanceof AccelGraphicsConfig) { - AccelGraphicsConfig agc = (AccelGraphicsConfig)gc; + if (gc instanceof AccelGraphicsConfig agc) { BufferedContext bc = agc.getContext(); if (bc != null) { rq = bc.getRenderQueue(); diff --git a/src/java.desktop/share/classes/sun/font/SunFontManager.java b/src/java.desktop/share/classes/sun/font/SunFontManager.java index 323f0d056e12..f9808530866a 100644 --- a/src/java.desktop/share/classes/sun/font/SunFontManager.java +++ b/src/java.desktop/share/classes/sun/font/SunFontManager.java @@ -437,11 +437,10 @@ protected SunFontManager() { public Font2DHandle getNewComposite(String family, int style, Font2DHandle handle) { - if (!(handle.font2D instanceof CompositeFont)) { + if (!(handle.font2D instanceof CompositeFont oldComp)) { return handle; } - CompositeFont oldComp = (CompositeFont)handle.font2D; PhysicalFont oldFont = oldComp.getSlotFont(0); if (family == null) { @@ -643,10 +642,8 @@ protected PhysicalFont addToFontList(PhysicalFont f, int rank) { * more complete (larger) one. */ if (oldFont.getRank() == rank) { - if (oldFont instanceof TrueTypeFont && - newFont instanceof TrueTypeFont) { - TrueTypeFont oldTTFont = (TrueTypeFont)oldFont; - TrueTypeFont newTTFont = (TrueTypeFont)newFont; + if (oldFont instanceof TrueTypeFont oldTTFont && + newFont instanceof TrueTypeFont newTTFont) { if (oldTTFont.fileSize >= newTTFont.fileSize) { return oldFont; } @@ -998,8 +995,8 @@ public PhysicalFont getDefaultPhysicalFont() { // findFont2D will load all fonts Font2D font2d = findFont2D(defaultFontName, Font.PLAIN, NO_FALLBACK); if (font2d != null) { - if (font2d instanceof PhysicalFont) { - defaultPhysicalFont = (PhysicalFont)font2d; + if (font2d instanceof PhysicalFont pf) { + defaultPhysicalFont = pf; } else { if (FontUtilities.isLogging()) { FontUtilities.logWarning("Font returned by findFont2D for default font name " + @@ -2264,14 +2261,12 @@ public synchronized String getFullNameByFileName(String fileName) { * make sense for a Composite to be "bad". */ public synchronized void deRegisterBadFont(Font2D font2D) { - if (!(font2D instanceof PhysicalFont)) { - /* We should never reach here, but just in case */ - return; - } else { + // this should always be a physical font, but check just in case + if (font2D instanceof PhysicalFont pf) { if (FontUtilities.isLogging()) { FontUtilities.logSevere("Deregister bad font: " + font2D); } - replaceFont((PhysicalFont)font2D, getDefaultPhysicalFont()); + replaceFont(pf, getDefaultPhysicalFont()); } } @@ -2382,8 +2377,7 @@ private synchronized void loadLocaleNames() { localeFullNamesToFont = new HashMap<>(); Font2D[] fonts = getRegisteredFonts(); for (int i=0; iReleasePrimitiveArrayCritical(env, rasterP->jdata, dataP, + JNI_ABORT); + return -1; + } else { unsigned int *dstP = (unsigned int *) mlib_ImageGetData(*mlibImagePP); int dstride = (*mlibImagePP)->stride>>2; @@ -2234,10 +2238,10 @@ allocateArray(JNIEnv *env, BufImageS_t *imageP, dP[x] = sP[x] | 0xff000000; } } + (*env)->ReleasePrimitiveArrayCritical(env, rasterP->jdata, dataP, + JNI_ABORT); + return 0; } - (*env)->ReleasePrimitiveArrayCritical(env, rasterP->jdata, dataP, - JNI_ABORT); - return 0; } else if ((hintP->packing & BYTE_INTERLEAVED) == BYTE_INTERLEAVED) { int nChans = (cmP->isDefaultCompatCM ? 4 : hintP->numChans); @@ -2252,6 +2256,11 @@ allocateArray(JNIEnv *env, BufImageS_t *imageP, hintP->sStride, (unsigned char *)dataP + hintP->dataOffset); + if (*mlibImagePP == NULL) { + (*env)->ReleasePrimitiveArrayCritical(env, rasterP->jdata, dataP, + JNI_ABORT); + return -1; + } } else if ((hintP->packing & SHORT_INTERLEAVED) == SHORT_INTERLEAVED) { *mlibImagePP = (*sMlibSysFns.createStructFP)(MLIB_SHORT, @@ -2261,6 +2270,11 @@ allocateArray(JNIEnv *env, BufImageS_t *imageP, imageP->raster.scanlineStride*2, (unsigned short *)dataP + hintP->channelOffset); + if (*mlibImagePP == NULL) { + (*env)->ReleasePrimitiveArrayCritical(env, rasterP->jdata, dataP, + JNI_ABORT); + return -1; + } } else { /* Release the data array */ @@ -2360,6 +2374,11 @@ allocateRasterArray(JNIEnv *env, RasterS_t *rasterP, width, height, rasterP->scanlineStride*4, (unsigned char *)dataP + offset); + if (*mlibImagePP == NULL) { + (*env)->ReleasePrimitiveArrayCritical(env, rasterP->jdata, dataP, + JNI_ABORT); + return -1; + } *dataPP = dataP; return 0; case sun_awt_image_IntegerComponentRaster_TYPE_BYTE_SAMPLES: @@ -2388,6 +2407,11 @@ allocateRasterArray(JNIEnv *env, RasterS_t *rasterP, width, height, rasterP->scanlineStride, (unsigned char *)dataP + offset); + if (*mlibImagePP == NULL) { + (*env)->ReleasePrimitiveArrayCritical(env, rasterP->jdata, dataP, + JNI_ABORT); + return -1; + } *dataPP = dataP; return 0; case sun_awt_image_IntegerComponentRaster_TYPE_USHORT_SAMPLES: @@ -2418,6 +2442,11 @@ allocateRasterArray(JNIEnv *env, RasterS_t *rasterP, width, height, rasterP->scanlineStride*2, (unsigned char *)dataP + offset); + if (*mlibImagePP == NULL) { + (*env)->ReleasePrimitiveArrayCritical(env, rasterP->jdata, dataP, + JNI_ABORT); + return -1; + } *dataPP = dataP; return 0; diff --git a/src/java.desktop/share/native/libjavajpeg/imageioJPEG.c b/src/java.desktop/share/native/libjavajpeg/imageioJPEG.c index a764eb1ae3bb..ac37ad8eab6f 100644 --- a/src/java.desktop/share/native/libjavajpeg/imageioJPEG.c +++ b/src/java.desktop/share/native/libjavajpeg/imageioJPEG.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -120,6 +120,7 @@ typedef struct streamBufferStruct { size_t bufferLength; // Allocated, nut just used int suspendable; // Set to true to suspend input long remaining_skip; // Used only on input + jboolean isCopy; // GetByteArrayElements copied/pinned the Java array } streamBuffer, *streamBufferPtr; /* @@ -200,7 +201,8 @@ static void destroyStreamBuffer(JNIEnv *env, streamBufferPtr sb) { // Forward reference static void unpinStreamBuffer(JNIEnv *env, streamBufferPtr sb, - const JOCTET *next_byte); + const JOCTET *next_byte, + int streamReleaseMode); /* * Resets the state of a streamBuffer object that has been in use. * The global reference to the stream is released, but the reference @@ -212,15 +214,16 @@ static void resetStreamBuffer(JNIEnv *env, streamBufferPtr sb) { (*env)->DeleteWeakGlobalRef(env, sb->ioRef); sb->ioRef = NULL; } - unpinStreamBuffer(env, sb, NULL); + unpinStreamBuffer(env, sb, NULL, JNI_ABORT); sb->bufferOffset = NO_DATA; sb->suspendable = FALSE; sb->remaining_skip = 0; } /* - * Pins the data buffer associated with this stream. Returns OK on - * success, NOT_OK on failure, as GetPrimitiveArrayCritical may fail. + * Pins/copies the data buffer associated with this stream. Returns OK on + * success, NOT_OK on failure, as GetByteArrayElements + * may fail. */ static int pinStreamBuffer(JNIEnv *env, streamBufferPtr sb, @@ -228,9 +231,9 @@ static int pinStreamBuffer(JNIEnv *env, if (sb->hstreamBuffer != NULL) { assert(sb->buf == NULL); sb->buf = - (JOCTET *)(*env)->GetPrimitiveArrayCritical(env, - sb->hstreamBuffer, - NULL); + (JOCTET *)(*env)->GetByteArrayElements(env, + sb->hstreamBuffer, + &sb->isCopy); if (sb->buf == NULL) { return NOT_OK; } @@ -242,11 +245,12 @@ static int pinStreamBuffer(JNIEnv *env, } /* - * Unpins the data buffer associated with this stream. + * Unpins/releases the data buffer associated with this stream. */ static void unpinStreamBuffer(JNIEnv *env, streamBufferPtr sb, - const JOCTET *next_byte) { + const JOCTET *next_byte, + int streamReleaseMode) { if (sb->buf != NULL) { assert(sb->hstreamBuffer != NULL); if (next_byte == NULL) { @@ -254,11 +258,13 @@ static void unpinStreamBuffer(JNIEnv *env, } else { sb->bufferOffset = next_byte - sb->buf; } - (*env)->ReleasePrimitiveArrayCritical(env, - sb->hstreamBuffer, - sb->buf, - 0); - sb->buf = NULL; + (*env)->ReleaseByteArrayElements(env, + sb->hstreamBuffer, + (jbyte *)sb->buf, + streamReleaseMode); + if (streamReleaseMode != JNI_COMMIT) { + sb->buf = NULL; + } } } @@ -276,6 +282,7 @@ static void clearStreamBuffer(streamBufferPtr sb) { typedef struct pixelBufferStruct { jobject hpixelObject; // Usually a DataBuffer bank as a byte array unsigned int byteBufferLength; + jboolean isCopy; // GetByteArrayElements copied/pinned the Java array union pixptr { INT32 *ip; // Pinned buffer pointer, as 32-bit ints unsigned char *bp; // Pinned buffer pointer, as bytes @@ -309,7 +316,7 @@ static int setPixelBuffer(JNIEnv *env, pixelBufferPtr pb, jobject obj) { } // Forward reference -static void unpinPixelBuffer(JNIEnv *env, pixelBufferPtr pb); +static void unpinPixelBuffer(JNIEnv *env, pixelBufferPtr pb, int pixelReleaseMode); /* * Resets a pixel buffer to its initial state. Unpins any pixel buffer, @@ -318,7 +325,7 @@ static void unpinPixelBuffer(JNIEnv *env, pixelBufferPtr pb); */ static void resetPixelBuffer(JNIEnv *env, pixelBufferPtr pb) { if (pb->hpixelObject != NULL) { - unpinPixelBuffer(env, pb); + unpinPixelBuffer(env, pb, JNI_ABORT); (*env)->DeleteGlobalRef(env, pb->hpixelObject); pb->hpixelObject = NULL; pb->byteBufferLength = 0; @@ -326,13 +333,13 @@ static void resetPixelBuffer(JNIEnv *env, pixelBufferPtr pb) { } /* - * Pins the data buffer. Returns OK on success, NOT_OK on failure. + * Pins/copies the data buffer. Returns OK on success, NOT_OK on failure. */ static int pinPixelBuffer(JNIEnv *env, pixelBufferPtr pb) { if (pb->hpixelObject != NULL) { assert(pb->buf.ip == NULL); - pb->buf.bp = (unsigned char *)(*env)->GetPrimitiveArrayCritical - (env, pb->hpixelObject, NULL); + pb->buf.bp = (unsigned char *)(*env)->GetByteArrayElements + (env, pb->hpixelObject, &pb->isCopy); if (pb->buf.bp == NULL) { return NOT_OK; } @@ -341,17 +348,19 @@ static int pinPixelBuffer(JNIEnv *env, pixelBufferPtr pb) { } /* - * Unpins the data buffer. + * Unpins/releases the pixel buffer. */ -static void unpinPixelBuffer(JNIEnv *env, pixelBufferPtr pb) { +static void unpinPixelBuffer(JNIEnv *env, pixelBufferPtr pb, int pixelReleaseMode) { if (pb->buf.ip != NULL) { assert(pb->hpixelObject != NULL); - (*env)->ReleasePrimitiveArrayCritical(env, - pb->hpixelObject, - pb->buf.ip, - 0); - pb->buf.ip = NULL; + (*env)->ReleaseByteArrayElements(env, + pb->hpixelObject, + (jbyte *)pb->buf.ip, + pixelReleaseMode); + if (pixelReleaseMode != JNI_COMMIT) { + pb->buf.ip = NULL; + } } } @@ -468,34 +477,28 @@ static j_common_ptr destroyImageioData(JNIEnv *env, imageIODataPtr data) { /******************** Java array pinning and unpinning *****************/ -/* We use Get/ReleasePrimitiveArrayCritical functions to avoid - * the need to copy array elements for the above two objects. - * - * MAKE SURE TO: - * - * - carefully insert pairs of RELEASE_ARRAYS and GET_ARRAYS around - * callbacks to Java. - * - call RELEASE_ARRAYS before returning to Java. - * - * Otherwise things will go horribly wrong. There may be memory leaks, - * excessive pinning, or even VM crashes! - * - * Note that GetPrimitiveArrayCritical may fail! +/* + * We use Get/ReleaseByteArrayElements functions for access stream + * and pixel information from Java level arrays. + * If we receive reference to copy of Java array make sure you update + * Java array also when the latest information is needed at Java level. + * Also we use specific release modes for performance optimizations. */ /* - * Release (unpin) all the arrays in use during a read. + * Release (unpin) both stream and pixel arrays. */ -static void RELEASE_ARRAYS(JNIEnv *env, imageIODataPtr data, const JOCTET *next_byte) +static void RELEASE_ARRAYS(JNIEnv *env, imageIODataPtr data, const JOCTET *next_byte, + int streamReleaseMode, int pixelReleaseMode) { - unpinStreamBuffer(env, &data->streamBuf, next_byte); + unpinStreamBuffer(env, &data->streamBuf, next_byte, streamReleaseMode); - unpinPixelBuffer(env, &data->pixelBuf); + unpinPixelBuffer(env, &data->pixelBuf, pixelReleaseMode); } /* - * Get (pin) all the arrays in use during a read. + * Get (pin) both stream and pixel arrays. */ static int GET_ARRAYS(JNIEnv *env, imageIODataPtr data, const JOCTET **next_byte) { if (pinStreamBuffer(env, &data->streamBuf, next_byte) == NOT_OK) { @@ -503,7 +506,7 @@ static int GET_ARRAYS(JNIEnv *env, imageIODataPtr data, const JOCTET **next_byte } if (pinPixelBuffer(env, &data->pixelBuf) == NOT_OK) { - RELEASE_ARRAYS(env, data, *next_byte); + RELEASE_ARRAYS(env, data, *next_byte, JNI_ABORT, JNI_ABORT); return NOT_OK; } return OK; @@ -570,26 +573,16 @@ sun_jpeg_output_message (j_common_ptr cinfo) theObject = data->imageIOobj; if (cinfo->is_decompressor) { - struct jpeg_source_mgr *src = ((j_decompress_ptr)cinfo)->src; - RELEASE_ARRAYS(env, data, src->next_input_byte); (*env)->CallVoidMethod(env, theObject, JPEGImageReader_warningWithMessageID, string); - if ((*env)->ExceptionCheck(env) - || !GET_ARRAYS(env, data, &(src->next_input_byte))) { - cinfo->err->error_exit(cinfo); - } } else { - struct jpeg_destination_mgr *dest = ((j_compress_ptr)cinfo)->dest; - RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte)); (*env)->CallVoidMethod(env, theObject, JPEGImageWriter_warningWithMessageID, string); - if ((*env)->ExceptionCheck(env) - || !GET_ARRAYS(env, data, - (const JOCTET **)(&dest->next_output_byte))) { - cinfo->err->error_exit(cinfo); - } + } + if ((*env)->ExceptionCheck(env)) { + cinfo->err->error_exit(cinfo); } } @@ -941,7 +934,7 @@ imageio_fill_input_buffer(j_decompress_ptr cinfo) #ifdef DEBUG_IIO_JPEG printf("Filling input buffer, remaining skip is %ld, ", sb->remaining_skip); - printf("Buffer length is %d\n", sb->bufferLength); + printf("Buffer length is %zu\n", sb->bufferLength); #endif /* @@ -956,8 +949,15 @@ imageio_fill_input_buffer(j_decompress_ptr cinfo) /* * Now fill a complete buffer, or as much of one as the stream * will give us if we are near the end. + * + * The native copy of java array is not valid anymore so we just + * release it and get new copy, if we don't have native copy we rely + * on JVM to maintain the pinned handle of java array. */ - RELEASE_ARRAYS(env, data, src->next_input_byte); + jboolean isCopy = sb->isCopy; + if (isCopy) { + unpinStreamBuffer(env, &data->streamBuf, src->next_input_byte, JNI_ABORT); + } GET_IO_REF(input); @@ -969,9 +969,12 @@ imageio_fill_input_buffer(j_decompress_ptr cinfo) if ((ret > 0) && ((unsigned int)ret > sb->bufferLength)) { ret = (int)sb->bufferLength; } - if ((*env)->ExceptionCheck(env) - || !GET_ARRAYS(env, data, &(src->next_input_byte))) { - cinfo->err->error_exit((j_common_ptr) cinfo); + + if ((*env)->ExceptionCheck(env) || + (isCopy && (pinStreamBuffer(env, + &data->streamBuf, + &(src->next_input_byte)) == NOT_OK))) { + cinfo->err->error_exit((j_common_ptr) cinfo); } #ifdef DEBUG_IIO_JPEG @@ -988,12 +991,10 @@ imageio_fill_input_buffer(j_decompress_ptr cinfo) #ifdef DEBUG_IIO_JPEG printf("YO! Early EOI! ret = %d\n", ret); #endif - RELEASE_ARRAYS(env, data, src->next_input_byte); (*env)->CallVoidMethod(env, reader, JPEGImageReader_warningOccurredID, READ_NO_EOI); - if ((*env)->ExceptionCheck(env) - || !GET_ARRAYS(env, data, &(src->next_input_byte))) { + if ((*env)->ExceptionCheck(env)) { cinfo->err->error_exit((j_common_ptr) cinfo); } @@ -1008,97 +1009,6 @@ imageio_fill_input_buffer(j_decompress_ptr cinfo) return TRUE; } -/* - * With I/O suspension turned on, the JPEG library requires that all - * buffer filling be done at the top application level, using this - * function. Due to the way that backtracking works, this procedure - * saves all of the data that was left in the buffer when suspension - * occurred and read new data only at the end. - */ - -GLOBAL(void) -imageio_fill_suspended_buffer(j_decompress_ptr cinfo) -{ - struct jpeg_source_mgr *src = cinfo->src; - imageIODataPtr data = (imageIODataPtr) cinfo->client_data; - streamBufferPtr sb = &data->streamBuf; - JNIEnv *env = (JNIEnv *)JNU_GetEnv(the_jvm, JNI_VERSION_1_2); - jint ret; - size_t offset, buflen; - jobject input = NULL; - - /* - * The original (jpegdecoder.c) had code here that called - * InputStream.available and just returned if the number of bytes - * available was less than any remaining skip. Presumably this was - * to avoid blocking, although the benefit was unclear, as no more - * decompression can take place until more data is available, so - * the code would block on input a little further along anyway. - * ImageInputStreams don't have an available method, so we'll just - * block in the skip if we have to. - */ - - if (sb->remaining_skip) { - src->skip_input_data(cinfo, 0); - } - - /* Save the data currently in the buffer */ - offset = src->bytes_in_buffer; - if (src->next_input_byte > sb->buf) { - memcpy(sb->buf, src->next_input_byte, offset); - } - - - RELEASE_ARRAYS(env, data, src->next_input_byte); - - GET_IO_REF(input); - - buflen = sb->bufferLength - offset; - if (buflen <= 0) { - if (!GET_ARRAYS(env, data, &(src->next_input_byte))) { - cinfo->err->error_exit((j_common_ptr) cinfo); - } - RELEASE_ARRAYS(env, data, src->next_input_byte); - return; - } - - ret = (*env)->CallIntMethod(env, input, - JPEGImageReader_readInputDataID, - sb->hstreamBuffer, - offset, buflen); - if ((ret > 0) && ((unsigned int)ret > buflen)) ret = (int)buflen; - if ((*env)->ExceptionCheck(env) - || !GET_ARRAYS(env, data, &(src->next_input_byte))) { - cinfo->err->error_exit((j_common_ptr) cinfo); - } - /* - * If we have reached the end of the stream, then the EOI marker - * is missing. We accept such streams but generate a warning. - * The image is likely to be corrupted, though everything through - * the end of the last complete MCU should be usable. - */ - if (ret <= 0) { - jobject reader = data->imageIOobj; - RELEASE_ARRAYS(env, data, src->next_input_byte); - (*env)->CallVoidMethod(env, reader, - JPEGImageReader_warningOccurredID, - READ_NO_EOI); - if ((*env)->ExceptionCheck(env) - || !GET_ARRAYS(env, data, &(src->next_input_byte))) { - cinfo->err->error_exit((j_common_ptr) cinfo); - } - - sb->buf[offset] = (JOCTET) 0xFF; - sb->buf[offset + 1] = (JOCTET) JPEG_EOI; - ret = 2; - } - - src->next_input_byte = sb->buf; - src->bytes_in_buffer = ret + offset; - - return; -} - /* * Skip num_bytes worth of data. The buffer pointer and count are * advanced over num_bytes input bytes, using the input stream @@ -1160,16 +1070,13 @@ imageio_skip_input_data(j_decompress_ptr cinfo, long num_bytes) return; } - RELEASE_ARRAYS(env, data, src->next_input_byte); - GET_IO_REF(input); ret = (*env)->CallLongMethod(env, input, JPEGImageReader_skipInputBytesID, (jlong) num_bytes); - if ((*env)->ExceptionCheck(env) - || !GET_ARRAYS(env, data, &(src->next_input_byte))) { + if ((*env)->ExceptionCheck(env)) { cinfo->err->error_exit((j_common_ptr) cinfo); } @@ -1181,15 +1088,12 @@ imageio_skip_input_data(j_decompress_ptr cinfo, long num_bytes) */ if (ret <= 0) { reader = data->imageIOobj; - RELEASE_ARRAYS(env, data, src->next_input_byte); (*env)->CallVoidMethod(env, reader, JPEGImageReader_warningOccurredID, READ_NO_EOI); - - if ((*env)->ExceptionCheck(env) - || !GET_ARRAYS(env, data, &(src->next_input_byte))) { - cinfo->err->error_exit((j_common_ptr) cinfo); + if ((*env)->ExceptionCheck(env)) { + cinfo->err->error_exit((j_common_ptr) cinfo); } sb->buf[0] = (JOCTET) 0xFF; sb->buf[1] = (JOCTET) JPEG_EOI; @@ -1215,7 +1119,7 @@ imageio_term_source(j_decompress_ptr cinfo) JNIEnv *env = (JNIEnv *)JNU_GetEnv(the_jvm, JNI_VERSION_1_2); jobject reader = data->imageIOobj; if (src->bytes_in_buffer > 0) { - RELEASE_ARRAYS(env, data, src->next_input_byte); + RELEASE_ARRAYS(env, data, src->next_input_byte, JNI_ABORT, 0); (*env)->CallVoidMethod(env, reader, JPEGImageReader_pushBackID, @@ -1659,7 +1563,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImageHeader if (setjmp(jerr->setjmp_buffer)) { /* If we get here, the JPEG code has signaled an error while reading the header. */ - RELEASE_ARRAYS(env, data, src->next_input_byte); + RELEASE_ARRAYS(env, data, src->next_input_byte, JNI_ABORT, JNI_ABORT); if (!(*env)->ExceptionCheck(env)) { char buffer[JMSG_LENGTH_MAX]; (*cinfo->err->format_message) ((struct jpeg_common_struct *) cinfo, @@ -1678,7 +1582,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImageHeader (*env)->ExceptionClear(env); JNU_ThrowByName(env, "javax/imageio/IIOException", - "Array pin failed"); + "Get array elements failed"); return retval; } @@ -1701,7 +1605,11 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImageHeader printf("just read tables-only image; q table 0 at %p\n", cinfo->quant_tbl_ptrs[0]); #endif - RELEASE_ARRAYS(env, data, src->next_input_byte); + /* + * readImageHeader can be called independently, so + * we release the arrays when we return back. + */ + RELEASE_ARRAYS(env, data, src->next_input_byte, JNI_ABORT, 0); } else { /* * Now adjust the jpeg_color_space variable, which was set in @@ -1802,7 +1710,6 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImageHeader /* Leave the output space as CMYK */ } } - RELEASE_ARRAYS(env, data, src->next_input_byte); /* read icc profile data */ profileData = read_icc_profile(env, cinfo); @@ -1819,14 +1726,17 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImageHeader cinfo->out_color_space, cinfo->num_components, profileData); - if ((*env)->ExceptionCheck(env) - || !GET_ARRAYS(env, data, &(src->next_input_byte))) { + if ((*env)->ExceptionCheck(env)) { cinfo->err->error_exit((j_common_ptr) cinfo); } if (reset) { jpeg_abort_decompress(cinfo); } - RELEASE_ARRAYS(env, data, src->next_input_byte); + /* + * readImageHeader can be called independently, so + * we release the arrays when we return back. + */ + RELEASE_ARRAYS(env, data, src->next_input_byte, JNI_ABORT, 0); } return retval; @@ -1987,7 +1897,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImage if (setjmp(jerr->setjmp_buffer)) { /* If we get here, the JPEG code has signaled an error while reading. */ - RELEASE_ARRAYS(env, data, src->next_input_byte); + RELEASE_ARRAYS(env, data, src->next_input_byte, JNI_ABORT, JNI_ABORT); if (!(*env)->ExceptionCheck(env)) { char buffer[JMSG_LENGTH_MAX]; (*cinfo->err->format_message) ((struct jpeg_common_struct *) cinfo, @@ -2005,7 +1915,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImage (*env)->ExceptionClear(env); JNU_ThrowByName(env, "javax/imageio/IIOException", - "Array pin failed"); + "Get array elements failed"); return data->abortFlag; } @@ -2037,7 +1947,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImage jpeg_start_decompress(cinfo); if (numBands != cinfo->output_components) { - RELEASE_ARRAYS(env, data, src->next_input_byte); + RELEASE_ARRAYS(env, data, src->next_input_byte, JNI_ABORT, JNI_ABORT); JNU_ThrowByName(env, "javax/imageio/IIOException", "Invalid argument to native readImage"); return data->abortFlag; @@ -2046,7 +1956,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImage if (cinfo->output_components <= 0 || cinfo->image_width > (0xffffffffu / (unsigned int)cinfo->output_components)) { - RELEASE_ARRAYS(env, data, src->next_input_byte); + RELEASE_ARRAYS(env, data, src->next_input_byte, JNI_ABORT, JNI_ABORT); JNU_ThrowByName(env, "javax/imageio/IIOException", "Invalid number of output components"); return data->abortFlag; @@ -2055,7 +1965,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImage // Allocate a 1-scanline buffer scanLinePtr = (JSAMPROW)malloc(cinfo->image_width*cinfo->output_components); if (scanLinePtr == NULL) { - RELEASE_ARRAYS(env, data, src->next_input_byte); + RELEASE_ARRAYS(env, data, src->next_input_byte, JNI_ABORT, JNI_ABORT); JNU_ThrowByName( env, "java/lang/OutOfMemoryError", "Reading JPEG Stream"); @@ -2070,22 +1980,18 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImage // the first interesting pass. jpeg_start_output(cinfo, cinfo->input_scan_number); if (wantUpdates) { - RELEASE_ARRAYS(env, data, src->next_input_byte); (*env)->CallVoidMethod(env, this, JPEGImageReader_passStartedID, cinfo->input_scan_number-1); - if ((*env)->ExceptionCheck(env) - || !GET_ARRAYS(env, data, &(src->next_input_byte))) { + if ((*env)->ExceptionCheck(env)) { cinfo->err->error_exit((j_common_ptr) cinfo); } } } else if (wantUpdates) { - RELEASE_ARRAYS(env, data, src->next_input_byte); (*env)->CallVoidMethod(env, this, JPEGImageReader_passStartedID, 0); - if ((*env)->ExceptionCheck(env) - || !GET_ARRAYS(env, data, &(src->next_input_byte))) { + if ((*env)->ExceptionCheck(env)) { cinfo->err->error_exit((j_common_ptr) cinfo); } } @@ -2136,16 +2042,20 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImage } } - // And call it back to Java - RELEASE_ARRAYS(env, data, src->next_input_byte); + /* + * Optimisation to just commit the native pixel buffer + * content back to java array without releasing the + * native buffer. + */ + if (pb->isCopy) { + unpinPixelBuffer(env, pb, JNI_COMMIT); + } (*env)->CallVoidMethod(env, this, JPEGImageReader_acceptPixelsID, targetLine++, progressive); - - if ((*env)->ExceptionCheck(env) - || !GET_ARRAYS(env, data, &(src->next_input_byte))) { + if ((*env)->ExceptionCheck(env)) { cinfo->err->error_exit((j_common_ptr) cinfo); } @@ -2175,11 +2085,9 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImage done = TRUE; } if (wantUpdates) { - RELEASE_ARRAYS(env, data, src->next_input_byte); (*env)->CallVoidMethod(env, this, JPEGImageReader_passCompleteID); - if ((*env)->ExceptionCheck(env) - || !GET_ARRAYS(env, data, &(src->next_input_byte))) { + if ((*env)->ExceptionCheck(env)) { cinfo->err->error_exit((j_common_ptr) cinfo); } } @@ -2204,13 +2112,16 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImage this, JPEGImageReader_skipPastImageID, imageIndex); + if ((*env)->ExceptionCheck(env)) { + cinfo->err->error_exit((j_common_ptr) cinfo); + } } else { jpeg_finish_decompress(cinfo); } free(scanLinePtr); - RELEASE_ARRAYS(env, data, src->next_input_byte); + RELEASE_ARRAYS(env, data, src->next_input_byte, JNI_ABORT, 0); return data->abortFlag; } @@ -2405,8 +2316,16 @@ imageio_empty_output_buffer (j_compress_ptr cinfo) JNIEnv *env = (JNIEnv *)JNU_GetEnv(the_jvm, JNI_VERSION_1_2); jobject output = NULL; - RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte)); - + /* + * Optimization to not delete the native copy of stream buffer, + * but just commit the content back to the Java array. + * In case where we don't have a copy, we rely on JVM to maintain + * the native reference of Java array. + */ + jboolean isCopy = sb->isCopy; + if (isCopy) { + unpinStreamBuffer(env, sb, dest->next_output_byte, JNI_COMMIT); + } GET_IO_REF(output); (*env)->CallVoidMethod(env, @@ -2415,10 +2334,8 @@ imageio_empty_output_buffer (j_compress_ptr cinfo) sb->hstreamBuffer, 0, sb->bufferLength); - if ((*env)->ExceptionCheck(env) - || !GET_ARRAYS(env, data, - (const JOCTET **)(&dest->next_output_byte))) { - cinfo->err->error_exit((j_common_ptr) cinfo); + if ((*env)->ExceptionCheck(env)) { + cinfo->err->error_exit((j_common_ptr) cinfo); } dest->next_output_byte = sb->buf; @@ -2447,7 +2364,16 @@ imageio_term_destination (j_compress_ptr cinfo) if (datacount != 0) { jobject output = NULL; - RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte)); + /* + * Optimization to not delete the native copy of stream buffer, + * but just commit the content back to the Java array. + * In case where we don't have a copy, we rely on JVM to maintain + * the native reference of Java array. + */ + jboolean isCopy = sb->isCopy; + if (isCopy) { + unpinStreamBuffer(env, sb, dest->next_output_byte, JNI_COMMIT); + } GET_IO_REF(output); @@ -2457,17 +2383,13 @@ imageio_term_destination (j_compress_ptr cinfo) sb->hstreamBuffer, 0, datacount); - - if ((*env)->ExceptionCheck(env) - || !GET_ARRAYS(env, data, - (const JOCTET **)(&dest->next_output_byte))) { + if ((*env)->ExceptionCheck(env)) { cinfo->err->error_exit((j_common_ptr) cinfo); } } dest->next_output_byte = NULL; dest->free_in_buffer = 0; - } /* @@ -2668,7 +2590,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeTables if (setjmp(jerr->setjmp_buffer)) { /* If we get here, the JPEG code has signaled an error while writing. */ - RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte)); + RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte), JNI_ABORT, JNI_ABORT); if (!(*env)->ExceptionCheck(env)) { char buffer[JMSG_LENGTH_MAX]; (*cinfo->err->format_message) ((j_common_ptr) cinfo, @@ -2683,7 +2605,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeTables (*env)->ExceptionClear(env); JNU_ThrowByName(env, "javax/imageio/IIOException", - "Array pin failed"); + "Get array elements failed"); return; } @@ -2703,7 +2625,15 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeTables } jpeg_write_tables(cinfo); // Flushes the buffer for you - RELEASE_ARRAYS(env, data, NULL); + /* + * writeTables can be called independently, so + * we release the arrays when we return back. + * Also the table content in output_buffer is + * already flushed, so no need to commit the + * native copy of stream content back to the + * Java array. + */ + RELEASE_ARRAYS(env, data, NULL, JNI_ABORT, 0); } static void freeArray(UINT8** arr, jint size) { @@ -2766,7 +2696,6 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeImage UINT8** scale = NULL; boolean success = TRUE; - /* verify the inputs */ if (data == NULL) { @@ -2891,7 +2820,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeImage if (setjmp(jerr->setjmp_buffer)) { /* If we get here, the JPEG code has signaled an error while writing. */ - RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte)); + RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte), JNI_ABORT, JNI_ABORT); if (!(*env)->ExceptionCheck(env)) { char buffer[JMSG_LENGTH_MAX]; (*cinfo->err->format_message) ((j_common_ptr) cinfo, @@ -2973,7 +2902,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeImage free(scanLinePtr); JNU_ThrowByName(env, "javax/imageio/IIOException", - "Array pin failed"); + "Get array elements failed"); return data->abortFlag; } @@ -3006,7 +2935,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeImage scanptr = (int *) cinfo->script_space; scanData = (*env)->GetIntArrayElements(env, scanInfo, NULL); if (scanData == NULL) { - RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte)); + RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte), JNI_ABORT, JNI_ABORT); freeArray(scale, numBands); free(scanLinePtr); return data->abortFlag; @@ -3034,16 +2963,13 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeImage if (haveMetadata) { // Flush the buffer imageio_flush_destination(cinfo); - // Call Java to write the metadata - RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte)); + // Call Java to write the metadata. (*env)->CallVoidMethod(env, this, JPEGImageWriter_writeMetadataID); - if ((*env)->ExceptionCheck(env) - || !GET_ARRAYS(env, data, - (const JOCTET **)(&dest->next_output_byte))) { - cinfo->err->error_exit((j_common_ptr) cinfo); - } + if ((*env)->ExceptionCheck(env)) { + cinfo->err->error_exit((j_common_ptr) cinfo); + } } targetLine = 0; @@ -3053,20 +2979,29 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeImage // for each line in destHeight while ((data->abortFlag == JNI_FALSE) && (cinfo->next_scanline < cinfo->image_height)) { - // get the line from Java - RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte)); + /* + * Get a line of pixel data from Java. + * In case where we have native copy of Java pixel array, + * we need to just use JNI_ABORT to exclude any copy operation + * and then get new copy for next scanline. + * + * If we have direct reference to Java array, we rely on + * JVM to maintain the reference appropriately. + */ + jboolean isCopy = pb->isCopy; + if (isCopy) { + unpinPixelBuffer(env, pb, JNI_ABORT); + } (*env)->CallVoidMethod(env, this, JPEGImageWriter_grabPixelsID, targetLine); - if ((*env)->ExceptionCheck(env) - || !GET_ARRAYS(env, data, - (const JOCTET **)(&dest->next_output_byte))) { - cinfo->err->error_exit((j_common_ptr) cinfo); - } + if ((*env)->ExceptionCheck(env) || + (isCopy && (pinPixelBuffer(env, pb) == NOT_OK))) { + cinfo->err->error_exit((j_common_ptr) cinfo); + } // subsample it into our buffer - in = data->pixelBuf.buf.bp; out = scanLinePtr; pixelLimit = in + ((pixelBufferSize > data->pixelBuf.byteBufferLength) ? @@ -3108,7 +3043,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeImage freeArray(scale, numBands); free(scanLinePtr); - RELEASE_ARRAYS(env, data, NULL); + RELEASE_ARRAYS(env, data, NULL, 0, JNI_ABORT); return data->abortFlag; } diff --git a/src/java.desktop/unix/classes/sun/font/FontConfigManager.java b/src/java.desktop/unix/classes/sun/font/FontConfigManager.java index aa3693743c2c..4afa7a43122d 100644 --- a/src/java.desktop/unix/classes/sun/font/FontConfigManager.java +++ b/src/java.desktop/unix/classes/sun/font/FontConfigManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -267,8 +267,8 @@ public PhysicalFont registerFromFcInfo(FcCompFont fcInfo) { Font2D f2d = fm.findFont2D(fcInfo.firstFont.familyName, fcInfo.style, FontManager.NO_FALLBACK); - if (f2d instanceof PhysicalFont) { /* paranoia */ - return (PhysicalFont)f2d; + if (f2d instanceof PhysicalFont pf) { /* paranoia */ + return pf; } else { return null; } @@ -295,8 +295,8 @@ public PhysicalFont registerFromFcInfo(FcCompFont fcInfo) { Font2D f2d = fm.findFont2D(fcInfo.firstFont.familyName, fcInfo.style, FontManager.NO_FALLBACK); - if (f2d instanceof PhysicalFont) { /* paranoia */ - return (PhysicalFont)f2d; + if (f2d instanceof PhysicalFont pf) { /* paranoia */ + return pf; } else { return null; } @@ -387,8 +387,8 @@ public CompositeFont getFontConfigFont(String name, int style) { PhysicalFont physFont = null; if (family != null) { Font2D f2D = family.getFontWithExactStyleMatch(fcInfo.style); - if (f2D instanceof PhysicalFont) { - physFont = (PhysicalFont)f2D; + if (f2D instanceof PhysicalFont pf) { + physFont = pf; } } diff --git a/src/java.desktop/unix/classes/sun/font/NativeFont.java b/src/java.desktop/unix/classes/sun/font/NativeFont.java index a3617fc3cd97..49b819091f78 100644 --- a/src/java.desktop/unix/classes/sun/font/NativeFont.java +++ b/src/java.desktop/unix/classes/sun/font/NativeFont.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -251,8 +251,8 @@ FontStrike createStrike(FontStrikeDesc desc) { /* If no FileFont's are found, delegate font may be * a NativeFont, so we need to avoid recursing here. */ - if (delegateFont instanceof NativeFont) { - return new NativeStrike((NativeFont)delegateFont, desc); + if (delegateFont instanceof NativeFont nf) { + return new NativeStrike(nf, desc); } FontStrike delegate = delegateFont.createStrike(desc); return new DelegateStrike(this, desc, delegate); diff --git a/src/java.desktop/windows/classes/sun/awt/windows/WFileDialogPeer.java b/src/java.desktop/windows/classes/sun/awt/windows/WFileDialogPeer.java index 1db4bd7e5d11..a99569f9dc92 100644 --- a/src/java.desktop/windows/classes/sun/awt/windows/WFileDialogPeer.java +++ b/src/java.desktop/windows/classes/sun/awt/windows/WFileDialogPeer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -44,6 +44,15 @@ final class WFileDialogPeer extends WWindowPeer implements FileDialogPeer { private WComponentPeer parent; private FilenameFilter fileFilter; + /* + * The file dialog runs a nested message loop while it is open. + * Allow only one native file dialog at a time to prevent reentrant + * GetOpenFileName/GetSaveFileName. + */ + private static final Object showLock = new Object(); + private volatile boolean isShowRequested; + private volatile boolean isShowFinished; + private Vector blockedWindows = new Vector<>(); //Needed to fix 4152317 @@ -86,20 +95,51 @@ void initialize() { private native void _dispose(); @Override protected void disposeImpl() { + isShowRequested = false; WToolkit.targetDisposedPeer(target, this); _dispose(); } - private native void _show(); + private native boolean _show(); private native void _hide(); @Override public void show() { - new Thread(null, this::_show, "FileDialog", 0, false).start(); + synchronized (this) { + isShowRequested = true; + isShowFinished = false; + } + new Thread(null, this::showFileDialog, "FileDialog", 0, false).start(); + } + + private void showFileDialog() { + synchronized (showLock) { + if (!isShowRequested || isDisposed()) { + return; + } + if (_show()) { + waitForShowFinished(); + } + isShowRequested = false; + } + } + + private synchronized void waitForShowFinished() { + while (!isShowFinished) { + try { + wait(); + } catch (InterruptedException ignored) {} + } + } + + private synchronized void showFinished() { + isShowFinished = true; + notifyAll(); } @Override void hide() { + isShowRequested = false; _hide(); } @@ -116,6 +156,9 @@ void setHWnd(long hwnd) { window.modalEnable((Dialog)target); } } + if (hwnd != 0 && !isShowRequested) { + WToolkit.executeOnEventHandlerThread(target, this::_hide); + } } /* @@ -174,6 +217,7 @@ public void run() { fileDialog.setVisible(false); } }); + showFinished(); } // handleSelected() // NOTE: This method is called by privileged threads. @@ -191,6 +235,7 @@ public void run() { fileDialog.setVisible(false); } }); + showFinished(); } // handleCancel() //This whole static block is a part of 4152317 fix diff --git a/src/java.desktop/windows/native/libawt/windows/awt_FileDialog.cpp b/src/java.desktop/windows/native/libawt/windows/awt_FileDialog.cpp index e8128372ceb0..520097e07fb1 100644 --- a/src/java.desktop/windows/native/libawt/windows/awt_FileDialog.cpp +++ b/src/java.desktop/windows/native/libawt/windows/awt_FileDialog.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -560,7 +560,7 @@ Java_sun_awt_windows_WFileDialogPeer_setFilterString(JNIEnv *env, jclass cls, CATCH_BAD_ALLOC; } -JNIEXPORT void JNICALL +JNIEXPORT jboolean JNICALL Java_sun_awt_windows_WFileDialogPeer__1show(JNIEnv *env, jobject peer) { TRY; @@ -574,9 +574,12 @@ Java_sun_awt_windows_WFileDialogPeer__1show(JNIEnv *env, jobject peer) if (!AwtToolkit::GetInstance().PostMessage(WM_AWT_INVOKE_METHOD, (WPARAM)AwtFileDialog::Show, (LPARAM)peerGlobal)) { env->DeleteGlobalRef(peerGlobal); + return false; } - CATCH_BAD_ALLOC; + return true; + + CATCH_BAD_ALLOC_RET(false); } JNIEXPORT void JNICALL diff --git a/src/java.desktop/windows/native/libawt/windows/awt_TrayIcon.cpp b/src/java.desktop/windows/native/libawt/windows/awt_TrayIcon.cpp index 8b72fa7e0084..4362da11c2f0 100644 --- a/src/java.desktop/windows/native/libawt/windows/awt_TrayIcon.cpp +++ b/src/java.desktop/windows/native/libawt/windows/awt_TrayIcon.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -185,35 +185,7 @@ AwtTrayIcon* AwtTrayIcon::Create(jobject self, jobject parent) void AwtTrayIcon::InitNID(UINT uID) { - // fix for 6271589: we MUST set the size of the structure to match - // the shell version, otherwise some errors may occur (like missing - // balloon messages on win2k) - DLLVERSIONINFO dllVersionInfo; - dllVersionInfo.cbSize = sizeof(DLLVERSIONINFO); - int shellVersion = 5; // WIN_2000 - // MSDN: DllGetVersion should not be implicitly called, but rather - // loaded using GetProcAddress - HMODULE hShell = JDK_LoadSystemLibrary("Shell32.dll"); - if (hShell != NULL) { - DLLGETVERSIONPROC proc = (DLLGETVERSIONPROC)GetProcAddress(hShell, "DllGetVersion"); - if (proc != NULL) { - if (proc(&dllVersionInfo) == NOERROR) { - shellVersion = dllVersionInfo.dwMajorVersion; - } - } - } - FreeLibrary(hShell); - switch (shellVersion) { - case 5: // WIN_2000 - m_nid.cbSize = (BYTE *)(&m_nid.guidItem) - (BYTE *)(&m_nid.cbSize); - break; - case 6: // WIN_XP - m_nid.cbSize = (BYTE *)(&m_nid.hBalloonIcon) - (BYTE *)(&m_nid.cbSize); - break; - default: // WIN_VISTA - m_nid.cbSize = sizeof(m_nid); - break; - } + m_nid.cbSize = sizeof(m_nid); m_nid.hWnd = AwtTrayIcon::sm_msgWindow; m_nid.uID = uID; m_nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; diff --git a/src/java.naming/share/classes/sun/security/provider/certpath/ldap/LDAPCertStoreImpl.java b/src/java.naming/share/classes/sun/security/provider/certpath/ldap/LDAPCertStoreImpl.java index 8f18e04760a2..ebf09e57bd1b 100644 --- a/src/java.naming/share/classes/sun/security/provider/certpath/ldap/LDAPCertStoreImpl.java +++ b/src/java.naming/share/classes/sun/security/provider/certpath/ldap/LDAPCertStoreImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -52,6 +52,7 @@ import sun.security.provider.certpath.X509CertificatePair; import sun.security.util.Cache; import sun.security.util.Debug; +import sun.security.util.SecurityProperties; /** * Core implementation of a LDAP Cert Store. @@ -96,6 +97,16 @@ final class LDAPCertStoreImpl { private static final String PROP_DISABLE_APP_RESOURCE_FILES = "sun.security.certpath.ldap.disable.app.resource.files"; + /** + * Maximum size for a CRL downloaded through an LDAPCertStoreImpl + * in bytes. This can be controlled by the com.sun.security.crl.maxSize + * Security or System property. The System property, if set, overrides + * the Security property. The default size is 20MiB. + */ + private static final long MAX_CRL_DOWNLOAD_SIZE = + SecurityProperties.getOverridableLongProp( + "com.sun.security.crl.maxSize", 20971520, debug); + static { String s = System.getProperty(PROP_LIFETIME); if (s != null) { @@ -103,6 +114,13 @@ final class LDAPCertStoreImpl { } else { LIFETIME = DEFAULT_CACHE_LIFETIME; } + + // Add a debug message for the configured CRL download limit + if (debug != null) { + debug.println("Maximum downloadable CRL size: " + + MAX_CRL_DOWNLOAD_SIZE + + ((MAX_CRL_DOWNLOAD_SIZE < 0) ? " (DISABLED)" : "")); + } } /** @@ -672,12 +690,12 @@ private Collection getMatchingCrossCerts( return certs; } - /* + /** * Gets CRLs from an attribute id and location in the LDAP directory. * Returns a Collection containing only the CRLs that match the * specified X509CRLSelector. * - * @param name the location holding the attribute + * @param request the LDAP request used for this CRL fetch operation * @param id the attribute identifier * @param sel a X509CRLSelector that the CRLs must match * @return a Collection of CRLs found @@ -689,7 +707,26 @@ private Collection getCRLs(LDAPRequest request, String id, /* fetch the encoded crls from storage */ byte[][] encodedCRL; try { - encodedCRL = request.getValues(id); + byte[][] tmpCrls = request.getValues(id); + if (MAX_CRL_DOWNLOAD_SIZE > -1) { + int totalSize = 0; + for (byte[] tCrl : tmpCrls) { + totalSize += tCrl.length; + } + if (totalSize <= MAX_CRL_DOWNLOAD_SIZE) { + encodedCRL = tmpCrls; + } else { + if (debug != null) { + debug.println("Received " + tmpCrls.length + + " CRL(s). Combined length of " + totalSize + + " exceeds configured maximum. Discarding."); + } + encodedCRL = new byte[0][]; + } + } else { + // Download limits disabled + encodedCRL = tmpCrls; + } } catch (NamingException namingEx) { throw new CertStoreException(namingEx); } diff --git a/src/java.sql/share/classes/java/sql/Date.java b/src/java.sql/share/classes/java/sql/Date.java index 56d138afdce7..8914b365c2c1 100644 --- a/src/java.sql/share/classes/java/sql/Date.java +++ b/src/java.sql/share/classes/java/sql/Date.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,6 +27,8 @@ import java.time.Instant; import java.time.LocalDate; +import java.util.Calendar; +import java.util.GregorianCalendar; /** *

A thin wrapper around a millisecond value that allows @@ -273,6 +275,17 @@ public void setSeconds(int i) { */ static final long serialVersionUID = 1511598038487230103L; + /** + * The epoch millisecond value of 0002-01-01T00:00:00.000 at UTC in + * the Julian-Gregorian hybrid calendar system. + * We cannot use 1st January 1AD as different timezones could possibly + * offset the date back into BC, thus resulting in the incorrect BC year. + * While a one year margin is considerably larger than any possible + * timezone offset, it gives us a comfortable distance while still + * providing a faster code path for almost 1970 years. + */ + private static final long TWO_AD_AT_UTC_EPOCH_MILLIS = -62104233600000L; + /** * Obtains an instance of {@code Date} from a {@link LocalDate} object * with the same year, month and day of month value as the given @@ -300,7 +313,40 @@ public static Date valueOf(LocalDate date) { */ @SuppressWarnings("deprecation") public LocalDate toLocalDate() { - return LocalDate.of(getYear() + 1900, getMonth() + 1, getDate()); + return LocalDate.of( + toGregorianProlepticYear(getYear() + 1900, getTime()), + getMonth() + 1, + getDate()); + } + + /** + * Converts an era-relative calendar year into the correct proleptic year. + *

+ * The milliseconds is required to determine if the given year is a BC or AD year. + * + * @param year the era-relative calendar year. + * @param millis the epoch millisecond represented by the date with the given year + * used to determine the calendar era. + * @return the proleptic year corresponding to {@code year} and {@code millis} + */ + static int toGregorianProlepticYear(int year, long millis) { + // We need to determine the era of the year using the given millis. + // However, deriving a new calendar is a relatively expensive operation + // that we would ideally avoid if possible. + // Given that we can comfortably state that any dates on or after + // 0002-01-01 are AD, we only need to test dates earlier than this cutoff. + if (millis < TWO_AD_AT_UTC_EPOCH_MILLIS) { + final GregorianCalendar calendar = new GregorianCalendar(); + calendar.setTimeInMillis(millis); + if (calendar.get(Calendar.ERA) == GregorianCalendar.BC) { + // Adjust the BC date into a negative astronomical date. + // As there is no year 0 in the Gregorian calendar + // we also have to adjust the BC year by 1. + // 1 BC becomes year 0, 2 BC becomes year -1 and so on. + year = 1 - year; + } + } + return year; } /** diff --git a/src/java.sql/share/classes/java/sql/Timestamp.java b/src/java.sql/share/classes/java/sql/Timestamp.java index c550291bb959..b2c05335d1ef 100644 --- a/src/java.sql/share/classes/java/sql/Timestamp.java +++ b/src/java.sql/share/classes/java/sql/Timestamp.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -517,13 +517,14 @@ public static Timestamp valueOf(LocalDateTime dateTime) { */ @SuppressWarnings("deprecation") public LocalDateTime toLocalDateTime() { - return LocalDateTime.of(getYear() + 1900, - getMonth() + 1, - getDate(), - getHours(), - getMinutes(), - getSeconds(), - getNanos()); + return LocalDateTime.of( + Date.toGregorianProlepticYear(getYear() + 1900, getTime()), + getMonth() + 1, + getDate(), + getHours(), + getMinutes(), + getSeconds(), + getNanos()); } /** diff --git a/src/jdk.accessibility/windows/native/jabswitch/jabswitch.cpp b/src/jdk.accessibility/windows/native/jabswitch/jabswitch.cpp index fdd7ff524da0..7e4f63d23632 100644 --- a/src/jdk.accessibility/windows/native/jabswitch/jabswitch.cpp +++ b/src/jdk.accessibility/windows/native/jabswitch/jabswitch.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -53,23 +53,6 @@ static LPCTSTR STR_ACCESSBRIDGE = FILE* origFile; FILE* tempFile; -bool isXP() -{ - static bool isXPFlag = false; - OSVERSIONINFO osvi; - - // Initialize the OSVERSIONINFO structure. - ZeroMemory( &osvi, sizeof( osvi ) ); - osvi.dwOSVersionInfoSize = sizeof( osvi ); - - GetVersionEx( &osvi ); - - if ( osvi.dwMajorVersion == 5 ) // For Windows XP and Windows 2000 - isXPFlag = true; - - return isXPFlag ; -} - void enableJAB() { // Copy lines from orig to temp modifying the line containing // assistive_technologies= @@ -458,16 +441,14 @@ int main(int argc, char* argv[]) { enableWasRequested = true; error = modify(true); if (error == 0) { - if( !isXP() ) - regEnable(); + regEnable(); } } else if (_stricmp(argv[1], "-disable") == 0 || _stricmp(argv[1], "/disable") == 0) { badParams = false; disableWasRequested = true; error = modify(false); if (error == 0) { - if( !isXP() ) - regDisable(); + regDisable(); } } } 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 4a1cd3aba78b..ae64c8609ae4 100644 --- a/src/jdk.attach/linux/classes/sun/tools/attach/VirtualMachineImpl.java +++ b/src/jdk.attach/linux/classes/sun/tools/attach/VirtualMachineImpl.java @@ -45,6 +45,7 @@ import sun.jvmstat.monitor.MonitoredHost; import sun.jvmstat.monitor.MonitorException; +import sun.jvmstat.PlatformSupport; /* * Linux implementation of HotSpotVirtualMachine @@ -55,12 +56,13 @@ public class VirtualMachineImpl extends HotSpotVirtualMachine { // .java_pid. and .attach_pid. It is important that this // location is the same for all processes, otherwise the tools // will not be able to find all Hotspot processes. - // Any changes to this needs to be synchronized with HotSpot. - private static final Path TMPDIR = Path.of("/tmp"); + // This calls a Hotspot native method to get a consistent temporary + // directory. + private static final String vmTemp = PlatformSupport.getTemporaryDirectory(); + private static final Path TMPDIR = Path.of(vmTemp); private static final Path PROC = Path.of("/proc"); private static final Path STATUS = Path.of("status"); - private static final Path ROOT_TMP = Path.of("root/tmp"); String socket_path; private OperationProperties props = new OperationProperties(VERSION_1); // updated in ctor @@ -86,6 +88,9 @@ public class VirtualMachineImpl extends HotSpotVirtualMachine { // Then we attempt to find the socket file again. final File socket_file = findSocketFile(pid, ns_pid); socket_path = socket_file.getPath(); + if (!validateSocketFileLength(socket_file.getPath())) { + throw new AttachNotSupportedException("Socket file path too long: " + socket_path); + } if (!socket_file.exists()) { // Keep canonical version of File, to delete, in case target process ends and /proc link has gone: File f = createAttachFile(pid, ns_pid).getCanonicalFile(); @@ -255,7 +260,8 @@ private File createAttachFile(long pid, long ns_pid) throws AttachNotSupportedEx } private String findTargetProcessTmpDirectory(long pid) throws IOException { - final var tmpOnProcPidRoot = PROC.resolve(Long.toString(pid)).resolve(ROOT_TMP); + final var tmpOnProcPidRoot = PROC.resolve(Long.toString(pid)).resolve("root") + .resolve(vmTemp.startsWith("/") ? vmTemp.substring(1) : vmTemp); /* We need to handle at least 4 different cases: * 1. Caller and target processes share PID namespace and root @@ -429,6 +435,8 @@ private static boolean checkCatchesAndSendQuitTo(int pid, boolean throwIfNotRead static native void write(int fd, byte buf[], int off, int bufLen) throws IOException; + static native boolean validateSocketFileLength(String socketPath); + static { System.loadLibrary("attach"); } diff --git a/src/jdk.attach/linux/native/libattach/VirtualMachineImpl.c b/src/jdk.attach/linux/native/libattach/VirtualMachineImpl.c index fc9af9018352..df4fc54cc966 100644 --- a/src/jdk.attach/linux/native/libattach/VirtualMachineImpl.c +++ b/src/jdk.attach/linux/native/libattach/VirtualMachineImpl.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -76,11 +76,15 @@ JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_connect memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; - /* strncpy is safe because addr.sun_path was zero-initialized before. */ - strncpy(addr.sun_path, p, sizeof(addr.sun_path) - 1); + if (strlen(p) >= sizeof(addr.sun_path)) { + JNU_ThrowIOException(env, "Socket file path too long"); + } else { + /* strncpy is safe because addr.sun_path was zero-initialized before. */ + strncpy(addr.sun_path, p, sizeof(addr.sun_path) - 1); - if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { - err = errno; + if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { + err = errno; + } } if (isCopy) { @@ -256,3 +260,30 @@ JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_write } while (remaining > 0); } + +/* + * Class: sun_tools_attach_VirtualMachineImpl + * Method: validateSocketFileLength + * Signature: (Ljava/lang/String;)Z + */ +JNIEXPORT jboolean JNICALL Java_sun_tools_attach_VirtualMachineImpl_validateSocketFileLength + (JNIEnv *env, jclass cls, jstring path) +{ + jboolean isCopy; + const char* p = GetStringPlatformChars(env, path, &isCopy); + if (p == NULL) { + JNU_ThrowIOException(env, "Socket file path is null"); + return JNI_FALSE; + } + + size_t pathLength = strlen(p); + + if (isCopy) { + JNU_ReleaseStringPlatformChars(env, path, p); + } + + struct sockaddr_un addr; + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + return pathLength < sizeof(addr.sun_path); +} diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Infer.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Infer.java index 6021dbd7057d..65e555209dcf 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Infer.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Infer.java @@ -683,7 +683,13 @@ public Type instantiatePatternType(Type expressionType, TypeSymbol patternTypeSy //step 1: List expressionTypes = List.nil(); - List params = patternTypeSymbol.type.allparams(); + + // replace tvars in generic pattern type P with + // fresh tvars A1', ..., An' so that occurrences of A1, ..., An + // in the expression type are not confused with inference tvars + List declaredParams = patternTypeSymbol.type.allparams(); + List params = types.newInstances(declaredParams); + Type patternType = types.subst(patternTypeSymbol.type, declaredParams, params); List capturedWildcards = List.nil(); List todo = List.of(expressionType); while (todo.nonEmpty()) { @@ -713,8 +719,8 @@ public Type instantiatePatternType(Type expressionType, TypeSymbol patternTypeSy } //add synthetic captured ivars InferenceContext c = new InferenceContext(this, params); - Type patternType = c.asUndetVar(patternTypeSymbol.type); - List exprTypes = expressionTypes.map(t -> c.asUndetVar(t)); + Type undetPatternType = c.asUndetVar(patternType); + List exprTypes = expressionTypes.map(c::asUndetVar); capturedWildcards.forEach(s -> ((UndetVar) c.asUndetVar(s)).setNormal()); @@ -723,9 +729,9 @@ public Type instantiatePatternType(Type expressionType, TypeSymbol patternTypeSy for (Type exprType : exprTypes) { if (exprType.isParameterized()) { Type patternAsExpression = - types.asSuper(patternType, exprType.tsym); + types.asSuper(undetPatternType, exprType.tsym); if (patternAsExpression == null || - !types.isSameType(patternAsExpression, exprType)) { + !types.isSameType(patternAsExpression, exprType)) { return null; } } @@ -736,7 +742,7 @@ public Type instantiatePatternType(Type expressionType, TypeSymbol patternTypeSy //step 3: List freshVars = instantiatePatternVars(params, c); - Type substituted = c.asInstType(patternTypeSymbol.type); + Type substituted = c.asInstType(patternType); //step 4: return types.upward(substituted, freshVars); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Modules.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Modules.java index 5a1fe70dd9b5..4b60fe5987f0 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Modules.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Modules.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -1520,21 +1520,19 @@ private void completeModule(ModuleSymbol msym) { } Set readable = new LinkedHashSet<>(); - Set requiresTransitive = new HashSet<>(); - - for (RequiresDirective d : msym.requires) { - d.module.complete(); - readable.add(d.module); - Set s = retrieveRequiresTransitive(d.module); - Assert.checkNonNull(s, () -> "no entry in cache for " + d.module); - readable.addAll(s); - if (d.flags.contains(RequiresFlag.TRANSITIVE)) { - requiresTransitive.add(d.module); - requiresTransitive.addAll(s); + + if ((msym.flags() & Flags.AUTOMATIC_MODULE) != 0) { + readable.addAll(allModules()); + readable.remove(msym); + readable.forEach(Symbol::complete); + } else { + for (RequiresDirective d : msym.requires) { + d.module.complete(); + readable.add(d.module); + readable.addAll(retrieveRequiresTransitive(d.module)); } } - requiresTransitiveCache.put(msym, requiresTransitive); initVisiblePackages(msym, readable); for (ExportsDirective d: msym.exports) { if (d.packge != null) { @@ -1576,6 +1574,7 @@ private Set retrieveRequiresTransitive(ModuleSymbol msym) { } requiresTransitive.remove(msym); + requiresTransitiveCache.putIfAbsent(msym, requiresTransitive); } return requiresTransitive; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransPatterns.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransPatterns.java index 29a9bf3c8cb0..8ea78a8cc78f 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransPatterns.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransPatterns.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -258,12 +258,14 @@ public void visitTypeTest(JCInstanceOf tree) { extraConditions = translate(extraConditions); resultExpression = makeBinary(Tag.AND, resultExpression, extraConditions); } - if (currentValue != exprSym) { - resultExpression = - make.at(tree.pos).LetExpr(make.VarDef(currentValue, translatedExpr), - resultExpression).setType(syms.booleanType); - ((LetExpr) resultExpression).needsCond = true; - } + List tempVars = currentValue != exprSym + ? List.of(make.VarDef(currentValue, translatedExpr)) + : List.nil(); + resultExpression = + make.at(tree.pos).LetExpr(tempVars, + resultExpression).setType(syms.booleanType); + ((LetExpr) resultExpression).needsCond = true; + ((LetExpr) resultExpression).needsLineNumberTableEntry = true; result = bindingContext.decorateExpression(resultExpression); } finally { currentValue = prevCurrentValue; @@ -1555,7 +1557,8 @@ JCExpression decorateExpression(JCExpression expr) { //=> //(let T N; (let T' N$temp = E; N$temp instanceof T && (N = (T) N$temp == (T) N$temp)) && /*use of N*/) for (VarSymbol vsym : hoistedVarMap.values()) { - expr = make.at(expr.pos).LetExpr(makeHoistedVarDecl(expr.pos, vsym), expr).setType(expr.type); + int pos = TreeInfo.getStartPos(expr); + expr = make.at(pos).LetExpr(makeHoistedVarDecl(pos, vsym), expr).setType(expr.type); } return expr; } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java index 688ea1bd720b..0b111e07c75f 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -741,6 +741,11 @@ public CondItem genCond(JCTree _tree, boolean markBranches) { code.resolvePending(); LetExpr tree = (LetExpr) inner_tree; + + if (tree.needsLineNumberTableEntry) { + code.statBegin(tree.pos); + } + int limit = code.nextreg; int prevLetExprStart = code.setLetExprStackPos(code.state.stacksize); try { @@ -2435,6 +2440,10 @@ public void visitLiteral(JCLiteral tree) { public void visitLetExpr(LetExpr tree) { code.resolvePending(); + if (tree.needsLineNumberTableEntry) { + code.statBegin(tree.pos); + } + int limit = code.nextreg; int prevLetExprStart = code.setLetExprStackPos(code.state.stacksize); try { diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java index f0a8b6034dfa..717f95390fc7 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java @@ -3448,6 +3448,7 @@ public static class LetExpr extends JCExpression { public JCExpression expr; /**true if a expr should be run through Gen.genCond:*/ public boolean needsCond; + public boolean needsLineNumberTableEntry; protected LetExpr(List defs, JCExpression expr) { this.defs = defs; this.expr = expr; diff --git a/src/jdk.hotspot.agent/linux/native/libsaproc/AARCH64DwarfParser.cpp b/src/jdk.hotspot.agent/linux/native/libsaproc/AARCH64DwarfParser.cpp new file mode 100644 index 000000000000..b6a5d7ec3185 --- /dev/null +++ b/src/jdk.hotspot.agent/linux/native/libsaproc/AARCH64DwarfParser.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2026, NTT DATA. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include + +#include "aarch64Dwarf.hpp" + +/* + * Class: sun_jvm_hotspot_debugger_linux_aarch64_AARCH64DwarfParser + * Method: createDwarfContext + * Signature: (J)J + */ +extern "C" +JNIEXPORT jlong JNICALL Java_sun_jvm_hotspot_debugger_linux_aarch64_AARCH64DwarfParser_createDwarfContext + (JNIEnv *env, jclass this_cls, jlong lib) { + AARCH64DwarfParser *parser = new AARCH64DwarfParser(reinterpret_cast(lib)); + if (!parser->is_parseable()) { + jclass ex_cls = env->FindClass("sun/jvm/hotspot/debugger/DebuggerException"); + if (!env->ExceptionCheck()) { + env->ThrowNew(ex_cls, "DWARF not found"); + } + delete parser; + return 0L; + } + + return reinterpret_cast(parser); +} + +/* + * Class: sun_jvm_hotspot_debugger_linux_aarch64_AARCH64DwarfParser + * Method: isRASigned0 + * Signature: (J)Z + */ +extern "C" +JNIEXPORT jboolean JNICALL Java_sun_jvm_hotspot_debugger_linux_aarch64_AARCH64DwarfParser_isRASigned0 + (JNIEnv *env, jobject this_obj, jlong inst) { + AARCH64DwarfParser *parser = reinterpret_cast(inst); + return static_cast(parser->is_ra_signed()); +} diff --git a/src/jdk.hotspot.agent/linux/native/libsaproc/LinuxAARCH64DebuggerLocal.cpp b/src/jdk.hotspot.agent/linux/native/libsaproc/LinuxAARCH64DebuggerLocal.cpp new file mode 100644 index 000000000000..4f96241674b1 --- /dev/null +++ b/src/jdk.hotspot.agent/linux/native/libsaproc/LinuxAARCH64DebuggerLocal.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2026, NTT DATA. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include +#include "libproc.h" + + +/* + * Class: sun_jvm_hotspot_debugger_linux_aarch64_LinuxAARCH64DebuggerLocal + * Method: isPACEnabled0 + * Signature: (J)Z + */ +extern "C" +JNIEXPORT jboolean JNICALL Java_sun_jvm_hotspot_debugger_linux_aarch64_LinuxAARCH64DebuggerLocal_isPACEnabled0 + (JNIEnv *env, jobject this_obj, jlong inst) { + return pac_enabled(reinterpret_cast(inst)); +} diff --git a/src/jdk.hotspot.agent/linux/native/libsaproc/LinuxDebuggerLocal.cpp b/src/jdk.hotspot.agent/linux/native/libsaproc/LinuxDebuggerLocal.cpp index 214e2f21ac6e..de6e9fcc4fc4 100644 --- a/src/jdk.hotspot.agent/linux/native/libsaproc/LinuxDebuggerLocal.cpp +++ b/src/jdk.hotspot.agent/linux/native/libsaproc/LinuxDebuggerLocal.cpp @@ -290,12 +290,6 @@ JNIEXPORT void JNICALL Java_sun_jvm_hotspot_debugger_linux_LinuxDebuggerLocal_at THROW_NEW_DEBUGGER_EXCEPTION(msg); } -#ifdef __aarch64__ - if (pac_enabled(ph)) { - printf("WARNING: PAC is enabled. Stack traces might be incomplete.\n"); - } -#endif - env->SetLongField(this_obj, p_ps_prochandle_ID, (jlong)(intptr_t)ph); fillThreadsAndLoadObjects(env, this_obj, ph); } @@ -321,12 +315,6 @@ JNIEXPORT void JNICALL Java_sun_jvm_hotspot_debugger_linux_LinuxDebuggerLocal_at THROW_NEW_DEBUGGER_EXCEPTION("Can't attach to the core file. For more information, export LIBSAPROC_DEBUG=1 and try again."); } -#ifdef __aarch64__ - if (pac_enabled(ph)) { - printf("WARNING: PAC is enabled. Stack traces might be incomplete.\n"); - } -#endif - env->SetLongField(this_obj, p_ps_prochandle_ID, (jlong)(intptr_t)ph); fillThreadsAndLoadObjects(env, this_obj, ph); } diff --git a/src/jdk.hotspot.agent/linux/native/libsaproc/aarch64Dwarf.cpp b/src/jdk.hotspot.agent/linux/native/libsaproc/aarch64Dwarf.cpp new file mode 100644 index 000000000000..e357dffddc2a --- /dev/null +++ b/src/jdk.hotspot.agent/linux/native/libsaproc/aarch64Dwarf.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2026, NTT DATA. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "aarch64Dwarf.hpp" + + +bool AARCH64DwarfParser::process_arch_specific_dwarf_instructions(const unsigned char op) { + switch(op) { + case 0x2d: // DW_CFA_AARCH64_negate_ra_state + if (_sign_state == RA_NOT_SIGNED) { + _sign_state = RA_SIGNED_SP; + } else if (_sign_state == RA_SIGNED_SP) { + _sign_state = RA_NOT_SIGNED; + } else { + print_error("DWARF: DW_CFA_AARCH64_negate_ra_state: illegal state (%d)\n", _sign_state); + return false; + } + break; + case 0x2c: // DW_CFA_AARCH64_negate_ra_state_with_pc + if (_sign_state == RA_NOT_SIGNED) { + _sign_state = RA_SIGNED_SP_PC; + } else if (_sign_state == RA_SIGNED_SP_PC) { + _sign_state = RA_NOT_SIGNED; + } else { + print_error("DWARF: DW_CFA_AARCH64_negate_ra_state_with_pc: illegal state (%d)\n", _sign_state); + return false; + } + break; + case 0x2b: // DW_CFA_AARCH64_set_ra_state + _sign_state = static_cast(read_leb(false)); + read_leb(false); // operand 2: dummy + break; + default: + return false; + } + return true; +} + +void AARCH64DwarfParser::remember_arch_specific_state() { + remember_state.push(_sign_state); +} + +void AARCH64DwarfParser::restore_arch_specific_state() { + if (remember_state.empty()) { + print_error("DWARF Error: DW_CFA_restore_state for AArch64 is empty.\n"); + return; + } + _sign_state = remember_state.top(); + remember_state.pop(); +} diff --git a/src/jdk.hotspot.agent/linux/native/libsaproc/aarch64Dwarf.hpp b/src/jdk.hotspot.agent/linux/native/libsaproc/aarch64Dwarf.hpp new file mode 100644 index 000000000000..d57ce9ab7118 --- /dev/null +++ b/src/jdk.hotspot.agent/linux/native/libsaproc/aarch64Dwarf.hpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2026, NTT DATA. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef AARCH64_DWARF_H +#define AARCH64_DWARF_H + +#include + +#include "dwarf.hpp" + +enum RASignState { + RA_NOT_SIGNED = 0, + RA_SIGNED_SP, + RA_SIGNED_SP_PC +}; + +class AARCH64DwarfParser : public DwarfParser { + private: + RASignState _sign_state; // RA_SIGN_STATE pseudo DWARF register + std::stack remember_state; + + protected: + virtual bool process_arch_specific_dwarf_instructions(const unsigned char op); + virtual void remember_arch_specific_state(); + virtual void restore_arch_specific_state(); + + public: + // RA_SIGN_STATE should be initialized by DW_AARCH64_RA_NOT_SIGNED. + AARCH64DwarfParser(lib_info* lib) : DwarfParser(lib), _sign_state(RA_NOT_SIGNED), remember_state() {} + ~AARCH64DwarfParser() {} + + bool is_ra_signed() { return _sign_state != RA_NOT_SIGNED; } +}; + +#endif diff --git a/src/jdk.hotspot.agent/linux/native/libsaproc/dwarf.cpp b/src/jdk.hotspot.agent/linux/native/libsaproc/dwarf.cpp index 22ddf8f167ec..bb7328379f02 100644 --- a/src/jdk.hotspot.agent/linux/native/libsaproc/dwarf.cpp +++ b/src/jdk.hotspot.agent/linux/native/libsaproc/dwarf.cpp @@ -210,6 +210,7 @@ void DwarfParser::parse_dwarf_instructions(uintptr_t begin, uintptr_t pc, const break; case 0x0a: // DW_CFA_remember_state remember_state.push(_state); + remember_arch_specific_state(); break; case 0x0b: // DW_CFA_restore_state if (remember_state.empty()) { @@ -218,29 +219,18 @@ void DwarfParser::parse_dwarf_instructions(uintptr_t begin, uintptr_t pc, const } _state = remember_state.top(); remember_state.pop(); + restore_arch_specific_state(); break; case 0xc0: {// DW_CFA_restore enum DWARF_Register reg = static_cast(opa); _state.offset_from_cfa[reg] = _initial_state.offset_from_cfa[reg]; break; } -#ifdef __aarch64__ - // SA hasn't yet supported Pointer Authetication Code (PAC), so following - // instructions would be ignored with warning message. - // https://github.com/ARM-software/abi-aa/blob/2025Q4/aadwarf64/aadwarf64.rst - case 0x2d: // DW_CFA_AARCH64_negate_ra_state - print_debug("DWARF: DW_CFA_AARCH64_negate_ra_state is unimplemented.\n", op); - break; - case 0x2c: // DW_CFA_AARCH64_negate_ra_state_with_pc - print_debug("DWARF: DW_CFA_AARCH64_negate_ra_state_with_pc is unimplemented.\n", op); - break; - case 0x2b: // DW_CFA_AARCH64_set_ra_state - print_debug("DWARF: DW_CFA_AARCH64_set_ra_state is unimplemented.\n", op); - break; -#endif default: - print_debug("DWARF: Unknown opcode: 0x%x\n", op); - return; + if (!process_arch_specific_dwarf_instructions(op)) { + print_debug("DWARF: Unknown opcode: 0x%x\n", op); + return; + } } } } diff --git a/src/jdk.hotspot.agent/linux/native/libsaproc/dwarf.hpp b/src/jdk.hotspot.agent/linux/native/libsaproc/dwarf.hpp index 30d5ddaa50b8..38ffaceb687e 100644 --- a/src/jdk.hotspot.agent/linux/native/libsaproc/dwarf.hpp +++ b/src/jdk.hotspot.agent/linux/native/libsaproc/dwarf.hpp @@ -73,16 +73,30 @@ class DwarfParser { struct DwarfState _state; void init_state(struct DwarfState& st); - uintptr_t read_leb(bool sign); uint64_t get_entry_length(); bool process_cie(unsigned char *start_of_entry, uint32_t id); void parse_dwarf_instructions(uintptr_t begin, uintptr_t pc, const unsigned char *end); uint32_t get_decoded_value(unsigned char enc); unsigned int get_pc_range(); + protected: + uintptr_t read_leb(bool sign); + + virtual bool process_arch_specific_dwarf_instructions(const unsigned char op) { + return false; // Do nothing by default - it should be implemented by inherited classes. + }; + + virtual void remember_arch_specific_state() { + // Do nothing by default - it should be implemented by inherited classes. + }; + + virtual void restore_arch_specific_state() { + // Do nothing by default - it should be implemented by inherited classes. + }; + public: DwarfParser(lib_info *lib); - ~DwarfParser() {} + virtual ~DwarfParser() {} bool process_dwarf(const uintptr_t pc); enum DWARF_Register get_cfa_register() { return _state.cfa_reg; } int get_cfa_offset() { return _state.cfa_offset; } diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/HotSpotAgent.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/HotSpotAgent.java index cb708f9b2849..cea23da2c575 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/HotSpotAgent.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/HotSpotAgent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2021, Azul Systems, Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -40,6 +40,7 @@ import sun.jvm.hotspot.debugger.NoSuchSymbolException; import sun.jvm.hotspot.debugger.bsd.BsdDebuggerLocal; import sun.jvm.hotspot.debugger.linux.LinuxDebuggerLocal; +import sun.jvm.hotspot.debugger.linux.aarch64.LinuxAARCH64DebuggerLocal; import sun.jvm.hotspot.debugger.remote.RemoteDebugger; import sun.jvm.hotspot.debugger.remote.RemoteDebuggerClient; import sun.jvm.hotspot.debugger.remote.RemoteDebuggerServer; @@ -551,28 +552,28 @@ private void setupJVMLibNamesWin32() { private void setupDebuggerLinux() { setupJVMLibNamesLinux(); - if (cpu.equals("amd64")) { - machDesc = new MachineDescriptionAMD64(); - } else if (cpu.equals("ppc64")) { - machDesc = new MachineDescriptionPPC64(); - } else if (cpu.equals("aarch64")) { + if (cpu.equals("aarch64")) { machDesc = new MachineDescriptionAArch64(); - } else if (cpu.equals("riscv64")) { - machDesc = new MachineDescriptionRISCV64(); + debugger = new LinuxAARCH64DebuggerLocal(machDesc, !isServer); } else { - try { - machDesc = (MachineDescription) - Class.forName("sun.jvm.hotspot.debugger.MachineDescription" + - cpu.toUpperCase()).getDeclaredConstructor().newInstance(); - } catch (Exception e) { - throw new DebuggerException("Linux not supported on machine type " + cpu); - } + if (cpu.equals("amd64")) { + machDesc = new MachineDescriptionAMD64(); + } else if (cpu.equals("ppc64")) { + machDesc = new MachineDescriptionPPC64(); + } else if (cpu.equals("riscv64")) { + machDesc = new MachineDescriptionRISCV64(); + } else { + try { + machDesc = (MachineDescription) + Class.forName("sun.jvm.hotspot.debugger.MachineDescription" + + cpu.toUpperCase()).getDeclaredConstructor().newInstance(); + } catch (Exception _) { + throw new DebuggerException("Linux not supported on machine type " + cpu); + } + } + debugger = new LinuxDebuggerLocal(machDesc, !isServer); } - LinuxDebuggerLocal dbg = - new LinuxDebuggerLocal(machDesc, !isServer); - debugger = dbg; - attachDebugger(); } diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/MachineDescriptionAArch64.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/MachineDescriptionAArch64.java index 449ab72be168..ab78cd8dfb9e 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/MachineDescriptionAArch64.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/MachineDescriptionAArch64.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2014, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,6 +25,13 @@ package sun.jvm.hotspot.debugger; public class MachineDescriptionAArch64 extends MachineDescriptionTwosComplement implements MachineDescription { + + private boolean pac; + + public MachineDescriptionAArch64() { + pac = false; + } + public long getAddressSize() { return 8; } @@ -36,4 +43,13 @@ public boolean isLP64() { public boolean isBigEndian() { return false; } + + public void enablePAC() { + pac = true; + } + + public boolean isPACEnabled() { + return pac; + } + } diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/DwarfCFrame.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/DwarfCFrame.java index 7baa399ea75e..bc789586388b 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/DwarfCFrame.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/DwarfCFrame.java @@ -32,6 +32,7 @@ import sun.jvm.hotspot.debugger.cdbg.CFrame; import sun.jvm.hotspot.debugger.cdbg.ClosestSymbol; import sun.jvm.hotspot.debugger.cdbg.basic.BasicCFrame; +import sun.jvm.hotspot.debugger.linux.aarch64.AARCH64DwarfParser; import sun.jvm.hotspot.runtime.VM; public class DwarfCFrame extends BasicCFrame { @@ -53,7 +54,8 @@ public class DwarfCFrame extends BasicCFrame { protected static DwarfParser createDwarfParser(LinuxDebugger linuxDbg, Address pc) { Address libptr = linuxDbg.findLibPtrByAddress(pc); if (libptr != null) { - DwarfParser dwarf = new DwarfParser(libptr); + DwarfParser dwarf = linuxDbg.getCPU().equals("aarch64") ? new AARCH64DwarfParser(libptr) + : new DwarfParser(libptr); dwarf.processDwarf(pc); return dwarf; } diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/DwarfParser.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/DwarfParser.java index 3e8099cf75b2..d3185302b9c9 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/DwarfParser.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/DwarfParser.java @@ -31,7 +31,7 @@ public class DwarfParser { private static final Cleaner CLEANER = Cleaner.create(); - private final long p_dwarf_context; // native dwarf context handle + protected final long p_dwarf_context; // native dwarf context handle private static native void init0(); private static native long createDwarfContext(long lib); @@ -46,8 +46,10 @@ private static Runnable cleanerFor(long context) { return () -> DwarfParser.destroyDwarfContext(context); } - public DwarfParser(Address lib) { - p_dwarf_context = createDwarfContext(lib.asLongValue()); + // Declare this constructor as protected because it is danger if 3rd party classes passes + // non-pointer value of DwarfParser class in JNI. + protected DwarfParser(long ptr) { + p_dwarf_context = ptr; if (p_dwarf_context == 0L) { throw new DebuggerException("Could not create DWARF context"); @@ -56,6 +58,10 @@ public DwarfParser(Address lib) { CLEANER.register(this, cleanerFor(p_dwarf_context)); } + public DwarfParser(Address lib) { + this(createDwarfContext(lib.asLongValue())); + } + public boolean isIn(Address pc) { return isIn0(pc.asLongValue()); } 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 856981bb73c3..0a1b8a7ebeb1 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 @@ -71,7 +71,7 @@ can be fetched. The readJ(Type) routines here will throw a public class LinuxDebuggerLocal extends DebuggerBase implements LinuxDebugger { private boolean useGCC32ABI; private boolean attached; - private long p_ps_prochandle; // native debugger handle + protected long p_ps_prochandle; // native debugger handle private boolean isCore; // CDebugger support @@ -298,6 +298,10 @@ private void fillNSpidMap(Path proc) { } } + protected void onAttach() { + // Do nothing by default: it should be implemented by inherited classes. + } + /** From the Debugger interface via JVMDebugger */ public synchronized void attach(int processID) throws DebuggerException { checkAttached(); @@ -322,6 +326,8 @@ public void doit(LinuxDebuggerLocal debugger) { debugger.attached = true; debugger.isCore = false; findABIVersion(); + + onAttach(); } } @@ -339,6 +345,8 @@ public synchronized void attach(String execName, String coreName) { attached = true; isCore = true; findABIVersion(); + + onAttach(); } /** From the Debugger interface via JVMDebugger */ diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/aarch64/AARCH64DwarfParser.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/aarch64/AARCH64DwarfParser.java new file mode 100644 index 000000000000..00f986aac7ff --- /dev/null +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/aarch64/AARCH64DwarfParser.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2026, NTT DATA. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +package sun.jvm.hotspot.debugger.linux.aarch64; + +import sun.jvm.hotspot.debugger.Address; +import sun.jvm.hotspot.debugger.linux.DwarfParser; + + +public class AARCH64DwarfParser extends DwarfParser { + + private static native long createDwarfContext(long lib); + + public AARCH64DwarfParser(Address lib) { + super(createDwarfContext(lib.asLongValue())); + } + + /** + * @return true if return address (RA) is signed by PAC. + */ + public boolean isRASigned() { + return isRASigned0(p_dwarf_context); + } + + public native boolean isRASigned0(long inst); + +} diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/aarch64/LinuxAARCH64CFrame.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/aarch64/LinuxAARCH64CFrame.java index 8e09502cc378..fe05fd13dee5 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/aarch64/LinuxAARCH64CFrame.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/aarch64/LinuxAARCH64CFrame.java @@ -120,6 +120,13 @@ private JavaThread getJavaThreadFromThreadProxy(ThreadProxy tp) { throw new DebuggerException("JavaThread not found"); } + // Most of code is copied from AARCH64Frame.java. + // CFrame need to consider PAC in native frame even if -XX:UseBranchProtection is disabled. + // (_rop_protection in HotSpot) + private Address stripPAC(Address addr) { + return addr.andWithMask(AARCH64Frame.pacMask()); + } + @Override public CFrame sender(ThreadProxy thread, Address senderSP, Address senderFP, Address senderPC) { if (linuxDbg().isSignalTrampoline(pc())) { @@ -167,6 +174,13 @@ public CFrame sender(ThreadProxy thread, Address senderSP, Address senderFP, Add return null; } + // Strip PAC + if (((MachineDescriptionAArch64)linuxDbg().getMachineDescription()).isPACEnabled() && + ((dwarf() != null && ((AARCH64DwarfParser)dwarf()).isRASigned() /* for native */ ) || + (AARCH64Frame.ropProtection() != 0 /* for Java */ ))) { + senderPC = stripPAC(senderPC); + } + DwarfParser senderDwarf = null; boolean fallback = false; try { diff --git a/test/hotspot/jtreg/compiler/integerArithmetic/TestDivByZeroInLiveCFGPath.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/aarch64/LinuxAARCH64DebuggerLocal.java similarity index 53% rename from test/hotspot/jtreg/compiler/integerArithmetic/TestDivByZeroInLiveCFGPath.java rename to src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/aarch64/LinuxAARCH64DebuggerLocal.java index 6eaf3d86e71f..8410143686e6 100644 --- a/test/hotspot/jtreg/compiler/integerArithmetic/TestDivByZeroInLiveCFGPath.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/aarch64/LinuxAARCH64DebuggerLocal.java @@ -1,6 +1,6 @@ - /* * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2026, NTT DATA. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -20,45 +20,34 @@ * 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 8383815 - * @summary C2: assert(false) failed: malformed IfNode with 1 outputs - * @run main/othervm -XX:CompileCommand=compileonly,${test.main.class}*::* -XX:-TieredCompilation -Xbatch -XX:PerMethodTrapLimit=0 ${test.main.class} - * @run main ${test.main.class} - */ +package sun.jvm.hotspot.debugger.linux.aarch64; -package compiler.integerArithmetic; +import sun.jvm.hotspot.debugger.linux.LinuxDebuggerLocal; +import sun.jvm.hotspot.debugger.DebuggerException; +import sun.jvm.hotspot.debugger.MachineDescription; +import sun.jvm.hotspot.debugger.MachineDescriptionAArch64; -public class TestDivByZeroInLiveCFGPath { - static long lFld; - static int iArr[] = new int[400]; - public static void main(String[] strArr) { - for (int i = 0; i < 10; i++) { - test(); - } +public class LinuxAARCH64DebuggerLocal extends LinuxDebuggerLocal { + + public LinuxAARCH64DebuggerLocal(MachineDescription machDesc, boolean useCache) throws DebuggerException { + super(machDesc, useCache); } - static void test() { - int x; - for (int i = 9; i < 100; ++i) { - int j = 100; - while (--j > 0) { - iArr[1] = (int) lFld; - } - try { - iArr[1] = (5 / j); - x = (i / iArr[8]); - } catch (ArithmeticException a_e) { - } + @Override + protected void onAttach() { + if (isPACEnabled()) { + ((MachineDescriptionAArch64)getMachineDescription()).enablePAC(); } + } - for (int i = 18; i < 50; i++) { - iArr[2] += lFld; - } + public boolean isPACEnabled() { + return isPACEnabled0(p_ps_prochandle); } -} + private native boolean isPACEnabled0(long inst); + +} diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/aarch64/AARCH64Frame.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/aarch64/AARCH64Frame.java index 5e73150c6cff..d94aef3b3286 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/aarch64/AARCH64Frame.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/aarch64/AARCH64Frame.java @@ -83,6 +83,14 @@ public void update(Observable o, Object data) { }); } + public static long ropProtection() { + return ropProtectionField.getValue(); + } + + public static long pacMask() { + return pacMaskField.getValue(); + } + private static synchronized void initialize(TypeDataBase db) { INTERPRETER_FRAME_MDX_OFFSET = INTERPRETER_FRAME_METHOD_OFFSET - 1; INTERPRETER_FRAME_PADDING_OFFSET = INTERPRETER_FRAME_MDX_OFFSET - 1; diff --git a/src/jdk.httpserver/share/classes/module-info.java b/src/jdk.httpserver/share/classes/module-info.java index 0a0e77c628f9..973fe3b87264 100644 --- a/src/jdk.httpserver/share/classes/module-info.java +++ b/src/jdk.httpserver/share/classes/module-info.java @@ -68,7 +68,7 @@ * cache. If not, then it will be closed. * *

  • {@systemProperty sun.net.httpserver.maxReqHeaders} (default: 200)
    - * The maxiumum number of header fields accepted in a request. If this limit is exceeded + * The maximum number of header fields accepted in a request. If this limit is exceeded * while the headers are being read, then the connection is terminated and the request ignored. * If the value is less than or equal to zero, then the default value is used. *

  • @@ -83,7 +83,7 @@ * If the value is less than or equal to zero, there is no limit. * *
  • {@systemProperty sun.net.httpserver.maxReqTime} (default: -1)
    - * The maximum time in milliseconds allowed to receive a request headers and body. + * The maximum time in seconds allowed to receive a request headers and body. * In practice, the actual time is a function of request size, network speed, and handler * processing delays. A value less than or equal to zero means the time is not limited. * If the limit is exceeded then the connection is terminated and the handler will receive a @@ -91,7 +91,7 @@ * that may mean requests are aborted later than the specified interval. *

  • *
  • {@systemProperty sun.net.httpserver.maxRspTime} (default: -1)
    - * The maximum time in milliseconds allowed to receive a response headers and body. + * The maximum time in seconds allowed to receive a response headers and body. * In practice, the actual time is a function of response size, network speed, and handler * processing delays. A value less than or equal to zero means the time is not limited. * If the limit is exceeded then the connection is terminated and the handler will receive a diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java index 3d77a61c0be1..f0a8efe1a6b1 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java @@ -770,15 +770,24 @@ public void run() { requestLine, "Bad request line"); return; } + + // Read the request URI String uriStr = requestLine.substring(start, space); + // Reject ambiguous URIs + if (uriStr.startsWith("//")) { + reject(Code.HTTP_BAD_REQUEST, + requestLine, "Bad request URI"); + return; + } URI uri; try { uri = new URI(uriStr); } catch (URISyntaxException e3) { reject(Code.HTTP_BAD_REQUEST, - requestLine, "URISyntaxException thrown"); + requestLine, "Bad request URI"); return; } + start = space+1; String version = requestLine.substring(start); Headers headers = req.headers(); diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/FileServerHandler.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/FileServerHandler.java index cbf032e83985..08ea357b7f4b 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/FileServerHandler.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/FileServerHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -106,16 +106,22 @@ private void handleGET(HttpExchange exchange, Path path) throws IOException { private void handleSupportedMethod(HttpExchange exchange, Path path, boolean writeBody) throws IOException { + boolean requestURIEndsWithSlash = pathEndsWithSlash(exchange); if (Files.isDirectory(path)) { - if (missingSlash(exchange)) { + if (!requestURIEndsWithSlash) { handleMovedPermanently(exchange); return; } - if (indexFile(path) != null) { - serveFile(exchange, indexFile(path), writeBody); + Path indexFile = indexFile(path); + if (indexFile != null) { + serveFile(exchange, indexFile, writeBody); } else { listFiles(exchange, path, writeBody); } + } + // Disallow non-directory paths ending with slash + else if (requestURIEndsWithSlash) { + handleNotFound(exchange); } else { serveFile(exchange, path, writeBody); } @@ -126,10 +132,6 @@ private void handleMovedPermanently(HttpExchange exchange) throws IOException { exchange.sendResponseHeaders(301, RSPBODY_EMPTY); } - private void handleForbidden(HttpExchange exchange) throws IOException { - exchange.sendResponseHeaders(403, RSPBODY_EMPTY); - } - private void handleNotFound(HttpExchange exchange) throws IOException { String fileNotFound = ResourceBundleHelper.getMessage("html.not.found"); var bytes = (openHTML @@ -161,8 +163,8 @@ private String getRedirectURI(URI uri) { return query == null ? redirectPath : redirectPath + "?" + query; } - private static boolean missingSlash(HttpExchange exchange) { - return !exchange.getRequestURI().getPath().endsWith("/"); + private static boolean pathEndsWithSlash(HttpExchange exchange) { + return exchange.getRequestURI().getPath().endsWith("/"); } private static String contextPath(HttpExchange exchange) { diff --git a/src/jdk.internal.jvmstat/linux/classes/sun/jvmstat/PlatformSupportImpl.java b/src/jdk.internal.jvmstat/linux/classes/sun/jvmstat/PlatformSupportImpl.java index d2c0fa29877a..c0733e65a741 100644 --- a/src/jdk.internal.jvmstat/linux/classes/sun/jvmstat/PlatformSupportImpl.java +++ b/src/jdk.internal.jvmstat/linux/classes/sun/jvmstat/PlatformSupportImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -75,6 +75,7 @@ private boolean tempDirectoryEquals(Path p) { * It is important that this directory is well-known and the * same for all VM instances. It cannot be affected by configuration * variables such as java.io.tmpdir. + * It can be affected by VM option -XX:AltTempDir, however. * * Implementation Details: * @@ -170,8 +171,8 @@ public boolean accept(File dir, String name) { /* - * Extract either the host PID or the NameSpace PID - * from a file path. + * Extract the VM ID (pid) from a file path, + * specifically the host pid for a container process. * * File path should be in 1 of these 2 forms: * @@ -179,6 +180,8 @@ public boolean accept(File dir, String name) { * or * /tmp/hsperfdata_{user}/{pid} * + * (where /tmp may be substituted due to -XX:AltTempDir) + * * In either case we want to return {pid} and NOT {nspid} * * This function filters out host pids which do not have @@ -189,24 +192,38 @@ public boolean accept(File dir, String name) { */ public int getLocalVmId(File file) throws NumberFormatException { String p = file.getAbsolutePath(); - String s[] = p.split("\\/"); - - // Determine if this file is from a container - if (s.length == 7 && s[1].equals("proc")) { - int hostpid = Integer.parseInt(s[2]); - int nspid = Integer.parseInt(s[6]); - if (nspid == hostpid || nspid == getNamespaceVmId(hostpid)) { - return hostpid; - } - else { - return -1; - } + String procParts[] = p.split("\\/"); // "/proc/hostpid/root//hsperfdata_user/nsid" + + int hostpid = -1; + int nspid = -1; + + // ["", "proc", "hostpid", "root", "tmpdir" .. "tmpdir", "hsperfdata_user", "nsid"] + if (procParts.length > 4 && procParts[1].equals("proc") && procParts[3].equals("root")) { + hostpid = Integer.parseInt(procParts[2]); } - else { - return Integer.parseInt(file.getName()); + + // Some invalid path. + if (procParts.length < 2) { + return -1; } - } + // Path at the end after tmp dir is: "hsperfdata_username/PID" + int end = procParts.length - 1; + if (!procParts[end-1].startsWith("hsperfdata_")) { + return -1; + } + if (hostpid == -1) { + hostpid = Integer.parseInt(procParts[end]); + } else { + nspid = Integer.parseInt(procParts[end]); + } + if (nspid == -1) { + return hostpid; + } else { + // We have both pids. + return nspid == getNamespaceVmId(hostpid) ? hostpid : -1; + } + } /* * Return the inner most namespaced PID if there is one, diff --git a/src/jdk.jartool/share/man/jarsigner.md b/src/jdk.jartool/share/man/jarsigner.md index d128b9c11ffb..b24382fdda5f 100644 --- a/src/jdk.jartool/share/man/jarsigner.md +++ b/src/jdk.jartool/share/man/jarsigner.md @@ -1,5 +1,5 @@ --- -# Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 1998, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -181,11 +181,7 @@ Currently, there are two command-line tools that use keystore implementations The default keystore implementation is `PKCS12`. This is a cross platform keystore based on the RSA PKCS12 Personal Information Exchange Syntax Standard. This standard is primarily meant for storing or transporting a user's private -keys, certificates, and miscellaneous secrets. There is another built-in -implementation, provided by Oracle. It implements the keystore as a file with a -proprietary keystore type (format) named `JKS`. It protects each private key -with its individual password, and also protects the integrity of the entire -keystore with a (possibly different) password. +keys, certificates, and miscellaneous secrets. Keystore implementations are provider-based, which means the application interfaces supplied by the `KeyStore` class are implemented in terms of a @@ -237,15 +233,11 @@ specified by the following line in the security properties file: > `keystore.type=pkcs12` -Case doesn't matter in keystore type designations. For example, `JKS` is the -same as `jks`. +Case doesn't matter in keystore type designations. For example, `PKCS12` is the +same as `pkcs12`. To have the tools utilize a keystore implementation other than the default, you -can change that line to specify a different keystore type. For example, if you -want to use the Oracle's `jks` keystore implementation, then change the line to -the following: - -> `keystore.type=jks` +can change that line to specify a different keystore type. ## Supported Algorithms diff --git a/src/jdk.jcmd/share/man/jcmd.md b/src/jdk.jcmd/share/man/jcmd.md index 23dfa67d8645..97e7385138b6 100644 --- a/src/jdk.jcmd/share/man/jcmd.md +++ b/src/jdk.jcmd/share/man/jcmd.md @@ -1,5 +1,5 @@ --- -# Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2012, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -77,8 +77,12 @@ jcmd - send diagnostic command requests to a running Java Virtual Machine The `jcmd` utility is used to send diagnostic command requests to the JVM. It must be used on the same machine on which the JVM is running, and have the same -effective user and group identifiers that were used to launch the JVM. Each -diagnostic command has its own set of options and arguments. To display the description, +effective user and group identifiers that were used to launch the JVM. Both must +use the same temporary file location for communication; this is true by default +but also see the [`-XX:AltTempDir`](./java.html#-XX_AltTempDir) option that can be +set for the JVM. + +Each diagnostic command has its own set of options and arguments. To display the description, syntax, and a list of available options and arguments for a diagnostic command, use the name of the command as the argument. For example: diff --git a/src/jdk.jcmd/share/man/jinfo.md b/src/jdk.jcmd/share/man/jinfo.md index 8365c5af8a56..cc582aa9d5c5 100644 --- a/src/jdk.jcmd/share/man/jinfo.md +++ b/src/jdk.jcmd/share/man/jinfo.md @@ -59,6 +59,10 @@ environment variable should contain the location of the `jvm.dll` that's used by the target process or the location from which the core dump file was produced. +If the target JVM is started with an alternate temporary file location, `jinfo` must +use the same temporary file location for communication; this is true by default +but see the [`-XX:AltTempDir`](./java.html#-XX_AltTempDir) option. + ## Options for the jinfo Command **Note:** diff --git a/src/jdk.jcmd/share/man/jmap.md b/src/jdk.jcmd/share/man/jmap.md index dd0be1b24ef6..2fe766c25b53 100644 --- a/src/jdk.jcmd/share/man/jmap.md +++ b/src/jdk.jcmd/share/man/jmap.md @@ -1,5 +1,5 @@ --- -# Copyright (c) 2004, 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -60,6 +60,11 @@ Debugging Tools for Windows must be installed to make these tools work. The that's used by the target process or the location from which the core dump file was produced. +If the target JVM is started with an alternate temporary file location, `jmap` must +use the same temporary file location for communication; this is true by default +but see the [`-XX:AltTempDir`](./java.html#-XX_AltTempDir) option. + + ## Options for the jmap Command [`-clstats`]{#option-clstats} *pid* diff --git a/src/jdk.jcmd/share/man/jps.md b/src/jdk.jcmd/share/man/jps.md index 2db93878801a..cdd5e8e8b9d7 100644 --- a/src/jdk.jcmd/share/man/jps.md +++ b/src/jdk.jcmd/share/man/jps.md @@ -1,5 +1,5 @@ --- -# Copyright (c) 2004, 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -99,6 +99,9 @@ permissions granted to the principal running the command. The command lists only the JVMs for which the principal has access rights as determined by operating system-specific access control mechanisms. +The list of JVMs is also limited to those that use the same temporary file location as the `jps` +command. That is normally the case but see the [`-XX:AltTempDir`](./java.html#-XX_AltTempDir) option. + ## Host Identifier The host identifier, or `hostid`, is a string that indicates the target system. diff --git a/src/jdk.jcmd/share/man/jstack.md b/src/jdk.jcmd/share/man/jstack.md index 2e95abf36c4d..b2cf02c6eb57 100644 --- a/src/jdk.jcmd/share/man/jstack.md +++ b/src/jdk.jcmd/share/man/jstack.md @@ -63,6 +63,11 @@ Debugging Tools for Windows must be installed so that these tools work. The is used by the target process, or the location from which the core dump file was produced. +If the target JVM is started with an alternate temporary file location, `jstack` must +use the same temporary file location for communication; this is true by default +but see the [`-XX:AltTempDir`](./java.html#-XX_AltTempDir) option. + + ## Options for the jstack Command `-l` diff --git a/src/jdk.jcmd/share/man/jstat.md b/src/jdk.jcmd/share/man/jstat.md index 624b675de765..4b686f73810b 100644 --- a/src/jdk.jcmd/share/man/jstat.md +++ b/src/jdk.jcmd/share/man/jstat.md @@ -1,5 +1,5 @@ --- -# Copyright (c) 2004, 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -85,6 +85,11 @@ statistical output. All options and their functionality are subject to change or removal in future releases. +If the target JVM is started with an alternate temporary file location, `jstat` must +use the same temporary file location for communication; this is true by default +but see the [`-XX:AltTempDir`](./java.html#-XX_AltTempDir) option. + + ## General Options If you specify one of the general options, then you can't specify any other diff --git a/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ConsoleIOContext.java b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ConsoleIOContext.java index eba81b4be075..c5cbb2382514 100644 --- a/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ConsoleIOContext.java +++ b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ConsoleIOContext.java @@ -1308,6 +1308,7 @@ public FixResult compute(JShellTool repl, String code, int cursor) { case COMPLETE: case COMPLETE_WITH_SEMI: case CONSIDERED_INCOMPLETE: + case PREFIX: break; case EMPTY: return reject(repl, "jshell.console.empty"); diff --git a/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java index 9a030f7e46b9..196d51685c50 100644 --- a/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java +++ b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -1130,7 +1130,20 @@ private void resetState() { ? currentNameSpace.tid(sn) : errorNamespace.tid(sn)) .remoteVMOptions(options.remoteVmOptions()) - .compilerOptions(options.compilerOptions()); + .compilerOptions(options.compilerOptions()) + .binarySourceMapping(binary -> { + String binaryPath = binary.toString(); + if (binaryPath.toLowerCase(Locale.ROOT).endsWith(".jar")) { + String sourceCandidatePath = + binaryPath.substring(0, binaryPath.length() - ".jar".length()) + "-sources.jar"; + Path sourceCandidate = Path.of(sourceCandidatePath); + + if (Files.exists(sourceCandidate)) { + return List.of(binary, sourceCandidate); + } + } + return List.of(binary); + }); if (executionControlSpec != null) { builder.executionEngine(executionControlSpec); } diff --git a/src/jdk.jshell/share/classes/jdk/internal/shellsupport/doc/JavadocHelper.java b/src/jdk.jshell/share/classes/jdk/internal/shellsupport/doc/JavadocHelper.java index 112808420bc3..6db40093863b 100644 --- a/src/jdk.jshell/share/classes/jdk/internal/shellsupport/doc/JavadocHelper.java +++ b/src/jdk.jshell/share/classes/jdk/internal/shellsupport/doc/JavadocHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -96,6 +96,7 @@ import com.sun.tools.javac.util.DefinedBy; import com.sun.tools.javac.util.DefinedBy.Api; import com.sun.tools.javac.util.Pair; +import java.util.function.Function; import jdk.internal.org.commonmark.ext.gfm.tables.TablesExtension; import jdk.internal.org.commonmark.node.Node; @@ -114,11 +115,12 @@ public abstract class JavadocHelper implements AutoCloseable { * @param sourceLocations paths where source files should be searched * @return a JavadocHelper */ - public static JavadocHelper create(JavacTask mainTask, Collection sourceLocations) { + public static JavadocHelper create(JavacTask mainTask, Collection sourceLocations, + Function extraBinaryName2FullSource) { StandardJavaFileManager fm = compiler.getStandardFileManager(null, null, null); try { fm.setLocationFromPaths(StandardLocation.SOURCE_PATH, sourceLocations); - return new OnDemandJavadocHelper(mainTask, fm, sourceLocations); + return new OnDemandJavadocHelper(mainTask, fm, extraBinaryName2FullSource); } catch (IOException ex) { try { fm.close(); @@ -140,16 +142,6 @@ public void close() throws IOException {} public String getResolvedDocComment(StoredElement forElement) throws IOException { return null; } - - @Override - public StoredElement getHandle(Element forElement) { - return null; - } - - @Override - public Collection getSourceLocations() { - return List.of(); - } }; } } @@ -174,9 +166,6 @@ public Collection getSourceLocations() { */ public abstract Element getSourceElement(Element forElement) throws IOException; - public abstract StoredElement getHandle(Element forElement); - public abstract Collection getSourceLocations(); - /**Closes the helper. * * @throws IOException if something foes wrong during the close @@ -184,20 +173,86 @@ public Collection getSourceLocations() { @Override public abstract void close() throws IOException; - public record StoredElement(String module, String binaryName, String handle) {} + public record StoredElement(String module, String binaryName, String handle) { + public static StoredElement getStoredElement(JavacTask mainTask, Element forElement) { + TypeElement type = topLevelType(forElement); + + if (type == null) + return null; + + Elements elements = mainTask.getElements(); + ModuleElement module = elements.getModuleOf(type); + String moduleName = module == null || module.isUnnamed() + ? null + : module.getQualifiedName().toString(); + String binaryName = elements.getBinaryName(type).toString(); + String handle = elementSignature(forElement); + + return new StoredElement(moduleName, binaryName, handle); + } + + } + + private static String elementSignature(Element el) { + switch (el.getKind()) { + case ANNOTATION_TYPE: case CLASS: case ENUM: case INTERFACE: case RECORD: + return ((TypeElement) el).getQualifiedName().toString(); + case FIELD: + return elementSignature(el.getEnclosingElement()) + "." + el.getSimpleName() + ":" + el.asType(); + case ENUM_CONSTANT: + return elementSignature(el.getEnclosingElement()) + "." + el.getSimpleName(); + case EXCEPTION_PARAMETER: case LOCAL_VARIABLE: case PARAMETER: case RESOURCE_VARIABLE: + return el.getSimpleName() + ":" + el.asType(); + case CONSTRUCTOR: case METHOD: + StringBuilder header = new StringBuilder(); + header.append(elementSignature(el.getEnclosingElement())); + if (el.getKind() == ElementKind.METHOD) { + header.append("."); + header.append(el.getSimpleName()); + } + header.append("("); + String sep = ""; + ExecutableElement method = (ExecutableElement) el; + for (Iterator i = method.getParameters().iterator(); i.hasNext();) { + VariableElement p = i.next(); + header.append(sep); + header.append(p.asType()); + sep = ", "; + } + header.append(")"); + return header.toString(); + case PACKAGE, STATIC_INIT, INSTANCE_INIT, TYPE_PARAMETER, + OTHER, MODULE, RECORD_COMPONENT, BINDING_VARIABLE: + return el.toString(); + default: + throw Assert.error(el.getKind().name()); + } + } + + private static TypeElement topLevelType(Element el) { + if (el.getKind() == ElementKind.PACKAGE) + return null; + + while (el != null && el.getEnclosingElement().getKind() != ElementKind.PACKAGE) { + el = el.getEnclosingElement(); + } + + return el != null && (el.getKind().isClass() || el.getKind().isInterface()) ? (TypeElement) el : null; + } private static final class OnDemandJavadocHelper extends JavadocHelper { private final JavacTask mainTask; private final JavaFileManager baseFileManager; private final StandardJavaFileManager fm; private final Map> signature2Source = new HashMap<>(); - private final Collection sourceLocations; + private final Function extraBinaryName2FullSource; - private OnDemandJavadocHelper(JavacTask mainTask, StandardJavaFileManager fm, Collection sourceLocations) { + private OnDemandJavadocHelper(JavacTask mainTask, StandardJavaFileManager fm, + Function extraBinaryName2FullSource) { this.mainTask = mainTask; this.baseFileManager = ((JavacTaskImpl) mainTask).getContext().get(JavaFileManager.class); this.fm = fm; - this.sourceLocations = sourceLocations; + this.extraBinaryName2FullSource = extraBinaryName2FullSource; } @Override @@ -235,29 +290,6 @@ public Element getSourceElement(Element forElement) throws IOException { return result; } - @Override - public StoredElement getHandle(Element forElement) { - TypeElement type = topLevelType(forElement); - - if (type == null) - return null; - - Elements elements = mainTask.getElements(); - ModuleElement module = elements.getModuleOf(type); - String moduleName = module == null || module.isUnnamed() - ? null - : module.getQualifiedName().toString(); - String binaryName = elements.getBinaryName(type).toString(); - String handle = elementSignature(forElement); - - return new StoredElement(moduleName, binaryName, handle); - } - - @Override - public Collection getSourceLocations() { - return sourceLocations; - } - private String getResolvedDocComment(JavacTask task, TreePath el) throws IOException { DocTrees trees = DocTrees.instance(task); Element element = trees.getElement(el); @@ -822,52 +854,6 @@ private Pair getSourceElement(JavacTask origin, Element el) } } //where: - private String elementSignature(Element el) { - switch (el.getKind()) { - case ANNOTATION_TYPE: case CLASS: case ENUM: case INTERFACE: case RECORD: - return ((TypeElement) el).getQualifiedName().toString(); - case FIELD: - return elementSignature(el.getEnclosingElement()) + "." + el.getSimpleName() + ":" + el.asType(); - case ENUM_CONSTANT: - return elementSignature(el.getEnclosingElement()) + "." + el.getSimpleName(); - case EXCEPTION_PARAMETER: case LOCAL_VARIABLE: case PARAMETER: case RESOURCE_VARIABLE: - return el.getSimpleName() + ":" + el.asType(); - case CONSTRUCTOR: case METHOD: - StringBuilder header = new StringBuilder(); - header.append(elementSignature(el.getEnclosingElement())); - if (el.getKind() == ElementKind.METHOD) { - header.append("."); - header.append(el.getSimpleName()); - } - header.append("("); - String sep = ""; - ExecutableElement method = (ExecutableElement) el; - for (Iterator i = method.getParameters().iterator(); i.hasNext();) { - VariableElement p = i.next(); - header.append(sep); - header.append(p.asType()); - sep = ", "; - } - header.append(")"); - return header.toString(); - case PACKAGE, STATIC_INIT, INSTANCE_INIT, TYPE_PARAMETER, - OTHER, MODULE, RECORD_COMPONENT, BINDING_VARIABLE: - return el.toString(); - default: - throw Assert.error(el.getKind().name()); - } - } - - private TypeElement topLevelType(Element el) { - if (el.getKind() == ElementKind.PACKAGE) - return null; - - while (el != null && el.getEnclosingElement().getKind() != ElementKind.PACKAGE) { - el = el.getEnclosingElement(); - } - - return el != null && (el.getKind().isClass() || el.getKind().isInterface()) ? (TypeElement) el : null; - } private void fillElementCache(JavacTask task, CompilationUnitTree cut) throws IOException { Trees trees = Trees.instance(task); @@ -907,8 +893,15 @@ private Pair findSource(String moduleName, binaryName, JavaFileObject.Kind.SOURCE); - if (jfo == null) - return null; + if (jfo == null) { + String fullSource = extraBinaryName2FullSource.apply(binaryName); + + if (fullSource == null) { + return null; + } + + jfo = SimpleJavaFileObject.forSource(URI.create("mem://Temp.java"), fullSource); + } List jfos = Arrays.asList(jfo); JavaFileManager patchFM = moduleName != null diff --git a/src/jdk.jshell/share/classes/jdk/jshell/Eval.java b/src/jdk.jshell/share/classes/jdk/jshell/Eval.java index 70f3b70fd66c..e2026eda08af 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/Eval.java +++ b/src/jdk.jshell/share/classes/jdk/jshell/Eval.java @@ -200,10 +200,11 @@ List toScratchSnippets(String userSource) { * @return usually a singleton list of Snippet, but may be empty or multiple */ private List sourceToSnippets(String userSource) { - String compileSource = Util.trimEnd(new MaskCommentsAndModifiers(userSource, false).cleared()); - if (compileSource.length() == 0) { + String emptyCheck = Util.trimEnd(new MaskCommentsAndModifiers(userSource, false, true).cleared()); + if (emptyCheck.isEmpty()) { return Collections.emptyList(); } + String compileSource = Util.trimEnd(new MaskCommentsAndModifiers(userSource, false, false).cleared()); return state.taskFactory.parse(compileSource, pt -> { List units = pt.units(); if (units.isEmpty()) { @@ -221,7 +222,7 @@ private List sourceToSnippets(String userSource) { } // Erase illegal/ignored modifiers - String compileSourceInt = new MaskCommentsAndModifiers(compileSource, true).cleared(); + String compileSourceInt = new MaskCommentsAndModifiers(compileSource, true, false).cleared(); state.debug(DBG_GEN, "Kind: %s -- %s\n", unitTree.getKind(), unitTree); return switch (unitTree.getKind()) { @@ -337,6 +338,7 @@ private List processVariables(String userSource, List u Set anonymousClasses = Collections.emptySet(); StringBuilder sbBrackets = new StringBuilder(); Tree baseType = vt.getType(); + int variableDeclarationStart = dis.getStartPosition(vt); if (vt.getType() != null && vt.getType().getKind() != Tree.Kind.VAR_TYPE) { tds.scan(baseType); // Not dependent on initializer fullTypeName = displayType = typeName = EvalPretty.prettyExpr((JCTree) vt.getType(), false); @@ -350,7 +352,7 @@ private List processVariables(String userSource, List u } else { DiagList dl = trialCompile(Wrap.methodWrap(compileSource)); if (dl.hasErrors()) { - return compileFailResult(dl, userSource, kindOfTree(unitTree)); + return compileFailResult(dl, userSource, kindOfTree(unitTree), variableDeclarationStart); } Tree init = vt.getInitializer(); if (init != null) { @@ -424,7 +426,10 @@ private List processVariables(String userSource, List u wname = new Wrap.RangeWrap(compileSource, rname); } } - Wrap guts = Wrap.varWrap(compileSource, typeWrap, sbBrackets.toString(), wname, + Wrap prefixWrap = variableDeclarationStart == 0 + ? null + : Wrap.rangeWrap(compileSource, new Range(0, variableDeclarationStart)); + Wrap guts = Wrap.varWrap(compileSource, prefixWrap, typeWrap, sbBrackets.toString(), wname, winit, enhancedDesugaring, anonDeclareWrap); DiagList modDiag = modifierDiagnostics(vt.getModifiers(), dis, true); Snippet snip = new VarSnippet(state.keyMap.keyForVariable(name), userSource, guts, @@ -730,7 +735,7 @@ private List processClass(String userSource, Tree unitTree, String comp // Corralling Wrap corralled = new Corraller(dis, key.index(), compileSource).corralType(klassTree); - Wrap guts = Wrap.classMemberWrap(compileSource); + Wrap guts = Wrap.classMemberWrap(compileSource, dis.getStartPosition(klassTree)); Snippet snip = new TypeDeclSnippet(key, userSource, guts, name, snippetKind, corralled, tds.declareReferences(), tds.bodyReferences(), modDiag); @@ -776,6 +781,7 @@ private List processMethod(String userSource, Tree unitTree, String com final MethodTree mt = (MethodTree) unitTree; //String name = userReadableName(mt.getName(), compileSource); final String name = mt.getName().toString(); + int methodDeclarationStart = dis.getStartPosition(mt); if (objectMethods.contains(name)) { // The name matches a method on Object, short of an overhaul, this // fails, see 8187137. Generate a descriptive error message @@ -790,7 +796,7 @@ private List processMethod(String userSource, Tree unitTree, String com ? possibleStart // something wrong, punt : possibleStart + offset; - return compileFailResult(new DiagList(objectMethodNameDiag(name, start)), userSource, Kind.METHOD); + return compileFailResult(new DiagList(objectMethodNameDiag(name, start)), userSource, Kind.METHOD, methodDeclarationStart); } String parameterTypes = mt.getParameters() @@ -800,7 +806,7 @@ private List processMethod(String userSource, Tree unitTree, String com Tree returnType = mt.getReturnType(); DiagList modDiag = modifierDiagnostics(mt.getModifiers(), dis, false); if (modDiag.hasErrors()) { - return compileFailResult(modDiag, userSource, Kind.METHOD); + return compileFailResult(modDiag, userSource, Kind.METHOD, methodDeclarationStart); } MethodKey key = state.keyMap.keyForMethod(name, parameterTypes); @@ -822,7 +828,7 @@ private List processMethod(String userSource, Tree unitTree, String com } else { // normal method corralled = new Corraller(dis, key.index(), compileSource).corralMethod(mt); - guts = Wrap.classMemberWrap(compileSource); + guts = Wrap.classMemberWrap(compileSource, methodDeclarationStart); unresolvedSelf = null; } Range typeRange = dis.treeToRange(returnType); @@ -874,19 +880,31 @@ private List compileFailResult(BaseTask xt, String userSource, Kind * @return a rejected snippet */ private List compileFailResult(DiagList diags, String userSource, Kind probableKind) { + return compileFailResult(diags, userSource, probableKind, 0); + } + + /** + * The snippet has failed, return with the rejected snippet + * + * @param diags the failure diagnostics + * @param userSource the incoming bad user source + * @param declarationStart the start offset of the declaration (if any) + * @return a rejected snippet + */ + private List compileFailResult(DiagList diags, String userSource, Kind probableKind, int declarationStart) { ErroneousKey key = state.keyMap.keyForErroneous(); Snippet snip = new ErroneousSnippet(key, userSource, null, probableKind, SubKind.UNKNOWN_SUBKIND); snip.setFailed(diags); // Install wrapper for query by SourceCodeAnalysis.wrapper - String compileSource = Util.trimEnd(new MaskCommentsAndModifiers(userSource, true).cleared()); + String compileSource = Util.trimEnd(new MaskCommentsAndModifiers(userSource, true, false).cleared()); OuterWrap outer = switch (probableKind) { case IMPORT -> state.outerMap.wrapImport(Wrap.simpleWrap(compileSource), snip); case EXPRESSION -> state.outerMap.wrapInTrialClass(Wrap.methodReturnWrap(compileSource)); case VAR, TYPE_DECL, - METHOD -> state.outerMap.wrapInTrialClass(Wrap.classMemberWrap(compileSource)); + METHOD -> state.outerMap.wrapInTrialClass(Wrap.classMemberWrap(compileSource, declarationStart)); default -> state.outerMap.wrapInTrialClass(Wrap.methodWrap(compileSource)); }; snip.setOuterWrap(outer); diff --git a/src/jdk.jshell/share/classes/jdk/jshell/JShell.java b/src/jdk.jshell/share/classes/jdk/jshell/JShell.java index 336746047dc7..535e4df02fb4 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/JShell.java +++ b/src/jdk.jshell/share/classes/jdk/jshell/JShell.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,9 +31,11 @@ import java.io.InterruptedIOException; import java.io.PrintStream; import java.net.InetAddress; +import java.nio.file.Path; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -100,6 +102,7 @@ public class JShell implements AutoCloseable { final List extraRemoteVMOptions; final List extraCompilerOptions; final Function fileManagerMapping; + final Function> binarySourceMapping; private int nextKeyIndex = 1; @@ -125,6 +128,7 @@ public class JShell implements AutoCloseable { this.extraRemoteVMOptions = b.extraRemoteVMOptions; this.extraCompilerOptions = b.extraCompilerOptions; this.fileManagerMapping = b.fileManagerMapping; + this.binarySourceMapping = b.binarySourceMapping; try { if (b.executionControlProvider != null) { executionControl = b.executionControlProvider.generate(new ExecutionEnvImpl(), @@ -183,6 +187,7 @@ public static class Builder { Map executionControlParameters; String executionControlSpec; Function fileManagerMapping; + Function> binarySourceMapping; Builder() { } @@ -414,6 +419,38 @@ public Builder fileManager(FunctionThe given {@code binarySourceMapping} will be called for various paths + * used by JShell. Some of them may be distinct from roots specified in classpath + * and module path. + * + *

    When looking for a source for a binary class or interface, the roots + * containing sources are searched in the given order, and once + * a source file corresponding to the given class or interface is found, + * the rest of the roots containing sources will be ignored. + * + *

    The results of calling {@code binarySourceMapping} may be cached, and the + * same file may or may not be queried again. + * + *

    The result of calling the {@code binarySourceMapping} may be {@code null}, + * which will be treated the same way as an empty collection. + * + *

    If the result value is of a type that is {@link AutoCloseable}, then it will + * be closed when not needed anymore. + * + * @param binarySourceMapping the binary to source mapper, or {@code null} if none. + * @return the {@code Builder} instance (for use in chained + * initialization) + * @since 28 + */ + public Builder binarySourceMapping(Function> binarySourceMapping) { + this.binarySourceMapping = binarySourceMapping; + return this; + } + /** * Builds a JShell state engine. This is the entry-point to all JShell * functionality. This creates a remote process for execution. It is diff --git a/src/jdk.jshell/share/classes/jdk/jshell/MaskCommentsAndModifiers.java b/src/jdk.jshell/share/classes/jdk/jshell/MaskCommentsAndModifiers.java index becdb60129aa..858c6a375090 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/MaskCommentsAndModifiers.java +++ b/src/jdk.jshell/share/classes/jdk/jshell/MaskCommentsAndModifiers.java @@ -72,11 +72,14 @@ class MaskCommentsAndModifiers { // initial modifier section private boolean maskModifiers; + //should documentation comments be masked? + private boolean maskDocComments; + // Does the string end with an unclosed '/*' style comment? private boolean openToken = false; - MaskCommentsAndModifiers(String s, boolean maskModifiers) { - this(s, maskModifiers, IGNORED_MODIFIERS); + MaskCommentsAndModifiers(String s, boolean maskModifiers, boolean maskDocComments) { + this(s, maskModifiers, IGNORED_MODIFIERS, maskDocComments); } MaskCommentsAndModifiers(String s, Set ignoredModifiers) { @@ -84,10 +87,15 @@ class MaskCommentsAndModifiers { } MaskCommentsAndModifiers(String s, boolean maskModifiers, Set ignoredModifiers) { + this(s, maskModifiers, ignoredModifiers, true); + } + + MaskCommentsAndModifiers(String s, boolean maskModifiers, Set ignoredModifiers, boolean maskDocComments) { this.str = s; this.length = s.length(); this.maskModifiers = maskModifiers; this.ignoredModifiers = ignoredModifiers; + this.maskDocComments = maskDocComments; read(); while (c >= 0) { next(); @@ -154,6 +162,14 @@ private void writeMask(CharSequence s) { } } + private void writeMaskOrNotMask(int ch, boolean mask) { + if (mask) { + writeMask(ch); + } else { + write(ch); + } + } + @SuppressWarnings("fallthrough") private void next() { switch (c) { @@ -199,30 +215,50 @@ private void next() { case '/': read(); switch (c) { - case '*': - writeMask('/'); - writeMask(c); - int prevc = 0; - while (read() >= 0 && (c != '/' || prevc != '*')) { + case '*' -> { + read(); + boolean mask; + if (maskDocComments || c != '*') { + writeMask("/*"); writeMask(c); + mask = true; + } else { + read(); + if (c == '/') { + //special case: /**/ + writeMask("/**/"); + openToken = false; + break; + } + write("/**"); + write(c); + mask = false; //do not mask javadoc comments + } + + int prevc = c; + while (read() >= 0 && (c != '/' || prevc != '*')) { + writeMaskOrNotMask(c, mask); prevc = c; } - writeMask(c); + writeMaskOrNotMask(c, mask); openToken = c < 0; - break; - case '/': - writeMask('/'); - writeMask(c); + } + case '/' -> { + read(); + boolean mask = maskDocComments || c != '/'; //do not mask javadoc comments + writeMaskOrNotMask('/', mask); + writeMaskOrNotMask('/', mask); + writeMaskOrNotMask(c, mask); while (read() >= 0 && c != '\n' && c != '\r') { - writeMask(c); + writeMaskOrNotMask(c, mask); } - writeMask(c); - break; - default: + writeMaskOrNotMask(c, mask); + } + default -> { maskModifiers = false; write('/'); unread(); - break; + } } break; case '@': diff --git a/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysis.java b/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysis.java index 91bdde888fa1..f78828e1c100 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysis.java +++ b/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysis.java @@ -273,6 +273,14 @@ public enum Completeness { */ CONSIDERED_INCOMPLETE(false), + /** + * Snippet is likely a prefix of a real snippet. Typically, the snippet + * is a documentation comment, after which it is expected that a declaration + * will follow. + * + * @since 28 + */ + PREFIX(false), /** * An empty input. diff --git a/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java b/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java index 215b412003d9..b6146fb9588f 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java +++ b/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java @@ -46,6 +46,7 @@ import com.sun.source.tree.TypeParameterTree; import com.sun.source.tree.VariableTree; import com.sun.source.tree.YieldTree; +import com.sun.source.util.JavacTask; import com.sun.source.util.SourcePositions; import com.sun.source.util.TreePath; import com.sun.source.util.TreePathScanner; @@ -173,6 +174,7 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { private final JShell proc; private final CompletenessAnalyzer ca; + private final Function binaryName2SnipperOuterWrap; private final List closeables = new ArrayList<>(); private final Map currentIndexes = new HashMap<>(); private int indexVersion; @@ -184,6 +186,14 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { this.proc = proc; this.ca = new CompletenessAnalyzer(proc); + this.binaryName2SnipperOuterWrap = binaryName -> { + return proc.snippets() + .filter(s -> binaryName.equals(s.classFullName())) + .map(s -> s.outerWrap().wrapped()) + .findAny() + .orElse(null); + }; + int cpVersion = classpathVersion = 1; INDEXER.submit(() -> refreshIndexes(cpVersion)); @@ -191,7 +201,7 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { @Override public CompletionInfo analyzeCompletion(String srcInput) { - MaskCommentsAndModifiers mcm = new MaskCommentsAndModifiers(srcInput, false); + MaskCommentsAndModifiers mcm = new MaskCommentsAndModifiers(srcInput, false, true); if (mcm.endsWithOpenToken()) { proc.debug(DBG_COMPA, "Incomplete (open comment): %s\n", srcInput); return new CompletionInfoImpl(DEFINITELY_INCOMPLETE, null, srcInput + '\n'); @@ -199,8 +209,9 @@ public CompletionInfo analyzeCompletion(String srcInput) { String cleared = mcm.cleared(); String trimmedInput = Util.trimEnd(cleared); if (trimmedInput.isEmpty()) { - // Just comment or empty - return new CompletionInfoImpl(Completeness.EMPTY, srcInput, ""); + // Just comment, empty, or javadoc prefix + boolean hasDocumentation = !Util.trimEnd(new MaskCommentsAndModifiers(srcInput, false, false).cleared()).isEmpty(); + return new CompletionInfoImpl(hasDocumentation ? Completeness.PREFIX : Completeness.EMPTY, srcInput, ""); } CaInfo info = ca.scan(trimmedInput); Completeness status = info.status; @@ -369,7 +380,7 @@ private List completionSuggestionsImpl(String code, int private List computeSuggestions(OuterWrap code, String inputCode, int cursor, String prefix, ElementSuggestionConvertor suggestionConvertor) { return proc.taskFactory.analyze(code, COMPLETION_EXTRA_PARAMETERS, at -> { - try (JavadocHelper javadoc = JavadocHelper.create(at.task, findSources())) { + try (JavadocConfig javadoc = new JavadocConfig(at.task, getAllPaths())) { SourcePositions sp = at.trees().getSourcePositions(); CompilationUnitTree topLevel = at.firstCuTree(); TreePath tp = pathFor(topLevel, sp, code, cursor); @@ -713,9 +724,6 @@ private List computeSuggestions(OuterWrap code, String return suggestionConvertor.convert(completionState, result); } - } catch (IOException ex) { - //TODO: - ex.printStackTrace(); } return Collections.emptyList(); }); @@ -805,7 +813,7 @@ public List highlights(String snippet) { //TODO: OuterWrap duplicated OuterWrap codeWrap = switch (guessKind(snippet)) { case IMPORT -> proc.outerMap.wrapImport(Wrap.simpleWrap(snippet + "any.any"), null); - case CLASS, METHOD -> proc.outerMap.wrapInTrialClass(Wrap.classMemberWrap(snippet)); + case CLASS, METHOD -> proc.outerMap.wrapInTrialClass(Wrap.classMemberWrap(snippet, 0)); default -> proc.outerMap.wrapInTrialClass(Wrap.methodWrap(snippet)); }; String wrappedCode = codeWrap.wrapped(); @@ -1227,7 +1235,7 @@ private boolean isPermittedAnnotationAttributeFieldType(AnalyzeTask at, TypeMirr }; private final Function> IDENTITY = Collections::singletonList; - private void addElements(JavadocHelper javadoc, Iterable elements, Predicate accept, Predicate smart, int anchor, String prefix, List result) { + private void addElements(JavadocConfig javadoc, Iterable elements, Predicate accept, Predicate smart, int anchor, String prefix, List result) { for (Element c : elements) { if (!accept.test(c) || !simpleContinuationName(c).startsWith(prefix)) continue; @@ -1237,10 +1245,11 @@ private void addElements(JavadocHelper javadoc, Iterable elem continue; } StoredElement stored = javadoc.getHandle(c); - Collection sourceLocations = javadoc.getSourceLocations(); + Collection binaryPaths = javadoc.getBinaryPaths(); result.add(new ElementSuggestionImpl(c, null, smart.test(c), anchor, () -> { return proc.taskFactory.analyze(proc.outerMap.wrapInTrialClass(Wrap.methodWrap(";")), task -> { - try (JavadocHelper nestedJavadoc = JavadocHelper.create(task.task, sourceLocations)) { + try (CloseableSources closeableSources = findSources(binaryPaths); + JavadocHelper nestedJavadoc = JavadocHelper.create(task.task, closeableSources.sources(), binaryName2SnipperOuterWrap)) { return nestedJavadoc.getResolvedDocComment(stored); } catch (IOException ex) { ex.printStackTrace(); @@ -1251,7 +1260,7 @@ private void addElements(JavadocHelper javadoc, Iterable elem } } - private void addModuleElements(AnalyzeTask at, JavadocHelper javadoc, int anchor, + private void addModuleElements(AnalyzeTask at, JavadocConfig javadoc, int anchor, String prefix, List result) { for (ModuleElement me : at.getElements().getAllModuleElements()) { @@ -1413,6 +1422,8 @@ void classpathChanged() { int cpVersion = ++classpathVersion; INDEXER.submit(() -> refreshIndexes(cpVersion)); + + allPaths = null; } } @@ -1704,7 +1715,7 @@ private TypeMirror resultTypeOf(Element el) { }; } - private void addScopeElements(AnalyzeTask at, JavadocHelper javadoc, Scope scope, Function> elementConvertor, Predicate filter, Predicate smartFilter, int anchor, String prefix, List result) { + private void addScopeElements(AnalyzeTask at, JavadocConfig javadoc, Scope scope, Function> elementConvertor, Predicate filter, Predicate smartFilter, int anchor, String prefix, List result) { addElements(javadoc, scopeContent(at, scope, elementConvertor), filter, smartFilter, anchor, prefix, result); } @@ -1903,7 +1914,8 @@ private List documentationImpl(String code, int cursor, boolean c List result = Collections.emptyList(); - try (JavadocHelper helper = JavadocHelper.create(at.task, findSources())) { + try (CloseableSources sources = findSources(getAllPaths()); + JavadocHelper helper = JavadocHelper.create(at.task, sources.sources(), binaryName2SnipperOuterWrap)) { int parameterIndexFin = parameterIndex; result = elements.map(el -> constructDocumentation(at, helper, el, parameterIndexFin, computeJavadoc)) .filter(Objects::nonNull) @@ -1933,7 +1945,11 @@ private Documentation constructDocumentation(AnalyzeTask at, JavadocHelper helpe } public void close() { - for (AutoCloseable closeable : closeables) { + close(closeables); + } + + private void close(List toClose) { + for (AutoCloseable closeable : toClose) { try { closeable.close(); } catch (Exception ex) { @@ -1966,17 +1982,51 @@ private boolean hasSyntheticParameterNames(Element el) { .allMatch(param -> param.getSimpleName().toString().startsWith("arg")); } - private static List availableSourcesOverride; //for tests - private List availableSources; + private CloseableSources findSources(Collection forPaths) { + List toClose = new ArrayList<>(); + + try { + List sourcePaths = new ArrayList<>(); + + sourcePaths.addAll(jdkSources()); + + if (proc.binarySourceMapping != null) { + for (Path binaryPath : forPaths) { + Iterable mappedSources = proc.binarySourceMapping.apply(binaryPath); + + if (mappedSources != null) { + if (mappedSources instanceof AutoCloseable closeable) { + toClose.add(closeable); + } + + mappedSources.forEach(sourcePaths::add); + } + } + } + + CloseableSources result = new CloseableSources(this, sourcePaths, toClose); + + toClose = List.of(); - private List findSources() { - if (availableSources != null) { - return availableSources; + return result; + } finally { + close(toClose); } - if (availableSourcesOverride != null) { - return availableSources = availableSourcesOverride; + } + + private static List jdkSourcesOverride; //for tests + private List jdkSources; + + private synchronized List jdkSources() { + if (jdkSources != null) { + return jdkSources; } - List result = new ArrayList<>(); + if (jdkSourcesOverride != null) { + return jdkSources = jdkSourcesOverride; + } + + jdkSources = new ArrayList<>(); + Path home = Paths.get(System.getProperty("java.home")); Path srcZip = home.resolve("lib").resolve("src.zip"); if (!Files.isReadable(srcZip)) @@ -1991,13 +2041,13 @@ private List findSources() { if (Files.exists(root.resolve("java/lang/Object.java".replace("/", zipFO.getSeparator())))) { //non-modular format: - result.add(srcZip); + jdkSources.add(srcZip); } else if (Files.exists(root.resolve("java.base/java/lang/Object.java".replace("/", zipFO.getSeparator())))) { //modular format: try (DirectoryStream ds = Files.newDirectoryStream(root)) { for (Path p : ds) { if (Files.isDirectory(p)) { - result.add(p); + jdkSources.add(p); } } } @@ -2011,18 +2061,21 @@ private List findSources() { if (keepOpen) { closeables.add(zipFO); } else { - try { - zipFO.close(); - } catch (IOException ex) { - proc.debug(ex, "SourceCodeAnalysisImpl.findSources()"); - } + close(List.of(zipFO)); } } } } - return availableSources = result; + + return jdkSources; } + record CloseableSources(SourceCodeAnalysisImpl analysis, List sources, List toClose) implements AutoCloseable { + @Override + public void close() { + analysis.close(toClose()); + } + } private Element getOriginalEnclosingElement(Element el) { if (el instanceof Symbol s) el = s.baseSymbol(); return el.getEnclosingElement(); @@ -2186,7 +2239,7 @@ public QualifiedNames listQualifiedNames(String code, int cursor) { case IMPORT: return new QualifiedNames(Collections.emptyList(), -1, true, false); case METHOD: - codeWrap = proc.outerMap.wrapInTrialClass(Wrap.classMemberWrap(codeFin)); + codeWrap = proc.outerMap.wrapInTrialClass(Wrap.classMemberWrap(codeFin, 0)); break; default: codeWrap = proc.outerMap.wrapInTrialClass(Wrap.methodWrap(codeFin)); @@ -2269,22 +2322,7 @@ public void resumeIndexing() { //update indexes, either initially or after a classpath change: private void refreshIndexes(int version) { try { - Collection paths = proc.taskFactory.parse("", task -> { - MemoryFileManager fm = proc.taskFactory.fileManager(); - Collection _paths = new ArrayList<>(); - try { - appendPaths(fm, StandardLocation.PLATFORM_CLASS_PATH, _paths); - appendPaths(fm, StandardLocation.CLASS_PATH, _paths); - appendPaths(fm, StandardLocation.SOURCE_PATH, _paths); - appendModulePaths(fm, StandardLocation.SYSTEM_MODULES, _paths); - appendModulePaths(fm, StandardLocation.UPGRADE_MODULE_PATH, _paths); - appendModulePaths(fm, StandardLocation.MODULE_PATH, _paths); - return _paths; - } catch (Exception ex) { - proc.debug(ex, "SourceCodeAnalysisImpl.refreshIndexes(" + version + ")"); - return List.of(); - } - }); + Collection paths = getAllPaths(); Map newIndexes = new HashMap<>(); @@ -2323,6 +2361,50 @@ private void refreshIndexes(int version) { } } + private Collection allPaths; + private Collection getAllPaths() { + int originalClasspathVersion; + + synchronized (currentIndexes) { + if (allPaths != null) { + return allPaths; + } + + originalClasspathVersion = classpathVersion; + } + + Collection paths = proc.taskFactory.parse("", task -> { + MemoryFileManager fm = proc.taskFactory.fileManager(); + Collection _paths = new ArrayList<>(); + try { + appendPaths(fm, StandardLocation.PLATFORM_CLASS_PATH, _paths); + appendPaths(fm, StandardLocation.CLASS_PATH, _paths); + appendPaths(fm, StandardLocation.SOURCE_PATH, _paths); + appendModulePaths(fm, StandardLocation.SYSTEM_MODULES, _paths); + appendModulePaths(fm, StandardLocation.UPGRADE_MODULE_PATH, _paths); + appendModulePaths(fm, StandardLocation.MODULE_PATH, _paths); + return _paths; + } catch (Exception ex) { + proc.debug(ex, "SourceCodeAnalysisImpl.getAllPaths(" + originalClasspathVersion + ")"); + return List.of(); + } + }); + + synchronized (currentIndexes) { + if (originalClasspathVersion != classpathVersion) { + paths = null; + } else { + allPaths = paths; + } + } + + if (paths == null) { + paths = getAllPaths(); + } + + return paths; + } + private void appendPaths(MemoryFileManager fm, Location loc, Collection paths) { Iterable locationPaths = fm.getLocationAsPaths(loc); if (locationPaths == null) @@ -2536,10 +2618,10 @@ private OuterWrap wrapCodeForCompletion(String code, int cursor, boolean wrapImp lastImportIsModuleImport = moduleImport[0]; } case CLASS, METHOD -> { - pendingWrap = declarationWrap = Wrap.classMemberWrap(whitespaces(code, startOffset) + current); + pendingWrap = declarationWrap = Wrap.classMemberWrap(whitespaces(code, startOffset) + current, 0); } case VARIABLE -> { - declarationWrap = Wrap.classMemberWrap(whitespaces(code, startOffset) + current); + declarationWrap = Wrap.classMemberWrap(whitespaces(code, startOffset) + current, 0); pendingWrap = Wrap.methodWrap(whitespaces(code, startOffset) + current); } default -> { @@ -2721,4 +2803,36 @@ public Types typeUtils() { } } + + private static class JavadocConfig implements AutoCloseable { + private JavacTask mainTask; + private Collection binaryPaths; + + public JavadocConfig(JavacTask mainTask, Collection binaryPaths) { + this.mainTask = mainTask; + this.binaryPaths = binaryPaths; + } + + public StoredElement getHandle(Element el) { + if (mainTask == null) { + throw new IllegalStateException("Already closed."); + } + + return StoredElement.getStoredElement(mainTask, el); + } + + public Collection getBinaryPaths() { + if (binaryPaths == null) { + throw new IllegalStateException("Already closed."); + } + + return binaryPaths; + } + + @Override + public void close() { + mainTask = null; + binaryPaths = null; + } + } } diff --git a/src/jdk.jshell/share/classes/jdk/jshell/Wrap.java b/src/jdk.jshell/share/classes/jdk/jshell/Wrap.java index 990dbd8bece7..b5d0592be389 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/Wrap.java +++ b/src/jdk.jshell/share/classes/jdk/jshell/Wrap.java @@ -87,11 +87,11 @@ private static String nlindent(int n) { * @return a Wrap that declares the given variable, potentially with * an initialization method */ - public static Wrap varWrap(String source, Wrap wtype, String brackets, + public static Wrap varWrap(String source, Wrap prefix, Wrap wtype, String brackets, Wrap wname, Wrap winit, boolean enhanced, Wrap anonDeclareWrap) { List components = new ArrayList<>(); - components.add(new VarDeclareWrap(wtype, brackets, wname)); + components.add(new VarDeclareWrap(prefix, wtype, brackets, wname)); Wrap wmeth; if (winit == null) { @@ -164,9 +164,10 @@ public static Wrap rangeWrap(String source, Range range) { return new RangeWrap(source, range); } - public static Wrap classMemberWrap(String source) { - Wrap w = new NoWrap(source); - return new CompoundWrap(" public static\n ", w); + public static Wrap classMemberWrap(String source, int modifierInsertPos) { + Wrap prefix = modifierInsertPos == 0 ? null : new RangeWrap(source, new Range(0, modifierInsertPos)); + Wrap suffix = new RangeWrap(source, new Range(modifierInsertPos, source.length())); + return new CompoundWrap(prefix, " public static ", suffix); } private static int countLines(String s) { @@ -257,6 +258,8 @@ public static class CompoundWrap extends Wrap { snlnLast = w.lastSnippetLine(); } sb.append(w.wrapped()); + } else if (o == null) { + //permit missing delegates } else { throw new InternalError("Bad object in CommoundWrap: " + o); } @@ -558,8 +561,8 @@ private static class DoitMethodWrap extends CompoundWrap { private static class VarDeclareWrap extends CompoundWrap { - VarDeclareWrap(Wrap wtype, String brackets, Wrap wname) { - super(" public static ", wtype, brackets + " ", wname, semi(wname)); + VarDeclareWrap(Wrap prefix, Wrap wtype, String brackets, Wrap wname) { + super(prefix, " public static ", wtype, brackets + " ", wname, semi(wname)); } } diff --git a/src/jdk.jshell/share/man/jshell.md b/src/jdk.jshell/share/man/jshell.md index 62237f1447f7..71381ad094fb 100644 --- a/src/jdk.jshell/share/man/jshell.md +++ b/src/jdk.jshell/share/man/jshell.md @@ -1,5 +1,5 @@ --- -# Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2017, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -772,6 +772,27 @@ JShell. item can't be determined from what was entered, then possible options are provided. + Use the Tab key after entering the name of a class, interface, method, or + field to see the description of the class, interface, method, or field. + The description is based on the javadoc comment for the declaration of the + class, interface, method, or field, when sources for the declaration are + available. + + The sources for the JDK classes are looked up in the JDK's sources, if they + can be found in the JDK installation. The sources may need to be added to + the JDK installation separately. + + The sources of classes, interfaces, methods, and fields declared on the class path + or module path are looked up in the entry on the class path or module path which + contains the declaration. If the entry is a JAR file and it does not contain the source, + then the source is looked up in a JAR file with the same path and name but with `.jar` + replaced by `-sources.jar`, if such a file exists. For example, if `/lib.jar` + does not contain the source, then `/lib-sources.jar` is searched if it exists. + + When searching for sources, JShell expects source files to be co-located with class files. + That is, the source for a given class or interface must be in a file with the same path and name + as the class file for the corresponding top-level class or interface, with `.class` replaced by `.java`. + When entering a method call, use the Tab key after the method call's opening parenthesis to see the parameters for the method. If the method has more than one signature, then all signatures are displayed. Pressing the diff --git a/test/hotspot/gtest/aarch64/aarch64-asmtest.py b/test/hotspot/gtest/aarch64/aarch64-asmtest.py index b5386d47a73b..f9eb3f7e7cc4 100644 --- a/test/hotspot/gtest/aarch64/aarch64-asmtest.py +++ b/test/hotspot/gtest/aarch64/aarch64-asmtest.py @@ -1121,6 +1121,8 @@ def __init__(self, args): self._bitwiseop = False if name[0] == 'f': self._width = RegVariant(2, 3) + elif name in ["sdiv", "udiv"]: + self._width = RegVariant(2, 3) elif not self._isPredicated and (name in ["and", "bic", "bsl", "eor", "eor3", "orr"]): self._width = RegVariant(3, 3) self._bitwiseop = True @@ -2220,6 +2222,8 @@ def generate(kind, names): ["lsl", "ZPZ", "m", "dn"], ["lsr", "ZPZ", "m", "dn"], ["mul", "ZPZ", "m", "dn"], + ["sdiv", "ZPZ", "m", "dn"], + ["udiv", "ZPZ", "m", "dn"], ["neg", "ZPZ", "m"], ["not", "ZPZ", "m"], ["orr", "ZPZ", "m", "dn"], diff --git a/test/hotspot/gtest/aarch64/asmtest.out.h b/test/hotspot/gtest/aarch64/asmtest.out.h index 95832a1faf61..070e8376d3b6 100644 --- a/test/hotspot/gtest/aarch64/asmtest.out.h +++ b/test/hotspot/gtest/aarch64/asmtest.out.h @@ -1382,80 +1382,82 @@ __ sve_lsl(z17, __ D, p1, z11); // lsl z17.d, p1/m, z17.d, z11.d __ sve_lsr(z16, __ B, p0, z16); // lsr z16.b, p0/m, z16.b, z16.b __ sve_mul(z28, __ D, p1, z23); // mul z28.d, p1/m, z28.d, z23.d - __ sve_neg(z28, __ S, p4, z10); // neg z28.s, p4/m, z10.s - __ sve_not(z17, __ S, p7, z7); // not z17.s, p7/m, z7.s - __ sve_orr(z4, __ H, p3, z24); // orr z4.h, p3/m, z4.h, z24.h - __ sve_rbit(z9, __ B, p2, z11); // rbit z9.b, p2/m, z11.b - __ sve_revb(z4, __ S, p5, z22); // revb z4.s, p5/m, z22.s - __ sve_smax(z4, __ H, p0, z15); // smax z4.h, p0/m, z4.h, z15.h - __ sve_smin(z4, __ D, p7, z26); // smin z4.d, p7/m, z4.d, z26.d - __ sve_umax(z5, __ H, p5, z26); // umax z5.h, p5/m, z5.h, z26.h - __ sve_umin(z31, __ B, p0, z25); // umin z31.b, p0/m, z31.b, z25.b - __ sve_sub(z8, __ S, p1, z3); // sub z8.s, p1/m, z8.s, z3.s - __ sve_fabs(z7, __ D, p6, z24); // fabs z7.d, p6/m, z24.d - __ sve_fadd(z24, __ S, p7, z17); // fadd z24.s, p7/m, z24.s, z17.s - __ sve_fdiv(z10, __ S, p3, z30); // fdiv z10.s, p3/m, z10.s, z30.s - __ sve_fmax(z8, __ S, p6, z29); // fmax z8.s, p6/m, z8.s, z29.s - __ sve_fmin(z31, __ D, p5, z31); // fmin z31.d, p5/m, z31.d, z31.d - __ sve_fmul(z0, __ D, p5, z7); // fmul z0.d, p5/m, z0.d, z7.d - __ sve_fneg(z29, __ S, p6, z22); // fneg z29.s, p6/m, z22.s - __ sve_frintm(z29, __ S, p6, z20); // frintm z29.s, p6/m, z20.s - __ sve_frintn(z6, __ S, p4, z18); // frintn z6.s, p4/m, z18.s - __ sve_frintp(z26, __ S, p5, z8); // frintp z26.s, p5/m, z8.s - __ sve_fsqrt(z19, __ S, p2, z28); // fsqrt z19.s, p2/m, z28.s - __ sve_fsub(z17, __ D, p1, z30); // fsub z17.d, p1/m, z17.d, z30.d - __ sve_fmad(z24, __ S, p7, z14, z17); // fmad z24.s, p7/m, z14.s, z17.s - __ sve_fmla(z19, __ D, p2, z26, z11); // fmla z19.d, p2/m, z26.d, z11.d - __ sve_fmls(z0, __ D, p2, z15, z28); // fmls z0.d, p2/m, z15.d, z28.d - __ sve_fmsb(z23, __ D, p5, z28, z23); // fmsb z23.d, p5/m, z28.d, z23.d - __ sve_fnmad(z29, __ S, p6, z0, z27); // fnmad z29.s, p6/m, z0.s, z27.s - __ sve_fnmsb(z23, __ S, p3, z12, z4); // fnmsb z23.s, p3/m, z12.s, z4.s - __ sve_fnmla(z31, __ S, p6, z23, z20); // fnmla z31.s, p6/m, z23.s, z20.s - __ sve_fnmls(z2, __ D, p7, z29, z0); // fnmls z2.d, p7/m, z29.d, z0.d - __ sve_mla(z23, __ H, p0, z4, z5); // mla z23.h, p0/m, z4.h, z5.h - __ sve_mls(z28, __ H, p3, z17, z13); // mls z28.h, p3/m, z17.h, z13.h - __ sve_and(z8, z10, z8); // and z8.d, z10.d, z8.d - __ sve_eor(z19, z0, z29); // eor z19.d, z0.d, z29.d - __ sve_orr(z16, z13, z23); // orr z16.d, z13.d, z23.d - __ sve_bic(z23, z30, z13); // bic z23.d, z30.d, z13.d - __ sve_uzp1(z25, __ H, z22, z0); // uzp1 z25.h, z22.h, z0.h - __ sve_uzp2(z25, __ H, z30, z11); // uzp2 z25.h, z30.h, z11.h - __ sve_fabd(z14, __ S, p5, z22); // fabd z14.s, p5/m, z14.s, z22.s - __ sve_bext(z5, __ H, z18, z0); // bext z5.h, z18.h, z0.h - __ sve_bdep(z9, __ D, z2, z3); // bdep z9.d, z2.d, z3.d - __ sve_bsl(z14, z4, z29); // bsl z14.d, z14.d, z4.d, z29.d - __ sve_eor3(z14, z22, z4); // eor3 z14.d, z14.d, z22.d, z4.d - __ sve_sqadd(z27, __ S, p3, z22); // sqadd z27.s, p3/m, z27.s, z22.s - __ sve_sqsub(z31, __ S, p6, z11); // sqsub z31.s, p6/m, z31.s, z11.s - __ sve_uqadd(z12, __ B, p4, z28); // uqadd z12.b, p4/m, z12.b, z28.b - __ sve_uqsub(z28, __ D, p4, z4); // uqsub z28.d, p4/m, z28.d, z4.d + __ sve_sdiv(z28, __ D, p4, z10); // sdiv z28.d, p4/m, z28.d, z10.d + __ sve_udiv(z17, __ D, p7, z7); // udiv z17.d, p7/m, z17.d, z7.d + __ sve_neg(z4, __ H, p3, z24); // neg z4.h, p3/m, z24.h + __ sve_not(z9, __ B, p2, z11); // not z9.b, p2/m, z11.b + __ sve_orr(z4, __ S, p5, z22); // orr z4.s, p5/m, z4.s, z22.s + __ sve_rbit(z4, __ H, p0, z15); // rbit z4.h, p0/m, z15.h + __ sve_revb(z4, __ D, p7, z26); // revb z4.d, p7/m, z26.d + __ sve_smax(z5, __ H, p5, z26); // smax z5.h, p5/m, z5.h, z26.h + __ sve_smin(z31, __ B, p0, z25); // smin z31.b, p0/m, z31.b, z25.b + __ sve_umax(z8, __ S, p1, z3); // umax z8.s, p1/m, z8.s, z3.s + __ sve_umin(z7, __ D, p6, z24); // umin z7.d, p6/m, z7.d, z24.d + __ sve_sub(z24, __ B, p7, z17); // sub z24.b, p7/m, z24.b, z17.b + __ sve_fabs(z10, __ S, p3, z30); // fabs z10.s, p3/m, z30.s + __ sve_fadd(z8, __ S, p6, z29); // fadd z8.s, p6/m, z8.s, z29.s + __ sve_fdiv(z31, __ D, p5, z31); // fdiv z31.d, p5/m, z31.d, z31.d + __ sve_fmax(z0, __ D, p5, z7); // fmax z0.d, p5/m, z0.d, z7.d + __ sve_fmin(z29, __ S, p6, z22); // fmin z29.s, p6/m, z29.s, z22.s + __ sve_fmul(z29, __ S, p6, z20); // fmul z29.s, p6/m, z29.s, z20.s + __ sve_fneg(z6, __ S, p4, z18); // fneg z6.s, p4/m, z18.s + __ sve_frintm(z26, __ S, p5, z8); // frintm z26.s, p5/m, z8.s + __ sve_frintn(z19, __ S, p2, z28); // frintn z19.s, p2/m, z28.s + __ sve_frintp(z17, __ D, p1, z30); // frintp z17.d, p1/m, z30.d + __ sve_fsqrt(z24, __ D, p7, z14); // fsqrt z24.d, p7/m, z14.d + __ sve_fsub(z14, __ D, p4, z10); // fsub z14.d, p4/m, z14.d, z10.d + __ sve_fmad(z11, __ S, p6, z0, z11); // fmad z11.s, p6/m, z0.s, z11.s + __ sve_fmla(z28, __ D, p5, z23, z20); // fmla z28.d, p5/m, z23.d, z20.d + __ sve_fmls(z23, __ S, p5, z29, z24); // fmls z23.s, p5/m, z29.s, z24.s + __ sve_fmsb(z27, __ S, p1, z23, z13); // fmsb z27.s, p1/m, z23.s, z13.s + __ sve_fnmad(z4, __ D, p3, z31, z26); // fnmad z4.d, p3/m, z31.d, z26.d + __ sve_fnmsb(z20, __ D, p1, z2, z29); // fnmsb z20.d, p1/m, z2.d, z29.d + __ sve_fnmla(z0, __ S, p7, z23, z3); // fnmla z0.s, p7/m, z23.s, z3.s + __ sve_fnmls(z5, __ D, p2, z28, z13); // fnmls z5.d, p2/m, z28.d, z13.d + __ sve_mla(z13, __ H, p3, z8, z10); // mla z13.h, p3/m, z8.h, z10.h + __ sve_mls(z9, __ S, p4, z0, z29); // mls z9.s, p4/m, z0.s, z29.s + __ sve_and(z16, z13, z23); // and z16.d, z13.d, z23.d + __ sve_eor(z23, z30, z13); // eor z23.d, z30.d, z13.d + __ sve_orr(z25, z22, z0); // orr z25.d, z22.d, z0.d + __ sve_bic(z25, z30, z11); // bic z25.d, z30.d, z11.d + __ sve_uzp1(z14, __ H, z23, z22); // uzp1 z14.h, z23.h, z22.h + __ sve_uzp2(z5, __ H, z18, z0); // uzp2 z5.h, z18.h, z0.h + __ sve_fabd(z9, __ D, p0, z3); // fabd z9.d, p0/m, z9.d, z3.d + __ sve_bext(z14, __ H, z4, z29); // bext z14.h, z4.h, z29.h + __ sve_bdep(z14, __ D, z22, z4); // bdep z14.d, z22.d, z4.d + __ sve_bsl(z27, z15, z22); // bsl z27.d, z27.d, z15.d, z22.d + __ sve_eor3(z31, z24, z11); // eor3 z31.d, z31.d, z24.d, z11.d + __ sve_sqadd(z12, __ B, p4, z28); // sqadd z12.b, p4/m, z12.b, z28.b + __ sve_sqsub(z28, __ D, p4, z4); // sqsub z28.d, p4/m, z28.d, z4.d + __ sve_uqadd(z6, __ S, p0, z15); // uqadd z6.s, p0/m, z6.s, z15.s + __ sve_uqsub(z1, __ S, p5, z18); // uqsub z1.s, p5/m, z1.s, z18.s // SVEReductionOp - __ sve_andv(v6, __ S, p0, z15); // andv s6, p0, z15.s - __ sve_orv(v1, __ S, p5, z18); // orv s1, p5, z18.s - __ sve_eorv(v2, __ H, p2, z4); // eorv h2, p2, z4.h - __ sve_smaxv(v11, __ S, p2, z28); // smaxv s11, p2, z28.s - __ sve_sminv(v3, __ H, p5, z31); // sminv h3, p5, z31.h - __ sve_umaxv(v24, __ H, p5, z15); // umaxv h24, p5, z15.h - __ sve_uminv(v6, __ H, p3, z8); // uminv h6, p3, z8.h - __ sve_fminv(v21, __ D, p7, z4); // fminv d21, p7, z4.d - __ sve_fmaxv(v24, __ S, p5, z6); // fmaxv s24, p5, z6.s - __ sve_fadda(v4, __ D, p2, z9); // fadda d4, p2, d4, z9.d - __ sve_uaddv(v10, __ S, p1, z31); // uaddv d10, p1, z31.s + __ sve_andv(v2, __ H, p2, z4); // andv h2, p2, z4.h + __ sve_orv(v11, __ S, p2, z28); // orv s11, p2, z28.s + __ sve_eorv(v3, __ H, p5, z31); // eorv h3, p5, z31.h + __ sve_smaxv(v24, __ H, p5, z15); // smaxv h24, p5, z15.h + __ sve_sminv(v6, __ H, p3, z8); // sminv h6, p3, z8.h + __ sve_umaxv(v21, __ D, p7, z4); // umaxv d21, p7, z4.d + __ sve_uminv(v24, __ B, p5, z6); // uminv b24, p5, z6.b + __ sve_fminv(v4, __ D, p2, z9); // fminv d4, p2, z9.d + __ sve_fmaxv(v10, __ D, p1, z31); // fmaxv d10, p1, z31.d + __ sve_fadda(v25, __ D, p3, z3); // fadda d25, p3, d25, z3.d + __ sve_uaddv(v14, __ H, p2, z2); // uaddv d14, p2, z2.h // AddWideNEONOp - __ saddwv(v25, v26, __ T8H, v27, __ T8B); // saddw v25.8H, v26.8H, v27.8B - __ saddwv2(v15, v16, __ T8H, v17, __ T16B); // saddw2 v15.8H, v16.8H, v17.16B - __ saddwv(v3, v4, __ T4S, v5, __ T4H); // saddw v3.4S, v4.4S, v5.4H - __ saddwv2(v18, v19, __ T4S, v20, __ T8H); // saddw2 v18.4S, v19.4S, v20.8H - __ saddwv(v14, v15, __ T2D, v16, __ T2S); // saddw v14.2D, v15.2D, v16.2S - __ saddwv2(v10, v11, __ T2D, v12, __ T4S); // saddw2 v10.2D, v11.2D, v12.4S - __ uaddwv(v2, v3, __ T8H, v4, __ T8B); // uaddw v2.8H, v3.8H, v4.8B - __ uaddwv2(v10, v11, __ T8H, v12, __ T16B); // uaddw2 v10.8H, v11.8H, v12.16B - __ uaddwv(v8, v9, __ T4S, v10, __ T4H); // uaddw v8.4S, v9.4S, v10.4H - __ uaddwv2(v11, v12, __ T4S, v13, __ T8H); // uaddw2 v11.4S, v12.4S, v13.8H - __ uaddwv(v22, v23, __ T2D, v24, __ T2S); // uaddw v22.2D, v23.2D, v24.2S - __ uaddwv2(v3, v4, __ T2D, v5, __ T4S); // uaddw2 v3.2D, v4.2D, v5.4S + __ saddwv(v8, v9, __ T8H, v10, __ T8B); // saddw v8.8H, v9.8H, v10.8B + __ saddwv2(v11, v12, __ T8H, v13, __ T16B); // saddw2 v11.8H, v12.8H, v13.16B + __ saddwv(v22, v23, __ T4S, v24, __ T4H); // saddw v22.4S, v23.4S, v24.4H + __ saddwv2(v3, v4, __ T4S, v5, __ T8H); // saddw2 v3.4S, v4.4S, v5.8H + __ saddwv(v12, v13, __ T2D, v14, __ T2S); // saddw v12.2D, v13.2D, v14.2S + __ saddwv2(v24, v25, __ T2D, v26, __ T4S); // saddw2 v24.2D, v25.2D, v26.4S + __ uaddwv(v9, v10, __ T8H, v11, __ T8B); // uaddw v9.8H, v10.8H, v11.8B + __ uaddwv2(v27, v28, __ T8H, v29, __ T16B); // uaddw2 v27.8H, v28.8H, v29.16B + __ uaddwv(v27, v28, __ T4S, v29, __ T4H); // uaddw v27.4S, v28.4S, v29.4H + __ uaddwv2(v6, v7, __ T4S, v8, __ T8H); // uaddw2 v6.4S, v7.4S, v8.8H + __ uaddwv(v20, v21, __ T2D, v22, __ T2S); // uaddw v20.2D, v21.2D, v22.2S + __ uaddwv2(v20, v21, __ T2D, v22, __ T4S); // uaddw2 v20.2D, v21.2D, v22.4S __ bind(forth); @@ -1474,30 +1476,30 @@ 0x9101a1a0, 0xb10a5cc8, 0xd10810aa, 0xf10fd061, 0x120cb166, 0x321764bc, 0x52174681, 0x720c0227, 0x9241018e, 0xb25a2969, 0xd278b411, 0xf26aad01, - 0x14000000, 0x17ffffd7, 0x140004d0, 0x94000000, - 0x97ffffd4, 0x940004cd, 0x3400000a, 0x34fffa2a, - 0x3400994a, 0x35000008, 0x35fff9c8, 0x350098e8, - 0xb400000b, 0xb4fff96b, 0xb400988b, 0xb500001d, - 0xb5fff91d, 0xb500983d, 0x10000013, 0x10fff8b3, - 0x100097d3, 0x90000013, 0x36300016, 0x3637f836, - 0x36309756, 0x3758000c, 0x375ff7cc, 0x375896ec, + 0x14000000, 0x17ffffd7, 0x140004d2, 0x94000000, + 0x97ffffd4, 0x940004cf, 0x3400000a, 0x34fffa2a, + 0x3400998a, 0x35000008, 0x35fff9c8, 0x35009928, + 0xb400000b, 0xb4fff96b, 0xb40098cb, 0xb500001d, + 0xb5fff91d, 0xb500987d, 0x10000013, 0x10fff8b3, + 0x10009813, 0x90000013, 0x36300016, 0x3637f836, + 0x36309796, 0x3758000c, 0x375ff7cc, 0x3758972c, 0x128313a0, 0x528a32c7, 0x7289173b, 0x92ab3acc, 0xd2a0bf94, 0xf2c285e8, 0x9358722f, 0x330e652f, 0x53067f3b, 0x93577c53, 0xb34a1aac, 0xd35a4016, 0x13946c63, 0x93c3dbc8, 0x54000000, 0x54fff5a0, - 0x540094c0, 0x54000001, 0x54fff541, 0x54009461, - 0x54000002, 0x54fff4e2, 0x54009402, 0x54000002, - 0x54fff482, 0x540093a2, 0x54000003, 0x54fff423, - 0x54009343, 0x54000003, 0x54fff3c3, 0x540092e3, - 0x54000004, 0x54fff364, 0x54009284, 0x54000005, - 0x54fff305, 0x54009225, 0x54000006, 0x54fff2a6, - 0x540091c6, 0x54000007, 0x54fff247, 0x54009167, - 0x54000008, 0x54fff1e8, 0x54009108, 0x54000009, - 0x54fff189, 0x540090a9, 0x5400000a, 0x54fff12a, - 0x5400904a, 0x5400000b, 0x54fff0cb, 0x54008feb, - 0x5400000c, 0x54fff06c, 0x54008f8c, 0x5400000d, - 0x54fff00d, 0x54008f2d, 0x5400000e, 0x54ffefae, - 0x54008ece, 0x5400000f, 0x54ffef4f, 0x54008e6f, + 0x54009500, 0x54000001, 0x54fff541, 0x540094a1, + 0x54000002, 0x54fff4e2, 0x54009442, 0x54000002, + 0x54fff482, 0x540093e2, 0x54000003, 0x54fff423, + 0x54009383, 0x54000003, 0x54fff3c3, 0x54009323, + 0x54000004, 0x54fff364, 0x540092c4, 0x54000005, + 0x54fff305, 0x54009265, 0x54000006, 0x54fff2a6, + 0x54009206, 0x54000007, 0x54fff247, 0x540091a7, + 0x54000008, 0x54fff1e8, 0x54009148, 0x54000009, + 0x54fff189, 0x540090e9, 0x5400000a, 0x54fff12a, + 0x5400908a, 0x5400000b, 0x54fff0cb, 0x5400902b, + 0x5400000c, 0x54fff06c, 0x54008fcc, 0x5400000d, + 0x54fff00d, 0x54008f6d, 0x5400000e, 0x54ffefae, + 0x54008f0e, 0x5400000f, 0x54ffef4f, 0x54008eaf, 0xd40658e1, 0xd4014d22, 0xd4046543, 0xd4273f60, 0xd44cad80, 0xd503201f, 0xd503203f, 0xd503205f, 0xd503209f, 0xd50320bf, 0xd503219f, 0xd50323bf, @@ -1540,7 +1542,7 @@ 0x39598921, 0x795d3077, 0x399d0675, 0x7998d8f3, 0x79dbd02a, 0xb99d068a, 0xfd5d11a0, 0xbd58d76b, 0xfd1ac72d, 0xbd1d9c14, 0x5800001a, 0x18ffda33, - 0xf8991100, 0xd8007920, 0xf8a758e0, 0xf9989d80, + 0xf8991100, 0xd8007960, 0xf8a758e0, 0xf9989d80, 0x1a0b0298, 0x3a1c01a0, 0x5a0400ea, 0x7a02020f, 0x9a1d028c, 0xba0e01ad, 0xda140186, 0xfa19022c, 0x0b2b877e, 0x2b21c8ee, 0xcb3ba47d, 0x6b3ae9a0, @@ -1765,23 +1767,24 @@ 0x04e71f54, 0x04d6b0d4, 0x044003ad, 0x041a0029, 0x041099fb, 0x04db1e24, 0x0419a302, 0x041abdba, 0x04d90e16, 0x04d38571, 0x04118210, 0x04d006fc, - 0x0497b15c, 0x049ebcf1, 0x04580f04, 0x05278969, - 0x05a496c4, 0x044801e4, 0x04ca1f44, 0x04491745, - 0x040b033f, 0x04810468, 0x04dcbb07, 0x65809e38, - 0x658d8fca, 0x65869ba8, 0x65c797ff, 0x65c294e0, - 0x049dbadd, 0x6582ba9d, 0x6580b246, 0x6581b51a, - 0x658dab93, 0x65c187d1, 0x65b19dd8, 0x65eb0b53, - 0x65fc29e0, 0x65f7b797, 0x65bbd81d, 0x65a4ed97, - 0x65b45aff, 0x65e07fa2, 0x04454097, 0x044d6e3c, - 0x04283148, 0x04bd3013, 0x047731b0, 0x04ed33d7, - 0x05606ad9, 0x056b6fd9, 0x658896ce, 0x4540b245, - 0x45c3b449, 0x04243fae, 0x0436388e, 0x44988edb, - 0x449a997f, 0x4419938c, 0x44db909c, 0x049a21e6, - 0x04983641, 0x04592882, 0x04882b8b, 0x044a37e3, - 0x044935f8, 0x044b2d06, 0x65c73c95, 0x658634d8, - 0x65d82924, 0x048127ea, 0x0e3b1359, 0x4e31120f, - 0x0e651083, 0x4e741272, 0x0eb011ee, 0x4eac116a, - 0x2e241062, 0x6e2c116a, 0x2e6a1128, 0x6e6d118b, - 0x2eb812f6, 0x6ea51083, + 0x04d4115c, 0x04d51cf1, 0x0457af04, 0x041ea969, + 0x049816c4, 0x056781e4, 0x05e49f44, 0x04481745, + 0x040a033f, 0x04890468, 0x04cb1b07, 0x04011e38, + 0x049cafca, 0x65809ba8, 0x65cd97ff, 0x65c694e0, + 0x65879add, 0x65829a9d, 0x049db246, 0x6582b51a, + 0x6580ab93, 0x65c1a7d1, 0x65cdbdd8, 0x65c1914e, + 0x65ab980b, 0x65f416fc, 0x65b837b7, 0x65ada6fb, + 0x65facfe4, 0x65fde454, 0x65a35ee0, 0x65ed6b85, + 0x044a4d0d, 0x049d7009, 0x043731b0, 0x04ad33d7, + 0x046032d9, 0x04eb33d9, 0x05766aee, 0x05606e45, + 0x65c88069, 0x455db08e, 0x45c4b6ce, 0x042f3edb, + 0x0438397f, 0x4418938c, 0x44da909c, 0x449981e6, + 0x449b9641, 0x045a2882, 0x04982b8b, 0x045937e3, + 0x044835f8, 0x044a2d06, 0x04c93c95, 0x040b34d8, + 0x65c72924, 0x65c627ea, 0x65d82c79, 0x0441284e, + 0x0e2a1128, 0x4e2d118b, 0x0e7812f6, 0x4e651083, + 0x0eae11ac, 0x4eba1338, 0x2e2b1149, 0x6e3d139b, + 0x2e7d139b, 0x6e6810e6, 0x2eb612b4, 0x6eb612b4, + }; // END Generated code -- do not edit diff --git a/test/hotspot/gtest/aarch64/test_assembler_aarch64.cpp b/test/hotspot/gtest/aarch64/test_assembler_aarch64.cpp index 63b8ddd865dd..db240aeee904 100644 --- a/test/hotspot/gtest/aarch64/test_assembler_aarch64.cpp +++ b/test/hotspot/gtest/aarch64/test_assembler_aarch64.cpp @@ -30,9 +30,15 @@ #include "asm/macroAssembler.hpp" #include "compiler/disassembler.hpp" #include "memory/resourceArea.hpp" +#include "runtime/threadWXSetters.inline.hpp" +#include "utilities/powerOfTwo.hpp" #include "nativeInst_aarch64.hpp" #include "unittest.hpp" +// remove comment for debug log +//#define LOG_PLEASE +#include "testutils.hpp" + #define __ _masm. static void asm_check(const unsigned int *insns, const unsigned int *insns1, size_t len) { @@ -511,4 +517,137 @@ TEST_VM(AssemblerAArch64, native_instruction_load_predicates) { EXPECT_FALSE(ni_ldrs->is_ldrw_gpr_literal()); } +struct GtestFriendToMacroAssembler { + + typedef MacroAssembler::KlassDecodeMode Mode; + + typedef address (*decode_function)(narrowKlass encoded); + typedef narrowKlass (*encode_function)(address decoded); + + using CKP = CompressedKlassPointers; + using MA = MacroAssembler; + + static void build_and_run_encode_decode_klass(address base, int shift, + Mode expected_mode) { + + if ((shift + CKP::narrow_klass_pointer_bits()) > 32) { + return; // unsupported + } + + LOG_HERE("base " PTR_FORMAT " shift %d => mode %d: ", + p2u(base), shift, (int)expected_mode); + + // Test if the given base+shift value (with an assumed maximum Klass* range) + // yields the expected decode mode + const Mode real_mode = MA::klass_decode_mode(base, shift, CKP::max_klass_range_size()); + + ASSERT_EQ(real_mode, expected_mode) << " different mode?"; + + // Now generate encode and decode functions for this base and shift ... + BufferBlob* bb = BufferBlob::create("test_decode_klass", 512); + CodeBuffer code(bb); + address entry_encode = nullptr; + address entry_decode = nullptr; + + { + MA masm(&code); + + entry_encode = masm.pc(); + masm.emit_encode_klass_not_null(c_rarg0, // x0: dst+return + c_rarg0, // x0: src + rscratch1, // x8: tmp + base, shift, + real_mode); + masm.ret(lr); + + entry_decode = masm.pc(); + masm.emit_decode_klass_not_null(c_rarg0, // x0: dst+return + c_rarg0, // x0: src + rscratch1, // x8: tmp + base, shift, + real_mode); + masm.ret(lr); + + masm.flush(); // icache invalidate + } + + { + MACOS_AARCH64_ONLY(ThreadWXEnable wx(WXExec, Thread::current())); + + // ... and call it with some values spread over the full width of the narrowKlass range. + const narrowKlass highest = right_n_bits(CKP::narrow_klass_pointer_bits()); + + const struct { narrowKlass encoded; address decoded; } testvalues [] = { + { 0, base }, + // The highest value we can express with the current narrowKlass width + { highest, (address)(p2u(base) + ((uint64_t)highest << shift)) }, + // midpoint + { highest / 2, (address)(p2u(base) + (((uint64_t)highest / 2) << shift)) } + }; + constexpr int num_testvalues = sizeof(testvalues) / sizeof(testvalues[0]); + + for (int i = 0; i < num_testvalues; i++) { + const narrowKlass encoded = testvalues[i].encoded; + const address decoded = testvalues[i].decoded; + + const narrowKlass encoded_real = ((encode_function)entry_encode)(decoded); + LOG_HERE(" encode: " PTR_FORMAT " => " UINT32_FORMAT_X, p2u(decoded), encoded_real); + EXPECT_EQ(encoded_real, encoded) << " bad encode?"; + + const address decoded_real = ((decode_function)entry_decode)(encoded); + LOG_HERE(" decode: " UINT32_FORMAT_X " => " PTR_FORMAT, encoded, p2u(decoded_real)); + EXPECT_EQ(decoded_real, decoded) << " bad decode?"; + } + } + BufferBlob::free(bb); + } + + static void test_decode_encode_klass() { + + for (int shift = 0; shift < CKP::max_shift(); shift++) { + + // test zero-based + build_and_run_encode_decode_klass((address)nullptr, shift, MA::KlassDecodeZero); + + // test XOR-based encoding + // Base must be a valid immediate that does not intersect the highest left-shifted nKlass + const int lowest_xor_base_bit = 32; + const int highest_xor_base_bit = 51; // highest user address space bit on all our platforms + + // Highest base bit set + build_and_run_encode_decode_klass((address)nth_bit(highest_xor_base_bit), shift, MA::KlassDecodeXor); + // lowest base bit set + build_and_run_encode_decode_klass((address)nth_bit(lowest_xor_base_bit), shift, MA::KlassDecodeXor); + // all base bits set + build_and_run_encode_decode_klass((address)(right_n_bits(highest_xor_base_bit - lowest_xor_base_bit) << lowest_xor_base_bit), + shift, MA::KlassDecodeXor); + + // test movk-based + // Only bits in the third quadrant and not a valid immediate + build_and_run_encode_decode_klass((address)0x0000'A000'0000'0000ULL, 0, MA::KlassDecodeMovk); + + // test Fallback mode. + // base has low bits that intersect with nKlass, no other mode would work + build_and_run_encode_decode_klass((address)(0x5'0000'0000ULL + os::vm_page_size()), + shift, MA::KlassDecodeFallback); + build_and_run_encode_decode_klass((address)(0x5'0000'0000ULL - os::vm_page_size()), + shift, MA::KlassDecodeFallback); + + // a base that has ones in all four quadrants to trigger the full movz+3*movk path + // when loading the immediate + build_and_run_encode_decode_klass((address)right_n_bits(52), + shift, MA::KlassDecodeFallback); + + // spread over multiple 16-bit quadrants and not encodable as immediate, + // no other mode would work + build_and_run_encode_decode_klass((address)0x00AA'AAA0'0000'0000ULL, + shift, MA::KlassDecodeFallback); + } + } +}; + +// Run this with and without UseCompactObjectHeaders +TEST_VM(AssemblerAArch64, decode_encode_klass_not_null) { + GtestFriendToMacroAssembler::test_decode_encode_klass(); +} #endif // AARCH64 diff --git a/test/hotspot/gtest/runtime/test_os_windows.cpp b/test/hotspot/gtest/runtime/test_os_windows.cpp index 6822e37b539d..5efa0580edad 100644 --- a/test/hotspot/gtest/runtime/test_os_windows.cpp +++ b/test/hotspot/gtest/runtime/test_os_windows.cpp @@ -32,6 +32,8 @@ #include "concurrentTestRunner.inline.hpp" #include "unittest.hpp" +#include + namespace { class MemoryReleaser { char* const _ptr; @@ -873,4 +875,242 @@ TEST_VM(os_windows, SafeFetch32_with_page_guard_protection) { ::VirtualFree(p, 0, MEM_RELEASE); } +#define SKIP_IF_PLACEHOLDER_NOT_SUPPORTED \ + if (os::win32::VirtualAlloc2 == nullptr) GTEST_SKIP() << "VirtualAlloc2 not available"; + +TEST_VM(os, placeholder_reserve_and_convert) { + SKIP_IF_PLACEHOLDER_NOT_SUPPORTED; + + const size_t size = 4 * os::vm_allocation_granularity(); + + os::win32::PlaceholderRegion region = os::win32::reserve_placeholder_memory(size, nullptr); + ASSERT_FALSE(region.is_empty()); + ASSERT_EQ(region.size(), size); + ASSERT_NE(region.base(), (char*)nullptr); + + char* reserved = os::win32::convert_to_reserved(region); + ASSERT_EQ(reserved, region.base()); + + // Commit, but bypass NMT + ASSERT_NE(::VirtualAlloc(reserved, size, MEM_COMMIT, PAGE_READWRITE), nullptr); + // Touch the memory to confirm it's usable. + memset(reserved, 0xAB, size); + EXPECT_EQ((unsigned char)reserved[0], 0xAB); + EXPECT_EQ((unsigned char)reserved[size - 1], 0xAB); + + ASSERT_TRUE(::VirtualFree(reserved, 0, MEM_RELEASE)); +} + +TEST_VM(os, placeholder_split_two_way) { + SKIP_IF_PLACEHOLDER_NOT_SUPPORTED; + + const size_t granularity = os::vm_allocation_granularity(); + const size_t total = 4 * granularity; + const size_t split_offset = 3 * granularity; + + os::win32::PlaceholderRegion region = os::win32::reserve_placeholder_memory(total, nullptr); + ASSERT_FALSE(region.is_empty()); + + char* original_base = region.base(); + os::win32::PlaceholderRegionPair split = os::win32::split_memory(region, split_offset); + + // Leading piece: [base, base+split_offset) + ASSERT_EQ(split.left.base(), original_base); + ASSERT_EQ(split.left.size(), split_offset); + + // Trailing piece: [base+split_offset, base+total) + ASSERT_EQ(split.right.base(), original_base + split_offset); + ASSERT_EQ(split.right.size(), total - split_offset); + + // Convert both and commit. + char* addr1 = os::win32::convert_to_reserved(split.left); + char* addr2 = os::win32::convert_to_reserved(split.right); + ASSERT_EQ(addr1, original_base); + ASSERT_EQ(addr2, original_base + split_offset); + + // Commit, but bypass NMT + ASSERT_NE(::VirtualAlloc(addr1, split_offset, MEM_COMMIT, PAGE_READWRITE), nullptr); + ASSERT_NE(::VirtualAlloc(addr2, total - split_offset, MEM_COMMIT, PAGE_READWRITE), nullptr); + + // Touch the memory to confirm it's usable. + memset(addr1, 0x11, split_offset); + memset(addr2, 0x22, total - split_offset); + EXPECT_EQ((unsigned char)addr1[0], 0x11); + EXPECT_EQ((unsigned char)addr2[0], 0x22); + + // Verify we can release the parts separately. + ASSERT_TRUE(::VirtualFree(addr1, 0, MEM_RELEASE)); + ASSERT_TRUE(::VirtualFree(addr2, 0, MEM_RELEASE)); +} + +TEST_VM(os, placeholder_split_consumes_full_range) { + SKIP_IF_PLACEHOLDER_NOT_SUPPORTED; + + const size_t region_size = os::vm_allocation_granularity(); + os::win32::PlaceholderRegion region = os::win32::reserve_placeholder_memory(region_size, nullptr); + ASSERT_FALSE(region.is_empty()); + + char* original_base = region.base(); + os::win32::PlaceholderRegionPair split = os::win32::split_memory(region, region_size); + + // Leading piece + ASSERT_EQ(split.left.base(), original_base); + ASSERT_EQ(split.left.size(), region_size); + + // Trailing piece + ASSERT_TRUE(split.right.is_empty()); + + // Commit and touch to confirm it's usable. + char* addr = os::win32::convert_to_reserved(split.left); + ASSERT_NE(::VirtualAlloc(addr, region_size, MEM_COMMIT, PAGE_READWRITE), nullptr); + memset(addr, 0x11, region_size); + EXPECT_EQ((unsigned char)addr[0], 0x11); + + ASSERT_TRUE(::VirtualFree(addr, 0, MEM_RELEASE)); +} + +TEST_VM(os, placeholder_split_consumes_nothing) { + SKIP_IF_PLACEHOLDER_NOT_SUPPORTED; + + const size_t region_size = os::vm_allocation_granularity(); + os::win32::PlaceholderRegion region = os::win32::reserve_placeholder_memory(region_size, nullptr); + ASSERT_FALSE(region.is_empty()); + + char* original_base = region.base(); + os::win32::PlaceholderRegionPair split = os::win32::split_memory(region, 0); + + // Leading piece + ASSERT_TRUE(split.left.is_empty()); + + // Trailing piece + ASSERT_EQ(split.right.base(), original_base); + ASSERT_EQ(split.right.size(), region_size); + + // Commit and touch to confirm it's usable. + char* addr = os::win32::convert_to_reserved(split.right); + ASSERT_NE(::VirtualAlloc(addr, region_size, MEM_COMMIT, PAGE_READWRITE), nullptr); + memset(addr, 0x11, region_size); + EXPECT_EQ((unsigned char)addr[0], 0x11); + + ASSERT_TRUE(::VirtualFree(addr, 0, MEM_RELEASE)); +} + +TEST_VM_FATAL_ERROR_MSG(os, placeholder_double_convert, ".*Failed to convert placeholder.*") { + SKIP_IF_PLACEHOLDER_NOT_SUPPORTED; + const size_t size = 4 * os::vm_allocation_granularity(); + + os::win32::PlaceholderRegion region = os::win32::reserve_placeholder_memory(size, nullptr); + ASSERT_FALSE(region.is_empty()); + ASSERT_EQ(region.size(), size); + ASSERT_NE(region.base(), (char*)nullptr); + + // Double convert + char* reserved = os::win32::convert_to_reserved(region); + ASSERT_EQ(reserved, region.base()); + // This second conversion attempt should crash producing the error "...Failed to convert placeholder..." + reserved = os::win32::convert_to_reserved(region); +} + +TEST_VM(os, placeholder_commit_before_convert) { + SKIP_IF_PLACEHOLDER_NOT_SUPPORTED; + const size_t size = 4 * os::vm_allocation_granularity(); + + os::win32::PlaceholderRegion region = os::win32::reserve_placeholder_memory(size, nullptr); + ASSERT_FALSE(region.is_empty()); + ASSERT_EQ(region.size(), size); + ASSERT_NE(region.base(), (char*)nullptr); + + // Committing should fail here, but not crash. + ASSERT_FALSE(::VirtualAlloc(region.base(), size, MEM_COMMIT, PAGE_READWRITE)); + ASSERT_TRUE(::VirtualFree(region.base(), 0, MEM_RELEASE)); +} + +TEST_VM(os, placeholder_release_before_convert) { + SKIP_IF_PLACEHOLDER_NOT_SUPPORTED; + + const size_t size = 4 * os::vm_allocation_granularity(); + + os::win32::PlaceholderRegion region = os::win32::reserve_placeholder_memory(size, nullptr); + ASSERT_FALSE(region.is_empty()); + ASSERT_EQ(region.size(), size); + ASSERT_NE(region.base(), (char*)nullptr); + + ASSERT_TRUE(::VirtualFree(region.base(), 0, MEM_RELEASE)); +} + +// Test that reserve_with_numa_placeholder works correctly. +// On NUMA systems with a single NUMA node, there is no true interleaving +// (all chunks are put on node 0) but the placeholder split/replace path +// is still properly exercised. +TEST_VM(os_windows, placeholder_numa_reserve_commit) { + SKIP_IF_PLACEHOLDER_NOT_SUPPORTED; + + const size_t num_nodes = os::numa_get_groups_num(); + + // Enable NUMA interleaving for this test so the correct code path is taken. + AutoSaveRestore FLAG_GUARD(UseNUMAInterleaving); + AutoSaveRestore FLAG_GUARD(UseLargePages); + FLAG_SET_CMDLINE(UseNUMAInterleaving, true); + FLAG_SET_CMDLINE(UseLargePages, false); + + // Allocate a region large enough to span multiple NUMA interleave chunks. + // NUMAInterleaveGranularity defaults to 2MB + const size_t chunk_size = NUMAInterleaveGranularity; + const size_t num_chunks = 4; + const size_t size = num_chunks * chunk_size; + + char* result = os::attempt_reserve_memory_at(nullptr, size, mtTest); + ASSERT_TRUE(result != nullptr) << "Failed to reserve memory"; + ASSERT_TRUE(is_aligned(result, os::vm_allocation_granularity())); + ASSERT_TRUE(os::commit_memory(result, size, false)); + + // Walk (and touch) the chunks using the same alignment logic as reserve_with_numa_placeholder: + // the first chunk may be shorter (up to the next chunk_size boundary), + // then full chunk_size pieces, with a possible shorter trailing chunk. + PSAPI_WORKING_SET_EX_INFORMATION wsi[num_chunks + 1]; + memset(wsi, 0, sizeof(wsi)); + size_t bytes_remaining = size; + char* addr = result; + size_t actual_chunks = 0; + + while (bytes_remaining > 0) { + size_t this_chunk_size = MIN2(bytes_remaining, chunk_size - ((size_t)addr % chunk_size)); + + memset(addr, 0xDA, this_chunk_size); + + wsi[actual_chunks] = {0}; + wsi[actual_chunks].VirtualAddress = addr; + actual_chunks++; + + bytes_remaining -= this_chunk_size; + addr += this_chunk_size; + } + + BOOL query_ok = QueryWorkingSetEx(GetCurrentProcess(), wsi, sizeof(wsi)); + ASSERT_TRUE(query_ok) << "QueryWorkingSetEx failed: " << GetLastError(); + + // Verify all pages are valid (in the working set). + for (size_t i = 0; i < actual_chunks; i++) { + EXPECT_TRUE(wsi[i].VirtualAttributes.Valid) << "Chunk " << i << " page not valid in working set"; + } + + if (num_nodes > 1) { + // On a multi-NUMA system, verify that not all chunks are assigned to the same node. + ULONG first_node = (ULONG)wsi[0].VirtualAttributes.Node; + bool found_different_node = false; + for (size_t i = 1; i < actual_chunks; i++) { + if (wsi[i].VirtualAttributes.Valid && + (ULONG)wsi[i].VirtualAttributes.Node != first_node) { + found_different_node = true; + break; + } + } + EXPECT_TRUE(found_different_node) + << "All " << actual_chunks << " chunks assigned to NUMA node " << first_node + << "; expected interleaving across " << num_nodes << " nodes"; + } + + os::release_memory(result, size); +} + #endif diff --git a/test/hotspot/gtest/utilities/test_parse_memory_size.cpp b/test/hotspot/gtest/utilities/test_parse_memory_size.cpp index fa4ec731189b..e50d81c7a3c1 100644 --- a/test/hotspot/gtest/utilities/test_parse_memory_size.cpp +++ b/test/hotspot/gtest/utilities/test_parse_memory_size.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2022 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -23,6 +23,7 @@ */ #include "jvm_io.h" +#include "runtime/os.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/macros.hpp" #include "utilities/ostream.hpp" @@ -156,3 +157,69 @@ TEST(ParseMemorySize, negatives_both) { do_test_invalid_for_parse_arguments("100 M"); // parse_memory_size would see "100", parse_argument_memory_size would reject it do_test_invalid_for_parse_arguments("100X"); // parse_memory_size would see "100", parse_argument_memory_size would reject it } + +// Hex prefix handling for negative numbers. Only signed types are covered here: +// parse_integer_impl() refuses a leading '-' for unsigned types before the base +// is ever used. + +template +static void test_negative_hex_prefix() { + T value = 17; + char* end = nullptr; + + // Both spellings of the prefix must be recognized, just as "0x"/"0X" are for + // positive numbers. + ASSERT_TRUE(parse_integer("-0x10", &end, &value)); + ASSERT_EQ(value, (T)-16); + EXPECT_STREQ(end, ""); + + ASSERT_TRUE(parse_integer("-0X10", &end, &value)); + ASSERT_EQ(value, (T)-16); + EXPECT_STREQ(end, ""); + + ASSERT_TRUE(parse_integer("-0xff", &end, &value)); + ASSERT_EQ(value, (T)-255); + + ASSERT_TRUE(parse_integer("-0XFF", &end, &value)); + ASSERT_EQ(value, (T)-255); + + // A unit suffix still applies after a negative hex number. + ASSERT_TRUE(parse_integer("-0X10k", &end, &value)); + ASSERT_EQ(value, (T)(-16 * K)); + EXPECT_STREQ(end, ""); + + // "-0" is decimal; the character after the '0' decides, and here there is + // none. The value is zero either way, but see minus_zero_no_overread below. + ASSERT_TRUE(parse_integer("-0", &end, &value)); + ASSERT_EQ(value, (T)0); + EXPECT_STREQ(end, ""); + + // Not a hex prefix: 'f' is not 'x'/'X', so this is decimal "-0" with "fX" + // left over, not hex "-0f". + value = 17; + ASSERT_TRUE(parse_integer("-0fX", &end, &value)); + ASSERT_EQ(value, (T)0); + EXPECT_STREQ(end, "fX"); + ASSERT_FALSE(parse_integer("-0fX", &value)); +} + +TEST(ParseMemorySize, negative_hex_prefix) { + test_negative_hex_prefix(); + test_negative_hex_prefix(); +} + +// "-0" holds just two characters plus the terminating NUL, so hex prefix +// detection must stop at the NUL rather than read the character beyond it. +// The string is copied into a tightly sized allocation so that an over-read +// is caught when running under a memory checker such as ASan. +TEST(ParseMemorySize, minus_zero_no_overread) { + char* s = os::strdup("-0", mtTest); + int64_t value = 17; + char* end = nullptr; + + ASSERT_TRUE(parse_integer(s, &end, &value)); + ASSERT_EQ(value, (int64_t)0); + EXPECT_STREQ(end, ""); + + os::free(s); +} diff --git a/test/hotspot/jtreg/ProblemList.txt b/test/hotspot/jtreg/ProblemList.txt index 0a98477be69f..71e626e5b396 100644 --- a/test/hotspot/jtreg/ProblemList.txt +++ b/test/hotspot/jtreg/ProblemList.txt @@ -69,6 +69,8 @@ compiler/c2/aarch64/TestStaticCallStub.java 8359963 generic-aarch64 compiler/escapeAnalysis/TestBCEscapeAnalyzerOverflow.java 8387392 windows-aarch64 +compiler/vectorapi/VectorStoreMaskIdentityTest.java 8388281 generic-all + ############################################################################# # :hotspot_gc diff --git a/test/hotspot/jtreg/TEST.groups b/test/hotspot/jtreg/TEST.groups index f400aa22f0b8..074bc514b35e 100644 --- a/test/hotspot/jtreg/TEST.groups +++ b/test/hotspot/jtreg/TEST.groups @@ -184,7 +184,8 @@ tier1_compiler_2 = \ -compiler/classUnloading/methodUnloading/TestOverloadCompileQueues.java \ -compiler/codecache/stress \ -compiler/codegen/aes \ - -compiler/gcbarriers/PreserveFPRegistersTest.java + -compiler/gcbarriers/PreserveFPRegistersTest.java \ + -compiler/igvn/TestMaskedStoreIdealization.java tier1_compiler_3 = \ compiler/intrinsics/ \ diff --git a/test/hotspot/jtreg/compiler/arraycopy/TestSubwordPartialInlining.java b/test/hotspot/jtreg/compiler/arraycopy/TestSubwordPartialInlining.java new file mode 100644 index 000000000000..42b5e6b8b361 --- /dev/null +++ b/test/hotspot/jtreg/compiler/arraycopy/TestSubwordPartialInlining.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8387073 + * @key randomness + * @summary Arrays.copyOf(Range) must pad overized elements with 0 with partial inlining as well. + * @library /test/lib / + * @run driver ${test.main.class} + */ + +package compiler.arraycopy; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.Set; +import java.util.stream.*; + + +import jdk.test.lib.Utils; +import compiler.lib.compile_framework.*; +import compiler.lib.template_framework.*; +import static compiler.lib.template_framework.Template.scope; +import static compiler.lib.template_framework.Template.let; +import compiler.lib.template_framework.library.*; + +public class TestSubwordPartialInlining { + private static final Random RANDOM = Utils.getRandomInstance(); + private static final String PACKAGE = "compiler.arraycopy.generated"; + private static final String CLASS_NAME = "TestSubwordPartialInliningGenerated"; + + public static void main(String[] args) { + final CompileFramework comp = new CompileFramework(); + comp.addJavaSourceCode(PACKAGE + "." + CLASS_NAME, generate(comp)); + comp.compile(); + comp.invoke(PACKAGE + "." + CLASS_NAME, "main", new Object[] { args }); + } + + private static String generate(CompileFramework comp) { + final Set imports = Set.of("java.util.Arrays", + "java.util.Random", + "jdk.test.lib.Utils", + "compiler.lib.generators.*"); + + final List tests = new ArrayList<>(); + tests.addAll(Stream.of(CodeGenerationDataNameType.booleans(), CodeGenerationDataNameType.bytes(), CodeGenerationDataNameType.shorts(), CodeGenerationDataNameType.chars()) + .map(pty -> new TestPerType(pty).generate()) + .toList()); + tests.add(PrimitiveType.generateLibraryRNG()); + + return TestFrameworkClass.render(PACKAGE, CLASS_NAME, imports, comp.getEscapedClassPathOfCompiledClasses(), tests); + } + + enum Operation { + COPY_OF, + COPY_OF_RANGE + } + + record TestPerType(PrimitiveType pty) { + private String getTestName(Operation op) { + return switch (op) { + case COPY_OF -> "CopyOf" + pty.boxedTypeName(); + case COPY_OF_RANGE -> "CopyOfRange" + pty.boxedTypeName(); + }; + } + + TemplateToken generate() { + final int maxSize = RANDOM.nextInt(0, 4) == 0 ? RANDOM.nextInt(5, 100) : 4; + final int inputSize = RANDOM.nextInt(1, maxSize); + final int copySize = RANDOM.nextInt(inputSize + 1, maxSize + 1); + + + var runTemplate = Template.make("op", (Operation op) -> scope( + let("pty", pty), + let("testName", getTestName(op)), + """ + @Run(test = "test#{testName}", mode = RunMode.STANDALONE) + static void run#{testName}() { + final #pty[] intRes = test#{testName}(); + for (int i = 0; i < 10_000; i++) { + test#{testName}(); + } + final #pty[] compRes = test#{testName}(); + if (!Arrays.equals(intRes, compRes)) { + throw new RuntimeException("wrong result:\\n" + + " interpreter result: " + Arrays.toString(intRes) + "\\n" + + " compiled result: " + Arrays.toString(compRes)); + } + } + """ + )); + + var testTemplate = Template.make("op", (Operation op) -> scope( + let("pty", pty), + let("testName", getTestName(op)), + let("len", copySize), + let("minRange", RANDOM.nextInt(0, 4) == 0 ? RANDOM.nextInt(0, inputSize) : 0), + """ + @Test + static #pty[] test#{testName}() { + """, + " return ", + switch (op) { + case COPY_OF -> "Arrays.copyOf(#{pty}Arr, #len)"; + case COPY_OF_RANGE -> "Arrays.copyOfRange(#{pty}Arr, #minRange, #len)"; + }, + ";\n", + """ + } + + """ + )); + + return Template.make(() -> scope( + Stream.of(Operation.COPY_OF, Operation.COPY_OF_RANGE) + .map(op -> scope(runTemplate.asToken(op), testTemplate.asToken(op))) + .toList(), + Hooks.CLASS_HOOK.insert(scope( + let("pty", pty), + let("arr", String.join(", ", Collections.nCopies(inputSize, (String) pty.callLibraryRNG()))), + """ + private static #pty[] #{pty}Arr = { #arr }; + """ + )) + )).asToken(); + } + } +} diff --git a/test/hotspot/jtreg/compiler/c2/TestDeadPathManyDeadDataNodes.java b/test/hotspot/jtreg/compiler/c2/TestDeadPathManyDeadDataNodes.java deleted file mode 100644 index e9c5a8f75290..000000000000 --- a/test/hotspot/jtreg/compiler/c2/TestDeadPathManyDeadDataNodes.java +++ /dev/null @@ -1,1301 +0,0 @@ -/* - * Copyright (c) 2026 IBM Corporation. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -/** - * @test - * @bug 8380166 - * @summary C2: crash in compiled code due to zero division because of widened CastII - * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions - * -Xcomp -XX:CompileOnly=TestDeadPathManyDeadDataNodes::test1 - * -XX:CompileCommand=quiet - * -XX:CompileCommand=inline,TestDeadPathManyDeadDataNodes::inlined1 - * -XX:MaxRecursiveInlineLevel=1000 -XX:MaxInlineLevel=1000 - * -XX:-TieredCompilation -XX:+AlwaysIncrementalInline - * -XX:+DelayAfterInliningCutoff -XX:+IncrementalInlineForceCleanup - * -XX:NodeCountInliningCutoff=100000 -XX:+StressIGVN - * ${test.main.class} - * @run main ${test.main.class} - */ - -package compiler.c2; - -public class TestDeadPathManyDeadDataNodes { - private static int field; - private static boolean boolField2; - private static int arrayLengthField; - - public static void main(String[] args) { - Object o = new Object(); - try { - test1(false, 0); - } catch (NegativeArraySizeException nase) { - } - } - - private static int test1(boolean boolParam, int intParam) { - int length; - int res = 0; - length = -1; - for (int i = 0; i < 2; i++) { - if (boolParam) { - field = 42; - } - int[] array = new int[length]; - arrayLengthField = array.length; - while(true) { - Object o = new Object(); - int arrayLength = arrayLengthField; - arrayLengthField = 0; - switch (intParam) { - case 0: - if (boolField2) { - break; - } - field = 42; - continue; - case 1: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 2: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 3: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 4: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 5: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 6: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 7: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 8: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 9: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 10: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 11: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 12: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 13: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 14: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 15: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 16: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 17: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 18: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 19: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 20: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 21: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 22: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 23: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 24: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 25: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 26: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 27: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 28: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 29: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 30: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 31: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 32: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 33: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 34: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 35: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 36: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 37: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 38: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 39: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 40: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 41: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 42: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 43: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 44: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 45: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 46: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 47: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 48: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 49: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 50: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 51: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 52: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 53: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 54: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 55: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 56: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 57: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 58: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 59: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 60: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 61: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 62: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 63: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 64: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 65: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 66: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 67: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 68: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 69: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 70: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 71: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 72: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 73: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 74: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 75: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 76: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 77: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 78: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 79: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 80: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 81: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 82: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 83: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 84: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 85: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 86: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 87: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 88: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 89: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 90: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 91: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 92: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 93: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 94: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 95: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 96: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 97: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 98: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 99: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - default: - res += inlined1(boolParam, intParam/100, arrayLength, 92); - continue; - } - field = 42; - break; - } - length = lastInlined(); - } - return res; - } - - static int lastInlined() { - return -1; - } - - static int inlined1(boolean boolParam, int intParam, int arrayLength, int count) { - int res = 0; - switch (intParam) { - case 0: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 1: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 2: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 3: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 4: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 5: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 6: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 7: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 8: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 9: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 10: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 11: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 12: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 13: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 14: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 15: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 16: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 17: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 18: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 19: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 20: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 21: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 22: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 23: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 24: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 25: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 26: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 27: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 28: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 29: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 30: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 31: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 32: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 33: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 34: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 35: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 36: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 37: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 38: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 39: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 40: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 41: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 42: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 43: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 44: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 45: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 46: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 47: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 48: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 49: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 50: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 51: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 52: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 53: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 54: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 55: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 56: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 57: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 58: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 59: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 60: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 61: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 62: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 63: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 64: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 65: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 66: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 67: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 68: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 69: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 70: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 71: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 72: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 73: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 74: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 75: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 76: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 77: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 78: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 79: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 80: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 81: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 82: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 83: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 84: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 85: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 86: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 87: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 88: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 89: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 90: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 91: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 92: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 93: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 94: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 95: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 96: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 97: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 98: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 99: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - default: - if (count == 0) { - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - } else { - return inlined1(boolParam, intParam / 100, arrayLength, count-1); - } - } - } -} diff --git a/test/hotspot/jtreg/compiler/c2/cr7200264/TestIntVect.java b/test/hotspot/jtreg/compiler/c2/cr7200264/TestIntVect.java index 336abf324803..8bf8c9846ec7 100644 --- a/test/hotspot/jtreg/compiler/c2/cr7200264/TestIntVect.java +++ b/test/hotspot/jtreg/compiler/c2/cr7200264/TestIntVect.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -555,12 +555,16 @@ void test_divc_n(int[] a0, int[] a1) { } } - // Not vectorized: no vector div. NOTE: This check does not document the - // _desired_ behavior of the system but the current behavior (no - // vectorization) + // Not vectorized: On AArch64 SVE, vectorization for this example results in + // DivVI nodes. @Test + @IR(counts = { IRNode.LOAD_VECTOR_I, "> 0", + IRNode.STORE_VECTOR, "> 0", + IRNode.DIV_VI, "> 0" }, + applyIfCPUFeature = {"sve", "true"}) @IR(counts = { IRNode.LOAD_VECTOR_I, "= 0", - IRNode.STORE_VECTOR, "= 0" }) + IRNode.STORE_VECTOR, "= 0" }, + applyIfCPUFeature = {"sve", "false"}) void test_divv(int[] a0, int[] a1, int b) { for (int i = 0; i < a0.length; i+=1) { a0[i] = (int)(a1[i]/b); diff --git a/test/hotspot/jtreg/compiler/cpuflags/TestEnableX86ECoreOptsWithAVX2Disabled.java b/test/hotspot/jtreg/compiler/cpuflags/TestEnableX86ECoreOptsWithAVX2Disabled.java new file mode 100644 index 000000000000..c31514bbba15 --- /dev/null +++ b/test/hotspot/jtreg/compiler/cpuflags/TestEnableX86ECoreOptsWithAVX2Disabled.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8388186 + * @summary Test for VM crash with -XX:+EnableX86ECoreOpts and UseAVX < 2. + * @requires vm.flagless + * @requires os.arch == "amd64" | os.arch == "x86_64" + * @library /test/lib + * @run driver ${test.main.class} + */ + +package compiler.cpuflags; + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +public class TestEnableX86ECoreOptsWithAVX2Disabled { + static final String[] OPTIONS = { + "-XX:UseSSE=2", + "-XX:UseSSE=3", + "-XX:UseAVX=0", + "-XX:UseAVX=1" + }; + + public static void main(String[] args) throws Exception { + for (String option : OPTIONS) { + OutputAnalyzer output = ProcessTools.executeLimitedTestJava( + "-XX:+UnlockDiagnosticVMOptions", + "-XX:+EnableX86ECoreOpts", + option, + "-version"); + output.shouldHaveExitValue(0); + } + } +} diff --git a/test/hotspot/jtreg/compiler/igv/TestIdealGraphDump.java b/test/hotspot/jtreg/compiler/igv/TestIdealGraphDump.java new file mode 100644 index 000000000000..d950908eeaac --- /dev/null +++ b/test/hotspot/jtreg/compiler/igv/TestIdealGraphDump.java @@ -0,0 +1,334 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test TestIdealGraphDump + * @bug 8370870 + * @summary Verify that IGV graph dumping produces well-structured XML at different print levels + * @library /test/lib + * @requires vm.debug == true & vm.compiler2.enabled & vm.flagless + * @run driver ${test.main.class} + */ + +package compiler.igv; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import jdk.test.lib.Asserts; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +public class TestIdealGraphDump { + + private static final String TEST_CLASS = TestMethods.class.getName(); + private static final String METHOD_COMPUTE = TEST_CLASS + "::compute"; + private static final String METHOD_BRANCH = TEST_CLASS + "::branchyMethod"; + + private static final Map dumpCache = new HashMap<>(); + + public static void main(String[] args) throws Exception { + testDisabled(); + testLevel0(); + testLevel1(); + testLevel2(); + testLevel3(); + testLevel4(); + testLevel5(); + testLevel6(); + testMonotonicallyIncreasingGraphCounts(); + testXmlWellFormedness(); + testMethodNameInGraph(); + testMultipleMethods(); + testIGVPrintLevelDirective(); + } + + private static void testDisabled() throws Exception { + Path xmlFile = getCachedDump(-1); + Asserts.assertTrue(Files.size(xmlFile) == 0, + "Level -1 (disabled) must produce an empty file"); + } + + private static void testLevel0() throws Exception { + Path xmlFile = getCachedDump(0); + Asserts.assertTrue(Files.size(xmlFile) == 0, + "Level 0 must produce an empty file (no system-wide dumps)"); + } + + private static void testLevel1() throws Exception { + String content = getCachedContent(1); + assertContainsPhase(content, "After Parsing", 1); + assertContainsPhase(content, "Before Matching", 1); + assertContainsPhase(content, "Final Code", 1); + assertNotContainsPhase(content, "PhaseCCP 1", 1); + } + + private static void testLevel2() throws Exception { + String content = getCachedContent(2); + assertContainsPhase(content, "After Parsing", 2); + assertContainsPhase(content, "Final Code", 2); + assertContainsPhase(content, "Iter GVN 1", 2); + assertContainsPhase(content, "PhaseCCP 1", 2); + assertNotContainsPhase(content, "Before Macro Expansion", 2); + } + + private static void testLevel3() throws Exception { + String content = getCachedContent(3); + assertContainsPhase(content, "Before Macro Expansion", 3); + assertNotContainsPhase(content, "Initial Liveness", 3); + } + + private static void testLevel4() throws Exception { + String content = getCachedContent(4); + assertContainsPhase(content, "Initial Liveness", 4); + assertNotContainsPhase(content, "After Iter GVN Step", 4); + } + + private static void testLevel5() throws Exception { + String content = getCachedContent(5); + assertContainsPhase(content, "After Iter GVN Step", 5); + assertNotContainsPhase(content, "Bytecode", 5); + } + + private static void testLevel6() throws Exception { + String content = getCachedContent(6); + Asserts.assertTrue(containsPhase(content, "Bytecode"), + "Level 6 must contain per-bytecode graphs (e.g., 'Bytecode 0: ...')"); + } + + private static void testMonotonicallyIncreasingGraphCounts() throws Exception { + int prevCount = 0; + for (int level = 1; level <= 6; level++) { + String content = getCachedContent(level); + int count = countGraphs(content); + Asserts.assertTrue(count >= prevCount, + "Level " + level + " (" + count + " graphs) must have at least as many as level " + + (level - 1) + " (" + prevCount + " graphs)"); + prevCount = count; + } + } + + private static void testXmlWellFormedness() throws Exception { + Path xmlFile = getCachedDump(2); + + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + try { + builder.parse(xmlFile.toFile()); + } catch (Exception e) { + Asserts.fail("IGV XML at level 2 is not well-formed: " + e.getMessage()); + } + + String content = getCachedContent(2); + Asserts.assertTrue(content.contains(""), "Must contain "); + Asserts.assertTrue(content.contains(""), "Must contain closing "); + Asserts.assertTrue(content.contains(""), "Must contain "); + Asserts.assertTrue(content.contains(""), "Must contain "); + Asserts.assertTrue(content.contains(""), "Must contain "); + Asserts.assertTrue(content.contains(" elements"); + Asserts.assertTrue(content.contains(""); + Asserts.assertTrue(content.contains(""), "Must contain "); + Asserts.assertTrue(content.contains(""), "Must contain "); + } + + private static void testMethodNameInGraph() throws Exception { + String content = getCachedContent(1); + Asserts.assertTrue(content.contains("TestMethods.compute"), + "Graph output must contain the compiled method name 'TestMethods.compute'"); + } + + private static void testMultipleMethods() throws Exception { + Path xmlFile = dumpMultipleMethods(1); + String content = Files.readString(xmlFile); + + Asserts.assertTrue(content.contains("TestMethods.compute"), + "Must contain graphs for 'compute' method"); + Asserts.assertTrue(content.contains("TestMethods.branchyMethod"), + "Must contain graphs for 'branchyMethod' method"); + + int computeFinalCode = countMethodPhase(content, "TestMethods.compute", "Final Code"); + int branchFinalCode = countMethodPhase(content, "TestMethods.branchyMethod", "Final Code"); + Asserts.assertEquals(computeFinalCode, 1, + "compute must emit exactly one 'Final Code' graph, got " + computeFinalCode); + Asserts.assertEquals(branchFinalCode, 1, + "branchyMethod must emit exactly one 'Final Code' graph, got " + branchFinalCode); + } + + private static void testIGVPrintLevelDirective() throws Exception { + Path xmlFile = Files.createTempFile("igv_directive_", ".xml"); + xmlFile.toFile().deleteOnExit(); + + List options = new ArrayList<>(); + options.add("-Xbatch"); + options.add("-XX:PrintIdealGraphLevel=0"); + options.add("-XX:PrintIdealGraphFile=" + xmlFile.toAbsolutePath()); + options.add("-XX:CompileCommand=IGVPrintLevel," + METHOD_COMPUTE + ",2"); + options.add(TEST_CLASS); + + OutputAnalyzer oa = ProcessTools.executeTestJava(options); + oa.shouldHaveExitValue(0); + oa.shouldNotContain("# A fatal error has been detected by the Java Runtime Environment"); + + String content = Files.readString(xmlFile); + Asserts.assertTrue(Files.size(xmlFile) > 0, + "Per-method IGVPrintLevel directive must produce output even with system level 0"); + Asserts.assertTrue(content.contains("TestMethods.compute"), + "Directive-based dump must contain the target method"); + Asserts.assertFalse(content.contains("TestMethods.branchyMethod"), + "Directive-based dump must NOT contain non-targeted method"); + assertContainsPhase(content, "After Parsing", 2); + } + + private static Path getCachedDump(int level) throws Exception { + if (!dumpCache.containsKey(level)) { + dumpCache.put(level, dumpAtLevel(level)); + } + return dumpCache.get(level); + } + + private static String getCachedContent(int level) throws Exception { + return Files.readString(getCachedDump(level)); + } + + private static Path dumpAtLevel(int level) throws Exception { + Path xmlFile = Files.createTempFile("igv_level" + level + "_", ".xml"); + xmlFile.toFile().deleteOnExit(); + + List options = new ArrayList<>(); + options.add("-Xbatch"); + options.add("-XX:PrintIdealGraphLevel=" + level); + options.add("-XX:PrintIdealGraphFile=" + xmlFile.toAbsolutePath()); + options.add("-XX:CompileCommand=compileonly," + METHOD_COMPUTE); + options.add(TEST_CLASS); + + OutputAnalyzer oa = ProcessTools.executeTestJava(options); + oa.shouldHaveExitValue(0); + oa.shouldNotContain("# A fatal error has been detected by the Java Runtime Environment"); + + return xmlFile; + } + + private static Path dumpMultipleMethods(int level) throws Exception { + Path xmlFile = Files.createTempFile("igv_multi_", ".xml"); + xmlFile.toFile().deleteOnExit(); + + List options = new ArrayList<>(); + options.add("-Xbatch"); + options.add("-XX:PrintIdealGraphLevel=" + level); + options.add("-XX:PrintIdealGraphFile=" + xmlFile.toAbsolutePath()); + options.add("-XX:CompileCommand=compileonly," + METHOD_COMPUTE); + options.add("-XX:CompileCommand=compileonly," + METHOD_BRANCH); + options.add(TEST_CLASS); + + OutputAnalyzer oa = ProcessTools.executeTestJava(options); + oa.shouldHaveExitValue(0); + oa.shouldNotContain("# A fatal error has been detected by the Java Runtime Environment"); + + return xmlFile; + } + + private static int countGraphs(String content) { + return countOccurrences(content, "" + phaseName + "<") || + content.contains("'" + phaseName); + } + + private static void assertContainsPhase(String content, String phaseName, int level) { + Asserts.assertTrue(containsPhase(content, phaseName), + "Level " + level + " must contain phase '" + phaseName + "'"); + } + + private static void assertNotContainsPhase(String content, String phaseName, int level) { + Asserts.assertFalse(containsPhase(content, phaseName), + "Level " + level + " must NOT contain phase '" + phaseName + "'"); + } + + private static int countMethodPhase(String content, String methodName, String phaseName) { + int count = 0; + int groupStart = 0; + while ((groupStart = content.indexOf("", groupStart)) != -1) { + int groupEnd = content.indexOf("", groupStart); + if (groupEnd == -1) { + break; + } + String group = content.substring(groupStart, groupEnd); + if (group.contains(methodName)) { + count += countOccurrences(group, ""); + } + groupStart = groupEnd; + } + return count; + } + + private static int countOccurrences(String str, String sub) { + int count = 0; + int idx = 0; + while ((idx = str.indexOf(sub, idx)) != -1) { + count++; + idx += sub.length(); + } + return count; + } + + public static class TestMethods { + public static void main(String[] args) { + int sum = 0; + for (int i = 0; i < 20_000; i++) { + sum += compute(i, i + 1); + sum += branchyMethod(i, i % 7); + } + System.out.println(sum); + } + + static int compute(int a, int b) { + int result = 0; + for (int i = 0; i < a % 10; i++) { + result += b * i; + } + return result; + } + + static int branchyMethod(int x, int y) { + if (x > y) { + return x * y + 1; + } else if (x == y) { + return x + y; + } else { + return y - x; + } + } + } +} diff --git a/test/hotspot/jtreg/compiler/igvn/TestMaskedStoreIdealization.java b/test/hotspot/jtreg/compiler/igvn/TestMaskedStoreIdealization.java new file mode 100644 index 000000000000..d70aeb747643 --- /dev/null +++ b/test/hotspot/jtreg/compiler/igvn/TestMaskedStoreIdealization.java @@ -0,0 +1,512 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8387073 + * @key randomness + * @summary Narrower stores preceding masked vector stores must not be eliminated. + * @modules jdk.incubator.vector + * @library /test/lib / + * @run driver ${test.main.class} + */ + +package compiler.igvn; + +import java.util.Arrays; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.Set; +import java.util.stream.*; + +import jdk.incubator.vector.VectorShape; + +import jdk.test.lib.Utils; +import compiler.lib.compile_framework.*; +import compiler.lib.template_framework.*; +import static compiler.lib.template_framework.Template.scope; +import static compiler.lib.template_framework.Template.let; +import compiler.lib.template_framework.library.*; + +public class TestMaskedStoreIdealization { + private static final Random RANDOM = Utils.getRandomInstance(); + private static final String PACKAGE = "compiler.igvn.generated"; + private static final String CLASS_NAME = "TestMaskedStoreIdealizationGenerated"; + + public static void main(String[] args) { + final CompileFramework comp = new CompileFramework(); + comp.addJavaSourceCode(PACKAGE + "." + CLASS_NAME, generate(comp)); + comp.compile("--add-modules=jdk.incubator.vector"); + + List vmArgs = new ArrayList<>(List.of( + "--add-modules=jdk.incubator.vector", + "--add-opens", "jdk.incubator.vector/jdk.incubator.vector=ALL-UNNAMED" + )); + vmArgs.addAll(Arrays.asList(args)); // Forward args + // Temporarily disable stress flag due to unrelated test failures. + // TODO: Remove when JDK-8388490 is fixed. + vmArgs.addAll(List.of("-XX:+IgnoreUnrecognizedVMOptions", "-XX:-StressReflectiveCode")); + String[] vmArgsArray = vmArgs.toArray(new String[0]); + + comp.invoke(PACKAGE + "." + CLASS_NAME, "main", new Object[] { vmArgsArray }); + } + + private static String generate(CompileFramework comp) { + final Set imports = Set.of("java.util.Arrays", + "java.util.Random", + "jdk.incubator.vector.*", + "jdk.test.lib.Utils", + "compiler.lib.generators.*"); + + // The preferred vector shape is the largest possible vector size. + final int maxVecByteSize = VectorShape.preferredShape().vectorBitSize() / 8; + + final List tests = new ArrayList<>(); + // Add tests only for the vector shapes that + tests.addAll(CodeGenerationDataNameType.VECTOR_VECTOR_TYPES + .stream() + .filter(vec -> vec.byteSize() <= maxVecByteSize && vec.elementType instanceof PrimitiveType) + .map(vec -> new TestPerShape(vec).generate()) + .collect(Collectors.toList())); + tests.add(PrimitiveType.generateLibraryRNG()); + + return TestFrameworkClass.render(PACKAGE, CLASS_NAME, imports, comp.getEscapedClassPathOfCompiledClasses(), tests); + } + + enum Operation { + STORE_SCATTER, + STORE_MASK, + STORE_SCATTER_MASK, + STORE_VECTOR_AFTER_SCATTER, + RANDOM + } + + record TestPerShape(VectorType.Vector vec) { + TemplateToken generate() { + final String testName = vec.elementType.boxedTypeName() + vec.length; + + // Select the index where we set the mask to false. The index is biased to + // zero, as the original bug only triggered with the first element. + final int idx = RANDOM.nextBoolean() ? RANDOM.nextInt(0, vec.length) : 0; + + var irVerification = Template.make("op", "arraySize", (Operation op, Integer arraySize) -> { + // No IR-verification for random test cases. + if (op == Operation.RANDOM) { + return scope(""); + } + + final PrimitiveType pty = (PrimitiveType) vec.elementType; + final String ptyIR = pty.abbrev().equals("S") ? "C" : pty.abbrev(); + + // Verify that the Scatter store for STORE_VECTOR_AFTER_SCATTER is not eliminated. + var opVerification = Template.make(() -> { + if (op != Operation.STORE_VECTOR_AFTER_SCATTER) { + return scope(""); + } + + if (vec.length <= 2) { + return scope(" // No Vector nodes are emitted for vectors of length 2 or shorter.\n"); + } + + if (vec.elementType.byteSize() < 4) { + return scope(" // StoreVectorScatter is not emitted for vectors of subword types.\n"); + } + + return scope( + """ + @IR(counts = {IRNode.STORE_VECTOR_SCATTER, "=1"}, + applyIfCPUFeatureOr = {"avx512", "true", "sve", "true"}) + """ + ); + }); + + return scope( + let("pty", vec.elementType.name()), + let("ptyIR", ptyIR), + let("idx", idx), + switch (op) { + case STORE_MASK, STORE_SCATTER_MASK -> + // For masked operations, depending on the generated mask and index map C2 does not manage to elide a branch from + // if (mask.allTrue()) { + // intoArray(a, offset); + // } else { + // intoArray(a, offset, mask); + // } + // leading to both stores of the diamond being live. This is highly profile dependent and cannot be predicted. + """ + @IR(counts = {IRNode.START + "Store#{ptyIR}" + IRNode.MID + "(Memory: @aryptr:#{pty}\\\\[int:#{arraySize}\\\\]).*(:NotNull:exact\\\\[\\\\d+\\\\]).*" + IRNode.END, ">=1", + IRNode.START + "Store#{ptyIR}" + IRNode.MID + "(Memory: @aryptr:#{pty}\\\\[int:#{arraySize}\\\\]).*(:NotNull:exact\\\\[\\\\d+\\\\]).*" + IRNode.END, "<=2"}, + applyIfCPUFeatureOr = {"avx512", "true", "sve", "true"}, + phase = CompilePhase.BEFORE_MATCHING) + """; + case STORE_SCATTER -> + """ + @IR(counts = {IRNode.START + "Store#{ptyIR}" + IRNode.MID + "(Memory: @aryptr:#{pty}\\\\[int:#{arraySize}\\\\]).*(:NotNull:exact\\\\[\\\\d+\\\\]).*" + IRNode.END, "=1"}, + applyIfCPUFeatureOr = {"avx512", "true", "sve", "true"}, + phase = CompilePhase.BEFORE_MATCHING) + """; + case STORE_VECTOR_AFTER_SCATTER -> opVerification.asToken(); + case RANDOM -> ""; + } + ); + }); + + var testBody = Template.make("testCaseRandom", "op", "arraySize", (Random testCaseRandom, Operation op, Integer arraySize) -> { + if (op == Operation.RANDOM) { + return scope(generateRandomTest(testCaseRandom).asToken()); + } + + var maskGeneration = Template.make(() -> scope( + let("idx", idx), + let("boxedTy", vec.elementType.boxedTypeName()), + let("species", vec.speciesName), + " VectorMask<#boxedTy> mask = VectorMask.fromLong(#species, ", + testCaseRandom.nextInt(0, 10) == 0 ? testCaseRandom.nextLong() : "-1 - (1 << #idx)", + ");\n" + )); + + var indexMapGeneration = Template.make(() -> { + // For the scatter tests, the array is one larger than the number of lanes so we can + // map indices starting at idx to the next index, omitting idx. + int[] indexMap = IntStream.range(0, vec.length) + .map(i -> i >= idx ? i + 1 : i) + .toArray(); + String indexMapStr = Arrays.toString(indexMap) + .replace('[', '{') + .replace(']', '}'); + return scope( + let("idx", idx), + let("len", vec.length), + let("idxMap", indexMapStr), + """ + final int[] indexMap = #{idxMap}; + """ + ); + }); + + var generation = switch (op) { + case STORE_SCATTER, STORE_VECTOR_AFTER_SCATTER -> indexMapGeneration.asToken(); + case STORE_MASK -> maskGeneration.asToken(); + case STORE_SCATTER_MASK -> + Template.make(() -> scope(indexMapGeneration.asToken(), maskGeneration.asToken())).asToken(); + case RANDOM -> throw new RuntimeException("unreachable"); + }; + + var initStore = switch (op) { + case STORE_SCATTER, STORE_VECTOR_AFTER_SCATTER -> " v.intoArray(a, 0, indexMap, 0);\n"; + case STORE_MASK -> " v.intoArray(a, 0, mask);\n"; + case STORE_SCATTER_MASK -> " v.intoArray(a, 0, indexMap, 0, mask);\n"; + case RANDOM -> throw new RuntimeException("unreachable"); + }; + + var keepStore = switch (op) { + case STORE_MASK, STORE_SCATTER, STORE_SCATTER_MASK -> " a[#idx] = arrVal;\n"; + case STORE_VECTOR_AFTER_SCATTER -> " v.intoArray(a, 0);\n"; + case RANDOM -> throw new RuntimeException("unreachable"); + }; + + var holeStore = switch (op) { + case STORE_SCATTER -> " v.intoArray(a, 0, indexMap, 0);\n"; + case STORE_MASK -> " v.intoArray(a, 0, mask);\n"; + case STORE_SCATTER_MASK -> " v.intoArray(a, 0, indexMap, 0, mask);\n"; + case STORE_VECTOR_AFTER_SCATTER -> ""; + case RANDOM -> throw new RuntimeException("unreachable"); + }; + + return scope( + let("pty", vec.elementType.name()), + let("vecTy", vec.name()), + let("species", vec.speciesName), + let("idx", idx), + """ + #pty[] a = new #pty[#arraySize]; + """, + generation, + """ + + var v = #vecTy.broadcast(#species, broadcastVal); + """, + initStore, + keepStore, + holeStore, + """ + return a; + """ + ); + }); + + var testCase = Template.make("op", (Operation op) -> { + // To get the same test body twice, use a new random instance for each generated test body with a seed fixed per test case. + final int testCaseSeed = RANDOM.nextInt(); + + String testCaseName = testName + switch (op) { + case STORE_SCATTER -> "Scatter"; + case STORE_MASK -> "Mask"; + case STORE_SCATTER_MASK -> "ScatterMask"; + case STORE_VECTOR_AFTER_SCATTER -> "VectorAfterScatter"; + case RANDOM -> "Random"; + }; + + // The array size needs to be one larger than the number of lanes for scatter tests, so we can not write to one element. + final int arraySize = switch (op) { + case STORE_SCATTER, STORE_SCATTER_MASK, STORE_VECTOR_AFTER_SCATTER -> vec.length + 1; + case STORE_MASK, RANDOM -> vec.length; + }; + + return scope( + let("pty", vec.elementType.name()), + let("testCaseName", testCaseName), + let("boxedTy", vec.elementType.boxedTypeName()), + let("vecTy", vec.name()), + let("lanes", vec.length), + let("species", vec.speciesName), + let("idx", idx), + let("rngCall", vec.elementType.callLibraryRNG()), + let("broadcastVal", vec.elementType.con()), + let("arrVal", vec.elementType.con()), + """ + @Run(test = "test#{testCaseName}") + @Warmup(10_000) + static void run#{testCaseName}(RunInfo info) { + final #pty broadcastVal = #broadcastVal; + final #pty arrVal = #arrVal; + final #pty[] compiledResult = test#{testCaseName}(broadcastVal, arrVal); + + if (!info.isWarmUp()) { + final #pty[] interpreterResult = reference#{testCaseName}(broadcastVal, arrVal); + if (!Arrays.equals(interpreterResult, compiledResult)) { + throw new RuntimeException("wrong result for test${testCaseName}:\\n" + + " interpreter result: " + Arrays.toString(interpreterResult) + "\\n" + + " compiled result: " + Arrays.toString(compiledResult)); + } + } + } + + @Test + """, + irVerification.asToken(op, arraySize), + """ + static #pty[] test#{testCaseName}(#pty broadcastVal, #pty arrVal) { + """, + testBody.asToken(new Random(testCaseSeed), op, arraySize), + """ + } + + @DontCompile + static #pty[] reference#{testCaseName}(#pty broadcastVal, #pty arrVal) { + """, + testBody.asToken(new Random(testCaseSeed), op, arraySize), + """ + } + + """ + ); + }); + + return Template.make(() -> scope( + Stream.of(Operation.class.getEnumConstants()) + .map(op -> testCase.asToken(op)) + .toList() + )).asToken(); + } + + Template.ZeroArgs generateRandomTest(Random testCaseRandom) { + final int arraySize = testCaseRandom.nextInt(vec.length + 1, 5 * vec.length); + var maskHook = new Hook("MaskHook"); + var genRandomMask = Template.make("maskName", "vecTy", (String maskName, VectorType.Vector vecTy) -> scope( + let("boxedTy", vecTy.elementType.boxedTypeName()), + let("species", vecTy.speciesName), + let("maskVal", testCaseRandom.nextLong()), + """ + VectorMask<#{boxedTy}> #maskName = VectorMask.fromLong(#species, #maskVal); + """ + )); + var genRandomIdxMap = Template.make("mapName", "maxIdx", "len", (String mapName, Integer maxIdx, Integer len) -> { + ArrayList possibleIndices = new ArrayList(IntStream.range(0, maxIdx).boxed().toList()); + Collections.shuffle(possibleIndices, testCaseRandom); + return scope( + let("map", String.join(", ", possibleIndices.stream().limit(vec.length).map(i -> i.toString()).toList())), + """ + int[] #mapName = { #map }; + """ + ); + }); + var initialStore = Template.make(() -> { + final int offset = testCaseRandom.nextInt(0, 4) == 0 ? testCaseRandom.nextInt(0, arraySize - vec.length) : 0; + return scope( + let("offset", offset), + switch (testCaseRandom.nextInt(0,4)) { + case 0 -> scope( + """ + v.intoArray(a, #offset); + """ + ); + case 1 -> scope( + maskHook.insert(genRandomMask.asToken("initMask", vec)), + """ + v.intoArray(a, #offset, initMask); + """ + ); + case 2 -> scope( + maskHook.insert(genRandomIdxMap.asToken("initMap", arraySize - offset, vec.length)), + """ + v.intoArray(a, #offset, initMap, 0); + """ + ); + case 3 -> scope( + maskHook.insert(scope( + genRandomIdxMap.asToken("initMap", arraySize - offset, vec.length), + genRandomMask.asToken("initMask", vec) + )), + """ + v.intoArray(a, #offset, initMap, 0, initMask); + """ + ); + default -> throw new RuntimeException("unreachable"); + } + ); + }); + + var storeThatShouldNotBeLost = Template.make(() -> { + final int selection = testCaseRandom.nextInt(0,6); + final VectorType.Vector narrowerVector = CodeGenerationDataNameType.VECTOR_VECTOR_TYPES + .stream() + .filter(v -> v.elementType == vec.elementType && v.length <= vec.length) + // Flip a coin on each reduction step to pick a random element. + .reduce(null, (l, r) -> l == null ? r : (testCaseRandom.nextBoolean() ? l : r)); + final int offset = testCaseRandom.nextInt(0, 4) == 0 ? testCaseRandom.nextInt(0, arraySize - narrowerVector.length) : 0; + return scope( + let("offset", offset), + switch (selection) { + case 0, 1, 2, 3 -> scope( + let("vecTy", narrowerVector), + let("species", narrowerVector.speciesName), + """ + var vKeep = #vecTy.broadcast(#species, arrVal); + """ + ); + default -> ""; + }, + switch (selection) { + case 0 -> scope( + """ + vKeep.intoArray(a, #offset); + """ + ); + case 1 -> scope( + maskHook.insert(genRandomMask.asToken("keepMask", narrowerVector)), + """ + vKeep.intoArray(a, #offset, keepMask); + """ + ); + case 2 -> scope( + maskHook.insert(genRandomIdxMap.asToken("keepMap", arraySize - offset, narrowerVector.length)), + """ + vKeep.intoArray(a, #offset, keepMap, 0); + """ + ); + case 3 -> scope( + maskHook.insert(scope( + genRandomIdxMap.asToken("keepMap", arraySize - offset, narrowerVector.length), + genRandomMask.asToken("keepMask", narrowerVector) + )), + """ + vKeep.intoArray(a, #offset, keepMap, 0, keepMask); + """ + ); + case 4 -> scope( + let("idx", testCaseRandom.nextInt(0, arraySize)), + """ + a[#idx] = arrVal; + """ + ); + case 5 -> scope( + let("idx", testCaseRandom.nextInt(0, arraySize - vec.length)), + let("len", testCaseRandom.nextInt(1, vec.length + 1)), + """ + Arrays.fill(a, #idx, #idx + #len, arrVal); + """ + ); + default -> throw new RuntimeException("unreachable"); + } + ); + }); + + var storeWithHole = Template.make(() -> { + final int offset = testCaseRandom.nextInt(0, 4) == 0 ? testCaseRandom.nextInt(0, arraySize - vec.length) : 0; + return scope( + let("offset", offset), + switch (testCaseRandom.nextInt(0,3)) { + case 0 -> scope( + maskHook.insert(genRandomMask.asToken("holeMask", vec)), + """ + v.intoArray(a, #offset, holeMask); + """ + ); + case 1 -> scope( + maskHook.insert(genRandomIdxMap.asToken("holeMap", arraySize - offset, vec.length)), + """ + v.intoArray(a, #offset, holeMap, 0); + """ + ); + case 2 -> scope( + maskHook.insert(scope( + genRandomIdxMap.asToken("holeMap", arraySize - offset, vec.length), + genRandomMask.asToken("holeMask", vec) + )), + """ + v.intoArray(a, #offset, holeMap, 0, holeMask); + """ + ); + default -> throw new RuntimeException("unreachable"); + } + ); + }); + + return Template.make(() -> scope( + let("pty", vec.elementType.name()), + let("arraySize", arraySize), + """ + #pty[] a = new #pty[#arraySize]; + + """, + maskHook.anchor(scope( + // The code for generating masks and index maps goes here. + let("vecTy", vec.name()), + let("species", vec.speciesName), + """ + var v = #vecTy.broadcast(#species, broadcastVal); + """, + initialStore.asToken(), + storeThatShouldNotBeLost.asToken(), + storeWithHole.asToken() + )), + """ + return a; + """ + )); + } + } +} diff --git a/test/hotspot/jtreg/compiler/integerArithmetic/TestZeroDivModWidenedCastII.java b/test/hotspot/jtreg/compiler/integerArithmetic/TestZeroDivModWidenedCastII.java deleted file mode 100644 index a5bc8fc9287c..000000000000 --- a/test/hotspot/jtreg/compiler/integerArithmetic/TestZeroDivModWidenedCastII.java +++ /dev/null @@ -1,1122 +0,0 @@ -/* - * Copyright (c) 2026 IBM Corporation. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -/** - * @test - * @bug 8380166 - * @summary C2: crash in compiled code due to zero division because of widened CastII - * - * @run main/othervm -XX:-TieredCompilation -XX:-UseOnStackReplacement -XX:-BackgroundCompilation - * ${test.main.class} - * @run main ${test.main.class} - * - */ - -package compiler.integerArithmetic; - -public class TestZeroDivModWidenedCastII { - private static int intField; - private static long longField; - private static volatile int volatileField; - - public static void main(String[] args) { - for (int i = 0; i < 20_000; i++) { - test1(0, 9, 1, true, false); - test1(0, 9, 1, false, false); - inlined1_2(9, 1, 1, true, 0); - inlined1_3(0, 0); - test2(0, 9, 1, true, false); - test2(0, 9, 1, false, false); - inlined2_2(9, 1, 1, true, 0); - inlined2_3(0, 0); - test3(0, 9, 1, true, false); - test3(0, 9, 1, false, false); - inlined3_2(9, 1, 1, true, 0); - inlined3_3(0, 0); - test4(0, 9, 1, true, false); - test4(0, 9, 1, false, false); - inlined4_2(9, 1, 1, true, 0); - inlined4_3(0, 0); - test5(0, 9, 1, true, false); - test5(0, 9, 1, false, false); - inlined5_2(9, 1, 1, true, 0); - inlined5_3(0, 0); - test6(0, 9, 1, true, false); - test6(0, 9, 1, false, false); - inlined6_2(9, 1, 1, true, 0); - inlined6_3(0, 0); - test7(0, 9, 1, true, false); - test7(0, 9, 1, false, false); - inlined7_2(9, 1, 1, true, 0); - inlined7_3(0, 0); - test8(0, 9, 1, true, false); - test8(0, 9, 1, false, false); - inlined8_2(9, 1, 1, true, 0); - inlined8_3(0, 0); - test9(0, 9, 1, true, false); - test9(0, 9, 1, false, false); - inlined9_2(9, 1, 1, true, 0); - inlined9_3(0, 0); - test10(0, 9, 1, false); - inlined10_2(9, 1, 1, true, 0); - inlined10_3(0, 0); - test11(0, 9, 1, false); - inlined11_2(9, 1, 1, true, 0); - inlined11_3(0, 0); - test12(0, 9, 1, false); - inlined12_2(9, 1, 1, true, 0); - inlined12_3(0, 0); - test13(0, 9, 1, false); - inlined13_2(9, 1, 1, true, 0); - inlined13_3(0, 0); - test14(0, 9, 1, false); - inlined14_2(9, 1, 1, true, 0); - inlined14_3(0, 0); - test15(0, 9, 1, false); - inlined15_2(9, 1, 1, true, 0); - inlined15_3(0, 0); - test16(0, 9, 1, false); - inlined16_2(9, 1, 1, true, 0); - inlined16_3(0, 0); - test17(0, 9, 1, false); - inlined17_2(9, 1, 1, true, 0); - inlined17_3(0, 0); - } - } - - private static void test1(int k, int j, int flag, boolean flag2, boolean flag3) { - int l = 0; - for (; l < 10; l++); - int m = inlined1_3(j, l); - - int i = inlined1(k, flag2); - j = Integer.min(j, 9); - int[] array = new int[10]; - if (flag == 0) { - throw new RuntimeException("never taken"); - } - if (flag2) { - inlined1_2(j, flag, i, flag3, m); - } else { - inlined1_2(j, flag, i, flag3, m); - } - } - - private static int inlined1_3(int j, int l) { - if (l == 10) { - j = 1; - } - return j; - } - - private static void inlined1_2(int j, int flag, int i, boolean flag3, int m) { - if (flag3) { - float[] newArray = new float[j + 1]; // j + 1 in [0..10] - // RC i B_SPECIES = ByteVector.SPECIES_MAX; + private static final VectorSpecies S_SPECIES = ShortVector.SPECIES_MAX; + private static final VectorSpecies I_SPECIES = IntVector.SPECIES_MAX; + private static final VectorSpecies L_SPECIES = LongVector.SPECIES_MAX; + private static final VectorSpecies F_SPECIES = FloatVector.SPECIES_MAX; + private static final VectorSpecies D_SPECIES = DoubleVector.SPECIES_MAX; + + private static final int BUF_LEN = 256; + + private static final byte[] ba = new byte[BUF_LEN]; + private static final byte[] bb = new byte[BUF_LEN]; + private static final byte[] br = new byte[BUF_LEN]; + + private static final short[] sa = new short[BUF_LEN]; + private static final short[] sb = new short[BUF_LEN]; + private static final short[] sr = new short[BUF_LEN]; + + private static final int[] ia = new int[BUF_LEN]; + private static final int[] ib = new int[BUF_LEN]; + private static final int[] ir = new int[BUF_LEN]; + + private static final long[] la = new long[BUF_LEN]; + private static final long[] lb = new long[BUF_LEN]; + private static final long[] lr = new long[BUF_LEN]; + + private static final float[] fa = new float[BUF_LEN]; + private static final float[] fb = new float[BUF_LEN]; + private static final float[] fr = new float[BUF_LEN]; + + private static final double[] da = new double[BUF_LEN]; + private static final double[] db = new double[BUF_LEN]; + private static final double[] dr = new double[BUF_LEN]; + + private static final boolean[] mask_arr = new boolean[BUF_LEN]; + + static { + Generator iGen = RD.ints(); + Generator lGen = RD.longs(); + Generator fGen = RD.floats(); + Generator dGen = RD.doubles(); + + for (int i = 0; i < BUF_LEN; i++) { + mask_arr[i] = (i & 1) != 0; + ba[i] = iGen.next().byteValue(); + // Integer divisors must be non-zero, otherwise lanewise DIV throws. + bb[i] = nonZeroByte(iGen.next().byteValue()); + sa[i] = iGen.next().shortValue(); + sb[i] = nonZeroShort(iGen.next().shortValue()); + ib[i] = nonZeroInt(iGen.next()); + lb[i] = nonZeroLong(lGen.next()); + } + + RD.fill(iGen, ia); + RD.fill(lGen, la); + RD.fill(fGen, fa); + // Floating-point division has no divide-by-zero exception. + RD.fill(fGen, fb); + RD.fill(dGen, da); + RD.fill(dGen, db); + } + + private static byte nonZeroByte(byte v) { return v == 0 ? (byte) 1 : v; } + private static short nonZeroShort(short v) { return v == 0 ? (short) 1 : v; } + private static int nonZeroInt(int v) { return v == 0 ? 1 : v; } + private static long nonZeroLong(long v) { return v == 0 ? 1L : v; } + + // Unmasked lanewise DIV. + + @Test + @IR(counts = { IRNode.DIV_VB, ">= 1" }, + applyIfCPUFeature = { "sve", "true" }) + public static void testDivByte() { + ByteVector va = ByteVector.fromArray(B_SPECIES, ba, 0); + ByteVector vb = ByteVector.fromArray(B_SPECIES, bb, 0); + va.lanewise(VectorOperators.DIV, vb).intoArray(br, 0); + } + + @Test + @IR(counts = { IRNode.DIV_VS, ">= 1" }, + applyIfCPUFeature = { "sve", "true" }) + public static void testDivShort() { + ShortVector va = ShortVector.fromArray(S_SPECIES, sa, 0); + ShortVector vb = ShortVector.fromArray(S_SPECIES, sb, 0); + va.lanewise(VectorOperators.DIV, vb).intoArray(sr, 0); + } + + @Test + @IR(counts = { IRNode.DIV_VI, ">= 1" }, + applyIfCPUFeature = { "sve", "true" }) + public static void testDivInt() { + IntVector va = IntVector.fromArray(I_SPECIES, ia, 0); + IntVector vb = IntVector.fromArray(I_SPECIES, ib, 0); + va.lanewise(VectorOperators.DIV, vb).intoArray(ir, 0); + } + + @Test + @IR(counts = { IRNode.DIV_VL, ">= 1" }, + applyIfCPUFeature = { "sve", "true" }) + public static void testDivLong() { + LongVector va = LongVector.fromArray(L_SPECIES, la, 0); + LongVector vb = LongVector.fromArray(L_SPECIES, lb, 0); + va.lanewise(VectorOperators.DIV, vb).intoArray(lr, 0); + } + + @Test + @IR(counts = { IRNode.DIV_VF, ">= 1" }, + applyIfCPUFeature = { "asimd", "true" }) + public static void testDivFloat() { + FloatVector va = FloatVector.fromArray(F_SPECIES, fa, 0); + FloatVector vb = FloatVector.fromArray(F_SPECIES, fb, 0); + va.lanewise(VectorOperators.DIV, vb).intoArray(fr, 0); + } + + @Test + @IR(counts = { IRNode.DIV_VD, ">= 1" }, + applyIfCPUFeature = { "asimd", "true" }) + public static void testDivDouble() { + DoubleVector va = DoubleVector.fromArray(D_SPECIES, da, 0); + DoubleVector vb = DoubleVector.fromArray(D_SPECIES, db, 0); + va.lanewise(VectorOperators.DIV, vb).intoArray(dr, 0); + } + + // Masked lanewise DIV. On AArch64, BYTE/SHORT have no native predicated + // divide, so they are lowered to an unpredicated divide combined with a + // VectorBlend. + + @Test + @IR(counts = { IRNode.DIV_VB, ">= 1", + IRNode.VECTOR_BLEND_B, ">= 1" }, + applyIfCPUFeature = { "sve", "true" }) + public static void testMaskedDivByte() { + VectorMask mask = VectorMask.fromArray(B_SPECIES, mask_arr, 0); + ByteVector va = ByteVector.fromArray(B_SPECIES, ba, 0); + ByteVector vb = ByteVector.fromArray(B_SPECIES, bb, 0); + va.lanewise(VectorOperators.DIV, vb, mask).intoArray(br, 0); + } + + @Test + @IR(counts = { IRNode.DIV_VS, ">= 1", + IRNode.VECTOR_BLEND_S, ">= 1" }, + applyIfCPUFeature = { "sve", "true" }) + public static void testMaskedDivShort() { + VectorMask mask = VectorMask.fromArray(S_SPECIES, mask_arr, 0); + ShortVector va = ShortVector.fromArray(S_SPECIES, sa, 0); + ShortVector vb = ShortVector.fromArray(S_SPECIES, sb, 0); + va.lanewise(VectorOperators.DIV, vb, mask).intoArray(sr, 0); + } + + @Test + @IR(counts = { IRNode.DIV_VI, ">= 1" }, + applyIfCPUFeature = { "sve", "true" }) + public static void testMaskedDivInt() { + VectorMask mask = VectorMask.fromArray(I_SPECIES, mask_arr, 0); + IntVector va = IntVector.fromArray(I_SPECIES, ia, 0); + IntVector vb = IntVector.fromArray(I_SPECIES, ib, 0); + va.lanewise(VectorOperators.DIV, vb, mask).intoArray(ir, 0); + } + + @Test + @IR(counts = { IRNode.DIV_VL, ">= 1" }, + applyIfCPUFeature = { "sve", "true" }) + public static void testMaskedDivLong() { + VectorMask mask = VectorMask.fromArray(L_SPECIES, mask_arr, 0); + LongVector va = LongVector.fromArray(L_SPECIES, la, 0); + LongVector vb = LongVector.fromArray(L_SPECIES, lb, 0); + va.lanewise(VectorOperators.DIV, vb, mask).intoArray(lr, 0); + } + + @Test + @IR(counts = { IRNode.DIV_VF, ">= 1" }, + applyIfCPUFeature = { "sve", "true" }) + public static void testMaskedDivFloat() { + VectorMask mask = VectorMask.fromArray(F_SPECIES, mask_arr, 0); + FloatVector va = FloatVector.fromArray(F_SPECIES, fa, 0); + FloatVector vb = FloatVector.fromArray(F_SPECIES, fb, 0); + va.lanewise(VectorOperators.DIV, vb, mask).intoArray(fr, 0); + } + + @Test + @IR(counts = { IRNode.DIV_VD, ">= 1" }, + applyIfCPUFeature = { "sve", "true" }) + public static void testMaskedDivDouble() { + VectorMask mask = VectorMask.fromArray(D_SPECIES, mask_arr, 0); + DoubleVector va = DoubleVector.fromArray(D_SPECIES, da, 0); + DoubleVector vb = DoubleVector.fromArray(D_SPECIES, db, 0); + va.lanewise(VectorOperators.DIV, vb, mask).intoArray(dr, 0); + } + + public static void main(String[] args) { + TestFramework testFramework = new TestFramework(); + testFramework.setDefaultWarmup(10000) + .addFlags("--add-modules=jdk.incubator.vector") + .start(); + } +} diff --git a/test/hotspot/jtreg/gtest/AssemblerGtests.java b/test/hotspot/jtreg/gtest/AssemblerGtests.java new file mode 100644 index 000000000000..19fb3398267a --- /dev/null +++ b/test/hotspot/jtreg/gtest/AssemblerGtests.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2026, IBM Corp. All rights reserved. + * + * 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. + * + */ + +/* + * This runs the MacroAssembler gtests related to Klass de- and encoding + * (for now, only on aarch64) with and without COH. + */ + +/* @test id=coh + * @summary Run Assembler-related gtests + * @library /test/lib + * @modules java.base/jdk.internal.misc + * java.xml + * @requires vm.flagless + * @requires os.arch=="aarch64" + * @run main/native GTestWrapper --gtest_filter=AssemblerAArch64::decode_encode_klass* -XX:+UseCompactObjectHeaders + */ + +/* @test id=noncoh + * @summary Run Assembler-related gtests + * @library /test/lib + * @modules java.base/jdk.internal.misc + * java.xml + * @requires vm.flagless + * @requires os.arch=="aarch64" + * @run main/native GTestWrapper --gtest_filter=AssemblerAArch64::decode_encode_klass* -XX:-UseCompactObjectHeaders + */ + diff --git a/test/hotspot/jtreg/runtime/CompressedOops/CompressedClassPointersEncodingScheme.java b/test/hotspot/jtreg/runtime/CompressedOops/CompressedClassPointersEncodingScheme.java index d14dbc932453..f4ef0800a73b 100644 --- a/test/hotspot/jtreg/runtime/CompressedOops/CompressedClassPointersEncodingScheme.java +++ b/test/hotspot/jtreg/runtime/CompressedOops/CompressedClassPointersEncodingScheme.java @@ -54,7 +54,7 @@ private static void test(long forceAddress, boolean COH, long classSpaceSize, lo "-XX:" + (COH ? "+" : "-") + "UseObjectMonitorTable", "-XX:CompressedClassSpaceBaseAddress=" + forceAddress, "-XX:CompressedClassSpaceSize=" + classSpaceSize, - "-Xmx128m", + "-Xmx64m", "-Xlog:metaspace*", "-version"); OutputAnalyzer output = new OutputAnalyzer(pb.start()); @@ -71,35 +71,6 @@ private static void test(long forceAddress, boolean COH, long classSpaceSize, lo output.shouldContain("Narrow klass base: " + expectedEncodingBaseString + ", Narrow klass shift: " + expectedEncodingShift); } - private static void testFailure(String forceAddressString) throws IOException { - ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder( - "-Xshare:off", // to make CompressedClassSpaceBaseAddress work - "-XX:+UnlockExperimentalVMOptions", - "-XX:+UnlockDiagnosticVMOptions", - "-XX:-UseCompactObjectHeaders", - "-XX:CompressedClassSpaceBaseAddress=" + forceAddressString, - "-Xmx128m", - "-Xlog:metaspace*", - "-version"); - OutputAnalyzer output = new OutputAnalyzer(pb.start()); - - output.reportDiagnosticSummary(); - - // We ignore cases where we were not able to map at the force address - if (!output.contains("Successfully forced class space address to " + forceAddressString)) { - throw new SkippedException("Skipping because we cannot force ccs to " + forceAddressString); - } - - if (Platform.isAArch64()) { - output.shouldHaveExitValue(1); - output.shouldContain("Error occurred during initialization of VM"); - output.shouldContain("CompressedClassSpaceBaseAddress=" + forceAddressString + - " given with shift 0, cannot be used to encode class pointers"); - } else { - output.shouldHaveExitValue(0); - } - } - final static long K = 1024; final static long M = K * 1024; final static long G = M * 1024; @@ -108,53 +79,47 @@ public static void main(String[] args) throws Exception { // Expecting base=0, shift=0 test(4 * G - 128 * M, false, 128 * M, 0, 0); - // Test ccs nestling right at the end of the 32G range - // Expecting: - // - non-aarch64: base=0, shift=3 - // - aarch64: base to start of class range, shift 0 - if (Platform.isAArch64()) { - // The best we can do on aarch64 is to be *near* the end of the 32g range, since a valid encoding base - // on aarch64 must be 4G aligned, and the max. class space size is 3G. - long forceAddress = 0x7_0000_0000L; // 28g, and also a valid EOR immediate - test(forceAddress, false, 3 * G, forceAddress, 0); - } else { - test(32 * G - 128 * M, false, 128 * M, 0, 3); - } - - // Test ccs starting *below* 4G, but extending upwards beyond 4G. All platforms except aarch64 should pick - // zero based encoding. On aarch64, this test is excluded since the only valid mode would be XOR, but bit - // pattern for base and bit pattern would overlap. - if (!Platform.isAArch64()) { - test(4 * G - 128 * M, false, 2 * 128 * M, 0, 3); - } - // add more... + // aarch64 does not do extended zero based encoding (shift>0) + boolean expectExtendedZeroBasedEncoding = !Platform.isAArch64(); + + // Test ccs nestling right at the end of the 32G range. + // Expect all platforms but aarch64 to do shift-extended zero-based encoding; + long forceAddress = 32 * G - 128 * M; + test(forceAddress, false, 128 * M, + expectExtendedZeroBasedEncoding ? 0 : forceAddress, // expected base + expectExtendedZeroBasedEncoding ? 3 : 0 // expected shift + ); + + // Test ccs starting *below* 4G, but extending upwards beyond 4G. + // Expect all platforms but aarch64 to do shift-extended zero-based encoding; aarch64 does not do that but + // drops right to non-zero-based with shift = 0 + forceAddress = 4 * G - 128 * M; + test(forceAddress, false, 2 * 128 * M, + expectExtendedZeroBasedEncoding ? 0 : forceAddress, // expected base + expectExtendedZeroBasedEncoding ? 3 : 0 // expected shift + ); // Compact Object Header Mode: - // On aarch64 and x64 we expect the VM to chose the smallest possible shift value needed to cover - // the encoding range. We expect the encoding Base to start at the class space start - but to enforce that, - // we choose a high address. - if (Platform.isAArch64() || Platform.isX64() || Platform.isRISCV64()) { - long forceAddress = 32 * G; - - long ccsSize = 128 * M; - int expectedShift = 6; - test(forceAddress, true, ccsSize, forceAddress, expectedShift); - - ccsSize = 512 * M; - expectedShift = 8; - test(forceAddress, true, ccsSize, forceAddress, expectedShift); - - ccsSize = G; - expectedShift = 9; - test(forceAddress, true, ccsSize, forceAddress, expectedShift); - - ccsSize = 3 * G; - expectedShift = 10; - test(forceAddress, true, ccsSize, forceAddress, expectedShift); - } - - // Test failure for -XX:CompressedClassBaseAddress and -Xshare:off - testFailure("0x0000040001000000"); + // We expect the VM to chose the smallest possible shift value needed to cover the encoding range. + // We expect the encoding Base to start at the class space start - but to enforce that, + // we choose unsuited to even shift-extended zero-based mode. + forceAddress = 32 * G; + + test(forceAddress, true, 128 * M, forceAddress, 6); + test(forceAddress, true, 256 * M, forceAddress, 7); + test(forceAddress, true, 512 * M, forceAddress, 8); + test(forceAddress, true, G, forceAddress, 9); + test(forceAddress, true, 3 * G, forceAddress, 10); + + // Test a "crooked" base address: + // - just aligned enough to pass metaspace reserve alignment test of 16MB. + // - not encodable on aarch64 as logical immediate + // - sufficiently complex enough to need multiple moves on risc platforms to materialize as immediate + // - small enough to not cause test errors on small devices (e.g. arm64 39bit address space) + // - large enough to not end up with zero-based encoding + forceAddress = 0x0000000d55000000L; + test(forceAddress, true, 32 * M, forceAddress, 6); + test(forceAddress, false, 32 * M, forceAddress, 0); } } diff --git a/test/hotspot/jtreg/runtime/ErrorHandling/AccessZeroNKlassHitsProtectionZone.java b/test/hotspot/jtreg/runtime/ErrorHandling/AccessZeroNKlassHitsProtectionZone.java index 61d017d22648..4e177a6fe1d9 100644 --- a/test/hotspot/jtreg/runtime/ErrorHandling/AccessZeroNKlassHitsProtectionZone.java +++ b/test/hotspot/jtreg/runtime/ErrorHandling/AccessZeroNKlassHitsProtectionZone.java @@ -126,7 +126,7 @@ private static OutputAnalyzer run_test(boolean COH, boolean CDS, String forceBas private static void run_test(boolean COH, boolean CDS) throws IOException, SkippedException { // Notes: - // We want to enforce zero-based encoding, to test the protection page in that case. For zero-based encoding, + // We want to enforce non-zero-based encoding, to test the protection page in that case. For zero-based encoding, // protection page is at address zero, no need to test that. // If CDS is on, we never use zero-based, forceBase is ignored. // If CDS is off, we use forceBase to (somewhat) reliably force the encoding base to beyond 32G, diff --git a/test/hotspot/jtreg/runtime/ErrorHandling/TestAVXRegisterDump.java b/test/hotspot/jtreg/runtime/ErrorHandling/TestAVXRegisterDump.java index 1f2fec74feef..59692d94d2f4 100644 --- a/test/hotspot/jtreg/runtime/ErrorHandling/TestAVXRegisterDump.java +++ b/test/hotspot/jtreg/runtime/ErrorHandling/TestAVXRegisterDump.java @@ -26,6 +26,7 @@ * @summary Test that YMM and ZMM registers are correctly dumped in hs_err for different UseAVX settings * @library /test/lib * @requires os.family == "linux" & os.arch == "amd64" + * @requires vm.cpu.features ~= ".*avx.*" * @requires vm.debug == true * @modules java.base/jdk.internal.misc * @build jdk.test.whitebox.WhiteBox diff --git a/test/hotspot/jtreg/runtime/Thread/TestSpinPause.java b/test/hotspot/jtreg/runtime/Thread/TestSpinPause.java index 7226c8ed0581..b939aceee950 100644 --- a/test/hotspot/jtreg/runtime/Thread/TestSpinPause.java +++ b/test/hotspot/jtreg/runtime/Thread/TestSpinPause.java @@ -21,38 +21,91 @@ * questions. */ -/** - * @test TestSpinPause - * @summary JVM runtime can use SpinPause function for synchronized statements. - * Check different implementations of JVM SpinPause don't crash JVM. +/* + * @test id=default + * @summary Check the default SpinPause implementation for synchronized statements. * @bug 8278241 * @library /test/lib - * * @requires os.arch=="aarch64" - * * @run main/othervm TestSpinPause - * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=none TestSpinPause - * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=nop TestSpinPause - * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=isb TestSpinPause - * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=yield TestSpinPause - * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=nop -XX:OnSpinWaitInstCount=10 TestSpinPause - * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=isb -XX:OnSpinWaitInstCount=3 TestSpinPause - * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=yield -XX:OnSpinWaitInstCount=3 TestSpinPause * @run main/othervm -Xint TestSpinPause - * @run main/othervm -Xint -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=none TestSpinPause - * @run main/othervm -Xint -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=nop TestSpinPause - * @run main/othervm -Xint -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=isb TestSpinPause - * @run main/othervm -Xint -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=yield TestSpinPause - * @run main/othervm -Xint -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=nop -XX:OnSpinWaitInstCount=10 TestSpinPause - * @run main/othervm -Xint -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=isb -XX:OnSpinWaitInstCount=3 TestSpinPause - * @run main/othervm -Xint -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=yield -XX:OnSpinWaitInstCount=3 TestSpinPause * @run main/othervm -Xcomp TestSpinPause + */ + +/* + * @test id=none + * @summary Check SpinPause for synchronized statements with OnSpinWaitInst=none. + * @bug 8278241 + * @library /test/lib + * @requires os.arch=="aarch64" + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=none TestSpinPause + * @run main/othervm -Xint -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=none TestSpinPause * @run main/othervm -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=none TestSpinPause + */ + +/* + * @test id=nop + * @summary Check SpinPause for synchronized statements with OnSpinWaitInst=nop. + * @bug 8278241 + * @library /test/lib + * @requires os.arch=="aarch64" + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=nop TestSpinPause + * @run main/othervm -Xint -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=nop TestSpinPause * @run main/othervm -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=nop TestSpinPause + */ + +/* + * @test id=isb + * @summary Check SpinPause for synchronized statements with OnSpinWaitInst=isb. + * @bug 8278241 + * @library /test/lib + * @requires os.arch=="aarch64" + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=isb TestSpinPause + * @run main/othervm -Xint -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=isb TestSpinPause * @run main/othervm -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=isb TestSpinPause + */ + +/* + * @test id=yield + * @summary Check SpinPause for synchronized statements with OnSpinWaitInst=yield. + * @bug 8278241 + * @library /test/lib + * @requires os.arch=="aarch64" + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=yield TestSpinPause + * @run main/othervm -Xint -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=yield TestSpinPause * @run main/othervm -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=yield TestSpinPause + */ + +/* + * @test id=nop-count-10 + * @summary Check SpinPause for synchronized statements with OnSpinWaitInst=nop and OnSpinWaitInstCount=10. + * @bug 8278241 + * @library /test/lib + * @requires os.arch=="aarch64" + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=nop -XX:OnSpinWaitInstCount=10 TestSpinPause + * @run main/othervm -Xint -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=nop -XX:OnSpinWaitInstCount=10 TestSpinPause * @run main/othervm -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=nop -XX:OnSpinWaitInstCount=10 TestSpinPause + */ + +/* + * @test id=isb-count-3 + * @summary Check SpinPause for synchronized statements with OnSpinWaitInst=isb and OnSpinWaitInstCount=3. + * @bug 8278241 + * @library /test/lib + * @requires os.arch=="aarch64" + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=isb -XX:OnSpinWaitInstCount=3 TestSpinPause + * @run main/othervm -Xint -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=isb -XX:OnSpinWaitInstCount=3 TestSpinPause * @run main/othervm -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=isb -XX:OnSpinWaitInstCount=3 TestSpinPause + */ + +/* + * @test id=yield-count-3 + * @summary Check SpinPause for synchronized statements with OnSpinWaitInst=yield and OnSpinWaitInstCount=3. + * @bug 8278241 + * @library /test/lib + * @requires os.arch=="aarch64" + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=yield -XX:OnSpinWaitInstCount=3 TestSpinPause + * @run main/othervm -Xint -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=yield -XX:OnSpinWaitInstCount=3 TestSpinPause * @run main/othervm -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=yield -XX:OnSpinWaitInstCount=3 TestSpinPause */ diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/VerifierFailOver.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/VerifierFailOver.java index 8107e3fe0a36..82a5ccb21423 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/VerifierFailOver.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/VerifierFailOver.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,6 +24,7 @@ /* * @test + * @bug 8365575 * @summary Sanity test for AOTCache * @requires vm.cds.supports.aot.class.linking * @library /test/lib @@ -33,24 +34,72 @@ * @run driver VerifierFailOver */ +import jdk.test.lib.cds.CDSAppTester; import jdk.test.lib.cds.SimpleCDSAppTester; +import jdk.test.lib.helpers.ClassFileInstaller; import jdk.test.lib.process.OutputAnalyzer; public class VerifierFailOver { + + static final String mainClass = VerifierFailOverApp.class.getName(); + static final String appJar = ClassFileInstaller.getJarPath("app.jar"); + public static void main(String... args) throws Exception { SimpleCDSAppTester.of("VerifierFailOver") .addVmArgs("-Xlog:aot,aot+class=debug") .classpath("app.jar") .appCommandLine("VerifierFailOverApp") .setTrainingChecker((OutputAnalyzer out) -> { - out.shouldContain("Skipping VerifierFailOver_Helper: Verified with old verifier"); + out.shouldMatch("class.* klasses.* VerifierFailOver_Helper"); }) .setAssemblyChecker((OutputAnalyzer out) -> { - // classes verified with fail-over mode should not be cached. - out.shouldMatch("class.* klasses.* VerifierFailOverApp"); - out.shouldNotMatch("class.* klasses.* VerifierFailOver_Helper"); + // Classes verified with fail-over can be cached if AOTClassLinking is on + out.shouldMatch("class.* klasses.* VerifierFailOverApp aot-linked"); + out.shouldMatch("class.* klasses.* VerifierFailOver_Helper aot-linked"); }) .runAOTWorkflow(); + + + // When running an assembly run without AOTClassLinking, any classes verified with + // fail-over need to be excluded. + Tester t = new Tester(); + t.runAOTWorkflow(); + } + + static class Tester extends CDSAppTester { + public Tester() { + super(mainClass); + } + + @Override + public String classpath(RunMode runMode) { + return appJar; + } + + @Override + public String[] vmArgs(RunMode runMode) { + if (runMode == RunMode.ASSEMBLY) { + return new String[] {"-XX:-AOTClassLinking", "-Xlog:aot,aot+class=debug"}; + } else { + return new String[] { "-Xlog:aot,aot+class=debug" }; + } + } + + @Override + public String[] appCommandLine(RunMode runMode) { + return new String[] { mainClass }; + } + + @Override + public void checkExecution(OutputAnalyzer out, RunMode runMode) throws Exception { + if (runMode == RunMode.TRAINING) { + out.shouldMatch("class.* klasses.* VerifierFailOver_Helper"); + } else if (runMode == RunMode.ASSEMBLY) { + out.shouldContain("Skipping VerifierFailOver_Helper: Old class has been linked"); + out.shouldMatch("class.* klasses.* VerifierFailOverApp"); + out.shouldNotMatch("class.* klasses.* VerifierFailOver_Helper"); + } + } } } diff --git a/test/hotspot/jtreg/serviceability/jvmti/GetLocalVariable/GetSetLocalSlotOverflow.java b/test/hotspot/jtreg/serviceability/jvmti/GetLocalVariable/GetSetLocalSlotOverflow.java new file mode 100644 index 000000000000..974729998e95 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/GetLocalVariable/GetSetLocalSlotOverflow.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8387718 + * @summary VM_GetOrSetLocal slot bounds check overflows for long/double slots, + * allowing an out-of-bounds StackValueCollection access when slot == INT_MAX. + * @requires vm.jvmti + * @compile GetSetLocalSlotOverflow.java + * @run main/othervm/native -agentlib:GetSetLocalSlotOverflow GetSetLocalSlotOverflow + */ + +/* + * Regression test / reproducer for the signed-overflow in + * VM_BaseGetOrSetLocal::check_slot_type_no_lvt (jvmtiImpl.cpp). + * + * For a T_LONG/T_DOUBLE access, the bounds check is + * if (_index < 0 || _index + extra_slot >= method->max_locals()) + * with extra_slot == 1. When the agent passes slot == INT_MAX, the + * sub-expression _index + extra_slot overflows to INT_MIN, which is < max_locals(), + * so the guard passes and the code goes on to index locals->at(INT_MAX). + * + * Expected (fixed) behavior: GetLocalLong/Double and SetLocalLong/Double with + * slot == INT_MAX return JVMTI_ERROR_INVALID_SLOT. JVMTI only supports SetLocal + * on the topmost frame of a virtual thread, and the targeted runner() frame is + * not topmost, so on a virtual thread SetLocal is rejected before the slot check + * is reached and cannot exercise the overflow; the set sub-tests are skipped there. + * + * On an unfixed VM this test does not merely fail: the out-of-bounds access + * crashes the VM (assertion failure in fastdebug, SIGSEGV / silent corruption + * in product). A clean PASS is only possible once the bounds check is fixed. + */ + +public class GetSetLocalSlotOverflow { + + // Invoked from runner(); the agent inspects the runner() frame at depth 1. + // JVMTI only supports SetLocal on the topmost frame of a virtual thread, so + // the agent skips the set sub-tests when the thread is virtual. + // Returns false if any accessor did not return JVMTI_ERROR_INVALID_SLOT. + static native boolean testOverflow(Thread thread, boolean isVirtual); + + public static void main(String[] args) throws Exception { + if (!runner()) { + throw new RuntimeException("Test GetSetLocalSlotOverflow failed"); + } + } + + // A Java frame holding a few locals. The agent targets this frame (depth 1) + // with slot == INT_MAX. The actual local contents are irrelevant: the + // overflow happens in the slot bounds check, before any local is read. + public static boolean runner() { + long l = 0xCAFEBABEL; + double d = 3.14d; + Thread self = Thread.currentThread(); + boolean ok = testOverflow(self, self.isVirtual()); + // Keep locals live across the native call. + if (l == 0 && d == 0) { + throw new AssertionError("unreachable"); + } + return ok; + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/GetLocalVariable/libGetSetLocalSlotOverflow.cpp b/test/hotspot/jtreg/serviceability/jvmti/GetLocalVariable/libGetSetLocalSlotOverflow.cpp new file mode 100644 index 000000000000..83e5dd5011a6 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/GetLocalVariable/libGetSetLocalSlotOverflow.cpp @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include +#include +#include "jvmti.h" +#include "jvmti_common.hpp" + +#ifdef __cplusplus +extern "C" { +#endif + +// The runner() frame at depth 1; INT_MAX makes (slot + extra_slot) overflow +// for the long/double accessors. +static const jint Depth = 1; +static const jint OverflowSlot = INT_MAX; // 0x7fffffff + +static jvmtiEnv *jvmti = nullptr; + +// Each access must come back as JVMTI_ERROR_INVALID_SLOT. On an unfixed VM the +// overflowing bounds check is bypassed and the subsequent locals->at(INT_MAX) +// access crashes the VM before we ever see a return code. +static bool expect_invalid_slot(const char* what, jvmtiError err) { + if (err == JVMTI_ERROR_INVALID_SLOT) { + LOG(" PASS: %s returned JVMTI_ERROR_INVALID_SLOT (%d) for slot=INT_MAX\n", what, err); + return true; + } + LOG(" FAIL: %s returned %d for slot=INT_MAX, expected JVMTI_ERROR_INVALID_SLOT (%d)\n", + what, err, JVMTI_ERROR_INVALID_SLOT); + return false; +} + +JNIEXPORT jboolean JNICALL +Java_GetSetLocalSlotOverflow_testOverflow(JNIEnv *env, jclass cls, jobject thread, jboolean isVirtual) { + if (jvmti == nullptr) { + LOG("JVMTI client was not properly loaded!\n"); + return JNI_FALSE; + } + + jlong lval = 0; + jdouble dval = 0; + + // T_LONG / T_DOUBLE => extra_slot == 1 => INT_MAX + 1 overflows to INT_MIN. + bool ok = true; + ok &= expect_invalid_slot("GetLocalLong", jvmti->GetLocalLong(thread, Depth, OverflowSlot, &lval)); + ok &= expect_invalid_slot("GetLocalDouble", jvmti->GetLocalDouble(thread, Depth, OverflowSlot, &dval)); + + // JVMTI only supports SetLocal on the topmost frame of a virtual thread. The + // runner() frame is not topmost, so on a virtual thread SetLocal is rejected + // before the slot check runs and cannot exercise the overflow -- skip it there. + if (!isVirtual) { + ok &= expect_invalid_slot("SetLocalLong", jvmti->SetLocalLong(thread, Depth, OverflowSlot, (jlong)0)); + ok &= expect_invalid_slot("SetLocalDouble", jvmti->SetLocalDouble(thread, Depth, OverflowSlot, (jdouble)0)); + } + return ok ? JNI_TRUE : JNI_FALSE; +} + +static jint Agent_Initialize(JavaVM *jvm, char *options, void *reserved) { + jint res; + jvmtiError err; + static jvmtiCapabilities caps; + + res = jvm->GetEnv((void **) &jvmti, JVMTI_VERSION_9); + if (res != JNI_OK || jvmti == nullptr) { + LOG("Wrong result of a valid call to GetEnv!\n"); + return JNI_ERR; + } + caps.can_access_local_variables = 1; + + err = jvmti->AddCapabilities(&caps); + if (err != JVMTI_ERROR_NONE) { + LOG("AddCapabilities: unexpected error: %d\n", err); + return JNI_ERR; + } + err = jvmti->GetCapabilities(&caps); + if (err != JVMTI_ERROR_NONE) { + LOG("GetCapabilities: unexpected error: %d\n", err); + return JNI_ERR; + } + if (!caps.can_access_local_variables) { + LOG("Warning: Access to local variables is not implemented\n"); + return JNI_ERR; + } + return JNI_OK; +} + +JNIEXPORT jint JNICALL +Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) { + return Agent_Initialize(jvm, options, reserved); +} + +JNIEXPORT jint JNICALL +Agent_OnAttach(JavaVM *jvm, char *options, void *reserved) { + return Agent_Initialize(jvm, options, reserved); +} + +#ifdef __cplusplus +} +#endif diff --git a/test/hotspot/jtreg/serviceability/jvmti/vthread/StopThreadTest2/StopThreadTest2.java b/test/hotspot/jtreg/serviceability/jvmti/vthread/StopThreadTest2/StopThreadTest2.java new file mode 100644 index 000000000000..166fb799da2b --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/vthread/StopThreadTest2/StopThreadTest2.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8386116 + * @summary Test suspending and sending async exception to a yielding virtual thread + * @requires vm.continuations + * @requires vm.jvmti + * @requires test.thread.factory == null + * @library /test/lib /test/hotspot/jtreg + * @run main/othervm/native -agentlib:StopThreadTest2 StopThreadTest2 1 + */ + +/* + * @test + * @bug 8386116 + * @summary Test suspending and sending async exception to a virtual thread with empty task + * @requires vm.continuations + * @requires vm.jvmti + * @requires test.thread.factory == null + * @library /test/lib /test/hotspot/jtreg + * @run main/othervm/native -agentlib:StopThreadTest2 StopThreadTest2 2 + */ + +import jdk.test.lib.Asserts; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; + +public class StopThreadTest2 { + static final int MAX_VTHREAD_COUNT = Runtime.getRuntime().availableProcessors(); + static volatile boolean done; + static AtomicInteger asyncThrownCounter = new AtomicInteger(); + + private static native void suspendAllVirtualThreads(); + private static native void resumeAllVirtualThreads(); + private static native boolean stopThread(Thread thread, Throwable th, boolean allowNotAlive); + + public static void main(String args[]) throws Exception { + int testCase = (args.length > 0) ? Integer.parseInt(args[0]) : 1; + switch (testCase) { + case 1 -> testStopAtYield(); + case 2 -> testStopAtEmptyTask(); + default -> throw new RuntimeException("Invalid test case"); + } + } + + public static void foo(CountDownLatch started) { + try { + started.countDown(); + while (!done) { + Thread.yield(); + } + } catch (MyException t) { + asyncThrownCounter.incrementAndGet(); + } + } + + /** + * Test StopThread targeting virtual thread calling Thread.yield + */ + static void testStopAtYield() throws Exception { + Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT]; + for (int i = 0; i < MAX_VTHREAD_COUNT; i++) { + var started = new CountDownLatch(1); + vthreads[i] = Thread.ofVirtual().name("VThread#" + i).start(() -> foo(started)); + started.await(); + } + + int asyncInstalledCounter = 0; + suspendAllVirtualThreads(); + for (Thread vthread : vthreads) { + if (stopThread(vthread, new MyException(), /*allowNotAlive*/false)) { + asyncInstalledCounter++; + } + } + resumeAllVirtualThreads(); + done = true; + + for (Thread vthread : vthreads) { + vthread.join(); + } + Asserts.assertEquals(asyncInstalledCounter, asyncThrownCounter.get()); + } + + /** + * Test StopThread targeting virtual thread executing empty task + */ + static void testStopAtEmptyTask() throws Exception { + Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT]; + for (int i = 0; i < MAX_VTHREAD_COUNT; i++) { + vthreads[i] = Thread.ofVirtual().name("VThread#" + i).start(() -> {}); + } + + suspendAllVirtualThreads(); + for (Thread vthread : vthreads) { + stopThread(vthread, new MyException(), /*allowNotAlive*/true); + } + resumeAllVirtualThreads(); + + for (Thread vthread : vthreads) { + vthread.join(); + } + } + + static class MyException extends RuntimeException {} +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/vthread/StopThreadTest2/libStopThreadTest2.cpp b/test/hotspot/jtreg/serviceability/jvmti/vthread/StopThreadTest2/libStopThreadTest2.cpp new file mode 100644 index 000000000000..ec591f9c5933 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/vthread/StopThreadTest2/libStopThreadTest2.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include +#include +#include +#include +#include "jvmti_common.hpp" + +// set by Agent_OnLoad +static jvmtiEnv* jvmti = nullptr; + +extern "C" { + +JNIEXPORT void JNICALL +Java_StopThreadTest2_suspendAllVirtualThreads(JNIEnv* jni, jclass cls) { + check_jvmti_status(jni, jvmti->SuspendAllVirtualThreads(0, nullptr), "Error in SuspendAllVirtualThreads"); +} + +JNIEXPORT void JNICALL +Java_StopThreadTest2_resumeAllVirtualThreads(JNIEnv* jni, jclass cls) { + check_jvmti_status(jni, jvmti->ResumeAllVirtualThreads(0, nullptr), "Error in ResumeAllVirtualThreads"); +} + +JNIEXPORT jboolean JNICALL +Java_StopThreadTest2_stopThread(JNIEnv* jni, jclass cls, jthread thread, jobject exception, jboolean allowNotAlive) { + jvmtiError err = jvmti->StopThread(thread, exception); + // The target might be suspended at a VirtualThread method + // so we ignore JVMTI_ERROR_OPAQUE_FRAME. + if (err == JVMTI_ERROR_OPAQUE_FRAME || (allowNotAlive && err == JVMTI_ERROR_THREAD_NOT_ALIVE)) { + return false; + } + check_jvmti_status(jni, err, "Error during StopThread()"); + return true; +} + +JNIEXPORT jint JNICALL +Agent_OnLoad(JavaVM* jvm, char* options, void* reserved) { + jvmtiCapabilities caps; + jvmtiError err; + + LOG("Agent_OnLoad: started\n"); + if (jvm->GetEnv((void **) (&jvmti), JVMTI_VERSION) != JNI_OK) { + LOG("Agent_OnLoad: error in GetEnv"); + return JNI_ERR; + } + + memset(&caps, 0, sizeof(caps)); + caps.can_suspend = 1; + caps.can_signal_thread = 1; + caps.can_support_virtual_threads = 1; + err = jvmti->AddCapabilities(&caps); + if (err != JVMTI_ERROR_NONE) { + LOG("Agent_OnLoad: error in JVMTI AddCapabilities: %d\n", err); + return JNI_ERR; + } + + LOG("Agent_OnLoad: finished\n"); + + return 0; +} + +} // extern "C" diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jdb/kill/kill001/kill001.java b/test/hotspot/jtreg/vmTestbase/nsk/jdb/kill/kill001/kill001.java index a96498954b69..885d5762922a 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jdb/kill/kill001/kill001.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jdb/kill/kill001/kill001.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -48,6 +48,7 @@ * * @library /vmTestbase * /test/lib + * @requires test.thread.factory == null * @build nsk.jdb.kill.kill001.kill001a * @run driver * nsk.jdb.kill.kill001.kill001 diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventRequestManager/threadStartRequests/thrstartreq001.java b/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventRequestManager/threadStartRequests/thrstartreq001.java index 7866547177f9..39dc949c2331 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventRequestManager/threadStartRequests/thrstartreq001.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventRequestManager/threadStartRequests/thrstartreq001.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -201,7 +201,7 @@ class EventListener extends Thread { public void run() { try { - do { + while (true) { EventSet eventSet = vm.eventQueue().remove(1000); if (eventSet != null) { // there is not a timeout EventIterator it = eventSet.eventIterator(); @@ -219,11 +219,15 @@ public void run() { log.display("EventListener: following JDI event occured: " + event.toString()); } - if (isConnected) { - eventSet.resume(); - } + eventSet.resume(); + // Even if isConnected has been set false, we need to continue consuming + // events until there are no more. So do a continue here rather than + // allowing continuing to be conditional on isConnected below. + continue; } - } while (isConnected); + if (!isConnected) + break; + } } catch (InterruptedException e) { tot_res = FAILED; log.complain("FAILURE in EventListener: caught unexpected " diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/IterateOverReachableObjects/iterreachobj002/iterreachobj002.cpp b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/IterateOverReachableObjects/iterreachobj002/iterreachobj002.cpp index 236094b50159..b368e0e932f5 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/IterateOverReachableObjects/iterreachobj002/iterreachobj002.cpp +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/IterateOverReachableObjects/iterreachobj002/iterreachobj002.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,13 +31,13 @@ extern "C" { static JNIEnv *jni = nullptr; static jvmtiEnv *jvmti = nullptr; -static jvmtiEventCallbacks callbacks; static jvmtiCapabilities caps; static jlong timeout = 0; /* ============================================================================= */ -static volatile long objectCount = 0, objectCountMax = 0; +static volatile jlong objectCountMax = 0; +static volatile jlong objectTagCount = 0; static int userData = 0, callbackAborted = 0; static int numberOfDeallocatedFromCallbacksDescriptors = 0; @@ -53,14 +53,6 @@ static short* deallocatedFlagsArr; /* ============================================================================= */ -void JNICALL -ObjectFree(jvmtiEnv *jvmti_env, jlong tag) { - /* decrement number of expected objects */ - objectCount--; -} - -/* ============================================================================= */ - /** jvmtiHeapRootCallback for first iteration. */ jvmtiIterationControl JNICALL @@ -72,15 +64,14 @@ heapRootCallbackForFirstObjectsIteration(jvmtiHeapRootKind root_kind, if (*tag_ptr != 0) return JVMTI_ITERATION_CONTINUE; - /* Set tag */ - *tag_ptr = (jlong)++objectCount; - if (!NSK_JVMTI_VERIFY(jvmti->Allocate((sizeof(ObjectDesc)), (unsigned char**)&objectDescBuf))) { nsk_jvmti_setFailStatus(); callbackAborted = 1; NSK_COMPLAIN0("heapRootCallbackForFirstObjectsIteration: Allocation failed. Iteration aborted.\n"); return JVMTI_ITERATION_ABORT; } + /* Set tag */ + *tag_ptr = ++objectTagCount; (*objectDescList).tag = *tag_ptr; (*objectDescList).size = size; @@ -100,24 +91,16 @@ heapRootCallbackForSecondObjectsIteration(jvmtiHeapRootKind root_kind, jlong* tag_ptr, void* user_data) { - long ind = (long)((*tag_ptr) - 1); + jlong ind = (*tag_ptr) - 1; if (*tag_ptr == 0) return JVMTI_ITERATION_CONTINUE; -/* - ObjectDesc *objectDesc = objectDescArr[ind]; - jlong tag = (*objectDesc).tag; -*/ - if (ind < 0 || ind > objectCountMax) { - NSK_COMPLAIN1("heapRootCallbackForSecondObjectsIteration: invalid object tag value: %d\n", (long)*tag_ptr); + if (ind < 0 || ind >= objectCountMax) { + NSK_COMPLAIN1("heapRootCallbackForSecondObjectsIteration: invalid object tag value: " JLONG_FORMAT "\n", *tag_ptr); nsk_jvmti_setFailStatus(); callbackAborted = 1; return JVMTI_ITERATION_ABORT; } -/* - NSK_DISPLAY3("heapRootCallbackForSecondObjectsIteration: *tag_ptr %6d , tag %6d , objectCount %6d\n", - (long)*tag_ptr, (long)tag, objectCount); -*/ /* Deallocate memory of list element*/ if (!NSK_JVMTI_VERIFY(jvmti->Deallocate((unsigned char*)objectDescArr[ind]))) { nsk_jvmti_setFailStatus(); @@ -131,7 +114,6 @@ heapRootCallbackForSecondObjectsIteration(jvmtiHeapRootKind root_kind, /* unset tag */ *tag_ptr = 0; - objectCount--; return JVMTI_ITERATION_CONTINUE; } @@ -150,15 +132,14 @@ stackReferenceCallbackForFirstObjectsIteration(jvmtiHeapRootKind root_kind, if (*tag_ptr != 0) return JVMTI_ITERATION_CONTINUE; - /* Set tag */ - *tag_ptr = (jlong)++objectCount; - if (!NSK_JVMTI_VERIFY(jvmti->Allocate((sizeof(ObjectDesc)), (unsigned char**)&objectDescBuf))) { nsk_jvmti_setFailStatus(); callbackAborted = 1; NSK_COMPLAIN0("stackReferenceCallbackForFirstObjectsIteration: Allocation failed. Iteration aborted.\n"); return JVMTI_ITERATION_ABORT; } + /* Set tag */ + *tag_ptr = ++objectTagCount; (*objectDescList).tag = *tag_ptr; (*objectDescList).size = size; @@ -182,24 +163,16 @@ stackReferenceCallbackForSecondObjectsIteration(jvmtiHeapRootKind root_kind, jint slot, void* user_data) { - long ind = (long)((*tag_ptr) - 1); + jlong ind = (*tag_ptr) - 1; if (*tag_ptr == 0) return JVMTI_ITERATION_CONTINUE; -/* - ObjectDesc *objectDesc = objectDescArr[ind]; - jlong tag = (*objectDesc).tag; -*/ - if (ind < 0 || ind > objectCountMax) { - NSK_COMPLAIN1("stackReferenceCallbackForSecondObjectsIteration: invalid object tag value: %d\n", (long)*tag_ptr); + if (ind < 0 || ind >= objectCountMax) { + NSK_COMPLAIN1("stackReferenceCallbackForSecondObjectsIteration: invalid object tag value: " JLONG_FORMAT "\n", *tag_ptr); nsk_jvmti_setFailStatus(); callbackAborted = 1; return JVMTI_ITERATION_ABORT; } -/* - NSK_DISPLAY3("stackReferenceCallbackForSecondObjectsIteration: *tag_ptr %6d , tag %6d , objectCount %6d\n", - (long)*tag_ptr, (long)tag, objectCount); -*/ /* Deallocate memory of list element*/ if (!NSK_JVMTI_VERIFY(jvmti->Deallocate((unsigned char*)objectDescArr[ind]))) { nsk_jvmti_setFailStatus(); @@ -213,7 +186,6 @@ stackReferenceCallbackForSecondObjectsIteration(jvmtiHeapRootKind root_kind, /* unset tag */ *tag_ptr = 0; - objectCount--; return JVMTI_ITERATION_CONTINUE; } @@ -230,15 +202,14 @@ objectReferenceCallbackForFirstObjectsIteration(jvmtiObjectReferenceKind referen if (*tag_ptr != 0) return JVMTI_ITERATION_CONTINUE; - /* Set tag */ - *tag_ptr = (jlong)++objectCount; - if (!NSK_JVMTI_VERIFY(jvmti->Allocate((sizeof(ObjectDesc)), (unsigned char**)&objectDescBuf))) { nsk_jvmti_setFailStatus(); callbackAborted = 1; NSK_COMPLAIN0("objectReferenceCallbackForFirstObjectsIteration: Allocation failed. Iteration aborted.\n"); return JVMTI_ITERATION_ABORT; } + /* Set tag */ + *tag_ptr = ++objectTagCount; (*objectDescList).tag = *tag_ptr; (*objectDescList).size = size; @@ -260,24 +231,16 @@ objectReferenceCallbackForSecondObjectsIteration(jvmtiObjectReferenceKind refere jint referrer_index, void* user_data) { - long ind = (long)((*tag_ptr) - 1); + jlong ind = (*tag_ptr) - 1; if (*tag_ptr == 0) return JVMTI_ITERATION_CONTINUE; -/* - ObjectDesc *objectDesc = objectDescArr[ind]; - jlong tag = (*objectDesc).tag; -*/ - if (ind < 0 || ind > objectCountMax) { - NSK_COMPLAIN1("objectReferenceCallbackForSecondObjectsIteration: invalid object tag value: %d\n", (long)*tag_ptr); + if (ind < 0 || ind >= objectCountMax) { + NSK_COMPLAIN1("objectReferenceCallbackForSecondObjectsIteration: invalid object tag value: " JLONG_FORMAT "\n", *tag_ptr); nsk_jvmti_setFailStatus(); callbackAborted = 1; return JVMTI_ITERATION_ABORT; } -/* - NSK_DISPLAY3("objectReferenceCallbackForSecondObjectsIteration: *tag_ptr %6d , tag %6d , objectCount %6d\n", - (long)*tag_ptr, (long)tag, objectCount); -*/ /* Deallocate memory of list element*/ if (!NSK_JVMTI_VERIFY(jvmti->Deallocate((unsigned char*)objectDescArr[ind]))) { nsk_jvmti_setFailStatus(); @@ -291,7 +254,6 @@ objectReferenceCallbackForSecondObjectsIteration(jvmtiObjectReferenceKind refere /* unset tag */ *tag_ptr = 0; - objectCount--; return JVMTI_ITERATION_CONTINUE; } @@ -302,7 +264,7 @@ objectReferenceCallbackForSecondObjectsIteration(jvmtiObjectReferenceKind refere static void JNICALL agentProc(jvmtiEnv* jvmti, JNIEnv* jni, void* arg) { - long ind; + jlong ind; NSK_DISPLAY0("Wait for debugee start\n"); if (!NSK_VERIFY(nsk_jvmti_waitForSync(timeout))) @@ -332,17 +294,29 @@ agentProc(jvmtiEnv* jvmti, JNIEnv* jni, void* arg) { } if (callbackAborted) break; - if (objectCount == 0) { + if (objectTagCount == 0) { NSK_COMPLAIN0("First IterateOverReachableObjects call had not visited any object\n"); nsk_jvmti_setFailStatus(); break; } else { - NSK_DISPLAY1("Number of objects the first IterateOverReachableObjects visited: %d\n", objectCount); + NSK_DISPLAY1("Number of objects the first IterateOverReachableObjects visited: " JLONG_FORMAT "\n", objectTagCount); } if (callbackAborted) break; - objectCountMax = objectCount; + /* This fragment is needed to stress test execution with extra GC's. */ + for (int gcCount = 0; gcCount < 5; gcCount++) { + NSK_DISPLAY1("Calling ForceGarbageCollection #%d before second iteration\n", gcCount + 1); + if (!NSK_JVMTI_VERIFY(jvmti->ForceGarbageCollection())) { + nsk_jvmti_setFailStatus(); + break; + } + } + if (nsk_jvmti_getStatus() != NSK_STATUS_PASSED) { + break; + } + + objectCountMax = objectTagCount; /* Deallocate last unnecessary descriptor */ if (!NSK_JVMTI_VERIFY(jvmti->Deallocate((unsigned char*)objectDescList))) { @@ -352,7 +326,7 @@ agentProc(jvmtiEnv* jvmti, JNIEnv* jni, void* arg) { } /* Allocate memory for array to save pointers to ObjectDescList elements */ - if (!NSK_JVMTI_VERIFY(jvmti->Allocate((objectCount * sizeof(ObjectDesc*)), + if (!NSK_JVMTI_VERIFY(jvmti->Allocate((objectCountMax * sizeof(ObjectDesc*)), (unsigned char**)&objectDescArr))) { nsk_jvmti_setFailStatus(); break; @@ -372,7 +346,7 @@ agentProc(jvmtiEnv* jvmti, JNIEnv* jni, void* arg) { objectDescList = objectDescListStart; { /* Save all pointers to ObjectDescList elements in objectDescArr */ - for (ind = 0; ind < objectCount; ind++) { + for (ind = 0; ind < objectCountMax; ind++) { objectDescArr[ind] = objectDescList; objectDescList = (*objectDescList).next; } @@ -390,6 +364,16 @@ agentProc(jvmtiEnv* jvmti, JNIEnv* jni, void* arg) { } } + if (callbackAborted) break; + + if (objectCountMax != objectTagCount) { + NSK_COMPLAIN2("objectCountMax: " JLONG_FORMAT " must match objectTagCount: " JLONG_FORMAT + " after second call to IterateOverReachableObjects\n", + objectCountMax, objectTagCount); + nsk_jvmti_setFailStatus(); + break; + } + if (numberOfDeallocatedFromCallbacksDescriptors == 0) { NSK_COMPLAIN1("Deallocate func. hasn't been called from IterateOverReachableObjects'callbacks. " "numberOfDeallocatedFromCallbacksDescriptors = %d\n", numberOfDeallocatedFromCallbacksDescriptors); @@ -399,7 +383,7 @@ agentProc(jvmtiEnv* jvmti, JNIEnv* jni, void* arg) { for (ind = 0; ind < objectCountMax; ind++) { if (!deallocatedFlagsArr[ind]) { if (!NSK_JVMTI_VERIFY(jvmti->Deallocate((unsigned char*)objectDescArr[ind]))) { - NSK_COMPLAIN1("Unable to deallocate descriptor. Index = %d \n", ind); + NSK_COMPLAIN1("Unable to deallocate descriptor. Index = " JLONG_FORMAT "\n", ind); nsk_jvmti_setFailStatus(); return; } @@ -451,7 +435,6 @@ jint Agent_Initialize(JavaVM *jvm, char *options, void *reserved) { memset(&caps, 0, sizeof(caps)); caps.can_tag_objects = 1; - caps.can_generate_object_free_events = 1; if (!NSK_JVMTI_VERIFY(jvmti->AddCapabilities(&caps))) { return JNI_ERR; } @@ -461,25 +444,6 @@ jint Agent_Initialize(JavaVM *jvm, char *options, void *reserved) { if (!caps.can_tag_objects) NSK_DISPLAY0("Warning: tagging objects is not available\n"); - if (!caps.can_generate_object_free_events) - NSK_DISPLAY0("Warning: generation of object free events is not available\n"); - - /* set event callback */ - NSK_DISPLAY0("setting event callbacks ...\n"); - (void) memset(&callbacks, 0, sizeof(callbacks)); - - callbacks.ObjectFree = &ObjectFree; - if (!NSK_JVMTI_VERIFY(jvmti->SetEventCallbacks(&callbacks, sizeof(callbacks)))) - return JNI_ERR; - - NSK_DISPLAY0("setting event callbacks done.\n"); - - NSK_DISPLAY0("enabling JVMTI events ...\n"); - if (!NSK_JVMTI_VERIFY(jvmti->SetEventNotificationMode(JVMTI_ENABLE, - JVMTI_EVENT_OBJECT_FREE, - nullptr))) - return JNI_ERR; - NSK_DISPLAY0("enabling the events done.\n"); if (!NSK_VERIFY(nsk_jvmti_setAgentProc(agentProc, nullptr))) return JNI_ERR; diff --git a/test/jdk/com/sun/crypto/provider/Cipher/PBE/PBEKeyTest.java b/test/jdk/com/sun/crypto/provider/Cipher/PBE/PBEKeyTest.java index 9091480fae07..c6284135a703 100644 --- a/test/jdk/com/sun/crypto/provider/Cipher/PBE/PBEKeyTest.java +++ b/test/jdk/com/sun/crypto/provider/Cipher/PBE/PBEKeyTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2007, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,7 +23,7 @@ /* * @test - * @bug 0000000 + * @bug 8348732 * @summary test PBEKey * @author Jan Luehe */ @@ -39,22 +39,26 @@ public static void main(String[] args) throws Exception { // Valid password char[] pass = new char[] { 'p', 'a', 's', 's', 'w', 'o', 'r', 'd' }; + testPassword(pass, fac, "ASCII password", true); + + pass = new char[] { 'p', 'a', 's', 's', 'w', 'o', 'r', '\u0019' }; + testPassword(pass, fac, "non-visible characters", true); + + pass = new char[] { 'p', 'a', 's', 's', 'w', 'o', 'r', (char)0xff }; + testPassword(pass, fac, "non-ASCII characters", false); + } + + private static void testPassword(char[] pass, SecretKeyFactory fac, String desc, + boolean expectPass) throws Exception { PBEKeySpec spec = new PBEKeySpec(pass); SecretKey skey = fac.generateSecret(spec); KeySpec spec1 = fac.getKeySpec(skey, PBEKeySpec.class); SecretKey skey1 = fac.generateSecret(spec1); - if (!skey.equals(skey1)) - throw new Exception("Equal keys not equal"); - System.out.println(new String(((PBEKeySpec)spec1).getPassword())); - - // Invalid password - pass = new char[] { 'p', 'a', 's', 's', 'w', 'o', 'r', '\u0019' }; - spec = new PBEKeySpec(pass); - try { - skey = fac.generateSecret(spec); - throw new Exception("Expected exception not thrown"); - } catch (Exception e) { - System.out.println("Expected exception thrown"); + if (expectPass && !skey.equals(skey1)) { + throw new Exception(desc + ": Equal keys not equal"); + } else if (!expectPass && skey.equals(skey1)) { + throw new Exception(desc + ": Keys should not be the same but are!"); } + System.out.println(new String(((PBEKeySpec)spec1).getPassword())); } } diff --git a/test/jdk/com/sun/net/httpserver/simpleserver/jwebserver/MaxRequestTimeTest.java b/test/jdk/com/sun/net/httpserver/simpleserver/jwebserver/MaxRequestTimeTest.java index c19ca632bf3b..836beeab8668 100644 --- a/test/jdk/com/sun/net/httpserver/simpleserver/jwebserver/MaxRequestTimeTest.java +++ b/test/jdk/com/sun/net/httpserver/simpleserver/jwebserver/MaxRequestTimeTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,27 +23,33 @@ /* * @test + * @key randomness * @bug 8278398 * @summary Tests the jwebserver's maximum request time * @modules jdk.httpserver * @library /test/lib + * @build jdk.test.lib.RandomFactory * @run junit/othervm MaxRequestTimeTest */ import java.io.IOException; import java.net.InetAddress; +import java.net.InetSocketAddress; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Random; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLException; + import jdk.test.lib.Platform; -import jdk.test.lib.net.SimpleSSLContext; +import jdk.test.lib.RandomFactory; import jdk.test.lib.process.OutputAnalyzer; import jdk.test.lib.process.ProcessTools; import jdk.test.lib.util.FileUtils; @@ -61,16 +67,16 @@ * * The jwebserver has a maximum request time of 5 seconds, which is set with the * "sun.net.httpserver.maxReqTime" system property. If this threshold is - * reached, for example in the case of an HTTPS request where the server keeps - * waiting for a plaintext request, the server closes the connection. Subsequent + * reached, the server closes the connection. Subsequent * requests are expected to be handled as normal. * * The test checks in the following order that: * 1. an HTTP request is handled successfully, - * 2. an HTTPS request fails due to the server closing the connection + * 2. an incomplete HTTP request fails due to the server closing the connection * 3. another HTTP request is handled successfully. */ public class MaxRequestTimeTest { + private static final Random RND = RandomFactory.getRandom(); static final Path JAVA_HOME = Path.of(System.getProperty("java.home")); static final String LOCALE_OPT = "-J-Duser.language=en -J-Duser.country=US"; static final String JWEBSERVER = getJwebserver(JAVA_HOME); @@ -79,8 +85,6 @@ public class MaxRequestTimeTest { static final String LOOPBACK_ADDR = InetAddress.getLoopbackAddress().getHostAddress(); static final AtomicInteger PORT = new AtomicInteger(); - private static final SSLContext sslContext = SimpleSSLContext.findSSLContext(); - @BeforeAll public static void setup() throws IOException { if (Files.exists(TEST_DIR)) { @@ -94,10 +98,10 @@ public void testMaxRequestTime() throws Throwable { final var sb = new StringBuffer(); // stdout & stderr final var p = startProcess("jwebserver", sb); try { - sendHTTPSRequest(); // server expected to terminate connection - sendHTTPRequest(); // server expected to respond successfully - sendHTTPSRequest(); // server expected to terminate connection - sendHTTPRequest(); // server expected to respond successfully + sendIncompleteRequest(); // server expected to terminate connection + sendCompleteRequest(); // server expected to respond successfully + sendIncompleteRequest(); // server expected to terminate connection + sendCompleteRequest(); // server expected to respond successfully } finally { p.destroy(); int exitCode = p.waitFor(); @@ -105,6 +109,12 @@ public void testMaxRequestTime() throws Throwable { } } + static String requestText = """ + GET / HTTP/1.1\r + Host: localhost\r + \r + """; + static ByteBuffer requestBuffer = ByteBuffer.wrap(requestText.getBytes(StandardCharsets.UTF_8)); static String expectedBody = """ @@ -119,8 +129,8 @@ public void testMaxRequestTime() throws Throwable { """; - static void sendHTTPRequest() throws IOException, InterruptedException { - out.println("\n--- sendHTTPRequest"); + static void sendCompleteRequest() throws IOException, InterruptedException { + out.println("\n--- sendCompleteRequest"); var client = HttpClient.newBuilder() .proxy(NO_PROXY) .build(); @@ -129,18 +139,21 @@ static void sendHTTPRequest() throws IOException, InterruptedException { assertEquals(expectedBody, response.body()); } - static void sendHTTPSRequest() throws IOException, InterruptedException { - out.println("\n--- sendHTTPSRequest"); - var client = HttpClient.newBuilder() - .sslContext(sslContext) - .proxy(NO_PROXY) - .build(); - var request = HttpRequest.newBuilder(URI.create("https://localhost:" + PORT.get() + "/")).build(); - try { - client.send(request, HttpResponse.BodyHandlers.ofString()); - throw new RuntimeException("Expected SSLException not thrown"); - } catch (SSLException expected) { // server closes connection when max request time is reached - expected.printStackTrace(System.out); + static void sendIncompleteRequest() throws IOException { + out.println("\n--- sendIncompleteRequest"); + try (SocketChannel sc = SocketChannel.open( + new InetSocketAddress(LOOPBACK_ADDR, PORT.get()))) { + requestBuffer.clear(); + // only send a part of the HTTP request + int numBytes = RND.nextInt(1, requestBuffer.limit()); + System.out.println("Sending " + numBytes + " bytes"); + requestBuffer.limit(numBytes); + while (requestBuffer.hasRemaining()) { + sc.write(requestBuffer); + } + ByteBuffer responseBuffer = ByteBuffer.allocate(1); + int result = sc.read(responseBuffer); + assertEquals(-1, result); } } diff --git a/test/jdk/com/sun/tools/attach/JvmTempDirTest.java b/test/jdk/com/sun/tools/attach/JvmTempDirTest.java new file mode 100644 index 000000000000..6729de149a68 --- /dev/null +++ b/test/jdk/com/sun/tools/attach/JvmTempDirTest.java @@ -0,0 +1,258 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import com.sun.tools.attach.*; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Properties; +import java.util.List; +import java.io.File; + +import jdk.test.lib.thread.ProcessThread; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +/* + * @test + * @bug 8384557 + * @summary Test to make sure attach and jvmstat work correctly when -XX:AltTempDir is set. + * + * @requires os.family == "linux" + * @library /test/lib + * @modules jdk.attach + * jdk.jartool/sun.tools.jar + * + * @run build Application RunnerUtil + * @run main/timeout=200 JvmTempDirTest + */ + +/* + * This test is similar to TempDirTest.java. The property java.io.tmpdir does not affect how + * jdk.attach works, but -XX:AltTempDir does. + * + * This test runs with an extra long timeout since it takes a really long time with -Xcomp + * when starting many processes. + */ + +import jdk.test.lib.util.FileUtils; + +public class JvmTempDirTest { + + private static long startTime; + + public static void main(String args[]) throws Throwable { + + startTime = System.currentTimeMillis(); + + Path clientTmpDir = Files.createTempDirectory(Path.of("/tmp"), "c"); + Path targetTmpDir = Files.createTempDirectory(Path.of("/tmp"), "t"); + + try { + // Run the test with all possible combinations of setting AltTempDir. + // Different setting will cause the attach mechanism to fail. + String notFound = "not found in VM list"; + runExperiment(null, null, true, null); + runExperiment(targetTmpDir, targetTmpDir, true, null); + runExperiment(clientTmpDir, clientTmpDir, true, null); + + runExperiment(clientTmpDir, null, false, notFound); + runExperiment(clientTmpDir, targetTmpDir, false, notFound); + runExperiment(null, targetTmpDir, false, notFound); + } finally { + FileUtils.deleteFileTreeWithRetry(clientTmpDir); + FileUtils.deleteFileTreeWithRetry(targetTmpDir); + } + + String name = String.valueOf('a').repeat(200); + Path veryLongDir = Files.createTempDirectory(Path.of("/tmp"), name); + try { + runExperiment(veryLongDir, veryLongDir, false, "Socket file path too long"); + } finally { + FileUtils.deleteFileTreeWithRetry(veryLongDir); + } + + // Test a directory with only proc in one part of the name. + Path procTempDir = Files.createTempDirectory(Path.of("/tmp"), "proc"); + Path procDir = Files.createDirectory(procTempDir.resolve("proc")); + try { + runExperiment(procDir, procDir, true, null); + } finally { + FileUtils.deleteFileTreeWithRetry(procDir); + FileUtils.deleteFileTreeWithRetry(procTempDir); + } + + Path hsperfDir = Files.createTempDirectory(Path.of("/tmp"), "hsperfdata_"); + try { + runExperiment(hsperfDir, hsperfDir, true, null); + } finally { + FileUtils.deleteFileTreeWithRetry(hsperfDir); + } + + // Create /tmp/tmp, and try to use /tmp/tmp/noexist + Path tmpDir = Files.createTempDirectory(Path.of("/tmp"), "tmp"); + try { + Path noExist = tmpDir.resolve("noexist"); + runNoExistTest(noExist); + } finally { + FileUtils.deleteFileTreeWithRetry(tmpDir); + } + + Path relativeDir = Files.createTempDirectory(Path.of("."), "a"); + try { + runRelativeTest(relativeDir); + } finally { + FileUtils.deleteFileTreeWithRetry(relativeDir); + } + } + + /* + * The actual test is in the nested class TestMain. + * The responsibility of this class is to: + * 1. Start the Application class in a separate process. + * 2. Find the pid and shutdown port of the running Application. + * 3. Launch the tests in nested class TestMain that will attach to the Application. + * 4. Shut down the Application. + */ + public static void runExperiment(Path clientTmpDir, Path targetTmpDir, boolean shouldPass, String message) throws Throwable { + + System.out.print("### Running tests with overridden tmpdir for"); + System.out.print(" client: " + (clientTmpDir == null ? "no" : "yes")); + System.out.print(" target: " + (targetTmpDir == null ? "no" : "yes")); + System.out.println(" ###"); + + long elapsedTime = (System.currentTimeMillis() - startTime) / 1000; + System.out.println("Started after " + elapsedTime + "s"); + + ProcessThread processThread = null; + try { + String[] tmpDirArg = null; + if (targetTmpDir != null) { + tmpDirArg = new String[] {"-XX:AltTempDir=" + targetTmpDir}; + } + processThread = RunnerUtil.startApplication(tmpDirArg); + launchTests(processThread.getPid(), clientTmpDir, shouldPass, message); + } catch (Throwable t) { + System.out.println("JvmTempDirTest got unexpected exception: " + t); + t.printStackTrace(); + throw t; + } finally { + // Make sure the Application process is stopped. + RunnerUtil.stopApplication(processThread); + } + + elapsedTime = (System.currentTimeMillis() - startTime) / 1000; + System.out.println("Completed after " + elapsedTime + "s"); + + } + + /** + * Runs the actual tests in nested class TestMain. + * The reason for running the tests in a separate process + * is that we need to modify the class path and + * the -XX:AltTempDir argument. + */ + private static void launchTests(long pid, Path clientTmpDir, boolean shouldPass, String message) throws Throwable { + + String classpath = + System.getProperty("test.class.path", ""); + + String[] tmpDirArg = null; + if (clientTmpDir != null) { + tmpDirArg = new String [] {"-XX:AltTempDir=" + clientTmpDir}; + } + + // Arguments : [-XX:AltTempDir=] -classpath cp JvmTempDirTest$TestMain pid + String[] args = RunnerUtil.concat( + tmpDirArg, + new String[] { + "-classpath", + classpath, + "JvmTempDirTest$TestMain", + Long.toString(pid) }); + OutputAnalyzer output = ProcessTools.executeTestJava(args); + if (shouldPass) { + output.shouldHaveExitValue(0); + } else { + output.shouldContain(message); + output.shouldNotHaveExitValue(0); + } + } + + /** + * This is the actual test. It will attach to the running Application + * and perform a number of basic attach tests. + */ + public static class TestMain { + public static void main(String args[]) throws Exception { + String pid = args[0]; + + // Test 1 - list method should list the target VM + System.out.println(" - Test: VirtualMachine.list"); + List l = VirtualMachine.list(); + boolean found = false; + for (VirtualMachineDescriptor vmd: l) { + if (vmd.id().equals(pid)) { + found = true; + break; + } + } + if (found) { + System.out.println(" - " + pid + " found."); + } else { + throw new RuntimeException(pid + " not found in VM list"); + } + + // Test 2 - try to attach and verify connection + + System.out.println(" - Attaching to application ..."); + VirtualMachine vm = VirtualMachine.attach(pid); + + System.out.println(" - Test: system properties in target VM"); + Properties props = vm.getSystemProperties(); + String value = props.getProperty("attach.test"); + if (value == null || !value.equals("true")) { + throw new RuntimeException("attach.test property not set"); + } + System.out.println(" - attach.test property set as expected"); + } + } + + private static void runNoExistTest(Path tmpDir) throws Throwable { + // Arguments : [-XX:AltTempDir=] -version + String[] args = new String[] { "-XX:AltTempDir=" + tmpDir, "-version" }; + OutputAnalyzer output = ProcessTools.executeTestJava(args); + output.shouldMatch("\\[warning\\]\\[os *\\] Warning: AltTempDir is not an existing or writable directory"); + // Still passes, it's just a warning. + output.shouldHaveExitValue(0); + } + + private static void runRelativeTest(Path tmpDir) throws Throwable { + // Arguments : [-XX:AltTempDir=] -version + String[] args = new String[] { "-XX:AltTempDir=" + tmpDir, "-version" }; + OutputAnalyzer output = ProcessTools.executeTestJava(args); + output.shouldMatch("\\[warning\\]\\[os *\\] Warning: AltTempDir is ignored because it must be an absolute pathname"); + // Still passes, it's just a warning. + output.shouldHaveExitValue(0); + } +} diff --git a/test/jdk/com/sun/tools/attach/TempDirTest.java b/test/jdk/com/sun/tools/attach/TempDirTest.java index e0552d15fcee..14b65207da9b 100644 --- a/test/jdk/com/sun/tools/attach/TempDirTest.java +++ b/test/jdk/com/sun/tools/attach/TempDirTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -64,7 +64,9 @@ public static void main(String args[]) throws Throwable { Path targetTmpDir = Files.createTempDirectory("TempDirTest-target"); targetTmpDir.toFile().deleteOnExit(); - // run the test with all possible combinations of setting java.io.tmpdir + // Run the test with all possible combinations of setting java.io.tmpdir. + // Note that the attach mechanism doesn't really use java.io.tmpdir, but this test verifies + // that different java.io.tmpdir settings for client and target don't break the attach mechanism. runExperiment(null, null); runExperiment(clientTmpDir, null); runExperiment(clientTmpDir, targetTmpDir); diff --git a/test/jdk/java/awt/Graphics2D/ClearPolyLineUsingXORTest.java b/test/jdk/java/awt/Graphics2D/ClearPolyLineUsingXORTest.java new file mode 100644 index 000000000000..7afecfdee3b8 --- /dev/null +++ b/test/jdk/java/awt/Graphics2D/ClearPolyLineUsingXORTest.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @key headful + * @bug 8387593 + * @summary Tests that clearing a polyline using XOR mode does not + * leave any traces. Using uiScale 1 helps us to + * reproduce the issue. + * @run main/othervm -Dsun.java2d.uiScale=1 ClearPolyLineUsingXORTest + */ + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.GraphicsConfiguration; +import java.awt.GraphicsEnvironment; +import java.awt.image.BufferedImage; +import java.awt.image.VolatileImage; + +public class ClearPolyLineUsingXORTest { + private static final int SIZE = 500; + + private static BufferedImage clearUsingXOR(GraphicsConfiguration gc) { + VolatileImage vImg = gc.createCompatibleVolatileImage(SIZE, SIZE); + int attempt = 0; + while (true) { + if (++attempt > 10) { + throw new RuntimeException("Unable to use VolatileImage after " + + attempt + " attempts"); + } + + int status = vImg.validate(gc); + if (status == VolatileImage.IMAGE_INCOMPATIBLE) { + vImg = gc.createCompatibleVolatileImage(SIZE, SIZE); + } + + Graphics2D g2d = vImg.createGraphics(); + g2d.setBackground(Color.BLACK); + g2d.clearRect(0, 0, 500, 500); + + int min = 10; + int max = 210; + int mid = 110; + + int xdp[] = {min, max, min, max, min, max}; + int ydp[] = {min, min, mid, max, max, mid}; + + g2d.setXORMode(Color.GREEN); + g2d.drawPolygon(xdp, ydp, xdp.length); + g2d.drawPolygon(xdp, ydp, xdp.length); + + BufferedImage snapshot = vImg.getSnapshot(); + if (vImg.contentsLost()) { + continue; + } + return snapshot; + } + } + public static void main(String[] args) { + GraphicsConfiguration gc = + GraphicsEnvironment.getLocalGraphicsEnvironment(). + getDefaultScreenDevice().getDefaultConfiguration(); + BufferedImage bImg = clearUsingXOR(gc); + + for (int x = 0; x < SIZE; x++) { + for (int y = 0; y < SIZE; y++) { + if (bImg.getRGB(x, y) != Color.BLACK.getRGB()) { + throw new RuntimeException("Clear using XOR is not" + + " working at x: " + x + " y: " + y); + } + } + } + } +} diff --git a/test/jdk/java/awt/dnd/FileDialogDropTargetTest.java b/test/jdk/java/awt/dnd/FileDialogDropTargetTest.java new file mode 100644 index 000000000000..c292639479d1 --- /dev/null +++ b/test/jdk/java/awt/dnd/FileDialogDropTargetTest.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * 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 4411368 8381565 + * @key headful + * @summary tests the app doesn't crash when a drop target registered + * on a file dialog is unregistered + * @run main/othervm/timeout=120 -Dsun.awt.disableGtkFileDialogs=true FileDialogDropTargetTest + */ + +import java.awt.EventQueue; +import java.awt.FileDialog; +import java.awt.Frame; +import java.awt.Robot; +import java.awt.dnd.DropTarget; +import java.awt.dnd.DropTargetAdapter; +import java.awt.dnd.DropTargetDropEvent; + +public class FileDialogDropTargetTest { + private static Robot robot; + private static Frame frame; + + public static void main(String[] args) throws Exception { + robot = new Robot(); + frame = new Frame(); + + try { + test(10); + test(100); + } finally { + EventQueue.invokeAndWait(frame::dispose); + } + + System.out.println("Test passed"); + } + + private static void test(int delay) { + System.out.printf("\nTesting FileDialogDropTarget with %d ms delay\n", delay); + for (int i = 0; i < 100; i++) { + final FileDialog fileDialog = new FileDialog(frame); + + fileDialog.setDropTarget(new DropTarget(fileDialog, + new DropTargetAdapter() { + public void drop(DropTargetDropEvent dtde) { + } + })); + fileDialog.pack(); + + new Thread(() -> { + robot.delay(delay); + fileDialog.dispose(); + }).start(); + + fileDialog.setVisible(true); + } + + robot.waitForIdle(); + robot.delay(1000); + } +} diff --git a/test/jdk/java/foreign/enablenativeaccess/TestEnableNativeAccessJarManifest.java b/test/jdk/java/foreign/enablenativeaccess/TestEnableNativeAccessJarManifest.java index 0ac7ea474a4a..3522921bdd34 100644 --- a/test/jdk/java/foreign/enablenativeaccess/TestEnableNativeAccessJarManifest.java +++ b/test/jdk/java/foreign/enablenativeaccess/TestEnableNativeAccessJarManifest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,7 +29,6 @@ * @requires jdk.foreign.linker != "UNSUPPORTED" * @requires !vm.musl * - * @enablePreview * @build TestEnableNativeAccessJarManifest * panama_module/* * org.openjdk.foreigntest.unnamed.PanamaMainUnnamedModule diff --git a/test/jdk/java/lang/String/ToLowerCase.java b/test/jdk/java/lang/String/ToLowerCase.java index 003cc882b9cb..da2c2bbc4b2f 100644 --- a/test/jdk/java/lang/String/ToLowerCase.java +++ b/test/jdk/java/lang/String/ToLowerCase.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,95 +23,136 @@ /* @test - @bug 4217441 4533872 4900935 8020037 8032012 8041791 8042589 8054307 + @bug 4217441 4533872 4900935 8020037 8032012 8041791 8042589 8054307 8133167 @summary toLowerCase should lower-case Greek Sigma correctly depending on the context (final/non-final). Also it should handle Locale specific (lt, tr, and az) lowercasings and supplementary characters correctly. + @run junit ToLowerCase */ import java.util.Locale; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import static org.junit.jupiter.api.Assertions.assertEquals; public class ToLowerCase { + private static final Locale TURKISH = Locale.of("tr"); + private static final Locale LITHUANIAN = Locale.of("lt"); + private static final Locale AZERI = Locale.of("az"); + private static final Locale GREEK = Locale.of("el"); + + @ParameterizedTest + @MethodSource + void testSimpleCases(String source, Locale locale, String expected) { + test(source, locale, expected); + } - public static void main(String[] args) { - Locale turkish = Locale.of("tr", "TR"); - Locale lt = Locale.of("lt"); // Lithanian - Locale az = Locale.of("az"); // Azeri - - // Greek Sigma final/non-final tests - test("\u03A3", Locale.US, "\u03C3"); - test("LAST\u03A3", Locale.US, "last\u03C2"); - test("MID\u03A3DLE", Locale.US, "mid\u03C3dle"); - test("WORD1 \u03A3 WORD3", Locale.US, "word1 \u03C3 word3"); - test("WORD1 LAST\u03A3 WORD3", Locale.US, "word1 last\u03C2 word3"); - test("WORD1 MID\u03A3DLE WORD3", Locale.US, "word1 mid\u03C3dle word3"); - test("\u0399\u0395\u03a3\u03a5\u03a3 \u03a7\u03a1\u0399\u03a3\u03a4\u039f\u03a3", Locale.US, - "\u03b9\u03b5\u03c3\u03c5\u03c2 \u03c7\u03c1\u03b9\u03c3\u03c4\u03bf\u03c2"); // "IESUS XRISTOS" - - // Explicit dot above for I's and J's whenever there are more accents above (Lithanian) - test("I", lt, "i"); - test("I\u0300", lt, "i\u0307\u0300"); // "I" followed by COMBINING GRAVE ACCENT (cc==230) - test("I\u0316", lt, "i\u0316"); // "I" followed by COMBINING GRAVE ACCENT BELOW (cc!=230) - test("J", lt, "j"); - test("J\u0300", lt, "j\u0307\u0300"); // "J" followed by COMBINING GRAVE ACCENT (cc==230) - test("J\u0316", lt, "j\u0316"); // "J" followed by COMBINING GRAVE ACCENT BELOW (cc!=230) - test("\u012E", lt, "\u012F"); - test("\u012E\u0300", lt, "\u012F\u0307\u0300"); // "I (w/ OGONEK)" followed by COMBINING GRAVE ACCENT (cc==230) - test("\u012E\u0316", lt, "\u012F\u0316"); // "I (w/ OGONEK)" followed by COMBINING GRAVE ACCENT BELOW (cc!=230) - test("\u00CC", lt, "i\u0307\u0300"); - test("\u00CD", lt, "i\u0307\u0301"); - test("\u0128", lt, "i\u0307\u0303"); - test("I\u0300", Locale.US, "i\u0300"); // "I" followed by COMBINING GRAVE ACCENT (cc==230) - test("J\u0300", Locale.US, "j\u0300"); // "J" followed by COMBINING GRAVE ACCENT (cc==230) - test("\u012E\u0300", Locale.US, "\u012F\u0300"); // "I (w/ OGONEK)" followed by COMBINING GRAVE ACCENT (cc==230) - test("\u00CC", Locale.US, "\u00EC"); - test("\u00CD", Locale.US, "\u00ED"); - test("\u0128", Locale.US, "\u0129"); - - // I-dot tests - test("\u0130", turkish, "i"); - test("\u0130", az, "i"); - test("\u0130", lt, "\u0069\u0307"); - test("\u0130", Locale.US, "\u0069\u0307"); - test("\u0130", Locale.JAPAN, "\u0069\u0307"); - test("\u0130", Locale.ROOT, "\u0069\u0307"); - - // Remove dot_above in the sequence I + dot_above (Turkish and Azeri) - test("I\u0307", turkish, "i"); - test("I\u0307", az, "i"); - test("J\u0307", turkish, "j\u0307"); - test("J\u0307", az, "j\u0307"); - - // Unless an I is before a dot_above, it turns into a dotless i (Turkish and Azeri) - test("I", turkish, "\u0131"); - test("I", az, "\u0131"); - test("I", Locale.US, "i"); - test("IABC", turkish, "\u0131abc"); - test("IABC", az, "\u0131abc"); - test("IABC", Locale.US, "iabc"); - - // Supplementary character tests - // - // U+10400 ("\uD801\uDC00"): DESERET CAPITAL LETTER LONG I - // U+10401 ("\uD801\uDC01"): DESERET CAPITAL LETTER LONG E - // U+10402 ("\uD801\uDC02"): DESERET CAPITAL LETTER LONG A - // U+10428 ("\uD801\uDC28"): DESERET SMALL LETTER LONG I - // U+10429 ("\uD801\uDC29"): DESERET SMALL LETTER LONG E - // U+1042A ("\uD801\uDC2A"): DESERET SMALL LETTER LONG A - // - // valid code point tests: - test("\uD801\uDC00\uD801\uDC01\uD801\uDC02", Locale.US, "\uD801\uDC28\uD801\uDC29\uD801\uDC2A"); - test("\uD801\uDC00A\uD801\uDC01B\uD801\uDC02C", Locale.US, "\uD801\uDC28a\uD801\uDC29b\uD801\uDC2Ac"); - // invalid code point tests: - test("\uD800\uD800\uD801A\uDC00\uDC00\uDC00B", Locale.US, "\uD800\uD800\uD801a\uDC00\uDC00\uDC00b"); - - // lower/uppercase + surrogates - test("a\uD801\uDC1c", Locale.ROOT, "a\uD801\uDC44"); - test("A\uD801\uDC1c", Locale.ROOT, "a\uD801\uDC44"); - test("a\uD801\uDC00\uD801\uDC01\uD801\uDC02", Locale.US, "a\uD801\uDC28\uD801\uDC29\uD801\uDC2A"); - test("A\uD801\uDC00\uD801\uDC01\uD801\uDC02", Locale.US, "a\uD801\uDC28\uD801\uDC29\uD801\uDC2A"); + private static Stream testSimpleCases() { + return Stream.of( + // Greek Sigma final/non-final tests + Arguments.of("\u03A3", Locale.US, "\u03C3"), + Arguments.of("LAST\u03A3", Locale.US, "last\u03C2"), + Arguments.of("MID\u03A3DLE", Locale.US, "mid\u03C3dle"), + Arguments.of("WORD1 \u03A3 WORD3", Locale.US, "word1 \u03C3 word3"), + Arguments.of("WORD1 LAST\u03A3 WORD3", Locale.US, "word1 last\u03C2 word3"), + Arguments.of("WORD1 MID\u03A3DLE WORD3", Locale.US, "word1 mid\u03C3dle word3"), + Arguments.of("\u0399\u0395\u03a3\u03a5\u03a3 \u03a7\u03a1\u0399\u03a3\u03a4\u039f\u03a3", Locale.US, + "\u03b9\u03b5\u03c3\u03c5\u03c2 \u03c7\u03c1\u03b9\u03c3\u03c4\u03bf\u03c2"), // "IESUS XRISTOS" + + // Final_Cased (Unicode 4.0) -> Final_Sigma (Unicode 5.0) specific tests + // ':' is a `Case_Ignorable` and a word boundary between letters, which + // should not end the final cased letter search in 5.0 spec + Arguments.of("A:\u03A3", Locale.ROOT, "a:\u03C2"), + Arguments.of("A:\u03A3", Locale.US, "a:\u03C2"), + Arguments.of("A:\u03A3", GREEK, "a:\u03C2"), + Arguments.of("A\u03A3:B", Locale.ROOT, "a\u03C3:b"), + Arguments.of("A\u03A3:B", Locale.US, "a\u03C3:b"), + Arguments.of("A\u03A3:B", GREEK, "a\u03C3:b"), + Arguments.of("A1\u03A3", Locale.ROOT, "a1\u03C3"), + Arguments.of("A1\u03A3", Locale.US, "a1\u03C3"), + Arguments.of("A1\u03A3", GREEK, "a1\u03C3"), + Arguments.of("A\u03A31B", Locale.ROOT, "a\u03C21b"), + Arguments.of("A\u03A31B", Locale.US, "a\u03C21b"), + Arguments.of("A\u03A31B", GREEK, "a\u03C21b"), + // U+10780 is supplementary, Cased, and Case_Ignorable. + Arguments.of("\uD801\uDF80\u03A3", Locale.ROOT, "\uD801\uDF80\u03C2"), + Arguments.of("\uD801\uDF80\u03A3", Locale.US, "\uD801\uDF80\u03C2"), + Arguments.of("\uD801\uDF80\u03A3", GREEK, "\uD801\uDF80\u03C2"), + Arguments.of("\u03A3\uD801\uDF80", Locale.ROOT, "\u03C3\uD801\uDF80"), + Arguments.of("\u03A3\uD801\uDF80", Locale.US, "\u03C3\uD801\uDF80"), + Arguments.of("\u03A3\uD801\uDF80", GREEK, "\u03C3\uD801\uDF80"), + + // Explicit dot above for I's and J's whenever there are more accents above (Lithuanian) + Arguments.of("I", LITHUANIAN, "i"), + Arguments.of("I\u0300", LITHUANIAN, "i\u0307\u0300"), // "I" followed by COMBINING GRAVE ACCENT (cc==230) + Arguments.of("I\u0316", LITHUANIAN, "i\u0316"), // "I" followed by COMBINING GRAVE ACCENT BELOW (cc!=230) + Arguments.of("J", LITHUANIAN, "j"), + Arguments.of("J\u0300", LITHUANIAN, "j\u0307\u0300"), // "J" followed by COMBINING GRAVE ACCENT (cc==230) + Arguments.of("J\u0316", LITHUANIAN, "j\u0316"), // "J" followed by COMBINING GRAVE ACCENT BELOW (cc!=230) + Arguments.of("\u012E", LITHUANIAN, "\u012F"), + Arguments.of("\u012E\u0300", LITHUANIAN, "\u012F\u0307\u0300"), // "I (w/ OGONEK)" followed by COMBINING GRAVE ACCENT (cc==230) + Arguments.of("\u012E\u0316", LITHUANIAN, "\u012F\u0316"), // "I (w/ OGONEK)" followed by COMBINING GRAVE ACCENT BELOW (cc!=230) + Arguments.of("\u00CC", LITHUANIAN, "i\u0307\u0300"), + Arguments.of("\u00CD", LITHUANIAN, "i\u0307\u0301"), + Arguments.of("\u0128", LITHUANIAN, "i\u0307\u0303"), + Arguments.of("I\u0300", Locale.US, "i\u0300"), // "I" followed by COMBINING GRAVE ACCENT (cc==230) + Arguments.of("J\u0300", Locale.US, "j\u0300"), // "J" followed by COMBINING GRAVE ACCENT (cc==230) + Arguments.of("\u012E\u0300", Locale.US, "\u012F\u0300"), // "I (w/ OGONEK)" followed by COMBINING GRAVE ACCENT (cc==230) + Arguments.of("\u00CC", Locale.US, "\u00EC"), + Arguments.of("\u00CD", Locale.US, "\u00ED"), + Arguments.of("\u0128", Locale.US, "\u0129"), + + // I-dot tests + Arguments.of("\u0130", TURKISH, "i"), + Arguments.of("\u0130", AZERI, "i"), + Arguments.of("\u0130", LITHUANIAN, "\u0069\u0307"), + Arguments.of("\u0130", Locale.US, "\u0069\u0307"), + Arguments.of("\u0130", Locale.JAPAN, "\u0069\u0307"), + Arguments.of("\u0130", Locale.ROOT, "\u0069\u0307"), + + // Remove dot_above in the sequence I + dot_above (Turkish and Azeri) + Arguments.of("I\u0307", TURKISH, "i"), + Arguments.of("I\u0307", AZERI, "i"), + Arguments.of("J\u0307", TURKISH, "j\u0307"), + Arguments.of("J\u0307", AZERI, "j\u0307"), + + // Unless an I is before a dot_above, it turns into a dotless i (Turkish and Azeri) + Arguments.of("I", TURKISH, "\u0131"), + Arguments.of("I", AZERI, "\u0131"), + Arguments.of("I", Locale.US, "i"), + Arguments.of("IABC", TURKISH, "\u0131abc"), + Arguments.of("IABC", AZERI, "\u0131abc"), + Arguments.of("IABC", Locale.US, "iabc"), + + // Supplementary character tests + // + // U+10400 ("\uD801\uDC00"): DESERET CAPITAL LETTER LONG I + // U+10401 ("\uD801\uDC01"): DESERET CAPITAL LETTER LONG E + // U+10402 ("\uD801\uDC02"): DESERET CAPITAL LETTER LONG A + // U+10428 ("\uD801\uDC28"): DESERET SMALL LETTER LONG I + // U+10429 ("\uD801\uDC29"): DESERET SMALL LETTER LONG E + // U+1042A ("\uD801\uDC2A"): DESERET SMALL LETTER LONG A + // + // valid code point tests: + Arguments.of("\uD801\uDC00\uD801\uDC01\uD801\uDC02", Locale.US, "\uD801\uDC28\uD801\uDC29\uD801\uDC2A"), + Arguments.of("\uD801\uDC00A\uD801\uDC01B\uD801\uDC02C", Locale.US, "\uD801\uDC28a\uD801\uDC29b\uD801\uDC2Ac"), + // invalid code point tests: + Arguments.of("\uD800\uD800\uD801A\uDC00\uDC00\uDC00B", Locale.US, "\uD800\uD800\uD801a\uDC00\uDC00\uDC00b"), + + // lower/uppercase + surrogates + Arguments.of("a\uD801\uDC1c", Locale.ROOT, "a\uD801\uDC44"), + Arguments.of("A\uD801\uDC1c", Locale.ROOT, "a\uD801\uDC44"), + Arguments.of("a\uD801\uDC00\uD801\uDC01\uD801\uDC02", Locale.US, "a\uD801\uDC28\uD801\uDC29\uD801\uDC2A"), + Arguments.of("A\uD801\uDC00\uD801\uDC01\uD801\uDC02", Locale.US, "a\uD801\uDC28\uD801\uDC29\uD801\uDC2A")); + } + @Test + void testBMPAndSupp1() { // test bmp + supp1 StringBuilder src = new StringBuilder(0x20000); StringBuilder exp = new StringBuilder(0x20000); @@ -134,9 +175,13 @@ public static void main(String[] args) { } test(src.toString(), Locale.US, exp.toString()); + } + + @Test + void testLatin1() { // test latin1 - src = new StringBuilder(0x100); - exp = new StringBuilder(0x100); + var src = new StringBuilder(0x100); + var exp = new StringBuilder(0x100); for (int cp = 0; cp < 0x100; cp++) { int lowerCase = Character.toLowerCase(cp); if (lowerCase == -1) { //Character.ERROR @@ -146,10 +191,13 @@ public static void main(String[] args) { exp.appendCodePoint(lowerCase); } test(src.toString(), Locale.US, exp.toString()); + } + @Test + void testNonLatin1ToLatin1() { // test non-latin1 -> latin1 - src = new StringBuilder(0x100).append("abc"); - exp = new StringBuilder(0x100).append("abc"); + var src = new StringBuilder(0x100).append("abc"); + var exp = new StringBuilder(0x100).append("abc"); for (int cp = 0x100; cp < 0x10000; cp++) { int lowerCase = Character.toLowerCase(cp); if (lowerCase < 0x100 && cp != '\u0130') { @@ -160,8 +208,9 @@ public static void main(String[] args) { test(src.toString(), Locale.US, exp.toString()); } - static void test(String in, Locale locale, String expected) { - test0(in, locale,expected); + private static void test(String in, Locale locale, String expected) { + assertEquals(expected, in.toLowerCase(locale)); + for (String[] ss : new String[][] { new String[] {"abc", "abc"}, new String[] {"aBc", "abc"}, @@ -177,17 +226,10 @@ static void test(String in, Locale locale, String expected) { new String[] {"AB\uD801\uDC1C", "ab\uD801\uDC44"}, }) { - test0(ss[0] + " " + in, locale, ss[1] + " " + expected); - test0(in + " " + ss[0], locale, expected + " " + ss[1]); - } - } - - static void test0(String in, Locale locale, String expected) { - String result = in.toLowerCase(locale); - if (!result.equals(expected)) { - System.err.println("input: " + in + ", locale: " + locale + - ", expected: " + expected + ", actual: " + result); - throw new RuntimeException(); + assertEquals(ss[1] + " " + expected, + (ss[0] + " " + in).toLowerCase(locale)); + assertEquals(expected + " " + ss[1], + (in + " " + ss[0]).toLowerCase(locale)); } } } diff --git a/test/jdk/java/lang/String/ToUpperCase.java b/test/jdk/java/lang/String/ToUpperCase.java index fd825f09fceb..1db93b8ede4e 100644 --- a/test/jdk/java/lang/String/ToUpperCase.java +++ b/test/jdk/java/lang/String/ToUpperCase.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,81 +23,98 @@ /* @test - @bug 4219630 4304573 4533872 4900935 8042589 8054307 + @bug 4219630 4304573 4533872 4900935 8042589 8054307 8133167 @summary toUpperCase should upper-case German sharp s correctly even if it's the only character in the string. should also uppercase all of the 1:M char mappings correctly. Also it should handle Locale specific (lt, tr, and az) uppercasings and supplementary characters correctly. + @run junit ToUpperCase */ import java.util.Locale; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import static org.junit.jupiter.api.Assertions.assertEquals; public class ToUpperCase { + private static final Locale TURKISH = Locale.of("tr"); + private static final Locale LITHUANIAN = Locale.of("lt"); + private static final Locale AZERI = Locale.of("az"); - public static void main(String[] args) { - Locale turkish = Locale.of("tr", "TR"); - Locale lt = Locale.of("lt"); // Lithanian - Locale az = Locale.of("az"); // Azeri + @ParameterizedTest + @MethodSource + void testSimpleCases(String source, Locale locale, String expected) { + test(source, locale, expected); + } - test("\u00DF", turkish, "SS"); - test("a\u00DF", turkish, "ASS"); - test("i", turkish, "\u0130"); - test("i", az, "\u0130"); - test("\u0131", turkish, "I"); - test("\u00DF", Locale.GERMANY, "SS"); - test("a\u00DF", Locale.GERMANY, "ASS"); - test("i", Locale.GERMANY, "I"); + private static Stream testSimpleCases() { + return Stream.of( + Arguments.of("\u00DF", TURKISH, "SS"), + Arguments.of("a\u00DF", TURKISH, "ASS"), + Arguments.of("i", TURKISH, "\u0130"), + Arguments.of("i", AZERI, "\u0130"), + Arguments.of("\u0131", TURKISH, "I"), + Arguments.of("\u00DF", Locale.GERMANY, "SS"), + Arguments.of("a\u00DF", Locale.GERMANY, "ASS"), + Arguments.of("i", Locale.GERMANY, "I"), - // test some of the 1:M uppercase mappings - test("abc\u00DF", Locale.US, "ABC\u0053\u0053"); - test("\u0149abc", Locale.US, "\u02BC\u004EABC"); - test("\u0149abc", turkish, "\u02BC\u004EABC"); - test("\u1F52", Locale.US, "\u03A5\u0313\u0300"); - test("\u0149\u1F52", Locale.US, "\u02BC\u004E\u03A5\u0313\u0300"); - test("\u1F54ZZZ", Locale.US, "\u03A5\u0313\u0301ZZZ"); - test("\u1F54ZZZ", turkish, "\u03A5\u0313\u0301ZZZ"); - test("a\u00DF\u1F56", Locale.US, "ASS\u03A5\u0313\u0342"); - test("\u1FAD", turkish, "\u1F6D\u0399"); - test("i\u1FC7", turkish, "\u0130\u0397\u0342\u0399"); - test("i\u1FC7", az, "\u0130\u0397\u0342\u0399"); - test("i\u1FC7", Locale.US, "I\u0397\u0342\u0399"); - test("\uFB04", Locale.US, "\u0046\u0046\u004C"); - test("\uFB17AbCdEfi", turkish, "\u0544\u053DABCDEF\u0130"); - test("\uFB17AbCdEfi", az, "\u0544\u053DABCDEF\u0130"); + // test some of the 1:M uppercase mappings + Arguments.of("abc\u00DF", Locale.US, "ABC\u0053\u0053"), + Arguments.of("\u0149abc", Locale.US, "\u02BC\u004EABC"), + Arguments.of("\u0149abc", TURKISH, "\u02BC\u004EABC"), + Arguments.of("\u1F52", Locale.US, "\u03A5\u0313\u0300"), + Arguments.of("\u0149\u1F52", Locale.US, "\u02BC\u004E\u03A5\u0313\u0300"), + Arguments.of("\u1F54ZZZ", Locale.US, "\u03A5\u0313\u0301ZZZ"), + Arguments.of("\u1F54ZZZ", TURKISH, "\u03A5\u0313\u0301ZZZ"), + Arguments.of("a\u00DF\u1F56", Locale.US, "ASS\u03A5\u0313\u0342"), + Arguments.of("\u1FAD", TURKISH, "\u1F6D\u0399"), + Arguments.of("i\u1FC7", TURKISH, "\u0130\u0397\u0342\u0399"), + Arguments.of("i\u1FC7", AZERI, "\u0130\u0397\u0342\u0399"), + Arguments.of("i\u1FC7", Locale.US, "I\u0397\u0342\u0399"), + Arguments.of("\uFB04", Locale.US, "\u0046\u0046\u004C"), + Arguments.of("\uFB17AbCdEfi", TURKISH, "\u0544\u053DABCDEF\u0130"), + Arguments.of("\uFB17AbCdEfi", AZERI, "\u0544\u053DABCDEF\u0130"), - // Remove DOT ABOVE after "i" in Lithuanian - test("i\u0307", lt, "I"); - test("\u0307", lt, "\u0307"); - test("\u0307i", lt, "\u0307I"); - test("j\u0307", lt, "J"); - test("abci\u0307def", lt, "ABCIDEF"); - test("a\u0307", lt, "A\u0307"); - test("abc\u0307def", lt, "ABC\u0307DEF"); - test("i\u0307", Locale.US, "I\u0307"); - test("i\u0307", turkish, "\u0130\u0307"); + // Remove DOT ABOVE after "i" in Lithuanian + Arguments.of("i\u0307", LITHUANIAN, "I"), + Arguments.of("\u0307", LITHUANIAN, "\u0307"), + Arguments.of("\u0307i", LITHUANIAN, "\u0307I"), + Arguments.of("j\u0307", LITHUANIAN, "J"), + Arguments.of("abci\u0307def", LITHUANIAN, "ABCIDEF"), + Arguments.of("a\u0307", LITHUANIAN, "A\u0307"), + Arguments.of("abc\u0307def", LITHUANIAN, "ABC\u0307DEF"), + Arguments.of("i\u0307", Locale.US, "I\u0307"), + Arguments.of("i\u0307", TURKISH, "\u0130\u0307"), - // Supplementary character tests - // - // U+10400 ("\uD801\uDC00"): DESERET CAPITAL LETTER LONG I - // U+10401 ("\uD801\uDC01"): DESERET CAPITAL LETTER LONG E - // U+10402 ("\uD801\uDC02"): DESERET CAPITAL LETTER LONG A - // U+10428 ("\uD801\uDC28"): DESERET SMALL LETTER LONG I - // U+10429 ("\uD801\uDC29"): DESERET SMALL LETTER LONG E - // U+1042A ("\uD801\uDC2A"): DESERET SMALL LETTER LONG A - // - // valid code point tests: - test("\uD801\uDC28\uD801\uDC29\uD801\uDC2A", Locale.US, "\uD801\uDC00\uD801\uDC01\uD801\uDC02"); - test("\uD801\uDC28a\uD801\uDC29b\uD801\uDC2Ac", Locale.US, "\uD801\uDC00A\uD801\uDC01B\uD801\uDC02C"); - // invalid code point tests: - test("\uD800\uD800\uD801a\uDC00\uDC00\uDC00b", Locale.US, "\uD800\uD800\uD801A\uDC00\uDC00\uDC00B"); + // Supplementary character tests + // + // U+10400 ("\uD801\uDC00"): DESERET CAPITAL LETTER LONG I + // U+10401 ("\uD801\uDC01"): DESERET CAPITAL LETTER LONG E + // U+10402 ("\uD801\uDC02"): DESERET CAPITAL LETTER LONG A + // U+10428 ("\uD801\uDC28"): DESERET SMALL LETTER LONG I + // U+10429 ("\uD801\uDC29"): DESERET SMALL LETTER LONG E + // U+1042A ("\uD801\uDC2A"): DESERET SMALL LETTER LONG A + // + // valid code point tests: + Arguments.of("\uD801\uDC28\uD801\uDC29\uD801\uDC2A", Locale.US, "\uD801\uDC00\uD801\uDC01\uD801\uDC02"), + Arguments.of("\uD801\uDC28a\uD801\uDC29b\uD801\uDC2Ac", Locale.US, "\uD801\uDC00A\uD801\uDC01B\uD801\uDC02C"), + // invalid code point tests: + Arguments.of("\uD800\uD800\uD801a\uDC00\uDC00\uDC00b", Locale.US, "\uD800\uD800\uD801A\uDC00\uDC00\uDC00B"), - // lower/uppercase + surrogates - test("a\uD801\uDC44", Locale.ROOT, "A\uD801\uDC1c"); - test("A\uD801\uDC44", Locale.ROOT, "A\uD801\uDC1c"); - test("a\uD801\uDC28\uD801\uDC29\uD801\uDC2A", Locale.US, "A\uD801\uDC00\uD801\uDC01\uD801\uDC02"); - test("A\uD801\uDC28a\uD801\uDC29b\uD801\uDC2Ac", Locale.US, "A\uD801\uDC00A\uD801\uDC01B\uD801\uDC02C"); + // lower/uppercase + surrogates + Arguments.of("a\uD801\uDC44", Locale.ROOT, "A\uD801\uDC1c"), + Arguments.of("A\uD801\uDC44", Locale.ROOT, "A\uD801\uDC1c"), + Arguments.of("a\uD801\uDC28\uD801\uDC29\uD801\uDC2A", Locale.US, "A\uD801\uDC00\uD801\uDC01\uD801\uDC02"), + Arguments.of("A\uD801\uDC28a\uD801\uDC29b\uD801\uDC2Ac", Locale.US, "A\uD801\uDC00A\uD801\uDC01B\uD801\uDC02C")); + } + @Test + void testLatin1() { // test latin1 only case StringBuilder src = new StringBuilder(0x100); StringBuilder exp = new StringBuilder(0x100); @@ -114,10 +131,13 @@ public static void main(String[] args) { } } test(src.toString(), Locale.US, exp.toString()); + } + @Test + void testNonLatin1ToLatin1() { // test non-latin1 -> latin1 - src = new StringBuilder(0x100).append("ABC"); - exp = new StringBuilder(0x100).append("ABC"); + var src = new StringBuilder(0x100).append("ABC"); + var exp = new StringBuilder(0x100).append("ABC"); for (int cp = 0x100; cp < 0x10000; cp++) { int upperCase = Character.toUpperCase(cp); if (upperCase < 0x100) { @@ -129,8 +149,9 @@ public static void main(String[] args) { } - static void test(String in, Locale locale, String expected) { - test0(in, locale,expected); + private static void test(String in, Locale locale, String expected) { + assertEquals(expected, in.toUpperCase(locale)); + // trigger different code paths for (String[] ss : new String[][] { new String[] {"abc", "ABC"}, @@ -146,17 +167,10 @@ static void test(String in, Locale locale, String expected) { new String[] {"Ab\uD801\uDC44", "AB\uD801\uDC1C"}, new String[] {"ab\uD801\uDC44", "AB\uD801\uDC1C"}, }) { - test0(ss[0] + " " + in, locale, ss[1] + " " + expected); - test0(in + " " + ss[0], locale, expected + " " + ss[1]); - } - } - - static void test0(String in, Locale locale, String expected) { - String result = in.toUpperCase(locale); - if (!result.equals(expected)) { - System.err.println("input: " + in + ", locale: " + locale + - ", expected: " + expected + ", actual: " + result); - throw new RuntimeException(); + assertEquals(ss[1] + " " + expected, + (ss[0] + " " + in).toUpperCase(locale)); + assertEquals(expected + " " + ss[1], + (in + " " + ss[0]).toUpperCase(locale)); } } } diff --git a/test/jdk/java/lang/reflect/Proxy/Basic1.java b/test/jdk/java/lang/reflect/Proxy/Basic1.java index b2440ccd9f95..dcbeca576d64 100644 --- a/test/jdk/java/lang/reflect/Proxy/Basic1.java +++ b/test/jdk/java/lang/reflect/Proxy/Basic1.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,12 +22,13 @@ */ /* @test - * @bug 4227192 4487672 - * @summary This is a basic functional test of the dynamic proxy API (part 1). - * @author Peter Jones + * @bug 4227192 4487672 8388553 + * @summary This is a basic functional test of the java.lang.reflect.Proxy API * - * @build Basic1 * @run main Basic1 + * @comment Verify that the APIs behave as expected even when the JDK internal + * debug system property "jdk.proxy.ProxyGenerator.saveGeneratedFiles" is set + * @run main/othervm -Djdk.proxy.ProxyGenerator.saveGeneratedFiles=true Basic1 */ import java.lang.reflect.*; @@ -38,9 +39,6 @@ public class Basic1 { public static void main(String[] args) { - System.err.println( - "\nBasic functional test of dynamic proxy API, part 1\n"); - try { Class[] interfaces = new Class[] { Runnable.class, Observer.class }; diff --git a/test/jdk/java/net/MulticastSocket/NoLoopbackPackets.java b/test/jdk/java/net/MulticastSocket/NoLoopbackPackets.java index 36b6f12413d5..73fd505845a3 100644 --- a/test/jdk/java/net/MulticastSocket/NoLoopbackPackets.java +++ b/test/jdk/java/net/MulticastSocket/NoLoopbackPackets.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -56,7 +56,7 @@ public static void main(String[] args) throws Exception { } NetworkConfiguration nc = NetworkConfiguration.probe(); - if (IPSupport.hasIPv6() && nc.hasTestableIPv6Address()) { + if (IPSupport.hasIPv6() && nc.ip6MulticastInterfaces().findAny().isPresent()) { groups.add(new InetSocketAddress(InetAddress.getByName("::ffff:224.1.1.2"), port)); groups.add(new InetSocketAddress(InetAddress.getByName("ff02::1:1"), port)); } diff --git a/test/jdk/java/net/MulticastSocket/Test.java b/test/jdk/java/net/MulticastSocket/Test.java index 01dd82ece757..633a2a4e9a80 100644 --- a/test/jdk/java/net/MulticastSocket/Test.java +++ b/test/jdk/java/net/MulticastSocket/Test.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -141,7 +141,7 @@ void allTests() throws IOException { doTest("224.80.80.80"); // If IPv6 is enabled perform multicast tests with various scopes - if (nc.hasTestableIPv6Address()) { + if (nc.ip6MulticastInterfaces().findAny().isPresent()) { doTest("ff01::a"); } diff --git a/test/jdk/java/sql/test/sql/DateTests.java b/test/jdk/java/sql/test/sql/DateTests.java index 0c66dd15c98b..0bbc8e7d11d6 100644 --- a/test/jdk/java/sql/test/sql/DateTests.java +++ b/test/jdk/java/sql/test/sql/DateTests.java @@ -327,6 +327,32 @@ public void test26() throws Exception { assertThrows(IllegalArgumentException.class, () -> d.setSeconds(0)); } + /* + * Validate that Date.valueOf and Date.toLocalDate yield the expected results for dates with BC years. + * Ensures that the fix for 8272194 has not regressed. + */ + @ParameterizedTest + @MethodSource("bcLocalDates") + public void test27(LocalDate bcLocalDate) { + Date bcDate = Date.valueOf(bcLocalDate); + // Previously, getYear() of a LocalDate created from a Date always returned a positive year, even if it was BC. + assertTrue(bcDate.toLocalDate().getYear() <= 0, "Expected a BC year."); + assertEquals(bcLocalDate, bcDate.toLocalDate(), "The LocalDate created from the Date does not match the original BC LocalDate."); + assertEquals(bcDate, Date.valueOf(bcDate.toLocalDate()), "The BC Date did not yield the expected result on a round trip Date / LocalDate conversion."); + } + + /* + * Ensure that LocalDate conversion of AD dates close to the BC check threshold + * behave as expected. + */ + @ParameterizedTest + @MethodSource("datesAroundBcCheckThreshold") + public void test28(LocalDate localDate) { + Date date = Date.valueOf(localDate); + assertEquals(localDate, date.toLocalDate(), "The LocalDate created from the Date does not match the original LocalDate."); + assertEquals(date, Date.valueOf(date.toLocalDate()), "The Date did not yield the expected result on a round trip Date / LocalDate conversion."); + } + /* * DataProvider used to provide Date which are not valid and are used * to validate that an IllegalArgumentException will be thrown from the @@ -372,4 +398,33 @@ private Stream validDateValues() { Arguments.of("2009-1-1", "2009-01-01") ); } + + /* + * DataProvider used to provide BC LocalDate values, used to validate that + * Date.valueOf and Date.toLocalDate yield the expected results for dates + * with BC years. + */ + private Stream bcLocalDates() { + return Stream.of( + LocalDate.of(0, 1, 1), + LocalDate.of(-4, 9, 8), + LocalDate.of(-1000, 3, 22) + ); + } + + /* + * DataProvider used to provide LocalDate values immediately surrounding + * the BC check threshold used internally by Date.toLocalDate(). + */ + private Stream datesAroundBcCheckThreshold() { + return Stream.of( + LocalDate.of(1, 12, 1), + LocalDate.of(1, 12, 30), + LocalDate.of(1, 12, 31), + LocalDate.of(2, 1, 1), + LocalDate.of(2, 1, 2), + LocalDate.of(2, 1, 3), + LocalDate.of(2, 2, 1) + ); + } } diff --git a/test/jdk/java/sql/test/sql/TimestampTests.java b/test/jdk/java/sql/test/sql/TimestampTests.java index 33a58c8d857e..130072618a2f 100644 --- a/test/jdk/java/sql/test/sql/TimestampTests.java +++ b/test/jdk/java/sql/test/sql/TimestampTests.java @@ -713,6 +713,32 @@ public void test55() { assertNotEquals(ts2.hashCode(), ts1.hashCode()); } + /* + * Validate that Timestamp.valueOf and Timestamp.toLocalDateTime yield the expected results for dates with BC years. + * Ensures that the fix for 8272194 has not regressed. + */ + @ParameterizedTest + @MethodSource("bcLocalDateTimes") + public void test56(LocalDateTime bcLocalDateTime) throws Exception { + Timestamp bcTimestamp = Timestamp.valueOf(bcLocalDateTime); + // Previously, getYear() of a LocalDateTime created from a Timestamp always returned a positive year, even if it was BC. + assertTrue(bcTimestamp.toLocalDateTime().getYear() <= 0, "Expected a BC year."); + assertEquals(bcLocalDateTime, bcTimestamp.toLocalDateTime(), "The LocalDateTime created from the Timestamp does not match the original BC LocalDateTime."); + assertEquals(bcTimestamp, Timestamp.valueOf(bcTimestamp.toLocalDateTime()), "The BC Timestamp did not yield the expected result on a round trip Timestamp / LocalDateTime conversion."); + } + + /* + * Ensure that LocalDateTime conversion of AD dates close to the BC check threshold + * behave as expected. + */ + @ParameterizedTest + @MethodSource("dateTimesAroundBcCheckThreshold") + public void test57(LocalDateTime localDateTime) { + Timestamp timestamp = Timestamp.valueOf(localDateTime); + assertEquals(localDateTime, timestamp.toLocalDateTime(), "The LocalDateTime created from the Timestamp does not match the original LocalDateTime."); + assertEquals(timestamp, Timestamp.valueOf(timestamp.toLocalDateTime()), "The Timestamp did not yield the expected result on a round trip Timestamp / LocalDateTime conversion."); + } + /* * DataProvider used to provide Timestamps which are not valid and are used * to validate that an IllegalArgumentException will be thrown from the @@ -841,4 +867,34 @@ private Stream validateNanos() { Arguments.of("1996-12-10 12:26:19.01230", 12300000) ); } + + /* + * DataProvider used to provide BC LocalDateTime values, used to validate + * that Timestamp.valueOf and Timestamp.toLocalDateTime yield the expected + * results for dates with BC years. + */ + private Stream bcLocalDateTimes() { + return Stream.of( + LocalDateTime.of(0, 1, 1, 0, 0, 0, 0), + LocalDateTime.of(-4, 9, 8, 23, 11, 59, 999_999_999), + LocalDateTime.of(-1000, 3, 22, 8, 30, 20, 0) + ); + } + + /* + * DataProvider used to provide LocalDateTime values immediately + * surrounding the BC check threshold used internally by + * Timestamp.toLocalDateTime(). + */ + private Stream dateTimesAroundBcCheckThreshold() { + return Stream.of( + LocalDateTime.of(1, 12, 1, 6, 15, 45, 500_000_000), + LocalDateTime.of(1, 12, 30, 12, 30, 0, 250_000_000), + LocalDateTime.of(1, 12, 31, 23, 59, 59, 999_999_999), + LocalDateTime.of(2, 1, 1, 0, 0, 0, 0), + LocalDateTime.of(2, 1, 2, 8, 20, 10, 750_750_750), + LocalDateTime.of(2, 1, 3, 16, 40, 20, 100_000_000), + LocalDateTime.of(2, 2, 1, 18, 45, 30, 123_000_000) + ); + } } diff --git a/test/jdk/java/util/Locale/LocaleMatchingTest.java b/test/jdk/java/util/Locale/LocaleMatchingTest.java index c5d8a00d4588..806229ca8eb9 100644 --- a/test/jdk/java/util/Locale/LocaleMatchingTest.java +++ b/test/jdk/java/util/Locale/LocaleMatchingTest.java @@ -23,7 +23,7 @@ /* * @test - * @bug 7069824 8042360 8032842 8175539 8210443 8242010 8276302 8381644 + * @bug 7069824 8042360 8032842 8175539 8210443 8242010 8276302 8381644 8387261 * @summary Verify implementation for Locale matching. * @run junit/othervm LocaleMatchingTest */ @@ -93,6 +93,7 @@ static Object[][] LRConstructorIAEData() { {"1996-de-Latn", MAX_WEIGHT}, // Testcase for 8042360 {"en-Latn-1234567890", MAX_WEIGHT}, + {"en", Double.NaN}, }; } @@ -146,6 +147,7 @@ static Object[][] LRParseIAEData() { // Ranges {""}, {"ja;q=3"}, + {"en;q=NaN"} }; } diff --git a/test/jdk/javax/accessibility/8385367/TestAddingOldAccessibleContextAsDocumentListener.java b/test/jdk/javax/accessibility/8385367/TestAddingOldAccessibleContextAsDocumentListener.java new file mode 100644 index 000000000000..8f0ad9b86fb8 --- /dev/null +++ b/test/jdk/javax/accessibility/8385367/TestAddingOldAccessibleContextAsDocumentListener.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +import javax.accessibility.AccessibleContext; +import javax.swing.JTextPane; +import javax.swing.event.DocumentListener; +import javax.swing.text.DefaultStyledDocument; +import javax.swing.text.html.HTMLDocument; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/* + * @test + * @bug 8385367 + * @summary make sure setDocument() doesn't reattach obsolete DocumentListener + * @run main TestAddingOldAccessibleContextAsDocumentListener + */ + +public class TestAddingOldAccessibleContextAsDocumentListener { + public static void main(String[] args) throws Exception { + JTextPane textPane = new JTextPane(); + + // This installs an HTMLDocument + textPane.setContentType("text/html"); + HTMLDocument htmlDoc = + (HTMLDocument) textPane.getDocument(); + + // this instantiates the AccessibleContext + AccessibleContext htmlAXContext = textPane.getAccessibleContext(); + + // this replaces the Document: + textPane.setContentType("text/plain"); + DefaultStyledDocument plainDoc = + (DefaultStyledDocument) textPane.getDocument(); + + if (htmlAXContext == textPane.getAccessibleContext()) { + throw new RuntimeException( + "this test assumes the AccessibleContext changed when " + + "the document changed"); + } + + List docListeners = Arrays.asList( + plainDoc.getDocumentListeners()); + assertEquals(0, Collections.frequency(docListeners, htmlAXContext)); + + AccessibleContext plainAXContext = textPane.getAccessibleContext(); + assertEquals(1, Collections.frequency(docListeners, plainAXContext)); + + // this is optional (we don't care about the HTMLDocument anymore). + // setDocument() automatically removes the old AX context listener + List htmlDocListeners = Arrays.asList( + htmlDoc.getDocumentListeners()); + assertEquals(0, Collections.frequency(htmlDocListeners, + htmlAXContext)); + + System.out.println("test passed"); + } + + private static void assertEquals(int expectedValue, int observedValue) { + if (expectedValue != observedValue) { + throw new RuntimeException("Expected: " + expectedValue + + ", observed: " + observedValue); + } + } +} \ No newline at end of file diff --git a/test/jdk/javax/net/ssl/ciphersuites/BulkCipherDisabledAlgorithms.java b/test/jdk/javax/net/ssl/ciphersuites/BulkCipherDisabledAlgorithms.java index 11f3efb1518f..8cf14a38af1a 100644 --- a/test/jdk/javax/net/ssl/ciphersuites/BulkCipherDisabledAlgorithms.java +++ b/test/jdk/javax/net/ssl/ciphersuites/BulkCipherDisabledAlgorithms.java @@ -24,56 +24,56 @@ /* * @test * @bug 8387124 - * @summary Test TLS cipher suite disabling via jdk.tls.disabledAlgorithms, - * including matching on bulk cipher components, covering both - * visibility and handshake behavior. + * @summary Verify that disabling bulk cipher algorithms in + * jdk.tls.disabledAlgorithms disables associated TLS cipher suites. * @library /test/lib * /javax/net/ssl/TLSCommon * /javax/net/ssl/templates - * @run main/othervm BulkCipherDisabledAlgorithms visibility - * @run main/othervm BulkCipherDisabledAlgorithms handshake */ +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; -import javax.net.ssl.*; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLServerSocket; +import javax.net.ssl.SSLSocket; import jdk.test.lib.process.Proc; -import java.security.NoSuchAlgorithmException; -import java.security.Security; - +/* + * Each test case is executed in a separate JVM because + * jdk.tls.disabledAlgorithms is evaluated during JSSE initialization and + * cannot be reliably reconfigured within the same VM. + */ public class BulkCipherDisabledAlgorithms { public static void main(String[] args) throws Exception { if (args.length == 0) { - throw new RuntimeException("Missing mode argument"); - } - - String mode = args[0]; - boolean isVisibilityTest = "visibility".equals(mode); - boolean isHandshakeTest = "handshake".equals(mode); + // Sanity check: all enabled cipher suites should work before + // applying any jdk.tls.disabledAlgorithms restrictions. + testAllCipherSuitesEnabled(); - if (args.length == 1) { - List tests = buildTests(isVisibilityTest); + // Verify that disabling a bulk cipher algorithm disables cipher + // suites that use that algorithm. + Map> cipherSuitesByBulkCipher = groupCipherSuitesByBulkCipher(); - for (String[] test : tests) { - String suite = test[0]; - String disabled = test[1]; - String expected = test[2]; - - System.out.println("================================================="); - System.out.println("Testing: " + mode + - ", suite=" + suite + - ", disabled=" + disabled + - ", expected=" + expected); + for (Map.Entry> entry : cipherSuitesByBulkCipher.entrySet()) { + String disabledBulkCipher = entry.getKey(); + List disabledCipherSuites = entry.getValue(); + // Verify that all cipher suites associated with the disabled + // bulk cipher become unavailable. Proc p = Proc.create( BulkCipherDisabledAlgorithms.class.getName()) - .args(mode, suite, expected) - .secprop("jdk.tls.disabledAlgorithms", disabled) + .args(disabledBulkCipher, + String.join(",", disabledCipherSuites)) + .secprop("jdk.tls.disabledAlgorithms", disabledBulkCipher) .inheritIO(); p.start().waitFor(0); @@ -83,71 +83,54 @@ public static void main(String[] args) throws Exception { return; } - String suite = args[1]; - String expected = args[2]; - boolean expectedDisabled = "disabled".equals(expected); + String disabledBulkCipher = args[0]; + List disabledCipherSuites = Arrays.asList(args[1].split(",")); + + for (String disabledCipherSuite : disabledCipherSuites) { + System.out.println("================================================="); + System.out.println("Testing: suite=" + disabledCipherSuite + + ", disabled bulk cipher=" + disabledBulkCipher); - if (isVisibilityTest) { - testCipherSuiteVisibility(suite, expectedDisabled); + testCipherSuiteDisabled(disabledCipherSuite); + testHandshake(disabledCipherSuite, true); } + } + + private static void testAllCipherSuitesEnabled() throws Exception { + CipherSuite[] suites = getCipherSuites(); - if (isHandshakeTest) { - testHandshake(suite, expectedDisabled); + for (CipherSuite suite : suites) { + testHandshake(suite.name(), false); } } - // Returns cipher suites for testing. - // - true: use all supported suites (independent of disabledAlgorithms) - // - false: use default enabled suites (candidates for handshake) - private static CipherSuite[] getCipherSuites(boolean useSupportedSuites) - throws NoSuchAlgorithmException { + private static CipherSuite[] getCipherSuites() throws NoSuchAlgorithmException { SSLEngine engine = SSLContext.getDefault().createSSLEngine(); - String[] suites = useSupportedSuites - ? engine.getSupportedCipherSuites() - : engine.getEnabledCipherSuites(); - + String[] suites = engine.getEnabledCipherSuites(); return Arrays.stream(suites) .map(CipherSuite::cipherSuite) .filter(cs -> cs != CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV) .toArray(CipherSuite[]::new); } - private static List buildTests(boolean useSupportedSuites) - throws NoSuchAlgorithmException { - if (useSupportedSuites) { - // disabledAlgorithms limits supported suites; clear to list all - Security.setProperty("jdk.tls.disabledAlgorithms", ""); - } - - List tests = new ArrayList<>(); - CipherSuite[] suites = getCipherSuites(useSupportedSuites); + private static Map> groupCipherSuitesByBulkCipher() throws NoSuchAlgorithmException { + Map> cipherSuitesByBulkCipher = new LinkedHashMap<>(); + CipherSuite[] suites = getCipherSuites(); for (CipherSuite suite : suites) { String suiteName = suite.name(); - String bulk = extractBulkCipher(suiteName); - - tests.add(new String[] { suiteName, suiteName, "disabled" }); - tests.add(new String[] { suiteName, bulk, "disabled" }); - - for (CipherSuite other : suites) { - // Negative test case: disable a different bulk cipher than the one - // used by the current suite. This ensures that the suite remains - // enabled and a successful TLS handshake can still be negotiated. - if (other == suite) { - continue; - } - - String otherBulk = extractBulkCipher(other.name()); - - if (!bulk.equals(otherBulk) - && !suiteName.contains(otherBulk)) { - tests.add(new String[] { suiteName, otherBulk, "enabled" }); - break; - } + String bulkCipher = extractBulkCipher(suiteName); + List suitesForBulk = cipherSuitesByBulkCipher.get(bulkCipher); + + if (suitesForBulk == null) { + suitesForBulk = new ArrayList<>(); + cipherSuitesByBulkCipher.put(bulkCipher, suitesForBulk); } + + suitesForBulk.add(suiteName); } - return tests; + return cipherSuitesByBulkCipher; } /** @@ -168,51 +151,47 @@ private static String extractBulkCipher(String suite) { } } - private static void testCipherSuiteVisibility(String suite, boolean expectedDisabled) - throws NoSuchAlgorithmException { - boolean visible = Arrays.asList(getCipherSuites(true)) + private static void testCipherSuiteDisabled(String suite) throws NoSuchAlgorithmException { + boolean visible = Arrays.asList(getCipherSuites()) .contains(CipherSuite.cipherSuite(suite)); - if (!expectedDisabled && !visible) { - throw new RuntimeException( - "Cipher suite '" + suite + "' not visible but expected to be enabled"); - } else if (expectedDisabled && visible) { + if (visible) { throw new RuntimeException( "Cipher suite '" + suite + "' visible but expected to be disabled"); } } - private static void testHandshake(String suite, boolean expectedDisabled) throws Exception { + private static void testHandshake(String cipherSuite, boolean expectedDisabled) throws Exception { try { - new TLSHandshakeTest(suite).run(); + new TLSHandshakeTest(cipherSuite).run(); if (expectedDisabled) { throw new RuntimeException( - "Handshake succeeded but should fail: " + suite); + "Handshake succeeded but should fail: " + cipherSuite); } } catch (SSLHandshakeException e) { if (!expectedDisabled) { throw new RuntimeException( - "Handshake failed unexpectedly: " + suite, e); + "Handshake failed unexpectedly: " + cipherSuite, e); } } } private static class TLSHandshakeTest extends SSLSocketTemplate { - private final String suite; + private final String cipherSuite; - TLSHandshakeTest(String suite) { - this.suite = suite; + TLSHandshakeTest(String cipherSuite) { + this.cipherSuite = cipherSuite; } @Override protected void configureClientSocket(SSLSocket socket) { - socket.setEnabledCipherSuites(new String[] { suite }); + socket.setEnabledCipherSuites(new String[] { cipherSuite }); } @Override protected void configureServerSocket(SSLServerSocket socket) { - socket.setEnabledCipherSuites(new String[] { suite }); + socket.setEnabledCipherSuites(new String[] { cipherSuite }); } } } diff --git a/test/jdk/jdk/jfr/startupargs/TestRedact.java b/test/jdk/jdk/jfr/startupargs/TestRedact.java index 2d96408a3f5c..f3f3a9e5fa6b 100644 --- a/test/jdk/jdk/jfr/startupargs/TestRedact.java +++ b/test/jdk/jdk/jfr/startupargs/TestRedact.java @@ -42,6 +42,7 @@ import jdk.jfr.consumer.RecordingFile; import jdk.test.lib.Asserts; import jdk.test.lib.jfr.CommonHelper; +import jdk.test.lib.Platform; import jdk.test.lib.process.OutputAnalyzer; import jdk.test.lib.process.ProcessTools; @@ -169,6 +170,7 @@ public static void main(String... args) throws Exception { testRedactKey(); testRedactArgument(); testRedactMultiple(); + testOptionVariable(); testWildcards(); testDefaults(); testRedactFile(); @@ -315,15 +317,6 @@ private static void testRedactFile() throws Exception { private static void testEmpty() throws Exception { var environment = Map.of("API_TOKEN", "Zebra1"); var properties = Map.of("API_KEY", "Zebra2"); - Execution e1 = run(environment, properties, - "-XX:FlightRecorderOptions:redact-key=,redact-argument=", "Zebra3" - ); - e1.output().shouldContain("Default redaction filters are replaced."); - e1.output().shouldContain("redact-key=none to disable filters without a warning"); - e1.output().shouldContain("redact-argument=none to disable filters without a warning"); - e1.assertUnredacted("Zebra1"); - e1.assertUnredacted("Zebra2"); - e1.assertUnredacted("Zebra3"); Execution e2 = run(environment, properties, "-XX:FlightRecorderOptions:redact-argument=none,redact-key=none", "Zebra3" @@ -377,6 +370,12 @@ private static void testRedactArgument() throws Exception { e.assertRedactedArgument("N4711"); e.assertRedactedArgument("Smith:abc123"); e.assertUnredacted("Banana"); + + String option = Platform.isWindows() ? + "-XX:FlightRecorderOptions:redact-argument='Foo,bar'" : + "-XX:FlightRecorderOptions:redact-argument=\"Foo,bar\""; + Execution e2 = run(option,"Foo,bar"); + e2.assertRedactedArgument("Foo,bar"); } private static void testRedactMultiple() throws Exception { @@ -390,6 +389,69 @@ private static void testRedactMultiple() throws Exception { e.assertRedactedArgument("Quz"); } + private static void testOptionVariable() throws Exception { + // Simulate shell expansion with the three options: + // SYSTEM_PROPS, JVM_OPTIONS and PROGRAM_OPTIONS + String systemProperty = "-Dsecret=apple"; + String jvmOption = "-XX:FlightRecorderOptions:stackdepth=32,redact-argument=+Aracuan"; + String programOption = "Aracuan"; + Execution e1 = run( + Map.of("SYSTEM_PROPS", systemProperty, + "JVM_OPTIONS", jvmOption, + "PROGRAM_OPTIONS", programOption), + Map.of("secret","apple"), + List.of(systemProperty, jvmOption), + programOption + ); + e1.assertRedactedKey("SYSTEM_PROPS"); + String redactedJVMOption = e1.environment.get("JVM_OPTIONS"); + if (!redactedJVMOption.equals("-XX:FlightRecorderOptions:stackdepth=32,redact-argument=[REDACTED]")) { + throw new Exception("Expected partial redaction for environment variable with -XX:FlightRecorderOptions:redact-argument="); + } + e1.assertRedactedKey("PROGRAM_OPTIONS"); + e1.assertRedactedKey("secret"); + e1.assertRedactedArgument("Aracuan"); + + Execution e2 = run( + Map.of("PROGRAM_OPTIONS", "BLUE RED GREEN GREDELINE"), + Map.of(), + "-XX:FlightRecorderOptions:redact-argument=+*red*", + "BLUE", "RED", "GREEN", "GREDELINE" + ); + String programOptions = e2.environment().get("PROGRAM_OPTIONS"); + if (!programOptions.equals("BLUE [REDACTED] GREEN [REDACTED]")) { + e2.print(); + throw new Exception("Missing redaction inside option variable"); + } + + Execution e3 = run( + Map.of("PROGRAM_OPTIONS", "ZEBRA FISH ZEBRACCOON FISH RACCOON"), + Map.of(), + "-XX:FlightRecorderOptions:redact-argument=+zebra;raccoon", + "ZEBRA", "FISH", "ZEBRACCOON", "FISH", "RACCOON" + ); + programOptions = e3.environment().get("PROGRAM_OPTIONS"); + if (!programOptions.equals("[REDACTED] FISH [REDACTED] FISH [REDACTED]")) { + e3.print(); + throw new Exception("Incorrect redaction when option arguments overlap"); + } + + String option1 = "-XX:FlightRecorderOptions:redact-argument=Zebra,gibberish=,,,"; + String option2 = "-XX:FlightRecorderOptions:redact-argument=Tiger"; + Execution e4 = run( + Map.of("MY_JVM_OPTIONS", option1 + " " + option2), + Map.of(), + List.of(option1, option2), + "TIGER" + ); + e4.assertRedactedArgument("TIGER"); + String redacted = e4.environment().get("MY_JVM_OPTIONS"); + if (!redacted.equals("[REDACTED] -XX:FlightRecorderOptions:redact-argument=[REDACTED]")) { + e4.print(); + throw new Exception("Incorrect redaction with multiple options in environment variables"); + } + } + private static void testRedactKey() throws Exception { Execution e = run( Map.of("cart", "wheel", "banana", "split", "rose", "bud"), @@ -407,14 +469,20 @@ private static Execution run(String options, String... args) throws Exception { return run(Map.of(), Map.of(), options, args); } - private static Execution run(Map environment, Map properties, String options, String... args) throws Exception { + private static Execution run(Map environment, Map properties, String option, String... args) throws Exception { + return run(environment, properties, List.of(option), args); + } + + private static Execution run(Map environment, Map properties, List options, String... args) throws Exception { List arguments = new ArrayList<>(); Path file = Path.of("file.jfr"); for (var entry : properties.entrySet()) { arguments.add("-D" + entry.getKey() + "=" + entry.getValue()); } arguments.add("-XX:StartFlightRecording:filename=" + file.toAbsolutePath().toString()); - arguments.add(options); + for (String option : options) { + arguments.add(option); + } arguments.add("jdk.jfr.startupargs.Application"); arguments.addAll(Arrays.asList(args)); diff --git a/test/jdk/sun/security/ssl/SSLAlgorithmDecomposer/BulkCipherDecomposition.java b/test/jdk/sun/security/ssl/SSLAlgorithmDecomposer/BulkCipherDecomposition.java new file mode 100644 index 000000000000..cb0bb9ecc3b8 --- /dev/null +++ b/test/jdk/sun/security/ssl/SSLAlgorithmDecomposer/BulkCipherDecomposition.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2026, IBM Corporation. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8387124 + * @summary Verify SSLAlgorithmDecomposer bulk cipher decomposition + * @library /javax/net/ssl/TLSCommon + * @run main/othervm + * --add-opens java.base/sun.security.ssl=ALL-UNNAMED + * BulkCipherDecomposition + */ + +import java.lang.reflect.Method; +import java.security.NoSuchAlgorithmException; +import java.security.Security; +import java.util.Arrays; +import java.util.Set; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; + +public class BulkCipherDecomposition { + + private static void testDecomposition(Object instance, Method decomposeMethod, String suite) + throws Exception { + @SuppressWarnings("unchecked") + Set result = (Set) decomposeMethod.invoke(instance, suite); + + String expectedBulk = extractBulkCipher(suite); + + System.out.println("================================================="); + System.out.println("Testing suite : " + suite); + System.out.println("Expected bulk : " + expectedBulk); + System.out.println("Decomposition : " + result); + + if (!result.contains(expectedBulk)) { + throw new RuntimeException( + "Missing bulk cipher decomposition\n" + + "Suite: " + suite + "\n" + + "Expected: " + expectedBulk + "\n" + + "Actual: " + result); + } + } + + /** + * Separator used in TLS cipher suite names to mark the start of + * the bulk cipher component (e.g. TLS_RSA_WITH_AES_128_CBC_SHA). + */ + private static final String WITH = "_WITH_"; + + private static String extractBulkCipher(String suite) { + if (suite.contains(WITH)) { + String after = suite.substring(suite.indexOf(WITH) + WITH.length()); + int last = after.lastIndexOf('_'); + return after.substring(0, last); + } else { + int first = suite.indexOf('_'); + int last = suite.lastIndexOf('_'); + return suite.substring(first + 1, last); + } + } + + private static String[] getCipherSuites() throws NoSuchAlgorithmException { + SSLEngine engine = SSLContext.getDefault().createSSLEngine(); + return Arrays.stream(engine.getSupportedCipherSuites()) + .map(CipherSuite::cipherSuite) + .filter(cs -> cs != CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV) + .map(CipherSuite::name) + .toArray(String[]::new); + } + + public static void main(String[] args) throws Exception { + // disabledAlgorithms limits supported suites; clear to list all + Security.setProperty("jdk.tls.disabledAlgorithms", ""); + + Class c = Class.forName("sun.security.ssl.SSLAlgorithmDecomposer"); + var ctor = c.getDeclaredConstructor(); + ctor.setAccessible(true); + Object instance = ctor.newInstance(); + Method decomposeMethod = c.getDeclaredMethod("decompose", String.class); + decomposeMethod.setAccessible(true); + + for (String suite : getCipherSuites()) { + testDecomposition(instance, decomposeMethod, suite); + } + + System.out.println("PASS"); + } +} diff --git a/test/jdk/sun/tools/jps/TestJpsTempDir.java b/test/jdk/sun/tools/jps/TestJpsTempDir.java new file mode 100644 index 000000000000..b54fedad2b84 --- /dev/null +++ b/test/jdk/sun/tools/jps/TestJpsTempDir.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * 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 8384557 + * @summary Test to make sure jps works correctly when -XX:AltTempDir is set. + * @library /test/lib + * @requires os.family == "linux" + * @modules jdk.jartool/sun.tools.jar + * @build jdk.test.lib.apps.LingeredApp + * @run main/othervm TestJpsTempDir + */ + +// Test that jps finds hsperfdata file in -XX:AltTempDir. + +import jdk.test.lib.apps.LingeredApp; +import java.util.ArrayList; +import java.util.List; +import java.nio.file.Path; +import java.nio.file.Files; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.util.FileUtils; + +public class TestJpsTempDir { + + public static void main(java.lang.String[] unused) throws Exception { + Path clientTmpDir = Files.createTempDirectory(Path.of("/tmp"), "c"); + String tmpdirString = "-XX:AltTempDir=" + clientTmpDir.toString(); + + LingeredAppForJps app = new LingeredAppForJps(); + + try { + // Start LingeredApp with AltTempDir + List vmArgs = new ArrayList<>(List.of(JpsHelper.getVmArgs())); + vmArgs.add(tmpdirString); + LingeredApp.startApp(app, vmArgs.toArray(String[]::new)); + + // Pass to jps (adds -J) + List jpsArgs = new ArrayList<>(); + jpsArgs.add(tmpdirString); + + OutputAnalyzer output = JpsHelper.jps(jpsArgs, null); + output.shouldContain(app.getProcessName()); + output.shouldContain(Long.toString(app.getPid())); + output.shouldHaveExitValue(0); + } finally { + LingeredApp.stopApp(app); + FileUtils.deleteFileTreeWithRetry(clientTmpDir); + } + } +} diff --git a/test/jdk/tools/launcher/Settings.java b/test/jdk/tools/launcher/Settings.java index 4df08edc7edb..18ae9b1113c4 100644 --- a/test/jdk/tools/launcher/Settings.java +++ b/test/jdk/tools/launcher/Settings.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,7 +25,7 @@ /* * @test - * @bug 6994753 7123582 8305950 8281658 8310201 8311653 8343804 8351354 8366364 + * @bug 6994753 7123582 8305950 8281658 8310201 8311653 8343804 8351354 8366364 8387750 * @summary tests -XshowSettings options * @modules jdk.compiler * jdk.zipfs @@ -86,6 +86,7 @@ static void checkNotContains(TestResult tr, String str) { private static final String ENABLED_GROUPS_SETTINGS = "Enabled Named Groups:"; private static final String ENABLED_SIG_SCHEMES_SETTINGS = "Enabled Signature Schemes:"; + private static final String USAGE_HEADER = "Usage: java"; /* * "all" should print verbose settings @@ -174,25 +175,32 @@ static void runTestOptionDefault() throws IOException { static void runTestOptionAll() throws IOException { init(); TestResult tr = doExec(javaCmd, "-XshowSettings:all"); + tr.checkPositive(); containsAllOptions(tr); + checkNotContains(tr, USAGE_HEADER); } static void runTestOptionVM() throws IOException { TestResult tr = doExec(javaCmd, "-XshowSettings:vm"); + tr.checkPositive(); checkContains(tr, VM_SETTINGS); checkNotContains(tr, PROP_SETTINGS); checkNotContains(tr, LOCALE_SETTINGS); + checkNotContains(tr, USAGE_HEADER); } static void runTestOptionProperty() throws IOException { TestResult tr = doExec(javaCmd, "-XshowSettings:properties"); + tr.checkPositive(); checkNotContains(tr, VM_SETTINGS); checkContains(tr, PROP_SETTINGS); checkNotContains(tr, LOCALE_SETTINGS); + checkNotContains(tr, USAGE_HEADER); } static void runTestOptionLocale() throws IOException { TestResult tr = doExec(javaCmd, "-XshowSettings:locale"); + tr.checkPositive(); checkNotContains(tr, VM_SETTINGS); checkNotContains(tr, PROP_SETTINGS); checkContains(tr, LOCALE_SETTINGS); @@ -200,28 +208,34 @@ static void runTestOptionLocale() throws IOException { checkNotContains(tr, LOCALE_SUMMARY_SETTINGS); checkContains(tr, TIMEZONE_SETTINGS); checkContains(tr, TZDATA_SETTINGS); + checkNotContains(tr, USAGE_HEADER); } static void runTestOptionSecurity() throws IOException { TestResult tr = doExec(javaCmd, "-XshowSettings:security"); + tr.checkPositive(); checkNotContains(tr, VM_SETTINGS); checkNotContains(tr, PROP_SETTINGS); checkContains(tr, SEC_PROPS_SETTINGS); checkContains(tr, SEC_PROVIDER_SETTINGS); checkContains(tr, SEC_TLS_SETTINGS); + checkNotContains(tr, USAGE_HEADER); } static void runTestOptionSecurityProps() throws IOException { TestResult tr = doExec(javaCmd, "-XshowSettings:security:properties"); + tr.checkPositive(); checkContains(tr, SEC_PROPS_SETTINGS); checkNotContains(tr, SEC_PROVIDER_SETTINGS); checkNotContains(tr, SEC_TLS_SETTINGS); // test a well known property for sanity checkContains(tr, "keystore.type=pkcs12"); + checkNotContains(tr, USAGE_HEADER); } static void runTestOptionSecurityProv() throws IOException { TestResult tr = doExec(javaCmd, "-XshowSettings:security:providers"); + tr.checkPositive(); checkNotContains(tr, SEC_PROPS_SETTINGS); checkContains(tr, SEC_PROVIDER_SETTINGS); checkNotContains(tr, SEC_TLS_SETTINGS); @@ -230,10 +244,12 @@ static void runTestOptionSecurityProv() throws IOException { // test for a well known alias (SunJCE: AlgorithmParameterGenerator.DiffieHellman) checkContains(tr, "aliases: [1.2.840.113549.1.3.1, " + "DH, OID.1.2.840.113549.1.3.1]"); + checkNotContains(tr, USAGE_HEADER); } static void runTestOptionSecurityTLS() throws IOException { TestResult tr = doExec(javaCmd, "-XshowSettings:security:tls"); + tr.checkPositive(); checkNotContains(tr, SEC_PROPS_SETTINGS); checkNotContains(tr, SEC_PROVIDER_SETTINGS); checkContains(tr, SEC_TLS_SETTINGS); @@ -241,6 +257,7 @@ static void runTestOptionSecurityTLS() throws IOException { checkContains(tr, "TLSv1.2"); checkContains(tr, ENABLED_GROUPS_SETTINGS); checkContains(tr, ENABLED_SIG_SCHEMES_SETTINGS); + checkNotContains(tr, USAGE_HEADER); } // ensure error message is printed when unrecognized option used @@ -255,6 +272,7 @@ static void runTestOptionBadSecurityOption() throws IOException { } static void runTestOptionSystem() throws IOException { TestResult tr = doExec(javaCmd, "-XshowSettings:system"); + tr.checkPositive(); if (System.getProperty("os.name").contains("Linux")) { checkNotContains(tr, VM_SETTINGS); checkNotContains(tr, PROP_SETTINGS); @@ -266,6 +284,7 @@ static void runTestOptionSystem() throws IOException { checkNotContains(tr, VM_SETTINGS); checkContains(tr, METRICS_NOT_AVAILABLE_MSG); } + checkNotContains(tr, USAGE_HEADER); } static void runTestBadOptions() throws IOException { diff --git a/test/langtools/jdk/internal/shellsupport/doc/JavadocHelperTest.java b/test/langtools/jdk/internal/shellsupport/doc/JavadocHelperTest.java index 4f739d38d3d2..6bd8ddf510a7 100644 --- a/test/langtools/jdk/internal/shellsupport/doc/JavadocHelperTest.java +++ b/test/langtools/jdk/internal/shellsupport/doc/JavadocHelperTest.java @@ -516,7 +516,7 @@ private void doTestJavadoc(String origJavadoc, Element el = getElement.apply(task); - try (JavadocHelper helper = JavadocHelper.create(task, Arrays.asList(srcZip))) { + try (JavadocHelper helper = JavadocHelper.create(task, Arrays.asList(srcZip), _ -> null)) { String javadoc = helper.getResolvedDocComment(el); assertEquals(expectedJavadoc, javadoc); @@ -608,7 +608,7 @@ protected void retrieveDocComments(BooleanSupplier shouldTest) throws IOExceptio List exports = ElementFilter.exportsIn(me.getDirectives()); for (ExportsDirective ed : exports) { - try (JavadocHelper helper = JavadocHelper.create(task, sources)) { + try (JavadocHelper helper = JavadocHelper.create(task, sources, _ -> null)) { List content = ed.getPackage().getEnclosedElements(); for (TypeElement clazz : ElementFilter.typesIn(content)) { diff --git a/test/langtools/jdk/jshell/AnalysisTest.java b/test/langtools/jdk/jshell/AnalysisTest.java index 478301f3fd59..3e22523e0b7e 100644 --- a/test/langtools/jdk/jshell/AnalysisTest.java +++ b/test/langtools/jdk/jshell/AnalysisTest.java @@ -47,6 +47,7 @@ public void testSourceSlashStar() { assertAnalyze("/*zoo*/int x=3 ;/*test*/", "/*zoo*/int x=3 ;/*test*/", "", true); assertAnalyze("int x=3 /*;*/", "int x=3; /*;*/", "", true); assertAnalyze("void m5() {} /*hgjghj*/", "void m5() {} /*hgjghj*/", "", true); + assertAnalyze("void m5() {} /**hgjghj*/", "void m5() {} /**hgjghj*/", "", true); assertAnalyze("int ff; int v /*hgjghj*/", "int ff;", " int v /*hgjghj*/", true); } @@ -65,4 +66,12 @@ public void testExpression() { assertAnalyze("/*zoo*/45;/*test*/", "/*zoo*/45;/*test*/", "", true); assertAnalyze("45/*;*/", "45/*;*/", "", true); } + + @Test + public void testOnlyComments() { + assertAnalyze("/*test*/", "/*test*/", "", false); + assertAnalyze("/**test*/", "/**test*/", "", false); + assertAnalyze("//test", "//test", "", false); + assertAnalyze("///test", "///test", "", false); + } } diff --git a/test/langtools/jdk/jshell/BinaryToSourceCodeMappingTest.java b/test/langtools/jdk/jshell/BinaryToSourceCodeMappingTest.java new file mode 100644 index 000000000000..b7ff55b3fd27 --- /dev/null +++ b/test/langtools/jdk/jshell/BinaryToSourceCodeMappingTest.java @@ -0,0 +1,310 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8186876 + * @summary Test binary to source mapping in completion + * @library /tools/lib + * @modules jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.main + * jdk.jdeps/com.sun.tools.javap + * jdk.jshell/jdk.jshell:open + * @build toolbox.ToolBox toolbox.JarTask toolbox.JavacTask + * @build KullaTesting TestingInputStream Compiler + * @run junit/othervm/timeout=480 BinaryToSourceCodeMappingTest + */ + +import java.io.IOException; +import java.io.Writer; +import java.lang.reflect.Field; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import jdk.jshell.JShell; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class BinaryToSourceCodeMappingTest extends KullaTesting { + + private static Path classesDir; + private static Path transientClassesDir; + private static Path srcDir; + private static Path srcZip; + private static Path srcZipWithNestedPath; + private static Path brokenSrcZip; + private final TestInfo testInfo; + private final AtomicBoolean closeCalled = new AtomicBoolean(); + + public BinaryToSourceCodeMappingTest(TestInfo info) { + this.testInfo = info; + } + + @Test + public void testNull() { + assertJavadoc("test.inner.Test|", + """ + test.inner.Test + null"""); + } + + @Test + public void testReturnsNull() { + assertJavadoc("test.inner.Test|", + """ + test.inner.Test + null"""); + } + + @Test + public void testSourcesAsDirectory() { + assertJavadoc("test.inner.Test|", + """ + test.inner.Test + Class javadoc."""); + assertJavadoc("test.inner.Test.test(|", + """ + void test.inner.Test.test(int i) + Test method. + @param i param"""); + } + + @Test + public void testSourcesAsZip() { + assertJavadoc("test.inner.Test|", + """ + test.inner.Test + Class javadoc."""); + assertJavadoc("test.inner.Test.test(|", + """ + void test.inner.Test.test(int i) + Test method. + @param i param"""); + } + + @Test + public void testSourcesDuplicate() { + //src.zip and broken-src.zip both available, + //only the first one should be used: + assertJavadoc("test.inner.Test|", + """ + test.inner.Test + Class javadoc."""); + assertJavadoc("test.inner.Test.test(|", + """ + void test.inner.Test.test(int i) + Test method. + @param i param"""); + } + + @Test + public void testSourcesAsZipNested() { + assertJavadoc("test.inner.Test|", + """ + test.inner.Test + Class javadoc."""); + assertJavadoc("test.inner.Test.test(|", + """ + void test.inner.Test.test(int i) + Test method. + @param i param"""); + } + + @Test + public void testClassPathModification() { + assertJavadoc("test.inner.Test|", + """ + test.inner.Test + null"""); + getState().addToClasspath(classesDir.toString()); + assertFalse(closeCalled.get()); + assertJavadoc("test.inner.Test|", + """ + test.inner.Test + Class javadoc."""); + assertTrue(closeCalled.get()); + closeCalled.set(false); + } + + @Test + public void testClosingAPIUse() { + getState().addToClasspath(classesDir.toString()); + String input = "test.inner.Test"; + AtomicReference> documentation = new AtomicReference<>(); + + getAnalysis().completionSuggestions(input, input.length(), (state, suggestions) -> { + Assertions.assertEquals(1, suggestions.size()); + documentation.set(suggestions.get(0).documentation()); + return List.of(""); + }); + //holding the documentation supplier, and using it later/outside of the convertor is OK; + //will open the sources, and should also close them: + assertFalse(closeCalled.get()); + Assertions.assertEquals("Class javadoc.", documentation.get().get()); + assertTrue(closeCalled.get()); + closeCalled.set(false); + } + + @Override + public void setUp(Consumer bc) { + super.setUp(bc.andThen(b -> { + b.binarySourceMapping(switch (testInfo.getTestMethod().orElseThrow().getName()) { + case "testNull" -> null; + case "testReturnsNull" -> _ -> null; + case "testSourcesAsDirectory" -> p -> classesDir.equals(p) ? List.of(srcDir) : List.of(); + case "testSourcesAsZip" -> + p -> classesDir.equals(p) ? List.of(srcZip) : List.of(); + case "testSourcesDuplicate" -> + p -> classesDir.equals(p) ? List.of(srcZip, brokenSrcZip) : List.of(); + case "testClassPathModification", "testSourcesAsZipNested", "testClosingAPIUse" -> p -> { + if (!classesDir.equals(p)) { + return List.of(); + } + try { + FileSystem withNested = FileSystems.newFileSystem(srcZipWithNestedPath); + class CloseableIterable extends ArrayList implements AutoCloseable { + public CloseableIterable() { + super(List.of(withNested.getPath("root"))); + } + @Override + public void close() throws Exception { + withNested.close(); + closeCalled.set(true); + } + } + + return new CloseableIterable(); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + }; + default -> throw new AssertionError(); + }); + })); + if ("testClassPathModification".equals(testInfo.getTestMethod().orElseThrow().getName())) { + addToClasspath(transientClassesDir); + } else { + addToClasspath(classesDir); + } + } + + @BeforeAll + public static void beforeAll() { + Compiler compiler = new Compiler(); + srcDir = Paths.get("src").toAbsolutePath(); + Path testOutDir = Paths.get("classes"); + String input = + """ + package test.inner; + ///Class javadoc. + public class Test { + ///Test method. + ///@param i param + public static void test(int i) { + } + } + """; + Path srcFile = srcDir.resolve("test").resolve("inner").resolve("Test.java"); + try { + Files.createDirectories(srcFile.getParent()); + try (Writer w = Files.newBufferedWriter(srcFile)) { + w.append(input); + } + } catch (IOException ex) { + throw new RuntimeException(ex); + } + compiler.compile(testOutDir, input); + classesDir = compiler.getPath(testOutDir); + Path transientClasses = Paths.get("transientClasses"); + compiler.compile(transientClasses, input); + transientClassesDir = compiler.getPath(transientClasses); + srcZip = Paths.get("src.zip"); + compiler.jar(srcDir, srcZip, srcDir.relativize(srcFile).toString()); + srcZipWithNestedPath = Paths.get("srcWithNestedPath.zip"); + Path srcDir2 = Paths.get("src2").toAbsolutePath(); + Path srcFile2 = srcDir2.resolve("root").resolve("test").resolve("inner").resolve("Test.java"); + try { + Files.createDirectories(srcFile2.getParent()); + try (Writer w = Files.newBufferedWriter(srcFile2)) { + w.append(input); + } + } catch (IOException ex) { + throw new RuntimeException(ex); + } + compiler.jar(srcDir2, srcZipWithNestedPath, srcDir2.relativize(srcFile2).toString()); + + brokenSrcZip = Paths.get("broken-src.zip"); + + try (JarOutputStream out = new JarOutputStream(Files.newOutputStream(brokenSrcZip))) { + out.putNextEntry(new JarEntry("test/inner/Test.java")); + out.write(""" + package test.inner; + ///broken + public class Test { + ///broken + ///@param i broken + public static void test(int i) { + } + } + """.getBytes()); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + @Override + protected void tearDownDone() { + switch(testInfo.getTestMethod().orElseThrow().getName()) { + case "testSourcesAsZipNested" -> assertTrue(closeCalled.get()); + case "testClassPathModification", "testClosingAPIUse" -> assertFalse(closeCalled.get()); + } + super.tearDownDone(); + } + + static { + try { + //disable reading of paramater names, to improve stability: + Class analysisClass = Class.forName("jdk.jshell.SourceCodeAnalysisImpl"); + Field params = analysisClass.getDeclaredField("COMPLETION_EXTRA_PARAMETERS"); + params.setAccessible(true); + params.set(null, List.of()); + } catch (ReflectiveOperationException ex) { + throw new IllegalStateException(ex); + } + } +} diff --git a/test/langtools/jdk/jshell/CompletenessTest.java b/test/langtools/jdk/jshell/CompletenessTest.java index 1d57aef82109..f938a74d8a02 100644 --- a/test/langtools/jdk/jshell/CompletenessTest.java +++ b/test/langtools/jdk/jshell/CompletenessTest.java @@ -229,6 +229,19 @@ public class CompletenessTest extends KullaTesting { "void t(int i) { int v = switch (i) { case 0 -> throw new IllegalStateException();", }; + static final String[] empty = new String[] { + " ", + "/*comment*/", + "/**/", + "//", + }; + + static final String[] prefix = new String[] { + "/**comment*/", + "/***/", + "///comment", + }; + static final String[] unknown = new String[] { "new ;", "\"", @@ -259,6 +272,7 @@ private void assertStatus(String input, Completeness status, String source) { break; case EMPTY: + case PREFIX: case COMPLETE: case UNKNOWN: augSrc = source; @@ -301,6 +315,16 @@ public void test_definitely_incomplete() { assertStatus(definitely_incomplete, DEFINITELY_INCOMPLETE); } + @Test + public void test_prefix() { + assertStatus(prefix, PREFIX); + } + + @Test + public void test_empty() { + assertStatus(empty, EMPTY); + } + @Test public void test_unknown() { assertStatus(definitely_incomplete, DEFINITELY_INCOMPLETE); diff --git a/test/langtools/jdk/jshell/CompletionAPITest.java b/test/langtools/jdk/jshell/CompletionAPITest.java index 932482ce6dc2..f5bc205c2085 100644 --- a/test/langtools/jdk/jshell/CompletionAPITest.java +++ b/test/langtools/jdk/jshell/CompletionAPITest.java @@ -49,6 +49,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiConsumer; +import java.util.function.Consumer; import java.util.function.Supplier; import java.util.jar.JarEntry; import java.util.jar.JarOutputStream; @@ -59,6 +60,7 @@ import javax.lang.model.element.QualifiedNameable; import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; +import jdk.jshell.JShell; import jdk.jshell.SourceCodeAnalysis.CompletionContext; import jdk.jshell.SourceCodeAnalysis.CompletionState; import jdk.jshell.SourceCodeAnalysis.ElementSuggestion; @@ -266,8 +268,6 @@ private Path prepareZip() { "public class JShellTest {\n" + "}\n"; - Path srcZip = Paths.get("src.zip"); - try (JarOutputStream out = new JarOutputStream(Files.newOutputStream(srcZip))) { out.putNextEntry(new JarEntry("jshelltest/JShellTest.java")); out.write(clazz.getBytes()); @@ -277,18 +277,15 @@ private Path prepareZip() { compiler.compile(clazz); - try { - Field availableSources = Class.forName("jdk.jshell.SourceCodeAnalysisImpl").getDeclaredField("availableSourcesOverride"); - availableSources.setAccessible(true); - availableSources.set(null, Arrays.asList(srcZip)); - } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException | ClassNotFoundException ex) { - throw new IllegalStateException(ex); - } - return compiler.getClassDir(); } //where: private final Compiler compiler = new Compiler(); + private final Path srcZip = Paths.get("src.zip"); + + public void setUp(Consumer bc) { + super.setUp(bc.andThen(b -> b.binarySourceMapping(p -> compiler.getClassDir().equals(p) ? List.of(srcZip) : null))); + } static { try { diff --git a/test/langtools/jdk/jshell/CompletionSuggestionTest.java b/test/langtools/jdk/jshell/CompletionSuggestionTest.java index 7a960258cc1e..44f21c4675e1 100644 --- a/test/langtools/jdk/jshell/CompletionSuggestionTest.java +++ b/test/langtools/jdk/jshell/CompletionSuggestionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -782,13 +782,13 @@ public void setUp() { throw new IllegalStateException(ex); } - try { - Field availableSources = getAnalysis().getClass().getDeclaredField("availableSources"); - availableSources.setAccessible(true); - availableSources.set(getAnalysis(), Arrays.asList(srcZip)); - } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException ex) { - throw new IllegalStateException(ex); - } + setJDKSourcesOverride(List.of(srcZip)); + } + + @Override + public void tearDown() { + setJDKSourcesOverride(null); + super.tearDown(); } private void dontReadParameterNamesFromClassFile() throws Exception { @@ -953,6 +953,17 @@ public void testMultiSnippet() { assertSignature("void f() { } f(|", "void f()"); } + private static void setJDKSourcesOverride(List paths) throws IllegalStateException { + try { + //to ensure test stability, don't use JDK's src.zip: + Field availableSources = Class.forName("jdk.jshell.SourceCodeAnalysisImpl").getDeclaredField("jdkSourcesOverride"); + availableSources.setAccessible(true); + availableSources.set(null, paths); + } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException | ClassNotFoundException ex) { + throw new IllegalStateException(ex); + } + } + static { try { //disable reading of paramater names, to improve stability: diff --git a/test/langtools/jdk/jshell/JavadocTest.java b/test/langtools/jdk/jshell/JavadocTest.java index 463c23a4f31a..3f25d1f11c5c 100644 --- a/test/langtools/jdk/jshell/JavadocTest.java +++ b/test/langtools/jdk/jshell/JavadocTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -39,9 +39,10 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Arrays; +import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarOutputStream; +import jdk.jshell.Snippet.Status; import org.junit.jupiter.api.Test; @@ -98,13 +99,7 @@ private void prepareZip() { compiler.compile(clazz); - try { - Field availableSources = getAnalysis().getClass().getDeclaredField("availableSources"); - availableSources.setAccessible(true); - availableSources.set(getAnalysis(), Arrays.asList(srcZip)); - } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException ex) { - throw new IllegalStateException(ex); - } + setJDKSourcesOverride(List.of(srcZip)); addToClasspath(compiler.getClassDir()); } @@ -147,13 +142,169 @@ private void prepareJavaUtilZip() { throw new IllegalStateException(ex); } + setJDKSourcesOverride(List.of(srcZip)); + } + + @Test + public void testJavadocFromSnippets() { + assertEval(""" + /**SnippetClazz top level.*/ + class SnippetClazz { + /**Method javadoc.*/ + public static void test() {} + /**SnippetClazzNested nested.*/ + static class SnippetClazzNested { + /**Nested method javadoc.*/ + public static void nestedMethod() {} + } + } + """); + assertEval(""" + /**topLevel method.*/ + void topLevel(SnippetClazz clazz) {} + """); + assertEval(""" + /**topLevel variable.*/ + int variable = 0; + """); + assertJavadoc("SnippetClazz|", + """ + SnippetClazz + SnippetClazz top level."""); + assertJavadoc("SnippetClazz.SnippetClazzNested|", + """ + SnippetClazz.SnippetClazzNested + SnippetClazzNested nested."""); + assertJavadoc("SnippetClazz.test(|", + """ + void SnippetClazz.test() + Method javadoc."""); + assertJavadoc("SnippetClazz.SnippetClazzNested.nestedMethod(|", + """ + void SnippetClazz.SnippetClazzNested.nestedMethod() + Nested method javadoc."""); + assertJavadoc("topLevel(|", + """ + void topLevel(SnippetClazz clazz) + topLevel method."""); + assertJavadoc("variable|", + """ + variable:int + topLevel variable."""); + } + + @Test + public void testJavadocFromSnippetsMarkdown() { + assertEval(""" + ///SnippetClazz top level. + class SnippetClazz { + ///Method javadoc. + public static void test() {} + ///SnippetClazzNested nested. + static class SnippetClazzNested { + ///Nested method javadoc. + public static void nestedMethod() {} + } + } + """); + assertEval(""" + ///topLevel method. + public static void topLevel(SnippetClazz clazz) {} + """); + assertEval(""" + ///topLevel variable. + int variable = 0; + """); + assertJavadoc("SnippetClazz|", + """ + SnippetClazz + SnippetClazz top level."""); + assertJavadoc("SnippetClazz.SnippetClazzNested|", + """ + SnippetClazz.SnippetClazzNested + SnippetClazzNested nested."""); + assertJavadoc("SnippetClazz.test(|", + """ + void SnippetClazz.test() + Method javadoc."""); + assertJavadoc("SnippetClazz.SnippetClazzNested.nestedMethod(|", + """ + void SnippetClazz.SnippetClazzNested.nestedMethod() + Nested method javadoc."""); + assertJavadoc("topLevel(|", + """ + void topLevel(SnippetClazz clazz) + topLevel method."""); + assertJavadoc("variable|", + """ + variable:int + topLevel variable."""); + } + + @Test + public void testOverrideSnippet() { + var originalClass = + classKey(assertEval(""" + ///bad javadoc + class SnippetClazz { + } + """)); + assertEval(""" + ///SnippetClazz top level. + class SnippetClazz implements java.io.Serializable { + } + """, + ste(MAIN_SNIPPET, Status.VALID, Status.VALID, true, null), + ste(originalClass, Status.VALID, Status.OVERWRITTEN, false, MAIN_SNIPPET)); + var originalMethod= + methodKey(assertEval(""" + ///bad javadoc + public static void topLevel(SnippetClazz clazz) {} + """)); + assertEval(""" + ///topLevel method. + public static String topLevel(SnippetClazz clazz) { return ""; } + """, + ste(MAIN_SNIPPET, Status.VALID, Status.VALID, true, null), + ste(originalMethod, Status.VALID, Status.OVERWRITTEN, false, MAIN_SNIPPET)); + var originalVar = + varKey(assertEval(""" + ///bad javadoc + int variable = 0; + """)); + assertEval(""" + ///topLevel variable. + String variable = ""; + """, + ste(MAIN_SNIPPET, Status.VALID, Status.VALID, true, null), + ste(originalVar, Status.VALID, Status.OVERWRITTEN, false, MAIN_SNIPPET)); + assertJavadoc("SnippetClazz|", + """ + SnippetClazz + SnippetClazz top level."""); + assertJavadoc("topLevel(|", + """ + String topLevel(SnippetClazz clazz) + topLevel method."""); + assertJavadoc("variable|", + """ + variable:java.lang.String + topLevel variable."""); + } + + @Override + public void tearDown() { + setJDKSourcesOverride(null); + super.tearDown(); + } + + private void setJDKSourcesOverride(List override) { try { - Field availableSources = getAnalysis().getClass().getDeclaredField("availableSources"); + Field availableSources = getAnalysis().getClass().getDeclaredField("jdkSourcesOverride"); availableSources.setAccessible(true); - availableSources.set(getAnalysis(), Arrays.asList(srcZip)); + availableSources.set(getAnalysis(), override); } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException ex) { throw new IllegalStateException(ex); } } - } diff --git a/test/langtools/jdk/jshell/KullaTesting.java b/test/langtools/jdk/jshell/KullaTesting.java index 8b62ce69a4c8..c9e531cd84c3 100644 --- a/test/langtools/jdk/jshell/KullaTesting.java +++ b/test/langtools/jdk/jshell/KullaTesting.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -210,8 +210,11 @@ public void tearDown() { analysis = null; allSnippets = null; idToSnippet = null; + tearDownDone(); } + protected void tearDownDone() {} + public ClassLoader createAndRunFromModule(String moduleName, Path modPath) { ModuleFinder finder = ModuleFinder.of(modPath); ModuleLayer parent = ModuleLayer.boot(); diff --git a/test/langtools/jdk/jshell/ToolSimpleTest.java b/test/langtools/jdk/jshell/ToolSimpleTest.java index 39ce248a521f..dc7921376633 100644 --- a/test/langtools/jdk/jshell/ToolSimpleTest.java +++ b/test/langtools/jdk/jshell/ToolSimpleTest.java @@ -73,6 +73,8 @@ public void testOpenComment() { test( (a) -> assertCommand(a, "int z = /* blah", ""), (a) -> assertCommand(a, "baz */ 5", "z ==> 5"), + (a) -> assertCommand(a, "/* hoge ", ""), + (a) -> assertCommand(a, "baz **/", ""), (a) -> assertCommand(a, "/** hoge ", ""), (a) -> assertCommand(a, "baz **/", ""), (a) -> assertCommand(a, "int v", "v ==> 0") diff --git a/test/langtools/jdk/jshell/ToolTabSnippetTest.java b/test/langtools/jdk/jshell/ToolTabSnippetTest.java index 112208c40b3c..3eb709f52f45 100644 --- a/test/langtools/jdk/jshell/ToolTabSnippetTest.java +++ b/test/langtools/jdk/jshell/ToolTabSnippetTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -38,17 +38,22 @@ */ import java.io.IOException; +import java.io.InputStream; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Arrays; +import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.jar.JarEntry; import java.util.jar.JarOutputStream; import jdk.internal.jshell.tool.ConsoleIOContextTestSupport; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; public class ToolTabSnippetTest extends UITesting { @@ -56,9 +61,10 @@ public ToolTabSnippetTest() { super(true); } - @Test - public void testExpression() throws Exception { - Path classes = prepareZip(); + @ParameterizedTest + @ValueSource(booleans={false, true}) + public void testExpression(boolean createCombinedJar) throws Exception { + Path classes = prepareZip(createCombinedJar); doRunTest((inputSink, out) -> { inputSink.write("/env -class-path " + classes.toString() + "\n"); waitOutput(out, resource("jshell.msg.set.restore") + "\n\\u001B\\[\\?2004h" + PROMPT); @@ -278,7 +284,7 @@ public void testCrash8221759() throws Exception { }); } - private Path prepareZip() { + private Path prepareZip(boolean createCombinedJar) { String clazz1 = "package jshelltest;\n" + "/**JShellTest 0" + @@ -308,28 +314,80 @@ private Path prepareZip() { " public JShellTestAux(String str, int i) { }\n" + "}\n"; - Path srcZip = Paths.get("src.zip"); + if (createCombinedJar) { + Path combinedJar = Paths.get("combined.jar"); - try (JarOutputStream out = new JarOutputStream(Files.newOutputStream(srcZip))) { - out.putNextEntry(new JarEntry("jshelltest/JShellTest.java")); - out.write(clazz1.getBytes()); - out.putNextEntry(new JarEntry("jshelltest/JShellTestAux.java")); - out.write(clazz2.getBytes()); - } catch (IOException ex) { - throw new IllegalStateException(ex); - } + try (JarOutputStream out = new JarOutputStream(Files.newOutputStream(combinedJar))) { + out.putNextEntry(new JarEntry("jshelltest/JShellTest.java")); + out.write(clazz1.getBytes()); + out.putNextEntry(new JarEntry("jshelltest/JShellTestAux.java")); + out.write(clazz2.getBytes()); - compiler.compile(clazz1, clazz2); + compiler.compile(clazz1, clazz2); - try { - Field availableSources = Class.forName("jdk.jshell.SourceCodeAnalysisImpl").getDeclaredField("availableSourcesOverride"); - availableSources.setAccessible(true); - availableSources.set(null, Arrays.asList(srcZip)); - } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException | ClassNotFoundException ex) { - throw new IllegalStateException(ex); - } + for (String clazz : new String[] {"jshelltest/JShellTest.class", "jshelltest/JShellTestAux.class"}) { + out.putNextEntry(new JarEntry(clazz)); - return compiler.getClassDir(); + try (InputStream in = Files.newInputStream(compiler.getClassDir().resolve(clazz))) { + in.transferTo(out); + } + } + } catch (IOException ex) { + throw new IllegalStateException(ex); + } + + //create a broken combined-sources.jar, which should not be used: + Path srcZip = Paths.get("combined-sources.jar"); + + try (JarOutputStream out = new JarOutputStream(Files.newOutputStream(srcZip))) { + out.putNextEntry(new JarEntry("jshelltest/JShellTest.java")); + out.write(""" + package jshelltest; + /** wrong */ + public class JShellTest { + /** wrong + */ + public JShellTest(String str) {} + /**JShellTest 2 */ + public JShellTest(String str, int i) {} + } + """.getBytes()); + + out.putNextEntry(new JarEntry("jshelltest/JShellTestAux.java")); + out.write(""" + package jshelltest; + /** wrong */ + public class JShellTestAux { + /** wrong */ + public JShellTestAux(String str) { } + /** wrong */ + public JShellTestAux(String str, int i) { } + } + """.getBytes()); + } catch (IOException ex) { + throw new IllegalStateException(ex); + } + + return combinedJar; + } else { + Path srcZip = Paths.get("test-sources.jar"); + + try (JarOutputStream out = new JarOutputStream(Files.newOutputStream(srcZip))) { + out.putNextEntry(new JarEntry("jshelltest/JShellTest.java")); + out.write(clazz1.getBytes()); + out.putNextEntry(new JarEntry("jshelltest/JShellTestAux.java")); + out.write(clazz2.getBytes()); + } catch (IOException ex) { + throw new IllegalStateException(ex); + } + + compiler.compile(clazz1, clazz2); + + Path binaryJar = Paths.get("test.jar"); + compiler.jar(compiler.getClassDir(), binaryJar, "jshelltest/JShellTest.class", "jshelltest/JShellTestAux.class"); + + return binaryJar; + } } //where: private final Compiler compiler = new Compiler(); @@ -364,4 +422,25 @@ public void testAnnotation() throws Exception { waitOutput(out, "CL\\u001B\\[2Djava.lang.annotation.RetentionPolicy.CLASS \\u0008"); }); } + + @BeforeAll + public static void noJDKSources() { + setJDKSourcesOverride(List.of()); + } + + @AfterAll + public static void restoreJDKSources() { + setJDKSourcesOverride(null); + } + + private static void setJDKSourcesOverride(List paths) throws IllegalStateException { + try { + //to ensure test stability, don't use JDK's src.zip: + Field availableSources = Class.forName("jdk.jshell.SourceCodeAnalysisImpl").getDeclaredField("jdkSourcesOverride"); + availableSources.setAccessible(true); + availableSources.set(null, paths); + } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException | ClassNotFoundException ex) { + throw new IllegalStateException(ex); + } + } } diff --git a/test/langtools/tools/javac/classfiles/attributes/LineNumberTable/LineNumberTestBase.java b/test/langtools/tools/javac/classfiles/attributes/LineNumberTable/LineNumberTestBase.java index 32ec2e07f73a..bcd032857e50 100644 --- a/test/langtools/tools/javac/classfiles/attributes/LineNumberTable/LineNumberTestBase.java +++ b/test/langtools/tools/javac/classfiles/attributes/LineNumberTable/LineNumberTestBase.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -92,6 +92,7 @@ protected void test(List testCases) throws Exception { if (expected != null) { verifyCoveredLines(methodCoveredLines, expected); + expected.validator().accept(classFile, m); } coveredLines.addAll(methodCoveredLines); diff --git a/test/langtools/tools/javac/classfiles/attributes/LineNumberTable/PatternMatching.java b/test/langtools/tools/javac/classfiles/attributes/LineNumberTable/PatternMatching.java new file mode 100644 index 000000000000..66dd3c7ee27b --- /dev/null +++ b/test/langtools/tools/javac/classfiles/attributes/LineNumberTable/PatternMatching.java @@ -0,0 +1,341 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8387965 + * @summary Tests a line number table attribute for pattern matching + * @library /tools/lib /tools/javac/lib ../lib + * @modules jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.main + * jdk.compiler/com.sun.tools.javac.util + * java.base/jdk.internal.classfile.impl + * @build toolbox.ToolBox InMemoryFileManager TestBase + * @build LineNumberTestBase TestCase + * @run main PatternMatching + */ + +import java.lang.classfile.ClassModel; +import java.lang.classfile.CodeElement; +import java.lang.classfile.CodeModel; +import java.lang.classfile.Instruction; +import java.lang.classfile.MethodModel; +import java.lang.classfile.instruction.LineNumber; +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiConsumer; + +import toolbox.ToolBox; + +public class PatternMatching extends LineNumberTestBase { + static ToolBox tb = new ToolBox(); + + public static void main(String[] args) throws Exception { + new PatternMatching().test(); + } + + public void test() throws Exception { + test(List.of(TEST_CASE)); + } + + private static final TestCase[] TEST_CASE = new TestCase[] { + new TestCase(""" + public class PatternMatching { // 1 + private void test(String s) { // 2 + Object obj = "abc"; // 3 + boolean isLong = obj instanceof String str // 4 + && // 5 + true; // 6 + } // 7 + } // 8 + """, + "PatternMatching", + new TestCase.MethodData("test", List.of(3, 4, 7), true, new DetailedValidator( + "line: 3", + "LDC", + "ASTORE_2", + "line: 4", + "ALOAD_2", + "INSTANCEOF", + "IFEQ", + "ALOAD_2", + "CHECKCAST", + "ASTORE", + "ICONST_1", + "GOTO", + "ICONST_0", + "ISTORE_3", + "line: 7", + "RETURN"))), + new TestCase(""" + public class PatternMatching { // 1 + private void test(String s) { // 2 + Object obj = "abc"; // 3 + boolean isLong = true // 4 + && // 5 + obj instanceof String str2; // 6 + } // 7 + } // 8 + """, + "PatternMatching", + new TestCase.MethodData("test", List.of(3, 6, 7), true, new DetailedValidator( + "line: 3", + "LDC", + "ASTORE_2", + "line: 6", + "ALOAD_2", + "INSTANCEOF", + "IFEQ", + "ALOAD_2", + "CHECKCAST", + "ASTORE", + "ICONST_1", + "GOTO", + "ICONST_0", + "ISTORE_3", + "line: 7", + "RETURN" + ))), + new TestCase(""" + public class PatternMatching { // 1 + private void test(String s) { // 2 + Object obj = "abc"; // 3 + boolean isLong = obj instanceof String str1 // 4 + && // 5 + obj instanceof String str2; // 6 + } // 7 + } // 8 + """, + "PatternMatching", + new TestCase.MethodData("test", List.of(3, 4, 6, 7), true, new DetailedValidator( + "line: 3", + "LDC", + "ASTORE_2", + "line: 4", + "ALOAD_2", + "INSTANCEOF", + "IFEQ", + "ALOAD_2", + "CHECKCAST", + "ASTORE", + "line: 6", + "ALOAD_2", + "INSTANCEOF", + "IFEQ", + "ALOAD_2", + "CHECKCAST", + "ASTORE", + "ICONST_1", + "GOTO", + "ICONST_0", + "ISTORE_3", + "line: 7", + "RETURN" + ))), + new TestCase(""" + public class PatternMatching { // 1 + private void test(String s) { // 2 + Object obj = "abc"; // 3 + boolean isLong = obj instanceof String str // 4 + && // 5 + check(); // 6 + } // 7 + private boolean check() { return true; } // 8 + } // 9 + """, + "PatternMatching", + new TestCase.MethodData("test", List.of(3, 4, 6, 7), true, new DetailedValidator( + "line: 3", + "LDC", + "ASTORE_2", + "line: 4", + "ALOAD_2", + "INSTANCEOF", + "IFEQ", + "ALOAD_2", + "CHECKCAST", + "ASTORE", + "ALOAD_0", + "line: 6", + "INVOKEVIRTUAL", + "IFEQ", + "ICONST_1", + "GOTO", + "ICONST_0", + "ISTORE_3", + "line: 7", + "RETURN"))), + new TestCase(""" + public class PatternMatching { // 1 + private void test(String s) { // 2 + Object obj = "abc"; // 3 + boolean isLong = check() // 4 + && // 5 + obj instanceof String str2; // 6 + } // 7 + private boolean check() { return true; } // 8 + } // 9 + """, + "PatternMatching", + new TestCase.MethodData("test", List.of(3, 4, 6, 7), true, new DetailedValidator( + "line: 3", + "LDC", + "ASTORE_2", + "line: 4", + "ALOAD_0", + "INVOKEVIRTUAL", + "IFEQ", + "line: 6", + "ALOAD_2", + "INSTANCEOF", + "IFEQ", + "ALOAD_2", + "CHECKCAST", + "ASTORE", + "ICONST_1", + "GOTO", + "ICONST_0", + "ISTORE_3", + "line: 7", + "RETURN" + ))), + new TestCase(""" + public class PatternMatching { // 1 + private void test(String s) { // 2 + boolean isLong = obj() instanceof String str // 3 + && // 4 + true; // 5 + } // 6 + private Object obj() { return "abc"; } // 7 + } // 8 + """, + "PatternMatching", + new TestCase.MethodData("test", List.of(3, 6), true, new DetailedValidator( + "line: 3", + "ALOAD_0", + "INVOKEVIRTUAL", + "ASTORE", + "ALOAD", + "INSTANCEOF", + "IFEQ", + "ALOAD", + "CHECKCAST", + "ASTORE_3", + "ICONST_1", + "GOTO", + "ICONST_0", + "ISTORE_2", + "line: 6", + "RETURN"))), + new TestCase(""" + public class PatternMatching { // 1 + private void test(String s) { // 2 + boolean isLong = true // 3 + && // 4 + obj() instanceof String str; // 5 + } // 6 + private Object obj() { return "abc"; } // 7 + } // 8 + """, + "PatternMatching", + new TestCase.MethodData("test", List.of(5, 6), true, new DetailedValidator( + "line: 5", + "ALOAD_0", + "INVOKEVIRTUAL", + "ASTORE", + "ALOAD", + "INSTANCEOF", + "IFEQ", + "ALOAD", + "CHECKCAST", + "ASTORE_3", + "ICONST_1", + "GOTO", + "ICONST_0", + "ISTORE_2", + "line: 6", + "RETURN"))), + new TestCase(""" + public class PatternMatching { // 1 + private void test(String s) { // 2 + boolean isLong = obj() instanceof String str1 // 3 + && // 4 + obj() instanceof String str2; // 5 + } // 6 + private Object obj() { return "abc"; } // 7 + } // 8 + """, + "PatternMatching", + new TestCase.MethodData("test", List.of(3, 5, 6), true, new DetailedValidator( + "line: 3", + "ALOAD_0", + "INVOKEVIRTUAL", + "ASTORE", + "ALOAD", + "INSTANCEOF", + "IFEQ", + "ALOAD", + "CHECKCAST", + "ASTORE", + "line: 5", + "ALOAD_0", + "INVOKEVIRTUAL", + "ASTORE", + "ALOAD", + "INSTANCEOF", + "IFEQ", + "ALOAD", + "CHECKCAST", + "ASTORE_3", + "ICONST_1", + "GOTO", + "ICONST_0", + "ISTORE_2", + "line: 6", + "RETURN"))), + }; + + private static final class DetailedValidator implements BiConsumer { + + private final List expectedMethodContent; + + public DetailedValidator(String... expectedMethodContent) { + this.expectedMethodContent = List.of(expectedMethodContent); + } + + @Override + public void accept(ClassModel classFile, MethodModel m) { + CodeModel code = (CodeModel) m.code().get(); + List methodContent = new ArrayList<>(); + for (CodeElement el : code) { + switch (el) { + case Instruction instr -> methodContent.add(instr.opcode().name()); + case LineNumber ln -> methodContent.add("line: " + ln.line()); + case CodeElement _ -> {} + } + } + methodContent.forEach(System.err::println); + tb.checkEqual(expectedMethodContent, methodContent); + } + } +} diff --git a/test/langtools/tools/javac/classfiles/attributes/LineNumberTable/TestCase.java b/test/langtools/tools/javac/classfiles/attributes/LineNumberTable/TestCase.java index 94d33c1cf8ca..0bb4303f5174 100644 --- a/test/langtools/tools/javac/classfiles/attributes/LineNumberTable/TestCase.java +++ b/test/langtools/tools/javac/classfiles/attributes/LineNumberTable/TestCase.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -21,10 +21,13 @@ * questions. */ +import java.lang.classfile.ClassModel; +import java.lang.classfile.MethodModel; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.function.BiConsumer; /** * TestCase contains source code to be compiled @@ -74,7 +77,11 @@ public MethodData findData(String methodName) { return null; } - record MethodData(String methodName, Collection expectedLines, boolean exactLines) { + record MethodData(String methodName, Collection expectedLines, boolean exactLines, BiConsumer validator) { + + public MethodData(String methodName, Collection expectedLines, boolean exactLines) { + this(methodName, expectedLines, exactLines, (_, _) -> {}); + } public MethodData { expectedLines = new HashSet<>(expectedLines); diff --git a/test/langtools/tools/javac/modules/EdgeCases.java b/test/langtools/tools/javac/modules/EdgeCases.java index 187ff5ed2773..4f5ca186f1b7 100644 --- a/test/langtools/tools/javac/modules/EdgeCases.java +++ b/test/langtools/tools/javac/modules/EdgeCases.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,7 +23,7 @@ /* * @test - * @bug 8154283 8167320 8171098 8172809 8173068 8173117 8176045 8177311 8241519 8297988 + * @bug 8154283 8167320 8171098 8172809 8173068 8173117 8176045 8177311 8241519 8297988 8387377 * @summary tests for multi-module mode compilation * @library /tools/lib * @modules @@ -37,7 +37,9 @@ */ import java.io.BufferedWriter; +import java.io.IOException; import java.io.Writer; +import java.net.URI; import java.nio.file.Files; import java.nio.file.FileSystems; import java.nio.file.Path; @@ -60,9 +62,14 @@ import javax.lang.model.element.TypeElement; import javax.lang.model.util.ElementFilter; import javax.lang.model.util.Elements; +import javax.tools.ForwardingJavaFileManager; import javax.tools.JavaCompiler; +import javax.tools.JavaCompiler.CompilationTask; +import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; +import javax.tools.SimpleJavaFileObject; import javax.tools.StandardJavaFileManager; +import javax.tools.StandardLocation; import javax.tools.ToolProvider; import com.sun.source.tree.CompilationUnitTree; @@ -1243,4 +1250,62 @@ public void finished(TaskEvent e) { }); }; } + + @Test //JDK-8387377 + public void testLargeNumberOfAutomaticModules(Path base) throws Exception { + final int moduleCount = 1_000; + Path lib = base.resolve("lib"); + + tb.createDirectories(lib); + + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + try (StandardJavaFileManager fm = compiler.getStandardFileManager(null, null, null)) { + JavaFileManager generateAutomaticModules = new ForwardingJavaFileManager(fm) { + @Override + public Iterable> listLocationsForModules(Location location) throws IOException { + if (location == StandardLocation.MODULE_PATH) { + Set modules = new HashSet<>(); + for (int i = 0; i < moduleCount; i++) { + modules.add(new AutomaticModuleLocation("m" + i)); + } + return List.of(modules); + } + return super.listLocationsForModules(location); + } + @Override + public String inferModuleName(Location location) throws IOException { + return location instanceof AutomaticModuleLocation aut + ? aut.name + : super.inferModuleName(location); + } + private class AutomaticModuleLocation implements Location { + private final String name; + public AutomaticModuleLocation(String name) { + this.name = name; + } + @Override + public String getName() { + return name; + } + @Override + public boolean isModuleOrientedLocation() { + return false; + } + @Override + public boolean isOutputLocation() { + return false; + } + } + }; + CompilationTask task = + compiler.getTask(null, + generateAutomaticModules, + null, + List.of("--add-modules", "ALL-MODULE-PATH"), + null, + List.of(SimpleJavaFileObject.forSource(URI.create("mem://Test.java"), + ""))); + task.call(); + } + } } diff --git a/test/langtools/tools/javac/patterns/GenericRecordDeconstructionPattern.java b/test/langtools/tools/javac/patterns/GenericRecordDeconstructionPattern.java index d87c025a7d6d..36de187906be 100644 --- a/test/langtools/tools/javac/patterns/GenericRecordDeconstructionPattern.java +++ b/test/langtools/tools/javac/patterns/GenericRecordDeconstructionPattern.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -48,6 +48,8 @@ void run() { testInference3(); assertEquals(1, runIfSuperBound(new Box<>(new StringBuilder()))); assertEquals(1, runIfSuperBound(new Box<>(0))); + assertEquals(1, new PairBox("", 0) + .selfSuperInference(new PairBox<>("x", 1))); } void runTest(Function, Integer> test) { @@ -124,6 +126,18 @@ sealed interface I {} record Box(V v) implements I { } + interface PairI {} + record PairBox(A a, B b) implements PairI { + int selfSuperInference(PairI p) { + if (p instanceof PairBox(var a, var b)) { + A aa = a; + B bb = b; + return 1; + } + return -1; + } + } + void assertEquals(Object expected, Object actual) { if (!Objects.equals(expected, actual)) { throw new AssertionError("Expected: " + expected + "," + diff --git a/test/lib-test/jdk/test/lib/security/DerUtilsTest.java b/test/lib-test/jdk/test/lib/security/DerUtilsTest.java new file mode 100644 index 000000000000..5ab6dcf59f4c --- /dev/null +++ b/test/lib-test/jdk/test/lib/security/DerUtilsTest.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.IOException; +import java.util.HexFormat; + +import jdk.test.lib.Asserts; +import jdk.test.lib.Utils; +import jdk.test.lib.security.DerUtils; +import sun.security.util.DerOutputStream; +import sun.security.util.DerValue; +import sun.security.util.KnownOIDs; +import sun.security.util.ObjectIdentifier; + +/* + * @test + * @bug 8381049 + * @library /test/lib + * @modules java.base/sun.security.util + * @summary Tests DerUtils navigation, assertions, and editing helpers + */ +public class DerUtilsTest { + + public static void main(String[] args) throws Exception { + //0000:0015 [] SEQUENCE + //0002:0004 [0] OID 1.2.3 + //0006:0003 [1] INTEGER 1 + //0009:000C [2] OCTET STRING + // >>> into 10 octets + //000B:000A [2c] SEQUENCE + //000D:0005 [2c0] OID 2.5.4.3 (CommonName) + //0012:0003 [2c1] INTEGER 2 + byte[] der = bytes("30 13 06022a03 020101 04 0a 30 08 0603550403 020102"); + + // Test innerDerValue + Asserts.assertEQ(DerUtils.innerDerValue(der, "0").getOID(), + ObjectIdentifier.of("1.2.3")); + Asserts.assertEQ(DerUtils.innerDerValue(der, "1").getInteger(), 1); + Asserts.assertEQ(DerUtils.innerDerValue(der, "2c0").getOID(), + ObjectIdentifier.of(KnownOIDs.CommonName)); + Asserts.assertEQ(DerUtils.innerDerValue(der, "2c1").getInteger(), 2); + Asserts.assertTrue(DerUtils.innerDerValue(der, "3") == null); + + // Test checks + DerUtils.checkAlg(der, "0", ObjectIdentifier.of("1.2.3")); + DerUtils.checkInt(der, "1", 1); + DerUtils.checkAlg(der, "2c0", ObjectIdentifier.of(KnownOIDs.CommonName)); + DerUtils.checkInt(der, "2c1", 2); + DerUtils.shouldNotExist(der, "3"); + + // Test edit + der = DerUtils.edit(der, "0", oidValue("1.2.3.4")); + Asserts.assertEqualsByteArray( + bytes("30 14 06032a0304 020101 04 0a 30 08 0603550403 020102"), der); + + der = DerUtils.edit(der, "2c1", intValue(8)); + Asserts.assertEqualsByteArray( + bytes("30 14 06032a0304 020101 04 0a 30 08 0603550403 020108"), der); + + der = DerUtils.edit(der, "1", null); + Asserts.assertEqualsByteArray( + bytes("30 11 06032a0304 04 0a 30 08 0603550403 020108"), der); + + // Test insert + der = DerUtils.insert(der, "0", oidValue("1.2.5")); + Asserts.assertEqualsByteArray( + bytes("30 15 06022a05 06032a0304 04 0a 30 08 0603550403 020108"), der); + + der = DerUtils.insert(der, "2c1", intValue(9)); + Asserts.assertEqualsByteArray( + bytes("30 18 06022a05 06032a0304 04 0d 30 0b 0603550403 020109 020108"), der); + + der = DerUtils.insert(der, "2c1", oidValue("1.2.6")); + Asserts.assertEqualsByteArray( + bytes("30 1c 06022a05 06032a0304 04 11 30 0f 0603550403 06022a06 020109 020108"), der); + + // Cannot insert into a position ends with "c" + var derClone = der.clone(); // non-final reference cannot be used in lambda + Utils.runAndCheckException(() -> DerUtils.insert(derClone, "2c", + oidValue("1.2.7")), IOException.class); + } + + static DerValue oidValue(String oid) throws IOException { + return DerValue.wrap(new DerOutputStream() + .putOID(ObjectIdentifier.of(oid)).toByteArray()); + } + + static DerValue intValue(int value) throws IOException { + return DerValue.wrap(new DerOutputStream() + .putInteger(value).toByteArray()); + } + + public static byte[] bytes(String hex) { + return HexFormat.of().parseHex(hex.replace(" ", "")); + } +} diff --git a/test/lib/jdk/test/lib/security/CertificateBuilder.java b/test/lib/jdk/test/lib/security/CertificateBuilder.java index 6bf554c3517e..86aaba2a0b15 100644 --- a/test/lib/jdk/test/lib/security/CertificateBuilder.java +++ b/test/lib/jdk/test/lib/security/CertificateBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,6 +24,7 @@ package jdk.test.lib.security; import java.io.*; +import java.net.IDN; import java.security.cert.*; import java.security.cert.Extension; import java.util.*; @@ -41,11 +42,15 @@ import sun.security.x509.AlgorithmId; import sun.security.x509.AuthorityInfoAccessExtension; import sun.security.x509.AuthorityKeyIdentifierExtension; +import sun.security.x509.CRLDistributionPointsExtension; +import sun.security.x509.GeneralSubtrees; import sun.security.x509.IPAddressName; +import sun.security.x509.NameConstraintsExtension; import sun.security.x509.SubjectKeyIdentifierExtension; import sun.security.x509.BasicConstraintsExtension; import sun.security.x509.CertificateSerialNumber; import sun.security.x509.ExtendedKeyUsageExtension; +import sun.security.x509.DistributionPoint; import sun.security.x509.DNSName; import sun.security.x509.GeneralName; import sun.security.x509.GeneralNames; @@ -58,13 +63,13 @@ /** * Helper class that builds and signs X.509 certificates. - * + *

    * A CertificateBuilder is created with a default constructor, and then * uses additional public methods to set the public key, desired validity * dates, serial number and extensions. It is expected that the caller will * have generated the necessary key pairs prior to using a CertificateBuilder * to generate certificates. - * + *

    * The following methods are mandatory before calling build(): *

      *
    • {@link #setSubjectName(java.lang.String)} @@ -78,12 +83,12 @@ * Additionally, the caller can either provide a {@link List} of * {@link Extension} objects, or use the helper classes to add specific * extension types. - * + *

      * When all required and desired parameters are set, the * {@link #build(java.security.cert.X509Certificate, java.security.PrivateKey, * java.lang.String)} method can be used to create the {@link X509Certificate} * object. - * + *

      * Multiple certificates may be cut from the same settings using subsequent * calls to the build method. Settings may be cleared using the * {@link #reset()} method. @@ -109,20 +114,23 @@ public enum KeyUsage { KEY_CERT_SIGN, CRL_SIGN, ENCIPHER_ONLY, - DECIPHER_ONLY; + DECIPHER_ONLY } /** - * Create a new CertificateBuilder instance. This method sets the subject name, - * public key, authority key id, and serial number. + * Create a new {@code CertificateBuilder} instance. This method sets the + * subject name, public key, authority key id, and serial number. * * @param subjectName entity associated with the public key * @param publicKey the entity's public key * @param caKey public key of certificate signer * @param keyUsages list of key uses - * @return - * @throws CertificateException - * @throws IOException + * @return a {@code CertificateBuilder} configured with the provided + * parameters + * + * @throws CertificateException if an error occurs when obtaining the + * underlying {@link CertificateFactory} + * @throws IOException if any extension encoding errors occur */ public static CertificateBuilder newCertificateBuilder(String subjectName, PublicKey publicKey, PublicKey caKey, KeyUsage... keyUsages) @@ -148,9 +156,13 @@ public static CertificateBuilder newCertificateBuilder(String subjectName, /** * Create a Subject Alternative Name extension for the given DNS name + * * @param critical Sets the extension to critical or non-critical - * @param dnsName DNS name to use in the extension - * @throws IOException + * @param dnsNames one or more DNS names to use in the extension + * @return a {@code SubjectAlternativeNameExtension} configured with + * the {@code dnsNames} as individual DNSName entries. + * + * @throws IOException if any encoding errors occur */ public static SubjectAlternativeNameExtension createDNSSubjectAltNameExt( boolean critical, String... dnsNames) throws IOException { @@ -163,9 +175,13 @@ public static SubjectAlternativeNameExtension createDNSSubjectAltNameExt( /** * Create a Subject Alternative Name extension for the given IP address + * * @param critical Sets the extension to critical or non-critical - * @param ipAddresses IP addresses to use in the extension - * @throws IOException + * @param ipAddresses one or more IP addresses to use in the extension + * @return a {@code SubjectAlternativeNameExtension} configured with + * the {@code ipAddresses} as individual IPAddressName entries. + * + * @throws IOException if any encoding errors occur */ public static SubjectAlternativeNameExtension createIPSubjectAltNameExt( boolean critical, String... ipAddresses) throws IOException { @@ -212,6 +228,9 @@ public CertificateBuilder setSubjectName(X500Principal name) { * Set the subject name for the certificate. * * @param name The subject name in RFC 2253 format + * + * @throws IllegalArgumentException if any parsing errors on the + * {@code name} parameter occur. */ public CertificateBuilder setSubjectName(String name) { try { @@ -238,6 +257,9 @@ public CertificateBuilder setSubjectName(X500Name name) { * Set the public key for this certificate. * * @param pubKey The {@link PublicKey} to be used on this certificate. + * + * @throws NullPointerException if the {@code pubKey} parameter + * is {@code null} */ public CertificateBuilder setPublicKey(PublicKey pubKey) { publicKey = Objects.requireNonNull(pubKey, "Caught null public key"); @@ -249,6 +271,9 @@ public CertificateBuilder setPublicKey(PublicKey pubKey) { * * @param nbDate A {@link Date} object specifying the start of the * certificate validity period. + * + * @throws NullPointerException if the {@code nbDate} parameter + * is {@code null} */ public CertificateBuilder setNotBefore(Date nbDate) { Objects.requireNonNull(nbDate, "Caught null notBefore date"); @@ -261,6 +286,9 @@ public CertificateBuilder setNotBefore(Date nbDate) { * * @param naDate A {@link Date} object specifying the end of the * certificate validity period. + * + * @throws NullPointerException if the {@code naDate} parameter + * is {@code null} */ public CertificateBuilder setNotAfter(Date naDate) { Objects.requireNonNull(naDate, "Caught null notAfter date"); @@ -275,6 +303,9 @@ public CertificateBuilder setNotAfter(Date naDate) { * certificate validity period. * @param naDate A {@link Date} object specifying the end of the * certificate validity period. + * + * @throws NullPointerException if either the {@code nbDate} or + * {@code naDate} parameters are {@code null} */ public CertificateBuilder setValidity(Date nbDate, Date naDate) { return setNotBefore(nbDate).setNotAfter(naDate); @@ -289,6 +320,8 @@ public CertificateBuilder setOneHourValidity() { * Set the serial number on the certificate. * * @param serial A serial number in {@link BigInteger} form. + * + * @throws NullPointerException if {@code serial} is {@code null} */ public CertificateBuilder setSerialNumber(BigInteger serial) { Objects.requireNonNull(serial, "Caught null serial number"); @@ -313,6 +346,8 @@ public CertificateBuilder addExtension(Extension ext) { * * @param extList The {@link List} of extensions to be added to * the certificate. + * + * @throws NullPointerException if {@code extList} is {@code null} */ public CertificateBuilder addExtensions(List extList) { Objects.requireNonNull(extList, "Caught null extension list"); @@ -334,7 +369,8 @@ public CertificateBuilder addSubjectAltNameDNSExt(List dnsNames) if (!dnsNames.isEmpty()) { GeneralNames gNames = new GeneralNames(); for (String name : dnsNames) { - gNames.add(new GeneralName(new DNSName(name))); + gNames.add(new GeneralName(new DNSName(new DerValue( + DerValue.tag_IA5String, IDN.toASCII(name))))); } addExtension(new SubjectAlternativeNameExtension(false, gNames)); @@ -347,6 +383,7 @@ public CertificateBuilder addSubjectAltNameDNSExt(List dnsNames) * * @param ipAddresses A {@code List} of names to add as IPAddress * types + * * @throws IOException if an encoding error occurs. */ public CertificateBuilder addSubjectAltNameIPExt(List ipAddresses) @@ -362,13 +399,41 @@ public CertificateBuilder addSubjectAltNameIPExt(List ipAddresses) return this; } + /** + * Helper method to add one or more distribution points to the CRL + * Distribution Points extension. This form of the method only supports + * URI name types, but can be extended in the future to support other types. + * + * @param uriNames a list of URIs in String form + * @return the {@code CertificateBuilder} configured to add this + * CRL Distribution Points extension. + * + * @throws IOException if any of the URIs in {@code uriNames} are + * malformed + */ + public CertificateBuilder addCrlDistributionPointsExt(List uriNames) + throws IOException { + if (uriNames != null && !uriNames.isEmpty()) { + GeneralNames gNames = new GeneralNames(); + for (String name : uriNames) { + gNames.add(new GeneralName(new URIName(name))); + } + addExtension(new CRLDistributionPointsExtension(List.of( + new DistributionPoint(gNames, null, null)))); + } + return this; + } + /** * Helper method to add one or more OCSP URIs to the Authority Info Access * certificate extension. Location strings can be in two forms: - * 1) Just a URI by itself: This will be treated as using the OCSP + *

        + *
      1. Just a URI by itself: This will be treated as using the OCSP * access description (legacy behavior). - * 2) An access description name (case-insensitive) followed by a - * pipe (|) and the URI (e.g. OCSP|http://ocsp.company.com/revcheck). + *
      2. An access description name (case-insensitive) followed by a + * pipe (|) and the URI (e.g. + * {@code OCSP|http://ocsp.company.com/revcheck}). + *
      * Current description names are OCSP and CAISSUER. Others may be * added later. * @@ -389,16 +454,12 @@ public CertificateBuilder addAIAExt(List locations) adObj = AccessDescription.Ad_OCSP_Id; uriLoc = tokens[0]; } else { - switch (tokens[0].toUpperCase()) { - case "OCSP": - adObj = AccessDescription.Ad_OCSP_Id; - break; - case "CAISSUER": - adObj = AccessDescription.Ad_CAISSUERS_Id; - break; - default: - throw new IOException("Unknown AD: " + tokens[0]); - } + adObj = switch (tokens[0].toUpperCase()) { + case "OCSP" -> AccessDescription.Ad_OCSP_Id; + case "CAISSUER" -> AccessDescription.Ad_CAISSUERS_Id; + default -> throw new IOException("Unknown AD: " + + tokens[0]); + }; uriLoc = tokens[1]; } acDescList.add(new AccessDescription(adObj, @@ -437,6 +498,17 @@ public CertificateBuilder addBasicConstraintsExt(boolean crit, boolean isCA, maxPathLen)); } + /** + * Set the Name Constraints Extension for a certificate. + * + * @param permitted permitted names + * @param excluded excluded names + */ + public CertificateBuilder addNameConstraintsExt( + GeneralSubtrees permitted, GeneralSubtrees excluded) { + return addExtension(new NameConstraintsExtension(permitted, excluded)); + } + /** * Add the Authority Key Identifier extension. * @@ -554,7 +626,7 @@ public X509Certificate build(X509Certificate issuerCert, } /** - * Encode the contents of the outer-most ASN.1 SEQUENCE: + * Encode the contents of the outermost ASN.1 SEQUENCE: * *
            *  Certificate  ::=  SEQUENCE  {
      diff --git a/test/lib/jdk/test/lib/security/DerUtils.java b/test/lib/jdk/test/lib/security/DerUtils.java
      index 234130aa4ca7..2323065b5952 100644
      --- a/test/lib/jdk/test/lib/security/DerUtils.java
      +++ b/test/lib/jdk/test/lib/security/DerUtils.java
      @@ -1,5 +1,5 @@
       /*
      - * Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved.
      + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved.
        * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
        *
        * This code is free software; you can redistribute it and/or modify it
      @@ -24,6 +24,7 @@
       
       import jdk.test.lib.Asserts;
       import sun.security.util.DerInputStream;
      +import sun.security.util.DerOutputStream;
       import sun.security.util.DerValue;
       import sun.security.util.KnownOIDs;
       import sun.security.util.ObjectIdentifier;
      @@ -124,4 +125,84 @@ public static void shouldNotExist(byte[] der, String location)
                   throws Exception {
               Asserts.assertTrue(innerDerValue(der, location) == null);
           }
      +
      +    /// Replaces a `DerValue` (deep) inside with another one.
      +    ///
      +    /// @param data the `DerValue`
      +    /// @param target the location to edit. Cannot be empty.
      +    /// @param replacement replace the value at `target` with this. Can be a `DerValue`
      +    ///         or a `DerOutputStream`. Remove if `null`.
      +    /// @return the new value
      +    public static byte[] edit(byte[] data, String target, Object replacement)
      +            throws IOException {
      +        if (target.isEmpty()) throw new IOException("Must be a sub-location");
      +        return modify0(data, "", target, replacement, false).toByteArray();
      +    }
      +
      +    /// Inserts a `DerValue` (deep) into another one.
      +    ///
      +    /// @param data the `DerValue`
      +    /// @param target the location to insert at. Cannot be empty. The new value
      +    ///        is inserted before the existing value at `target`, and following
      +    ///        values are shifted. After insertion, the value at `target`
      +    ///        is the inserted value. A target ending exactly with `c` is not
      +    ///        a valid insertion position because the content of an OCTET
      +    ///        STRING must be only one `DerValue`.
      +    /// @param addition the value to insert. Can be a `DerValue` or a
      +    ///        `DerOutputStream`
      +    /// @return the new value
      +    public static byte[] insert(byte[] data, String target, Object addition)
      +            throws IOException {
      +        if (target.isEmpty()) throw new IOException("Must be a sub-location");
      +        return modify0(data, "", target, addition, true).toByteArray();
      +    }
      +
      +    /// Implementation of [#edit] and [#insert], recursively.
      +    ///
      +    /// @param data the `DerValue`
      +    /// @param now the current location
      +    /// @param target the location to edit or insert at
      +    /// @param replacement the replacement or inserted value
      +    /// @param insert true to insert before the target, false to replace it
      +    /// @return the new value at this location
      +    private static DerOutputStream modify0(byte[] data, String now, String target,
      +            Object replacement, boolean insert) throws IOException {
      +        var out = new DerOutputStream();
      +        var parent = DerUtils.innerDerValue(data, now);
      +        if (target.equals(now + "c")) {
      +            if (insert) {
      +                throw new IOException("Action cannot be performed at position " + target);
      +            }
      +            if (replacement instanceof DerValue v) {
      +                out.putDerValue(v);
      +            } else if (replacement instanceof DerOutputStream s) {
      +                out.write(s);
      +            }
      +        } else if (target.startsWith(now + "c")) { // not there yet, go inside
      +            return out.write(parent.tag, modify0(data, now + "c", target, replacement, insert));
      +        } else {
      +            for (int i = 0; ; i++) {
      +                // We only support locations of one digit now
      +                if (i > 9) throw new IllegalStateException("Too big " + i);
      +                String pos = now + i;
      +                var sub = DerUtils.innerDerValue(data, pos); // current value
      +                if (sub == null) break; // at the end
      +                if (target.equals(pos)) { // the one we want to change
      +                    if (replacement instanceof DerValue v) {
      +                        out.putDerValue(v);
      +                    } else if (replacement instanceof DerOutputStream s) {
      +                        out.write(s);
      +                    }
      +                    if (insert) {
      +                        out.putDerValue(sub);
      +                    }
      +                } else if (target.startsWith(pos)) { // not there yet, go inside
      +                    out.write(modify0(data, pos, target, replacement, insert));
      +                } else { // the untouched values
      +                    out.putDerValue(sub);
      +                }
      +            }
      +        }
      +        return new DerOutputStream().write(parent.tag, out);
      +    }
       }
      diff --git a/test/lib/jdk/test/lib/security/timestamp/TsaHandler.java b/test/lib/jdk/test/lib/security/timestamp/TsaHandler.java
      index d42d9f031d47..fa3d6abe599b 100644
      --- a/test/lib/jdk/test/lib/security/timestamp/TsaHandler.java
      +++ b/test/lib/jdk/test/lib/security/timestamp/TsaHandler.java
      @@ -1,5 +1,5 @@
       /*
      - * Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved.
      + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved.
        * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
        *
        * This code is free software; you can redistribute it and/or modify it
      @@ -148,7 +148,6 @@ protected SignerEntry createSignerEntry(String alias) throws Exception {
            */
           protected TsaParam getParam(URI uri) {
               String query = uri.getQuery();
      -
               TsaParam param = TsaParam.newInstance();
               if (query != null) {
                   for (String bufParam : query.split("&")) {
      @@ -186,6 +185,12 @@ protected TsaParam getParam(URI uri) {
                       } else if ("certReq".equalsIgnoreCase(pair[0])) {
                           param.certReq(Boolean.valueOf(pair[1]));
                           System.out.println("certReq: " + param.certReq());
      +                } else if ("noSignedAttrs".equalsIgnoreCase(pair[0])) {
      +                    param.noSignedAttrs(Boolean.valueOf(pair[1]));
      +                    System.out.println("noSignedAttrs: " + param.noSignedAttrs());
      +                } else if ("notTimestampOID".equalsIgnoreCase(pair[0])) {
      +                    param.notTimestampOID(Boolean.valueOf(pair[1]));
      +                    System.out.println("notTimestampOID: " + param.notTimestampOID());
                       }
                   }
               }
      diff --git a/test/lib/jdk/test/lib/security/timestamp/TsaParam.java b/test/lib/jdk/test/lib/security/timestamp/TsaParam.java
      index fca684bd5e6d..8838fb71a3a7 100644
      --- a/test/lib/jdk/test/lib/security/timestamp/TsaParam.java
      +++ b/test/lib/jdk/test/lib/security/timestamp/TsaParam.java
      @@ -1,5 +1,5 @@
       /*
      - * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
      + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved.
        * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
        *
        * This code is free software; you can redistribute it and/or modify it
      @@ -71,6 +71,12 @@ public class TsaParam {
           // Indicate if request TSA server certificate
           private Boolean certReq;
       
      +    // Do not create signedAttrs
      +    private Boolean noSignedAttrs;
      +
      +    // Do not use TIMESTAMP_TOKEN_INFO_OID
      +    private Boolean notTimestampOID;
      +
           public static TsaParam newInstance() {
               return new TsaParam();
           }
      @@ -177,4 +183,22 @@ public TsaParam certReq(Boolean certReq) {
               this.certReq = certReq;
               return this;
           }
      +
      +    public Boolean noSignedAttrs() {
      +        return noSignedAttrs;
      +    }
      +
      +    public TsaParam noSignedAttrs(Boolean noSignedAttrs) {
      +        this.noSignedAttrs = noSignedAttrs;
      +        return this;
      +    }
      +
      +    public Boolean notTimestampOID() {
      +        return notTimestampOID;
      +    }
      +
      +    public TsaParam notTimestampOID(Boolean notTimestampOID) {
      +        this.notTimestampOID = notTimestampOID;
      +        return this;
      +    }
       }
      diff --git a/test/lib/jdk/test/lib/security/timestamp/TsaSigner.java b/test/lib/jdk/test/lib/security/timestamp/TsaSigner.java
      index 22d630f3aa88..7ec2609e779f 100644
      --- a/test/lib/jdk/test/lib/security/timestamp/TsaSigner.java
      +++ b/test/lib/jdk/test/lib/security/timestamp/TsaSigner.java
      @@ -1,5 +1,5 @@
       /*
      - * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
      + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved.
        * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
        *
        * This code is free software; you can redistribute it and/or modify it
      @@ -23,8 +23,8 @@
       
       package jdk.test.lib.security.timestamp;
       
      -import java.io.ByteArrayOutputStream;
       import java.math.BigInteger;
      +import java.security.MessageDigest;
       import java.security.Signature;
       import java.security.cert.X509Certificate;
       import java.util.Date;
      @@ -33,6 +33,8 @@
       import jdk.test.lib.hexdump.HexPrinter;
       import sun.security.pkcs.ContentInfo;
       import sun.security.pkcs.PKCS7;
      +import sun.security.pkcs.PKCS9Attribute;
      +import sun.security.pkcs.PKCS9Attributes;
       import sun.security.pkcs.SignerInfo;
       import sun.security.util.*;
       import sun.security.x509.AlgorithmId;
      @@ -202,8 +204,11 @@ private byte[] createResponse(TsaParam requestParam) throws Exception {
                   DerOutputStream eContentOut = new DerOutputStream();
                   eContentOut.putOctetString(tstInfoSeqData);
       
      +            ObjectIdentifier infoOid = respParam.notTimestampOID() == Boolean.TRUE
      +                    ? ContentInfo.DATA_OID
      +                    : ContentInfo.TIMESTAMP_TOKEN_INFO_OID;
                   ContentInfo eContentInfo = new ContentInfo(
      -                    ObjectIdentifier.of(KnownOIDs.TimeStampTokenInfo),
      +                    infoOid,
                           new DerValue(eContentOut.toByteArray()));
       
                   String defaultSigAlgo =  SignatureUtil.getDefaultSigAlgForKey(
      @@ -213,16 +218,36 @@ private byte[] createResponse(TsaParam requestParam) throws Exception {
                   System.out.println(
                           "Signature algorithm: " + signature.getAlgorithm());
                   signature.initSign(signerEntry.privateKey);
      -            signature.update(tstInfoSeqData);
      +
      +            AlgorithmId digestAlg = SignatureUtil.getDigestAlgInPkcs7SignerInfo(
      +                    signature, sigAlgo, signerEntry.privateKey,
      +                    signerEntry.cert.getPublicKey(), false);
      +
      +            PKCS9Attributes authAttrs = null;
      +
      +            if (respParam.noSignedAttrs() == Boolean.TRUE) {
      +                signature.update(tstInfoSeqData);
      +            } else {
      +                authAttrs = new PKCS9Attributes(new PKCS9Attribute[]{
      +                        new PKCS9Attribute(PKCS9Attribute.CONTENT_TYPE_OID,
      +                                infoOid),
      +                        new PKCS9Attribute(PKCS9Attribute.SIGNING_TIME_OID,
      +                                new Date()),
      +                        new PKCS9Attribute(PKCS9Attribute.MESSAGE_DIGEST_OID,
      +                                MessageDigest.getInstance(digestAlg.getName())
      +                                        .digest(tstInfoSeqData))
      +                });
      +                signature.update(authAttrs.getDerEncoding());
      +            }
       
                   SignerInfo signerInfo = new SignerInfo(
                           new X500Name(issuerName),
                           signerEntry.cert.getSerialNumber(),
      -                    SignatureUtil.getDigestAlgInPkcs7SignerInfo(
      -                            signature, sigAlgo, signerEntry.privateKey,
      -                            signerEntry.cert.getPublicKey(), false),
      +                    digestAlg,
      +                    authAttrs,
                           AlgorithmId.get(sigAlgo),
      -                    signature.sign());
      +                    signature.sign(),
      +                    null);
       
                   X509Certificate[] signerCertChain = interceptor.getSignerCertChain(
                           signerEntry.certChain, requestParam.certReq());
      diff --git a/test/micro/org/openjdk/bench/javax/imageio/plugins/jpeg/LargeJpegReadWithProgressBench.java b/test/micro/org/openjdk/bench/javax/imageio/plugins/jpeg/LargeJpegReadWithProgressBench.java
      new file mode 100644
      index 000000000000..70f8020f358c
      --- /dev/null
      +++ b/test/micro/org/openjdk/bench/javax/imageio/plugins/jpeg/LargeJpegReadWithProgressBench.java
      @@ -0,0 +1,166 @@
      +/*
      + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
      + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
      + *
      + * This code is free software; you can redistribute it and/or modify it
      + * under the terms of the GNU General Public License version 2 only, as
      + * published by the Free Software Foundation.
      + *
      + * This code is distributed in the hope that it will be useful, but WITHOUT
      + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
      + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
      + * version 2 for more details (a copy is included in the LICENSE file that
      + * accompanied this code).
      + *
      + * You should have received a copy of the GNU General Public License version
      + * 2 along with this work; if not, write to the Free Software Foundation,
      + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
      + *
      + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
      + * or visit www.oracle.com if you need additional information or have any
      + * questions.
      + */
      +package org.openjdk.bench.javax.imageio.plugins.jpeg;
      +
      +import java.awt.Color;
      +import java.awt.image.BufferedImage;
      +import java.io.File;
      +import java.io.IOException;
      +import java.util.Iterator;
      +
      +import java.util.concurrent.TimeUnit;
      +
      +import org.openjdk.jmh.annotations.Benchmark;
      +import org.openjdk.jmh.annotations.BenchmarkMode;
      +import org.openjdk.jmh.annotations.Fork;
      +import org.openjdk.jmh.annotations.Measurement;
      +import org.openjdk.jmh.annotations.Mode;
      +import org.openjdk.jmh.annotations.OutputTimeUnit;
      +import org.openjdk.jmh.annotations.Scope;
      +import org.openjdk.jmh.annotations.Setup;
      +import org.openjdk.jmh.annotations.State;
      +import org.openjdk.jmh.annotations.Warmup;
      +import org.openjdk.jmh.infra.Blackhole;
      +
      +import javax.imageio.ImageIO;
      +import javax.imageio.ImageReader;
      +import javax.imageio.event.IIOReadProgressListener;
      +import javax.imageio.stream.ImageInputStream;
      +
      +/**
      + * Measure time taken to read large jpeg image
      + * make test TEST="micro:javax.imageio.plugins.jpeg.LargeJpegReadWithProgressBench"
      + */
      +@BenchmarkMode(Mode.AverageTime)
      +@OutputTimeUnit(TimeUnit.MILLISECONDS)
      +@Warmup(iterations = 5, time = 1)
      +@Measurement(iterations = 5, time = 1)
      +@Fork(3)
      +@State(Scope.Benchmark)
      +public class LargeJpegReadWithProgressBench {
      +
      +    private static final File pwd = new File(".");
      +    private static ImageReader reader;
      +
      +    @Setup
      +    public void setup() throws IOException {
      +        BufferedImage src = createSource();
      +        ImageInputStream iis = prepareInput(src);
      +        reader = null;
      +        Iterator it = ImageIO.getImageReadersByFormatName("jpeg");
      +        if (it.hasNext()) {
      +            reader = (ImageReader)it.next();
      +        } else {
      +            throw new RuntimeException("Could not find JPEG reader");
      +        }
      +        reader.setInput(iis);
      +        ImageReadProgressListener listener = new ImageReadProgressListener();
      +        reader.addIIOReadProgressListener(listener);
      +    }
      +
      +    @Benchmark
      +    public void readLargeJpegImage(Blackhole bh) throws IOException {
      +        reader.read(0);
      +    }
      +
      +    private static BufferedImage createSource() {
      +        int width = 2000;
      +        int height = 2000;
      +        int squareSize = 20;
      +
      +        Color red = Color.RED;
      +        Color green = Color.GREEN;
      +        BufferedImage image = new BufferedImage(width, height,
      +            BufferedImage.TYPE_INT_RGB);
      +        for (int y = 0; y < height; y++) {
      +            for (int x = 0; x < width; x++) {
      +                if (((x / squareSize) + (y / squareSize)) % 2 == 0) {
      +                    image.setRGB(x, y, red.getRGB());
      +                } else {
      +                    image.setRGB(x, y, green.getRGB());
      +                }
      +            }
      +        }
      +        return image;
      +    }
      +
      +    private static ImageInputStream prepareInput(BufferedImage src)
      +        throws IOException {
      +        File f = File.createTempFile("src_", ".jpeg", pwd);
      +        if (ImageIO.write(src, "jpeg", f)) {
      +            ImageInputStream iis = ImageIO.createImageInputStream(f);
      +            f.deleteOnExit();
      +            return iis;
      +        } else {
      +            throw new RuntimeException("Unable to write jpeg image");
      +        }
      +    }
      +}
      +
      +class ImageReadProgressListener implements IIOReadProgressListener {
      +    // This class is a no-op, it is added just to have a progress listener
      +    @Override
      +    public void sequenceStarted(ImageReader source, int minIndex) {
      +
      +    }
      +
      +    @Override
      +    public void sequenceComplete(ImageReader source) {
      +
      +    }
      +
      +    @Override
      +    public void imageStarted(ImageReader source, int imageIndex) {
      +
      +    }
      +
      +    @Override
      +    public void imageProgress(ImageReader source, float percentageDone) {
      +
      +    }
      +
      +    @Override
      +    public void imageComplete(ImageReader source) {
      +
      +    }
      +
      +    @Override
      +    public void thumbnailStarted(ImageReader source, int imageIndex, int thumbnailIndex) {
      +
      +    }
      +
      +    @Override
      +    public void thumbnailProgress(ImageReader source, float percentageDone) {
      +
      +    }
      +
      +    @Override
      +    public void thumbnailComplete(ImageReader source) {
      +
      +    }
      +
      +    @Override
      +    public void readAborted(ImageReader source) {
      +
      +    }
      +}
      diff --git a/test/micro/org/openjdk/bench/javax/imageio/plugins/jpeg/LargeJpegReadWriteBench.java b/test/micro/org/openjdk/bench/javax/imageio/plugins/jpeg/LargeJpegReadWriteBench.java
      new file mode 100644
      index 000000000000..8a84eab4da7e
      --- /dev/null
      +++ b/test/micro/org/openjdk/bench/javax/imageio/plugins/jpeg/LargeJpegReadWriteBench.java
      @@ -0,0 +1,141 @@
      +/*
      + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
      + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
      + *
      + * This code is free software; you can redistribute it and/or modify it
      + * under the terms of the GNU General Public License version 2 only, as
      + * published by the Free Software Foundation.
      + *
      + * This code is distributed in the hope that it will be useful, but WITHOUT
      + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
      + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
      + * version 2 for more details (a copy is included in the LICENSE file that
      + * accompanied this code).
      + *
      + * You should have received a copy of the GNU General Public License version
      + * 2 along with this work; if not, write to the Free Software Foundation,
      + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
      + *
      + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
      + * or visit www.oracle.com if you need additional information or have any
      + * questions.
      + */
      +package org.openjdk.bench.javax.imageio.plugins.jpeg;
      +
      +import java.awt.Color;
      +import java.awt.image.BufferedImage;
      +import java.io.File;
      +import java.io.IOException;
      +import java.util.Iterator;
      +
      +import java.util.concurrent.TimeUnit;
      +
      +import org.openjdk.jmh.annotations.Benchmark;
      +import org.openjdk.jmh.annotations.BenchmarkMode;
      +import org.openjdk.jmh.annotations.Fork;
      +import org.openjdk.jmh.annotations.Measurement;
      +import org.openjdk.jmh.annotations.Mode;
      +import org.openjdk.jmh.annotations.OutputTimeUnit;
      +import org.openjdk.jmh.annotations.Scope;
      +import org.openjdk.jmh.annotations.Setup;
      +import org.openjdk.jmh.annotations.State;
      +import org.openjdk.jmh.annotations.Warmup;
      +import org.openjdk.jmh.infra.Blackhole;
      +
      +import javax.imageio.ImageIO;
      +import javax.imageio.ImageReader;
      +import javax.imageio.ImageWriter;
      +import javax.imageio.stream.ImageInputStream;
      +import javax.imageio.stream.ImageOutputStream;
      +
      +/**
      + * Measure time taken to read large jpeg image
      + * make test TEST="micro:javax.imageio.plugins.jpeg.LargeJpegReadWriteBench"
      + */
      +@BenchmarkMode(Mode.AverageTime)
      +@OutputTimeUnit(TimeUnit.MILLISECONDS)
      +@Warmup(iterations = 5, time = 1)
      +@Measurement(iterations = 5, time = 1)
      +@Fork(3)
      +@State(Scope.Benchmark)
      +public class LargeJpegReadWriteBench {
      +
      +    private static final File pwd = new File(".");
      +    private static ImageReader reader;
      +    private static ImageWriter writer;
      +    private static BufferedImage src;
      +
      +    @Setup
      +    public void setup() throws IOException {
      +        src = createSource();
      +        ImageInputStream iis = prepareInput(src);
      +        reader = null;
      +        Iterator readerIterator = ImageIO.getImageReadersByFormatName("jpeg");
      +        if (readerIterator.hasNext()) {
      +            reader = readerIterator.next();
      +        } else {
      +            throw new RuntimeException("Could not find JPEG reader");
      +        }
      +        reader.setInput(iis);
      +
      +        ImageOutputStream ios = prepareOutput(src);
      +        writer = null;
      +        Iterator writerIterator = ImageIO.getImageWritersByFormatName("jpeg");
      +        if (writerIterator.hasNext()) {
      +            writer = writerIterator.next();
      +        } else {
      +            throw new RuntimeException("Could not find JPEG writer");
      +        }
      +        writer.setOutput(ios);
      +    }
      +
      +    @Benchmark
      +    public void readLargeJpegImage(Blackhole bh) throws IOException {
      +        reader.read(0);
      +    }
      +
      +    @Benchmark
      +    public void writeLargeJpegImage(Blackhole bh) throws IOException {
      +        writer.write(src);
      +    }
      +
      +    private static BufferedImage createSource() {
      +        int width = 2000;
      +        int height = 2000;
      +        int squareSize = 20;
      +
      +        Color red = Color.RED;
      +        Color green = Color.GREEN;
      +        BufferedImage image = new BufferedImage(width, height,
      +            BufferedImage.TYPE_INT_RGB);
      +        for (int y = 0; y < height; y++) {
      +            for (int x = 0; x < width; x++) {
      +                if (((x / squareSize) + (y / squareSize)) % 2 == 0) {
      +                    image.setRGB(x, y, red.getRGB());
      +                } else {
      +                    image.setRGB(x, y, green.getRGB());
      +                }
      +            }
      +        }
      +        return image;
      +    }
      +
      +    private static ImageInputStream prepareInput(BufferedImage src)
      +        throws IOException {
      +        File f = File.createTempFile("src_", ".jpeg", pwd);
      +        if (ImageIO.write(src, "jpeg", f)) {
      +            ImageInputStream iis = ImageIO.createImageInputStream(f);
      +            f.deleteOnExit();
      +            return iis;
      +        } else {
      +            throw new RuntimeException("Unable to write jpeg image");
      +        }
      +    }
      +
      +    private static ImageOutputStream prepareOutput(BufferedImage src) throws IOException {
      +        File f = File.createTempFile("dest_", ".jpeg", pwd);
      +        ImageOutputStream ios = ImageIO.createImageOutputStream(f);
      +        f.deleteOnExit();
      +        return ios;
      +    }
      +}
      diff --git a/test/micro/org/openjdk/bench/vm/compiler/VectorIntegerDiv.java b/test/micro/org/openjdk/bench/vm/compiler/VectorIntegerDiv.java
      new file mode 100644
      index 000000000000..110e984dcfe2
      --- /dev/null
      +++ b/test/micro/org/openjdk/bench/vm/compiler/VectorIntegerDiv.java
      @@ -0,0 +1,79 @@
      +/*
      + * Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
      + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
      + *
      + * This code is free software; you can redistribute it and/or modify it
      + * under the terms of the GNU General Public License version 2 only, as
      + * published by the Free Software Foundation.
      + *
      + * This code is distributed in the hope that it will be useful, but WITHOUT
      + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
      + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
      + * version 2 for more details (a copy is included in the LICENSE file that
      + * accompanied this code).
      + *
      + * You should have received a copy of the GNU General Public License version
      + * 2 along with this work; if not, write to the Free Software Foundation,
      + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
      + *
      + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
      + * or visit www.oracle.com if you need additional information or have any
      + * questions.
      + */
      +package org.openjdk.bench.vm.compiler;
      +
      +import org.openjdk.jmh.annotations.*;
      +import java.util.Random;
      +import java.util.concurrent.TimeUnit;
      +
      +// Measures auto-vectorization of integer division by a loop-invariant divisor.
      +
      +@BenchmarkMode(Mode.AverageTime)
      +@OutputTimeUnit(TimeUnit.NANOSECONDS)
      +@State(Scope.Thread)
      +@Warmup(iterations = 5, time = 2)
      +@Measurement(iterations = 5, time = 2)
      +@Fork(value = 3)
      +public class VectorIntegerDiv {
      +
      +    @Param({"1024"})
      +    private int size;
      +
      +    // Loop-invariant divisors. These are non-final fields, so the JIT sees a
      +    // runtime value and cannot strength-reduce the division to a multiply.
      +    private int  iDivisor;
      +    private long lDivisor;
      +
      +    private int[]  ia, ir;
      +    private long[] la, lr;
      +
      +    @Setup
      +    public void setup() {
      +        Random r = new Random(42);
      +        iDivisor = r.nextInt() | 1;
      +        lDivisor = r.nextLong() | 1L;
      +
      +        ia = new int[size];
      +        ir = new int[size];
      +        la = new long[size];
      +        lr = new long[size];
      +        for (int i = 0; i < size; i++) {
      +            ia[i] = r.nextInt();
      +            la[i] = r.nextLong();
      +        }
      +    }
      +
      +    @Benchmark
      +    public void intDiv() {
      +        for (int i = 0; i < ia.length; i++) {
      +            ir[i] = ia[i] / iDivisor;
      +        }
      +    }
      +
      +    @Benchmark
      +    public void longDiv() {
      +        for (int i = 0; i < la.length; i++) {
      +            lr[i] = la[i] / lDivisor;
      +        }
      +    }
      +}