diff --git a/doc/testing.html b/doc/testing.html index b9838735e4f..31f4fbd1778 100644 --- a/doc/testing.html +++ b/doc/testing.html @@ -535,6 +535,8 @@ failure. This helps to reproduce intermittent test failures. Defaults to

REPORT

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

+

MANUAL

+

Set to true to execute manual tests only.

Gtest keywords

REPEAT

The number of times to repeat the tests diff --git a/doc/testing.md b/doc/testing.md index 0144610a5bf..b95f59de9fd 100644 --- a/doc/testing.md +++ b/doc/testing.md @@ -512,6 +512,10 @@ helps to reproduce intermittent test failures. Defaults to 0. Use this report style when reporting test results (sent to JTReg as `-report`). Defaults to `files`. +#### MANUAL + +Set to `true` to execute manual tests only. + ### Gtest keywords #### REPEAT diff --git a/make/RunTests.gmk b/make/RunTests.gmk index 947389f64f9..1f50b97531b 100644 --- a/make/RunTests.gmk +++ b/make/RunTests.gmk @@ -206,7 +206,7 @@ $(eval $(call ParseKeywordVariable, JTREG, \ SINGLE_KEYWORDS := JOBS TIMEOUT_FACTOR FAILURE_HANDLER_TIMEOUT \ TEST_MODE ASSERT VERBOSE RETAIN TEST_THREAD_FACTORY JVMTI_STRESS_AGENT \ MAX_MEM RUN_PROBLEM_LISTS RETRY_COUNT REPEAT_COUNT MAX_OUTPUT REPORT \ - AOT_JDK $(CUSTOM_JTREG_SINGLE_KEYWORDS), \ + AOT_JDK MANUAL $(CUSTOM_JTREG_SINGLE_KEYWORDS), \ STRING_KEYWORDS := OPTIONS JAVA_OPTIONS VM_OPTIONS KEYWORDS \ EXTRA_PROBLEM_LISTS LAUNCHER_OPTIONS \ $(CUSTOM_JTREG_STRING_KEYWORDS), \ @@ -911,7 +911,13 @@ define SetupRunJtregTestBody -vmoption:-Dtest.boot.jdk="$$(BOOT_JDK)" \ -vmoption:-Djava.io.tmpdir="$$($1_TEST_TMP_DIR)" - $1_JTREG_BASIC_OPTIONS += -automatic -ignore:quiet + $1_JTREG_BASIC_OPTIONS += -ignore:quiet + + ifeq ($$(JTREG_MANUAL), true) + $1_JTREG_BASIC_OPTIONS += -manual + else + $1_JTREG_BASIC_OPTIONS += -automatic + endif # Make it possible to specify the JIB_DATA_DIR for tests using the # JIB Artifact resolver @@ -1151,6 +1157,7 @@ define SetupRunJtregTestBody $$(EXPR) $$($1_PASSED) + $$($1_FAILED) + $$($1_ERROR) + $$($1_SKIPPED))) \ , \ $$(eval $1_PASSED_AND_RUNTIME_SKIPPED := 0) \ + $$(eval $1_PASSED := 0) \ $$(eval $1_RUNTIME_SKIPPED := 0) \ $$(eval $1_SKIPPED := 0) \ $$(eval $1_FAILED := 0) \ diff --git a/make/autoconf/flags-cflags.m4 b/make/autoconf/flags-cflags.m4 index 9d58a280998..6298bcae416 100644 --- a/make/autoconf/flags-cflags.m4 +++ b/make/autoconf/flags-cflags.m4 @@ -282,10 +282,17 @@ AC_DEFUN([FLAGS_SETUP_OPTIMIZATION], C_O_FLAG_DEBUG_JVM="-O0" C_O_FLAG_NONE="-O0" + if test "x$TOOLCHAIN_TYPE" = xgcc; then + C_O_FLAG_LTO="-flto=auto -fuse-linker-plugin -fno-strict-aliasing -fno-fat-lto-objects" + else + C_O_FLAG_LTO="-flto -fno-strict-aliasing" + fi + if test "x$TOOLCHAIN_TYPE" = xclang && test "x$OPENJDK_TARGET_OS" = xaix; then C_O_FLAG_HIGHEST_JVM="${C_O_FLAG_HIGHEST_JVM} -finline-functions" C_O_FLAG_HIGHEST="${C_O_FLAG_HIGHEST} -finline-functions" C_O_FLAG_HI="${C_O_FLAG_HI} -finline-functions" + C_O_FLAG_LTO="${C_O_FLAG_LTO} -ffat-lto-objects" fi # -D_FORTIFY_SOURCE=2 hardening option needs optimization (at least -O1) enabled @@ -317,6 +324,7 @@ AC_DEFUN([FLAGS_SETUP_OPTIMIZATION], C_O_FLAG_DEBUG_JVM="" C_O_FLAG_NONE="-Od" C_O_FLAG_SIZE="-O1" + C_O_FLAG_LTO="-GL" fi # Now copy to C++ flags @@ -328,6 +336,7 @@ AC_DEFUN([FLAGS_SETUP_OPTIMIZATION], CXX_O_FLAG_DEBUG_JVM="$C_O_FLAG_DEBUG_JVM" CXX_O_FLAG_NONE="$C_O_FLAG_NONE" CXX_O_FLAG_SIZE="$C_O_FLAG_SIZE" + CXX_O_FLAG_LTO="$C_O_FLAG_LTO" # Adjust optimization flags according to debug level. case $DEBUG_LEVEL in @@ -360,12 +369,15 @@ AC_DEFUN([FLAGS_SETUP_OPTIMIZATION], AC_SUBST(C_O_FLAG_NORM) AC_SUBST(C_O_FLAG_NONE) AC_SUBST(C_O_FLAG_SIZE) + AC_SUBST(C_O_FLAG_LTO) + AC_SUBST(CXX_O_FLAG_HIGHEST_JVM) AC_SUBST(CXX_O_FLAG_HIGHEST) AC_SUBST(CXX_O_FLAG_HI) AC_SUBST(CXX_O_FLAG_NORM) AC_SUBST(CXX_O_FLAG_NONE) AC_SUBST(CXX_O_FLAG_SIZE) + AC_SUBST(CXX_O_FLAG_LTO) ]) AC_DEFUN([FLAGS_SETUP_CFLAGS], diff --git a/make/autoconf/flags-ldflags.m4 b/make/autoconf/flags-ldflags.m4 index 66f8904db89..572790b567b 100644 --- a/make/autoconf/flags-ldflags.m4 +++ b/make/autoconf/flags-ldflags.m4 @@ -50,7 +50,14 @@ AC_DEFUN([FLAGS_SETUP_LDFLAGS_HELPER], # add -z,relro (mark relocations read only) for all libs # add -z,now ("full relro" - more of the Global Offset Table GOT is marked read only) # add --no-as-needed to disable default --as-needed link flag on some GCC toolchains + # add --icf=all (Identical Code Folding — merges identical functions) BASIC_LDFLAGS="-Wl,-z,defs -Wl,-z,relro -Wl,-z,now -Wl,--no-as-needed -Wl,--exclude-libs,ALL" + if test "x$LINKER_TYPE" = "xgold"; then + if test x$DEBUG_LEVEL = xrelease; then + BASIC_LDFLAGS="$BASIC_LDFLAGS -Wl,--icf=all" + fi + fi + # Linux : remove unused code+data in link step if test "x$ENABLE_LINKTIME_GC" = xtrue; then if test "x$OPENJDK_TARGET_CPU" = xs390x; then @@ -61,6 +68,7 @@ AC_DEFUN([FLAGS_SETUP_LDFLAGS_HELPER], fi BASIC_LDFLAGS_JVM_ONLY="" + LDFLAGS_LTO="-flto=auto -fuse-linker-plugin -fno-strict-aliasing" LDFLAGS_CXX_PARTIAL_LINKING="$MACHINE_FLAG -r" @@ -68,6 +76,7 @@ AC_DEFUN([FLAGS_SETUP_LDFLAGS_HELPER], BASIC_LDFLAGS_JVM_ONLY="-mno-omit-leaf-frame-pointer -mstack-alignment=16 \ -fPIC" + LDFLAGS_LTO="-flto=auto -fuse-linker-plugin -fno-strict-aliasing" LDFLAGS_CXX_PARTIAL_LINKING="$MACHINE_FLAG -r" if test "x$OPENJDK_TARGET_OS" = xlinux; then @@ -87,6 +96,7 @@ AC_DEFUN([FLAGS_SETUP_LDFLAGS_HELPER], BASIC_LDFLAGS="-opt:ref" BASIC_LDFLAGS_JDK_ONLY="-incremental:no" BASIC_LDFLAGS_JVM_ONLY="-opt:icf,8 -subsystem:windows" + LDFLAGS_LTO="-LTCG:INCREMENTAL" fi if (test "x$TOOLCHAIN_TYPE" = xgcc || test "x$TOOLCHAIN_TYPE" = xclang) \ @@ -148,6 +158,7 @@ AC_DEFUN([FLAGS_SETUP_LDFLAGS_HELPER], # Export some intermediate variables for compatibility LDFLAGS_CXX_JDK="$DEBUGLEVEL_LDFLAGS_JDK_ONLY" + AC_SUBST(LDFLAGS_LTO) AC_SUBST(LDFLAGS_CXX_JDK) AC_SUBST(LDFLAGS_CXX_PARTIAL_LINKING) ]) diff --git a/make/autoconf/spec.gmk.template b/make/autoconf/spec.gmk.template index 0b336721d65..b3d58704c50 100644 --- a/make/autoconf/spec.gmk.template +++ b/make/autoconf/spec.gmk.template @@ -513,12 +513,14 @@ C_O_FLAG_HI := @C_O_FLAG_HI@ C_O_FLAG_NORM := @C_O_FLAG_NORM@ C_O_FLAG_NONE := @C_O_FLAG_NONE@ C_O_FLAG_SIZE := @C_O_FLAG_SIZE@ +C_O_FLAG_LTO := @C_O_FLAG_LTO@ CXX_O_FLAG_HIGHEST_JVM := @CXX_O_FLAG_HIGHEST_JVM@ CXX_O_FLAG_HIGHEST := @CXX_O_FLAG_HIGHEST@ CXX_O_FLAG_HI := @CXX_O_FLAG_HI@ CXX_O_FLAG_NORM := @CXX_O_FLAG_NORM@ CXX_O_FLAG_NONE := @CXX_O_FLAG_NONE@ CXX_O_FLAG_SIZE := @CXX_O_FLAG_SIZE@ +CXX_O_FLAG_LTO := @CXX_O_FLAG_LTO@ GENDEPS_FLAGS := @GENDEPS_FLAGS@ @@ -587,6 +589,9 @@ LDFLAGS_CXX_JDK := @LDFLAGS_CXX_JDK@ # LDFLAGS specific to partial linking. LDFLAGS_CXX_PARTIAL_LINKING := @LDFLAGS_CXX_PARTIAL_LINKING@ +# LDFLAGS specific to link time optimization +LDFLAGS_LTO := @LDFLAGS_LTO@ + # Sometimes a different linker is needed for c++ libs LDCXX := @LDCXX@ # The flags for linking libstdc++ linker. diff --git a/make/autoconf/toolchain.m4 b/make/autoconf/toolchain.m4 index 4662c62d901..15210efe4a7 100644 --- a/make/autoconf/toolchain.m4 +++ b/make/autoconf/toolchain.m4 @@ -516,6 +516,7 @@ AC_DEFUN([TOOLCHAIN_EXTRACT_LD_VERSION], if [ [[ "$LINKER_VERSION_STRING" == *gold* ]] ]; then [ LINKER_VERSION_NUMBER=`$ECHO $LINKER_VERSION_STRING | \ $SED -e 's/.* \([0-9][0-9]*\(\.[0-9][0-9]*\)*\).*) .*/\1/'` ] + LINKER_TYPE=gold else [ LINKER_VERSION_NUMBER=`$ECHO $LINKER_VERSION_STRING | \ $SED -e 's/.* \([0-9][0-9]*\(\.[0-9][0-9]*\)*\).*/\1/'` ] diff --git a/make/common/NativeCompilation.gmk b/make/common/NativeCompilation.gmk index 9721f1c0aca..28e186adf5f 100644 --- a/make/common/NativeCompilation.gmk +++ b/make/common/NativeCompilation.gmk @@ -98,6 +98,7 @@ include native/Paths.gmk # SYSROOT_CFLAGS the compiler flags for using the specific sysroot # SYSROOT_LDFLAGS the linker flags for using the specific sysroot # OPTIMIZATION sets optimization level to NONE, LOW, HIGH, HIGHEST, HIGHEST_JVM, SIZE +# LINK_TIME_OPTIMIZATION if set to true, enables link time optimization # DISABLED_WARNINGS_ Disable the given warnings for the specified toolchain # DISABLED_WARNINGS__ Disable the given warnings for the specified # toolchain and target OS diff --git a/make/common/native/Flags.gmk b/make/common/native/Flags.gmk index 747e090b816..843701cb4db 100644 --- a/make/common/native/Flags.gmk +++ b/make/common/native/Flags.gmk @@ -194,6 +194,11 @@ define SetupCompilerFlags $1_EXTRA_CXXFLAGS += $(CFLAGS_WARNINGS_ARE_ERRORS) endif + ifeq (true, $$($1_LINK_TIME_OPTIMIZATION)) + $1_EXTRA_CFLAGS += $(C_O_FLAG_LTO) + $1_EXTRA_CXXFLAGS += $(CXX_O_FLAG_LTO) + endif + ifeq (NONE, $$($1_OPTIMIZATION)) $1_OPT_CFLAGS := $(C_O_FLAG_NONE) $1_OPT_CXXFLAGS := $(CXX_O_FLAG_NONE) @@ -222,6 +227,10 @@ define SetupLinkerFlags # Pickup extra OPENJDK_TARGET_OS_TYPE, OPENJDK_TARGET_OS and TOOLCHAIN_TYPE # dependent variables for LDFLAGS and LIBS, and additionally the pair dependent # TOOLCHAIN_TYPE plus OPENJDK_TARGET_OS + ifeq ($$($1_LINK_TIME_OPTIMIZATION), true) + $1_EXTRA_LDFLAGS += $(LDFLAGS_LTO) + endif + $1_EXTRA_LDFLAGS += $$($1_LDFLAGS_$(OPENJDK_TARGET_OS_TYPE)) $$($1_LDFLAGS_$(OPENJDK_TARGET_OS)) \ $$($1_LDFLAGS_$(TOOLCHAIN_TYPE)) $$($1_LDFLAGS_$(TOOLCHAIN_TYPE)_$(OPENJDK_TARGET_OS)) $1_EXTRA_LIBS += $$($1_LIBS_$(OPENJDK_TARGET_OS_TYPE)) $$($1_LIBS_$(OPENJDK_TARGET_OS)) \ diff --git a/make/hotspot/lib/CompileJvm.gmk b/make/hotspot/lib/CompileJvm.gmk index a8b90c92e4d..b0ea27e5081 100644 --- a/make/hotspot/lib/CompileJvm.gmk +++ b/make/hotspot/lib/CompileJvm.gmk @@ -234,6 +234,7 @@ $(eval $(call SetupJdkLibrary, BUILD_LIBJVM, \ LDFLAGS := $(JVM_LDFLAGS), \ LIBS := $(JVM_LIBS), \ OPTIMIZATION := $(JVM_OPTIMIZATION), \ + LINK_TIME_OPTIMIZATION := $(JVM_LTO), \ OBJECT_DIR := $(JVM_OUTPUTDIR)/objs, \ STRIPFLAGS := $(JVM_STRIPFLAGS), \ EMBED_MANIFEST := true, \ diff --git a/make/hotspot/lib/JvmFeatures.gmk b/make/hotspot/lib/JvmFeatures.gmk index 79bbd6a4106..90ea8a985e3 100644 --- a/make/hotspot/lib/JvmFeatures.gmk +++ b/make/hotspot/lib/JvmFeatures.gmk @@ -175,22 +175,12 @@ ifeq ($(call check-jvm-feature, link-time-opt), true) # Set JVM_OPTIMIZATION directly so other jvm-feature flags can override it # later on if desired JVM_OPTIMIZATION := HIGHEST_JVM - ifeq ($(call isCompiler, gcc), true) - JVM_CFLAGS_FEATURES += -flto=auto -fuse-linker-plugin -fno-strict-aliasing \ - -fno-fat-lto-objects - JVM_LDFLAGS_FEATURES += $(CXX_O_FLAG_HIGHEST_JVM) -flto=auto \ - -fuse-linker-plugin -fno-strict-aliasing - else ifeq ($(call isCompiler, clang), true) - JVM_CFLAGS_FEATURES += -flto -fno-strict-aliasing - ifeq ($(call isBuildOs, aix), true) - JVM_CFLAGS_FEATURES += -ffat-lto-objects - endif - JVM_LDFLAGS_FEATURES += $(CXX_O_FLAG_HIGHEST_JVM) -flto -fno-strict-aliasing - else ifeq ($(call isCompiler, microsoft), true) - JVM_CFLAGS_FEATURES += -GL - JVM_LDFLAGS_FEATURES += -LTCG:INCREMENTAL + JVM_LTO := true + ifneq ($(call isCompiler, microsoft), true) + JVM_LDFLAGS_FEATURES += $(CXX_O_FLAG_HIGHEST_JVM) endif else + JVM_LTO := false ifeq ($(call isCompiler, gcc), true) JVM_LDFLAGS_FEATURES += -O1 endif diff --git a/make/modules/java.desktop/lib/ClientLibraries.gmk b/make/modules/java.desktop/lib/ClientLibraries.gmk index 2c29092cdd6..4cd7f5bac90 100644 --- a/make/modules/java.desktop/lib/ClientLibraries.gmk +++ b/make/modules/java.desktop/lib/ClientLibraries.gmk @@ -226,6 +226,7 @@ ifeq ($(ENABLE_HEADLESS_ONLY), false) EXCLUDE_FILES := imageioJPEG.c jpegdecoder.c pngtest.c, \ EXCLUDES := $(LIBSPLASHSCREEN_EXCLUDES), \ OPTIMIZATION := SIZE, \ + LINK_TIME_OPTIMIZATION := true, \ CFLAGS := $(LIBSPLASHSCREEN_CFLAGS) \ $(GIFLIB_CFLAGS) $(LIBJPEG_CFLAGS) $(PNG_CFLAGS) $(LIBZ_CFLAGS) \ $(ICONV_CFLAGS), \ @@ -236,7 +237,7 @@ ifeq ($(ENABLE_HEADLESS_ONLY), false) DISABLED_WARNINGS_gcc_dgif_lib.c := sign-compare, \ DISABLED_WARNINGS_gcc_jcmaster.c := implicit-fallthrough, \ DISABLED_WARNINGS_gcc_jdphuff.c := shift-negative-value, \ - DISABLED_WARNINGS_gcc_png.c := maybe-uninitialized unused-function, \ + DISABLED_WARNINGS_gcc_png.c := maybe-uninitialized, \ DISABLED_WARNINGS_gcc_pngerror.c := maybe-uninitialized, \ DISABLED_WARNINGS_gcc_splashscreen_gfx_impl.c := implicit-fallthrough \ maybe-uninitialized, \ @@ -247,7 +248,6 @@ ifeq ($(ENABLE_HEADLESS_ONLY), false) DISABLED_WARNINGS_clang := deprecated-non-prototype, \ DISABLED_WARNINGS_clang_dgif_lib.c := sign-compare, \ DISABLED_WARNINGS_clang_gzwrite.c := format-nonliteral, \ - DISABLED_WARNINGS_clang_png.c := unused-function, \ DISABLED_WARNINGS_clang_splashscreen_impl.c := sign-compare \ unused-but-set-variable unused-function, \ DISABLED_WARNINGS_clang_splashscreen_png.c := \ diff --git a/src/hotspot/cpu/aarch64/aarch64.ad b/src/hotspot/cpu/aarch64/aarch64.ad index e8f9733fe7e..fc53c10311b 100644 --- a/src/hotspot/cpu/aarch64/aarch64.ad +++ b/src/hotspot/cpu/aarch64/aarch64.ad @@ -1,6 +1,7 @@ // // Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. // Copyright (c) 2014, 2024, Red Hat, Inc. All rights reserved. +// Copyright 2025 Arm Limited and/or its affiliates. // DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. // // This code is free software; you can redistribute it and/or modify it @@ -1194,15 +1195,10 @@ class HandlerImpl { public: - static int emit_exception_handler(C2_MacroAssembler *masm); static int emit_deopt_handler(C2_MacroAssembler* masm); - static uint size_exception_handler() { - return MacroAssembler::far_codestub_branch_size(); - } - static uint size_deopt_handler() { - // count one adr and one far branch instruction + // count one branch instruction and one far call instruction sequence return NativeInstruction::instruction_size + MacroAssembler::far_codestub_branch_size(); } }; @@ -2261,25 +2257,6 @@ uint MachUEPNode::size(PhaseRegAlloc* ra_) const //============================================================================= -// Emit exception handler code. -int HandlerImpl::emit_exception_handler(C2_MacroAssembler* masm) -{ - // mov rscratch1 #exception_blob_entry_point - // br rscratch1 - // Note that the code buffer's insts_mark is always relative to insts. - // That's why we must use the macroassembler to generate a handler. - address base = __ start_a_stub(size_exception_handler()); - if (base == nullptr) { - ciEnv::current()->record_failure("CodeCache is full"); - return 0; // CodeBuffer::expand failed - } - int offset = __ offset(); - __ far_jump(RuntimeAddress(OptoRuntime::exception_blob()->entry_point())); - assert(__ offset() - offset <= (int) size_exception_handler(), "overflow"); - __ end_a_stub(); - return offset; -} - // Emit deopt handler code. int HandlerImpl::emit_deopt_handler(C2_MacroAssembler* masm) { @@ -2290,14 +2267,20 @@ int HandlerImpl::emit_deopt_handler(C2_MacroAssembler* masm) ciEnv::current()->record_failure("CodeCache is full"); return 0; // CodeBuffer::expand failed } - int offset = __ offset(); - __ adr(lr, __ pc()); - __ far_jump(RuntimeAddress(SharedRuntime::deopt_blob()->unpack())); + int offset = __ offset(); + Label start; + __ bind(start); + __ far_call(RuntimeAddress(SharedRuntime::deopt_blob()->unpack())); + + int entry_offset = __ offset(); + __ b(start); assert(__ offset() - offset == (int) size_deopt_handler(), "overflow"); + assert(__ offset() - entry_offset >= NativePostCallNop::first_check_size, + "out of bounds read in post-call NOP check"); __ end_a_stub(); - return offset; + return entry_offset; } // REQUIRED MATCHER CODE @@ -2473,6 +2456,10 @@ bool Matcher::is_reg2reg_move(MachNode* m) { return false; } +bool Matcher::is_register_biasing_candidate(const MachNode* mdef, int oper_index) { + return false; +} + bool Matcher::is_generic_vector(MachOper* opnd) { return opnd->opcode() == VREG; } @@ -3388,28 +3375,28 @@ encode %{ // aarch64_enc_cmpxchg_acq is that we use load-acquire in the // CompareAndSwap sequence to serve as a barrier on acquiring a // lock. - enc_class aarch64_enc_cmpxchg_acq(memory mem, iRegLNoSp oldval, iRegLNoSp newval) %{ + enc_class aarch64_enc_cmpxchg_acq(memory mem, iRegL oldval, iRegL newval) %{ guarantee($mem$$index == -1 && $mem$$disp == 0, "impossible encoding"); __ cmpxchg($mem$$base$$Register, $oldval$$Register, $newval$$Register, Assembler::xword, /*acquire*/ true, /*release*/ true, /*weak*/ false, noreg); %} - enc_class aarch64_enc_cmpxchgw_acq(memory mem, iRegINoSp oldval, iRegINoSp newval) %{ + enc_class aarch64_enc_cmpxchgw_acq(memory mem, iRegI oldval, iRegI newval) %{ guarantee($mem$$index == -1 && $mem$$disp == 0, "impossible encoding"); __ cmpxchg($mem$$base$$Register, $oldval$$Register, $newval$$Register, Assembler::word, /*acquire*/ true, /*release*/ true, /*weak*/ false, noreg); %} - enc_class aarch64_enc_cmpxchgs_acq(memory mem, iRegINoSp oldval, iRegINoSp newval) %{ + enc_class aarch64_enc_cmpxchgs_acq(memory mem, iRegI oldval, iRegI newval) %{ guarantee($mem$$index == -1 && $mem$$disp == 0, "impossible encoding"); __ cmpxchg($mem$$base$$Register, $oldval$$Register, $newval$$Register, Assembler::halfword, /*acquire*/ true, /*release*/ true, /*weak*/ false, noreg); %} - enc_class aarch64_enc_cmpxchgb_acq(memory mem, iRegINoSp oldval, iRegINoSp newval) %{ + enc_class aarch64_enc_cmpxchgb_acq(memory mem, iRegI oldval, iRegI newval) %{ guarantee($mem$$index == -1 && $mem$$disp == 0, "impossible encoding"); __ cmpxchg($mem$$base$$Register, $oldval$$Register, $newval$$Register, Assembler::byte, /*acquire*/ true, /*release*/ true, @@ -3417,7 +3404,7 @@ encode %{ %} // auxiliary used for CompareAndSwapX to set result register - enc_class aarch64_enc_cset_eq(iRegINoSp res) %{ + enc_class aarch64_enc_cset_eq(iRegI res) %{ Register res_reg = as_Register($res$$reg); __ cset(res_reg, Assembler::EQ); %} @@ -8403,7 +8390,7 @@ instruct castVVMask(pRegGov dst) // XXX No flag versions for CompareAndSwap{I,L,P,N} because matcher // can't match them -instruct compareAndSwapB(iRegINoSp res, indirect mem, iRegINoSp oldval, iRegINoSp newval, rFlagsReg cr) %{ +instruct compareAndSwapB(iRegINoSp res, indirect mem, iRegI oldval, iRegI newval, rFlagsReg cr) %{ match(Set res (CompareAndSwapB mem (Binary oldval newval))); ins_cost(2 * VOLATILE_REF_COST); @@ -8421,7 +8408,7 @@ instruct compareAndSwapB(iRegINoSp res, indirect mem, iRegINoSp oldval, iRegINoS ins_pipe(pipe_slow); %} -instruct compareAndSwapS(iRegINoSp res, indirect mem, iRegINoSp oldval, iRegINoSp newval, rFlagsReg cr) %{ +instruct compareAndSwapS(iRegINoSp res, indirect mem, iRegI oldval, iRegI newval, rFlagsReg cr) %{ match(Set res (CompareAndSwapS mem (Binary oldval newval))); ins_cost(2 * VOLATILE_REF_COST); @@ -8439,7 +8426,7 @@ instruct compareAndSwapS(iRegINoSp res, indirect mem, iRegINoSp oldval, iRegINoS ins_pipe(pipe_slow); %} -instruct compareAndSwapI(iRegINoSp res, indirect mem, iRegINoSp oldval, iRegINoSp newval, rFlagsReg cr) %{ +instruct compareAndSwapI(iRegINoSp res, indirect mem, iRegI oldval, iRegI newval, rFlagsReg cr) %{ match(Set res (CompareAndSwapI mem (Binary oldval newval))); ins_cost(2 * VOLATILE_REF_COST); @@ -8457,7 +8444,7 @@ instruct compareAndSwapI(iRegINoSp res, indirect mem, iRegINoSp oldval, iRegINoS ins_pipe(pipe_slow); %} -instruct compareAndSwapL(iRegINoSp res, indirect mem, iRegLNoSp oldval, iRegLNoSp newval, rFlagsReg cr) %{ +instruct compareAndSwapL(iRegINoSp res, indirect mem, iRegL oldval, iRegL newval, rFlagsReg cr) %{ match(Set res (CompareAndSwapL mem (Binary oldval newval))); ins_cost(2 * VOLATILE_REF_COST); @@ -8494,7 +8481,7 @@ instruct compareAndSwapP(iRegINoSp res, indirect mem, iRegP oldval, iRegP newval ins_pipe(pipe_slow); %} -instruct compareAndSwapN(iRegINoSp res, indirect mem, iRegNNoSp oldval, iRegNNoSp newval, rFlagsReg cr) %{ +instruct compareAndSwapN(iRegINoSp res, indirect mem, iRegN oldval, iRegN newval, rFlagsReg cr) %{ match(Set res (CompareAndSwapN mem (Binary oldval newval))); predicate(n->as_LoadStore()->barrier_data() == 0); @@ -8515,7 +8502,7 @@ instruct compareAndSwapN(iRegINoSp res, indirect mem, iRegNNoSp oldval, iRegNNoS // alternative CompareAndSwapX when we are eliding barriers -instruct compareAndSwapBAcq(iRegINoSp res, indirect mem, iRegINoSp oldval, iRegINoSp newval, rFlagsReg cr) %{ +instruct compareAndSwapBAcq(iRegINoSp res, indirect mem, iRegI oldval, iRegI newval, rFlagsReg cr) %{ predicate(needs_acquiring_load_exclusive(n)); match(Set res (CompareAndSwapB mem (Binary oldval newval))); @@ -8534,7 +8521,7 @@ instruct compareAndSwapBAcq(iRegINoSp res, indirect mem, iRegINoSp oldval, iRegI ins_pipe(pipe_slow); %} -instruct compareAndSwapSAcq(iRegINoSp res, indirect mem, iRegINoSp oldval, iRegINoSp newval, rFlagsReg cr) %{ +instruct compareAndSwapSAcq(iRegINoSp res, indirect mem, iRegI oldval, iRegI newval, rFlagsReg cr) %{ predicate(needs_acquiring_load_exclusive(n)); match(Set res (CompareAndSwapS mem (Binary oldval newval))); @@ -8553,7 +8540,7 @@ instruct compareAndSwapSAcq(iRegINoSp res, indirect mem, iRegINoSp oldval, iRegI ins_pipe(pipe_slow); %} -instruct compareAndSwapIAcq(iRegINoSp res, indirect mem, iRegINoSp oldval, iRegINoSp newval, rFlagsReg cr) %{ +instruct compareAndSwapIAcq(iRegINoSp res, indirect mem, iRegI oldval, iRegI newval, rFlagsReg cr) %{ predicate(needs_acquiring_load_exclusive(n)); match(Set res (CompareAndSwapI mem (Binary oldval newval))); @@ -8572,7 +8559,7 @@ instruct compareAndSwapIAcq(iRegINoSp res, indirect mem, iRegINoSp oldval, iRegI ins_pipe(pipe_slow); %} -instruct compareAndSwapLAcq(iRegINoSp res, indirect mem, iRegLNoSp oldval, iRegLNoSp newval, rFlagsReg cr) %{ +instruct compareAndSwapLAcq(iRegINoSp res, indirect mem, iRegL oldval, iRegL newval, rFlagsReg cr) %{ predicate(needs_acquiring_load_exclusive(n)); match(Set res (CompareAndSwapL mem (Binary oldval newval))); @@ -8610,7 +8597,7 @@ instruct compareAndSwapPAcq(iRegINoSp res, indirect mem, iRegP oldval, iRegP new ins_pipe(pipe_slow); %} -instruct compareAndSwapNAcq(iRegINoSp res, indirect mem, iRegNNoSp oldval, iRegNNoSp newval, rFlagsReg cr) %{ +instruct compareAndSwapNAcq(iRegINoSp res, indirect mem, iRegN oldval, iRegN newval, rFlagsReg cr) %{ predicate(needs_acquiring_load_exclusive(n) && n->as_LoadStore()->barrier_data() == 0); match(Set res (CompareAndSwapN mem (Binary oldval newval))); diff --git a/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp index 9ab463125fe..37a6a130e0d 100644 --- a/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp @@ -449,12 +449,20 @@ int LIR_Assembler::emit_deopt_handler() { int offset = code_offset(); - __ adr(lr, pc()); - __ far_jump(RuntimeAddress(SharedRuntime::deopt_blob()->unpack())); + Label start; + __ bind(start); + + __ far_call(RuntimeAddress(SharedRuntime::deopt_blob()->unpack())); + + int entry_offset = __ offset(); + __ b(start); + guarantee(code_offset() - offset <= deopt_handler_size(), "overflow"); + assert(code_offset() - entry_offset >= NativePostCallNop::first_check_size, + "out of bounds read in post-call NOP check"); __ end_a_stub(); - return offset; + return entry_offset; } void LIR_Assembler::add_debug_info_for_branch(address adr, CodeEmitInfo* info) { diff --git a/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.hpp b/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.hpp index 12b941fc4f7..729cd2827b7 100644 --- a/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.hpp @@ -71,7 +71,7 @@ friend class ArrayCopyStub; // CompiledDirectCall::to_trampoline_stub_size() _call_stub_size = 13 * NativeInstruction::instruction_size, _exception_handler_size = DEBUG_ONLY(1*K) NOT_DEBUG(175), - _deopt_handler_size = 7 * NativeInstruction::instruction_size + _deopt_handler_size = 4 * NativeInstruction::instruction_size }; public: diff --git a/src/hotspot/cpu/aarch64/nativeInst_aarch64.cpp b/src/hotspot/cpu/aarch64/nativeInst_aarch64.cpp index 5a7fececafa..f2003dd9b55 100644 --- a/src/hotspot/cpu/aarch64/nativeInst_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/nativeInst_aarch64.cpp @@ -394,12 +394,6 @@ void NativePostCallNop::make_deopt() { NativeDeoptInstruction::insert(addr_at(0)); } -#ifdef ASSERT -static bool is_movk_to_zr(uint32_t insn) { - return ((insn & 0xffe0001f) == 0xf280001f); -} -#endif - bool NativePostCallNop::patch(int32_t oopmap_slot, int32_t cb_offset) { if (((oopmap_slot & 0xff) != oopmap_slot) || ((cb_offset & 0xffffff) != cb_offset)) { return false; // cannot encode diff --git a/src/hotspot/cpu/aarch64/nativeInst_aarch64.hpp b/src/hotspot/cpu/aarch64/nativeInst_aarch64.hpp index df5d97c2376..c30cb911d96 100644 --- a/src/hotspot/cpu/aarch64/nativeInst_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/nativeInst_aarch64.hpp @@ -526,14 +526,31 @@ inline NativeLdSt* NativeLdSt_at(address addr) { // can store an offset from the initial nop to the nmethod. class NativePostCallNop: public NativeInstruction { +private: + static bool is_movk_to_zr(uint32_t insn) { + return ((insn & 0xffe0001f) == 0xf280001f); + } + public: + enum AArch64_specific_constants { + // The two parts should be checked separately to prevent out of bounds access in case + // the return address points to the deopt handler stub code entry point which could be + // at the end of page. + first_check_size = instruction_size + }; + bool check() const { - uint64_t insns = *(uint64_t*)addr_at(0); - // Check for two instructions: nop; movk zr, xx - // These instructions only ever appear together in a post-call - // NOP, so it's unnecessary to check that the third instruction is - // a MOVK as well. - return (insns & 0xffe0001fffffffff) == 0xf280001fd503201f; + // Check the first instruction is NOP. + if (is_nop()) { + uint32_t insn = *(uint32_t*)addr_at(first_check_size); + // Check next instruction is MOVK zr, xx. + // These instructions only ever appear together in a post-call + // NOP, so it's unnecessary to check that the third instruction is + // a MOVK as well. + return is_movk_to_zr(insn); + } + + return false; } bool decode(int32_t& oopmap_slot, int32_t& cb_offset) const { diff --git a/src/hotspot/cpu/aarch64/runtime_aarch64.cpp b/src/hotspot/cpu/aarch64/runtime_aarch64.cpp index d45f9865bd2..e36aa21b567 100644 --- a/src/hotspot/cpu/aarch64/runtime_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/runtime_aarch64.cpp @@ -260,8 +260,6 @@ UncommonTrapBlob* OptoRuntime::generate_uncommon_trap_blob() { //------------------------------generate_exception_blob--------------------------- // creates exception blob at the end -// Using exception blob, this code is jumped from a compiled method. -// (see emit_exception_handler in aarch64.ad file) // // Given an exception pc at a call we call into the runtime for the // handler in this method. This handler might merely restore state diff --git a/src/hotspot/cpu/arm/arm.ad b/src/hotspot/cpu/arm/arm.ad index 92c0df68deb..606275d7666 100644 --- a/src/hotspot/cpu/arm/arm.ad +++ b/src/hotspot/cpu/arm/arm.ad @@ -105,14 +105,8 @@ class HandlerImpl { public: - static int emit_exception_handler(C2_MacroAssembler *masm); static int emit_deopt_handler(C2_MacroAssembler* masm); - static uint size_exception_handler() { - return ( 3 * 4 ); - } - - static uint size_deopt_handler() { return ( 9 * 4 ); } @@ -876,26 +870,6 @@ uint MachUEPNode::size(PhaseRegAlloc *ra_) const { //============================================================================= -// Emit exception handler code. -int HandlerImpl::emit_exception_handler(C2_MacroAssembler* masm) { - address base = __ start_a_stub(size_exception_handler()); - if (base == nullptr) { - ciEnv::current()->record_failure("CodeCache is full"); - return 0; // CodeBuffer::expand failed - } - - int offset = __ offset(); - - // OK to trash LR, because exception blob will kill it - __ jump(OptoRuntime::exception_blob()->entry_point(), relocInfo::runtime_call_type, LR_tmp); - - assert(__ offset() - offset <= (int) size_exception_handler(), "overflow"); - - __ end_a_stub(); - - return offset; -} - int HandlerImpl::emit_deopt_handler(C2_MacroAssembler* masm) { // Can't use any of the current frame's registers as we may have deopted // at a poll and everything can be live. @@ -906,19 +880,28 @@ int HandlerImpl::emit_deopt_handler(C2_MacroAssembler* masm) { } int offset = __ offset(); - address deopt_pc = __ pc(); - __ sub(SP, SP, wordSize); // make room for saved PC - __ push(LR); // save LR that may be live when we get here - __ mov_relative_address(LR, deopt_pc); - __ str(LR, Address(SP, wordSize)); // save deopt PC - __ pop(LR); // restore LR + Label start; + __ bind(start); + __ jump(SharedRuntime::deopt_blob()->unpack(), relocInfo::runtime_call_type, noreg); + int entry_offset = __ offset(); + address deopt_pc = __ pc(); + // Preserve R0 and reserve space for the address of the entry point + __ push(RegisterSet(R0) | RegisterSet(R1)); + // Store the entry point address + __ mov_relative_address(R0, deopt_pc); + __ str(R0, Address(SP, wordSize)); + __ pop(R0); // restore R0 + __ b(start); + assert(__ offset() - offset <= (int) size_deopt_handler(), "overflow"); + assert(__ offset() - entry_offset >= NativePostCallNop::first_check_size, + "out of bounds read in post-call NOP check"); __ end_a_stub(); - return offset; + return entry_offset; } bool Matcher::match_rule_supported(int opcode) { @@ -1080,6 +1063,10 @@ bool Matcher::is_reg2reg_move(MachNode* m) { return false; } +bool Matcher::is_register_biasing_candidate(const MachNode* mdef, int oper_index) { + return false; +} + bool Matcher::is_generic_vector(MachOper* opnd) { ShouldNotReachHere(); // generic vector operands not supported return false; diff --git a/src/hotspot/cpu/arm/arm_32.ad b/src/hotspot/cpu/arm/arm_32.ad index 00bf3bd61e4..9438e8da8b5 100644 --- a/src/hotspot/cpu/arm/arm_32.ad +++ b/src/hotspot/cpu/arm/arm_32.ad @@ -62,22 +62,22 @@ register %{ // Integer/Long Registers // ---------------------------- -reg_def R_R0 (SOC, SOC, Op_RegI, 0, R(0)->as_VMReg()); -reg_def R_R1 (SOC, SOC, Op_RegI, 1, R(1)->as_VMReg()); -reg_def R_R2 (SOC, SOC, Op_RegI, 2, R(2)->as_VMReg()); -reg_def R_R3 (SOC, SOC, Op_RegI, 3, R(3)->as_VMReg()); -reg_def R_R4 (SOC, SOE, Op_RegI, 4, R(4)->as_VMReg()); -reg_def R_R5 (SOC, SOE, Op_RegI, 5, R(5)->as_VMReg()); -reg_def R_R6 (SOC, SOE, Op_RegI, 6, R(6)->as_VMReg()); -reg_def R_R7 (SOC, SOE, Op_RegI, 7, R(7)->as_VMReg()); -reg_def R_R8 (SOC, SOE, Op_RegI, 8, R(8)->as_VMReg()); -reg_def R_R9 (SOC, SOE, Op_RegI, 9, R(9)->as_VMReg()); -reg_def R_R10(NS, SOE, Op_RegI, 10, R(10)->as_VMReg()); -reg_def R_R11(NS, SOE, Op_RegI, 11, R(11)->as_VMReg()); -reg_def R_R12(SOC, SOC, Op_RegI, 12, R(12)->as_VMReg()); -reg_def R_R13(NS, NS, Op_RegI, 13, R(13)->as_VMReg()); -reg_def R_R14(SOC, SOC, Op_RegI, 14, R(14)->as_VMReg()); -reg_def R_R15(NS, NS, Op_RegI, 15, R(15)->as_VMReg()); +reg_def R_R0 (SOC, SOC, Op_RegI, 0, as_Register(0)->as_VMReg()); +reg_def R_R1 (SOC, SOC, Op_RegI, 1, as_Register(1)->as_VMReg()); +reg_def R_R2 (SOC, SOC, Op_RegI, 2, as_Register(2)->as_VMReg()); +reg_def R_R3 (SOC, SOC, Op_RegI, 3, as_Register(3)->as_VMReg()); +reg_def R_R4 (SOC, SOE, Op_RegI, 4, as_Register(4)->as_VMReg()); +reg_def R_R5 (SOC, SOE, Op_RegI, 5, as_Register(5)->as_VMReg()); +reg_def R_R6 (SOC, SOE, Op_RegI, 6, as_Register(6)->as_VMReg()); +reg_def R_R7 (SOC, SOE, Op_RegI, 7, as_Register(7)->as_VMReg()); +reg_def R_R8 (SOC, SOE, Op_RegI, 8, as_Register(8)->as_VMReg()); +reg_def R_R9 (SOC, SOE, Op_RegI, 9, as_Register(9)->as_VMReg()); +reg_def R_R10(NS, SOE, Op_RegI, 10, as_Register(10)->as_VMReg()); +reg_def R_R11(NS, SOE, Op_RegI, 11, as_Register(11)->as_VMReg()); +reg_def R_R12(SOC, SOC, Op_RegI, 12, as_Register(12)->as_VMReg()); +reg_def R_R13(NS, NS, Op_RegI, 13, as_Register(13)->as_VMReg()); +reg_def R_R14(SOC, SOC, Op_RegI, 14, as_Register(14)->as_VMReg()); +reg_def R_R15(NS, NS, Op_RegI, 15, as_Register(15)->as_VMReg()); // ---------------------------- // Float/Double Registers diff --git a/src/hotspot/cpu/arm/assembler_arm_32.hpp b/src/hotspot/cpu/arm/assembler_arm_32.hpp index ae13644ecf9..d6524f08680 100644 --- a/src/hotspot/cpu/arm/assembler_arm_32.hpp +++ b/src/hotspot/cpu/arm/assembler_arm_32.hpp @@ -114,7 +114,7 @@ class RegisterSet { } RegisterSet(Register first, Register last) { - assert(first < last, "encoding constraint"); + assert(first->encoding() < last->encoding(), "encoding constraint"); _encoding = (1 << (last->encoding() + 1)) - (1 << first->encoding()); } diff --git a/src/hotspot/cpu/arm/c1_CodeStubs_arm.cpp b/src/hotspot/cpu/arm/c1_CodeStubs_arm.cpp index 8e49cfcbcaa..3ef02e44b65 100644 --- a/src/hotspot/cpu/arm/c1_CodeStubs_arm.cpp +++ b/src/hotspot/cpu/arm/c1_CodeStubs_arm.cpp @@ -181,7 +181,7 @@ void MonitorEnterStub::emit_code(LIR_Assembler* ce) { const Register lock_reg = _lock_reg->as_pointer_register(); ce->verify_reserved_argument_area_size(2); - if (obj_reg < lock_reg) { + if (obj_reg->encoding() < lock_reg->encoding()) { __ stmia(SP, RegisterSet(obj_reg) | RegisterSet(lock_reg)); } else { __ str(obj_reg, Address(SP)); diff --git a/src/hotspot/cpu/arm/c1_LIRAssembler_arm.cpp b/src/hotspot/cpu/arm/c1_LIRAssembler_arm.cpp index 219c49d1f14..b314577c2c8 100644 --- a/src/hotspot/cpu/arm/c1_LIRAssembler_arm.cpp +++ b/src/hotspot/cpu/arm/c1_LIRAssembler_arm.cpp @@ -272,14 +272,22 @@ int LIR_Assembler::emit_deopt_handler() { int offset = code_offset(); - __ mov_relative_address(LR, __ pc()); - __ push(LR); // stub expects LR to be saved + Label start; + __ bind(start); + __ jump(SharedRuntime::deopt_blob()->unpack(), relocInfo::runtime_call_type, noreg); + int entry_offset = __ offset(); + __ mov_relative_address(LR, __ pc()); + __ push(LR); // stub expects LR to be saved + __ b(start); + assert(code_offset() - offset <= deopt_handler_size(), "overflow"); + assert(code_offset() - entry_offset >= NativePostCallNop::first_check_size, + "out of bounds read in post-call NOP check"); __ end_a_stub(); - return offset; + return entry_offset; } @@ -2631,11 +2639,11 @@ void LIR_Assembler::volatile_move_op(LIR_Opr src, LIR_Opr dest, BasicType type, const Register src_hi = src->as_register_hi(); assert(addr->index()->is_illegal() && addr->disp() == 0, "The address is simple already"); - if (src_lo < src_hi) { + if (src_lo->encoding() < src_hi->encoding()) { null_check_offset = __ offset(); __ stmia(addr->base()->as_register(), RegisterSet(src_lo) | RegisterSet(src_hi)); } else { - assert(src_lo < Rtemp, "Rtemp is higher than any allocatable register"); + assert(src_lo->encoding() < Rtemp->encoding(), "Rtemp is higher than any allocatable register"); __ mov(Rtemp, src_hi); null_check_offset = __ offset(); __ stmia(addr->base()->as_register(), RegisterSet(src_lo) | RegisterSet(Rtemp)); @@ -2648,10 +2656,10 @@ void LIR_Assembler::volatile_move_op(LIR_Opr src, LIR_Opr dest, BasicType type, assert(addr->index()->is_illegal() && addr->disp() == 0, "The address is simple already"); null_check_offset = __ offset(); - if (dest_lo < dest_hi) { + if (dest_lo->encoding() < dest_hi->encoding()) { __ ldmia(addr->base()->as_register(), RegisterSet(dest_lo) | RegisterSet(dest_hi)); } else { - assert(dest_lo < Rtemp, "Rtemp is higher than any allocatable register"); + assert(dest_lo->encoding() < Rtemp->encoding(), "Rtemp is higher than any allocatable register"); __ ldmia(addr->base()->as_register(), RegisterSet(dest_lo) | RegisterSet(Rtemp)); __ mov(dest_hi, Rtemp); } diff --git a/src/hotspot/cpu/arm/c1_LIRAssembler_arm.hpp b/src/hotspot/cpu/arm/c1_LIRAssembler_arm.hpp index 77d13532685..615d2f188ff 100644 --- a/src/hotspot/cpu/arm/c1_LIRAssembler_arm.hpp +++ b/src/hotspot/cpu/arm/c1_LIRAssembler_arm.hpp @@ -54,7 +54,7 @@ enum { _call_stub_size = 16, _exception_handler_size = PRODUCT_ONLY(68) NOT_PRODUCT(68+60), - _deopt_handler_size = 16 + _deopt_handler_size = 20 }; public: diff --git a/src/hotspot/cpu/arm/interp_masm_arm.cpp b/src/hotspot/cpu/arm/interp_masm_arm.cpp index 720413c9c5b..23ecea24eb2 100644 --- a/src/hotspot/cpu/arm/interp_masm_arm.cpp +++ b/src/hotspot/cpu/arm/interp_masm_arm.cpp @@ -409,7 +409,7 @@ void InterpreterMacroAssembler::pop_i(Register r) { void InterpreterMacroAssembler::pop_l(Register lo, Register hi) { assert_different_registers(lo, hi); - assert(lo < hi, "lo must be < hi"); + assert(lo->encoding() < hi->encoding(), "lo must be < hi"); pop(RegisterSet(lo) | RegisterSet(hi)); } @@ -459,7 +459,7 @@ void InterpreterMacroAssembler::push_i(Register r) { void InterpreterMacroAssembler::push_l(Register lo, Register hi) { assert_different_registers(lo, hi); - assert(lo < hi, "lo must be < hi"); + assert(lo->encoding() < hi->encoding(), "lo must be < hi"); push(RegisterSet(lo) | RegisterSet(hi)); } diff --git a/src/hotspot/cpu/arm/nativeInst_arm_32.hpp b/src/hotspot/cpu/arm/nativeInst_arm_32.hpp index ee856bcfe60..82385bf0244 100644 --- a/src/hotspot/cpu/arm/nativeInst_arm_32.hpp +++ b/src/hotspot/cpu/arm/nativeInst_arm_32.hpp @@ -430,6 +430,13 @@ inline NativeCall* nativeCall_before(address return_address) { class NativePostCallNop: public NativeInstruction { public: + enum arm_specific_constants { + // If the check is adjusted to read beyond size of the instruction sequence at the deopt + // handler stub code entry point, it has to happen in two stages - to prevent out of bounds + // access in case the return address points to the entry point which could be at + // the end of page. + first_check_size = instruction_size + }; bool check() const { return is_nop(); } bool decode(int32_t& oopmap_slot, int32_t& cb_offset) const { return false; } bool patch(int32_t oopmap_slot, int32_t cb_offset) { return false; } diff --git a/src/hotspot/cpu/arm/register_arm.cpp b/src/hotspot/cpu/arm/register_arm.cpp index ea3ef87e670..296c55e2e16 100644 --- a/src/hotspot/cpu/arm/register_arm.cpp +++ b/src/hotspot/cpu/arm/register_arm.cpp @@ -25,12 +25,19 @@ #include "register_arm.hpp" #include "utilities/debug.hpp" -const int ConcreteRegisterImpl::max_gpr = ConcreteRegisterImpl::num_gpr; -const int ConcreteRegisterImpl::max_fpr = ConcreteRegisterImpl::num_fpr + - ConcreteRegisterImpl::max_gpr; +Register::RegisterImpl all_RegisterImpls [Register::number_of_registers + 1]; +FloatRegister::FloatRegisterImpl all_FloatRegisterImpls [FloatRegister::number_of_registers + 1]; +VFPSystemRegister::VFPSystemRegisterImpl all_VFPSystemRegisterImpls [VFPSystemRegister::number_of_registers + 1] { + { -1 }, //vfpsnoreg + { VFPSystemRegister::FPSID }, + { VFPSystemRegister::FPSCR }, + { VFPSystemRegister::MVFR0 }, + { VFPSystemRegister::MVFR1 } +}; -const char* RegisterImpl::name() const { - const char* names[number_of_registers] = { +const char* Register::RegisterImpl::name() const { + static const char* names[number_of_registers + 1] = { + "noreg", "r0", "r1", "r2", "r3", "r4", "r5", "r6", #if (FP_REG_NUM == 7) "fp", @@ -45,13 +52,14 @@ const char* RegisterImpl::name() const { #endif "r12", "sp", "lr", "pc" }; - return is_valid() ? names[encoding()] : "noreg"; + return names[encoding() + 1]; } -const char* FloatRegisterImpl::name() const { - const char* names[number_of_registers] = { - "s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", - "s8", "s9", "s10", "s11", "s12", "s13", "s14", "s15", +const char* FloatRegister::FloatRegisterImpl::name() const { + static const char* names[number_of_registers + 1] = { + "fnoreg", + "s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", + "s8", "s9", "s10", "s11", "s12", "s13", "s14", "s15", "s16", "s17", "s18", "s19", "s20", "s21", "s22", "s23", "s24", "s25", "s26", "s27", "s28", "s29", "s30", "s31" #ifdef COMPILER2 @@ -61,5 +69,5 @@ const char* FloatRegisterImpl::name() const { "s56", "s57?","s58", "s59?","s60", "s61?","s62", "s63?" #endif }; - return is_valid() ? names[encoding()] : "fnoreg"; + return names[encoding() + 1]; } diff --git a/src/hotspot/cpu/arm/register_arm.hpp b/src/hotspot/cpu/arm/register_arm.hpp index e0688af0d36..401d25a4fce 100644 --- a/src/hotspot/cpu/arm/register_arm.hpp +++ b/src/hotspot/cpu/arm/register_arm.hpp @@ -31,26 +31,6 @@ class VMRegImpl; typedef VMRegImpl* VMReg; -// These are declared ucontext.h -#undef R0 -#undef R1 -#undef R2 -#undef R3 -#undef R4 -#undef R5 -#undef R6 -#undef R7 -#undef R8 -#undef R9 -#undef R10 -#undef R11 -#undef R12 -#undef R13 -#undef R14 -#undef R15 - -#define R(r) ((Register)(r)) - ///////////////////////////////// // Support for different ARM ABIs // Note: default ABI is for linux @@ -94,25 +74,86 @@ typedef VMRegImpl* VMReg; #define ALIGN_WIDE_ARGUMENTS 1 #endif -#define R0 ((Register)0) -#define R1 ((Register)1) -#define R2 ((Register)2) -#define R3 ((Register)3) -#define R4 ((Register)4) -#define R5 ((Register)5) -#define R6 ((Register)6) -#define R7 ((Register)7) -#define R8 ((Register)8) -#define R9 ((Register)9) -#define R10 ((Register)10) -#define R11 ((Register)11) -#define R12 ((Register)12) -#define R13 ((Register)13) -#define R14 ((Register)14) -#define R15 ((Register)15) +class Register { + private: + int _encoding; + + constexpr explicit Register(int encoding) : _encoding(encoding) {} + + public: + enum { + number_of_registers = 16, + max_slots_per_register = 1 + }; + + class RegisterImpl : public AbstractRegisterImpl { + friend class Register; + + static constexpr const RegisterImpl* first(); + + public: + + // accessors and testers + int raw_encoding() const { return this - first(); } + int encoding() const { assert(is_valid(), "invalid register"); return raw_encoding(); } + bool is_valid() const { return 0 <= raw_encoding() && raw_encoding() < number_of_registers; } + + inline Register successor() const; + + VMReg as_VMReg() const; + + const char* name() const; + }; -#define FP ((Register)FP_REG_NUM) + inline friend constexpr Register as_Register(int encoding); + + constexpr Register() : _encoding(-1) {} //noreg + + int operator==(const Register r) const { return _encoding == r._encoding; } + int operator!=(const Register r) const { return _encoding != r._encoding; } + + const RegisterImpl* operator->() const { return RegisterImpl::first() + _encoding; } +}; + +extern Register::RegisterImpl all_RegisterImpls[Register::number_of_registers + 1] INTERNAL_VISIBILITY; + +inline constexpr const Register::RegisterImpl* Register::RegisterImpl::first() { + return all_RegisterImpls + 1; +} + +constexpr Register noreg = Register(); + +inline constexpr Register as_Register(int encoding) { + if (0 <= encoding && encoding < Register::number_of_registers) { + return Register(encoding); + } + return noreg; +} + +inline Register Register::RegisterImpl::successor() const { + assert(is_valid(), "sainty"); + return as_Register(encoding() + 1); +} + +constexpr Register R0 = as_Register( 0); +constexpr Register R1 = as_Register( 1); +constexpr Register R2 = as_Register( 2); +constexpr Register R3 = as_Register( 3); +constexpr Register R4 = as_Register( 4); +constexpr Register R5 = as_Register( 5); +constexpr Register R6 = as_Register( 6); +constexpr Register R7 = as_Register( 7); +constexpr Register R8 = as_Register( 8); +constexpr Register R9 = as_Register( 9); +constexpr Register R10 = as_Register(10); +constexpr Register R11 = as_Register(11); +constexpr Register R12 = as_Register(12); +constexpr Register R13 = as_Register(13); +constexpr Register R14 = as_Register(14); +constexpr Register R15 = as_Register(15); + +constexpr Register FP = as_Register(FP_REG_NUM); // Safe use of registers which may be FP on some platforms. // @@ -122,185 +163,170 @@ typedef VMRegImpl* VMReg; // as FP on supported ABIs (and replace R# by altFP_#_11). altFP_#_11 // must be #define to R11 if and only if # is FP_REG_NUM. #if (FP_REG_NUM == 7) -#define altFP_7_11 ((Register)11) +constexpr Register altFP_7_11 = R11; #else -#define altFP_7_11 ((Register)7) +constexpr Register altFP_7_11 = R7; #endif -#define SP R13 -#define LR R14 -#define PC R15 +constexpr Register SP = R13; +constexpr Register LR = R14; +constexpr Register PC = R15; -class RegisterImpl; -typedef RegisterImpl* Register; +class FloatRegister { + private: + int _encoding; -inline Register as_Register(int encoding) { - return (Register)(intptr_t)encoding; -} + constexpr explicit FloatRegister(int encoding) : _encoding(encoding) {} -class RegisterImpl : public AbstractRegisterImpl { public: enum { - number_of_registers = 16 + number_of_registers = NOT_COMPILER2(32) COMPILER2_PRESENT(64), + max_slots_per_register = 1 }; - Register successor() const { return as_Register(encoding() + 1); } + class FloatRegisterImpl : public AbstractRegisterImpl { + friend class FloatRegister; - inline friend Register as_Register(int encoding); + static constexpr const FloatRegisterImpl* first(); - VMReg as_VMReg(); + public: - // accessors - int encoding() const { assert(is_valid(), "invalid register"); return value(); } - const char* name() const; + // accessors and testers + int raw_encoding() const { return this - first(); } + int encoding() const { assert(is_valid(), "invalid register"); return raw_encoding(); } + bool is_valid() const { return 0 <= raw_encoding() && raw_encoding() < number_of_registers; } + inline FloatRegister successor() const; - // testers - bool is_valid() const { return 0 <= value() && value() < number_of_registers; } + VMReg as_VMReg() const; + int hi_bits() const { + return (encoding() >> 1) & 0xf; + } + + int lo_bit() const { + return encoding() & 1; + } + + int hi_bit() const { + return encoding() >> 5; + } + + const char* name() const; + }; + + inline friend constexpr FloatRegister as_FloatRegister(int encoding); + + constexpr FloatRegister() : _encoding(-1) {} // fnoreg + + int operator==(const FloatRegister r) const { return _encoding == r._encoding; } + int operator!=(const FloatRegister r) const { return _encoding != r._encoding; } + + const FloatRegisterImpl* operator->() const { return FloatRegisterImpl::first() + _encoding; } }; -CONSTANT_REGISTER_DECLARATION(Register, noreg, (-1)); +extern FloatRegister::FloatRegisterImpl all_FloatRegisterImpls[FloatRegister::number_of_registers + 1] INTERNAL_VISIBILITY; - -// Use FloatRegister as shortcut -class FloatRegisterImpl; -typedef FloatRegisterImpl* FloatRegister; - -inline FloatRegister as_FloatRegister(int encoding) { - return (FloatRegister)(intptr_t)encoding; +inline constexpr const FloatRegister::FloatRegisterImpl* FloatRegister::FloatRegisterImpl::first() { + return all_FloatRegisterImpls + 1; } -class FloatRegisterImpl : public AbstractRegisterImpl { - public: - enum { - number_of_registers = NOT_COMPILER2(32) COMPILER2_PRESENT(64) - }; +constexpr FloatRegister fnoreg = FloatRegister(); - inline friend FloatRegister as_FloatRegister(int encoding); - - VMReg as_VMReg(); - - int encoding() const { assert(is_valid(), "invalid register"); return value(); } - bool is_valid() const { return 0 <= (intx)this && (intx)this < number_of_registers; } - FloatRegister successor() const { return as_FloatRegister(encoding() + 1); } - - const char* name() const; - - int hi_bits() const { - return (encoding() >> 1) & 0xf; +inline constexpr FloatRegister as_FloatRegister(int encoding) { + if (0 <= encoding && encoding < FloatRegister::number_of_registers) { + return FloatRegister(encoding); } + return fnoreg; +} - int lo_bit() const { - return encoding() & 1; - } - - int hi_bit() const { - return encoding() >> 5; - } -}; - -CONSTANT_REGISTER_DECLARATION(FloatRegister, fnoreg, (-1)); +inline FloatRegister FloatRegister::FloatRegisterImpl::successor() const { + assert(is_valid(), "sainty"); + return as_FloatRegister(encoding() + 1); +} /* * S1-S6 are named with "_reg" suffix to avoid conflict with * constants defined in sharedRuntimeTrig.cpp */ -CONSTANT_REGISTER_DECLARATION(FloatRegister, S0, ( 0)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S1_reg, ( 1)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S2_reg, ( 2)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S3_reg, ( 3)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S4_reg, ( 4)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S5_reg, ( 5)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S6_reg, ( 6)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S7, ( 7)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S8, ( 8)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S9, ( 9)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S10, (10)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S11, (11)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S12, (12)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S13, (13)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S14, (14)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S15, (15)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S16, (16)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S17, (17)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S18, (18)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S19, (19)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S20, (20)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S21, (21)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S22, (22)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S23, (23)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S24, (24)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S25, (25)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S26, (26)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S27, (27)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S28, (28)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S29, (29)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S30, (30)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S31, (31)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, Stemp, (30)); +constexpr FloatRegister S0 = as_FloatRegister( 0); +constexpr FloatRegister S1_reg = as_FloatRegister(1); +constexpr FloatRegister S2_reg = as_FloatRegister(2); +constexpr FloatRegister S3_reg = as_FloatRegister(3); +constexpr FloatRegister S4_reg = as_FloatRegister(4); +constexpr FloatRegister S5_reg = as_FloatRegister(5); +constexpr FloatRegister S6_reg = as_FloatRegister(6); +constexpr FloatRegister S7 = as_FloatRegister( 7); +constexpr FloatRegister S8 = as_FloatRegister( 8); +constexpr FloatRegister S9 = as_FloatRegister( 9); +constexpr FloatRegister S10 = as_FloatRegister(10); +constexpr FloatRegister S11 = as_FloatRegister(11); +constexpr FloatRegister S12 = as_FloatRegister(12); +constexpr FloatRegister S13 = as_FloatRegister(13); +constexpr FloatRegister S14 = as_FloatRegister(14); +constexpr FloatRegister S15 = as_FloatRegister(15); +constexpr FloatRegister S16 = as_FloatRegister(16); +constexpr FloatRegister S17 = as_FloatRegister(17); +constexpr FloatRegister S18 = as_FloatRegister(18); +constexpr FloatRegister S19 = as_FloatRegister(19); +constexpr FloatRegister S20 = as_FloatRegister(20); +constexpr FloatRegister S21 = as_FloatRegister(21); +constexpr FloatRegister S22 = as_FloatRegister(22); +constexpr FloatRegister S23 = as_FloatRegister(23); +constexpr FloatRegister S24 = as_FloatRegister(24); +constexpr FloatRegister S25 = as_FloatRegister(25); +constexpr FloatRegister S26 = as_FloatRegister(26); +constexpr FloatRegister S27 = as_FloatRegister(27); +constexpr FloatRegister S28 = as_FloatRegister(28); +constexpr FloatRegister S29 = as_FloatRegister(29); +constexpr FloatRegister S30 = as_FloatRegister(30); +constexpr FloatRegister S31 = as_FloatRegister(31); +constexpr FloatRegister Stemp = S30; -CONSTANT_REGISTER_DECLARATION(FloatRegister, D0, ( 0)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D1, ( 2)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D2, ( 4)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D3, ( 6)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D4, ( 8)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D5, ( 10)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D6, ( 12)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D7, ( 14)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D8, ( 16)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D9, ( 18)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D10, ( 20)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D11, ( 22)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D12, ( 24)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D13, ( 26)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D14, ( 28)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D15, (30)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D16, (32)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D17, (34)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D18, (36)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D19, (38)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D20, (40)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D21, (42)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D22, (44)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D23, (46)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D24, (48)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D25, (50)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D26, (52)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D27, (54)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D28, (56)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D29, (58)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D30, (60)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D31, (62)); +constexpr FloatRegister D0 = as_FloatRegister( 0); +constexpr FloatRegister D1 = as_FloatRegister( 2); +constexpr FloatRegister D2 = as_FloatRegister( 4); +constexpr FloatRegister D3 = as_FloatRegister( 6); +constexpr FloatRegister D4 = as_FloatRegister( 8); +constexpr FloatRegister D5 = as_FloatRegister(10); +constexpr FloatRegister D6 = as_FloatRegister(12); +constexpr FloatRegister D7 = as_FloatRegister(14); +constexpr FloatRegister D8 = as_FloatRegister(16); +constexpr FloatRegister D9 = as_FloatRegister(18); +constexpr FloatRegister D10 = as_FloatRegister(20); +constexpr FloatRegister D11 = as_FloatRegister(22); +constexpr FloatRegister D12 = as_FloatRegister(24); +constexpr FloatRegister D13 = as_FloatRegister(26); +constexpr FloatRegister D14 = as_FloatRegister(28); +constexpr FloatRegister D15 = as_FloatRegister(30); +constexpr FloatRegister D16 = as_FloatRegister(32); +constexpr FloatRegister D17 = as_FloatRegister(34); +constexpr FloatRegister D18 = as_FloatRegister(36); +constexpr FloatRegister D19 = as_FloatRegister(38); +constexpr FloatRegister D20 = as_FloatRegister(40); +constexpr FloatRegister D21 = as_FloatRegister(42); +constexpr FloatRegister D22 = as_FloatRegister(44); +constexpr FloatRegister D23 = as_FloatRegister(46); +constexpr FloatRegister D24 = as_FloatRegister(48); +constexpr FloatRegister D25 = as_FloatRegister(50); +constexpr FloatRegister D26 = as_FloatRegister(52); +constexpr FloatRegister D27 = as_FloatRegister(54); +constexpr FloatRegister D28 = as_FloatRegister(56); +constexpr FloatRegister D29 = as_FloatRegister(58); +constexpr FloatRegister D30 = as_FloatRegister(60); +constexpr FloatRegister D31 = as_FloatRegister(62); class ConcreteRegisterImpl : public AbstractRegisterImpl { public: enum { - log_vmregs_per_word = LogBytesPerWord - LogBytesPerInt, // VMRegs are of 4-byte size -#ifdef COMPILER2 - log_bytes_per_fpr = 2, // quad vectors -#else - log_bytes_per_fpr = 2, // double vectors -#endif - log_words_per_fpr = log_bytes_per_fpr - LogBytesPerWord, - words_per_fpr = 1 << log_words_per_fpr, - log_vmregs_per_fpr = log_bytes_per_fpr - LogBytesPerInt, - log_vmregs_per_gpr = log_vmregs_per_word, - vmregs_per_gpr = 1 << log_vmregs_per_gpr, - vmregs_per_fpr = 1 << log_vmregs_per_fpr, + max_gpr = Register::number_of_registers * Register::max_slots_per_register, + max_fpr = max_gpr + FloatRegister::number_of_registers * FloatRegister::max_slots_per_register, - num_gpr = RegisterImpl::number_of_registers << log_vmregs_per_gpr, - max_gpr0 = num_gpr, - num_fpr = FloatRegisterImpl::number_of_registers << log_vmregs_per_fpr, - max_fpr0 = max_gpr0 + num_fpr, - number_of_registers = num_gpr + num_fpr + 1+1 // APSR and FPSCR so that c2's REG_COUNT <= ConcreteRegisterImpl::number_of_registers + number_of_registers = max_fpr + 1+1 // APSR and FPSCR so that c2's REG_COUNT <= ConcreteRegisterImpl::number_of_registers }; - - static const int max_gpr; - static const int max_fpr; }; typedef AbstractRegSet RegSet; @@ -328,100 +354,156 @@ inline FloatRegister AbstractRegSet::last() { -class VFPSystemRegisterImpl; -typedef VFPSystemRegisterImpl* VFPSystemRegister; -class VFPSystemRegisterImpl : public AbstractRegisterImpl { +class VFPSystemRegister { + private: + int _store_idx; + + constexpr explicit VFPSystemRegister(int store_idx) : _store_idx(store_idx) {} + + enum { + _FPSID_store_idx = 0, + _FPSCR_store_idx = 1, + _MVFR0_store_idx = 2, + _MVFR1_store_idx = 3 + }; + public: - int encoding() const { return value(); } + enum { + FPSID = 0, + FPSCR = 1, + MVFR0 = 6, + MVFR1 = 7, + number_of_registers = 4 + }; + + class VFPSystemRegisterImpl : public AbstractRegisterImpl { + friend class VFPSystemRegister; + + int _encoding; + + static constexpr const VFPSystemRegisterImpl* first(); + + public: + constexpr VFPSystemRegisterImpl(int encoding) : _encoding(encoding) {} + + int encoding() const { return _encoding; } + }; + + inline friend constexpr VFPSystemRegister as_VFPSystemRegister(int encoding); + + constexpr VFPSystemRegister() : _store_idx(-1) {} // vfpsnoreg + + int operator==(const VFPSystemRegister r) const { return _store_idx == r._store_idx; } + int operator!=(const VFPSystemRegister r) const { return _store_idx != r._store_idx; } + + const VFPSystemRegisterImpl* operator->() const { return VFPSystemRegisterImpl::first() + _store_idx; } }; -#define FPSID ((VFPSystemRegister)0) -#define FPSCR ((VFPSystemRegister)1) -#define MVFR0 ((VFPSystemRegister)0x6) -#define MVFR1 ((VFPSystemRegister)0x7) +extern VFPSystemRegister::VFPSystemRegisterImpl all_VFPSystemRegisterImpls[VFPSystemRegister::number_of_registers + 1] INTERNAL_VISIBILITY; + +inline constexpr const VFPSystemRegister::VFPSystemRegisterImpl* VFPSystemRegister::VFPSystemRegisterImpl::first() { + return all_VFPSystemRegisterImpls + 1; +} + +constexpr VFPSystemRegister vfpsnoreg = VFPSystemRegister(); + +inline constexpr VFPSystemRegister as_VFPSystemRegister(int encoding) { + switch (encoding) { + case VFPSystemRegister::FPSID: return VFPSystemRegister(VFPSystemRegister::_FPSID_store_idx); + case VFPSystemRegister::FPSCR: return VFPSystemRegister(VFPSystemRegister::_FPSCR_store_idx); + case VFPSystemRegister::MVFR0: return VFPSystemRegister(VFPSystemRegister::_MVFR0_store_idx); + case VFPSystemRegister::MVFR1: return VFPSystemRegister(VFPSystemRegister::_MVFR1_store_idx); + default: return vfpsnoreg; + } +} + +constexpr VFPSystemRegister FPSID = as_VFPSystemRegister(VFPSystemRegister::FPSID); +constexpr VFPSystemRegister FPSCR = as_VFPSystemRegister(VFPSystemRegister::FPSCR); +constexpr VFPSystemRegister MVFR0 = as_VFPSystemRegister(VFPSystemRegister::MVFR0); +constexpr VFPSystemRegister MVFR1 = as_VFPSystemRegister(VFPSystemRegister::MVFR1); /* * Register definitions shared across interpreter and compiler */ -#define Rexception_obj R4 -#define Rexception_pc R5 +constexpr Register Rexception_obj = R4; +constexpr Register Rexception_pc = R5; /* * Interpreter register definitions common to C++ and template interpreters. */ -#define Rlocals R8 -#define Rmethod R9 -#define Rthread R10 -#define Rtemp R12 +constexpr Register Rlocals = R8; +constexpr Register Rmethod = R9; +constexpr Register Rthread = R10; +constexpr Register Rtemp = R12; // Interpreter calling conventions -#define Rparams SP -#define Rsender_sp R4 +constexpr Register Rparams = SP; +constexpr Register Rsender_sp = R4; // JSR292 // Note: R5_mh is needed only during the call setup, including adapters // This does not seem to conflict with Rexception_pc // In case of issues, R3 might be OK but adapters calling the runtime would have to save it -#define R5_mh R5 // MethodHandle register, used during the call setup +constexpr Register R5_mh = R5; // MethodHandle register, used during the call setup /* * C++ Interpreter Register Defines */ -#define Rsave0 R4 -#define Rsave1 R5 -#define Rsave2 R6 -#define Rstate altFP_7_11 // R7 or R11 -#define Ricklass R8 +constexpr Register Rsave0 = R4; +constexpr Register Rsave1 = R5; +constexpr Register Rsave2 = R6; +constexpr Register Rstate = altFP_7_11; // R7 or R11 +constexpr Register Ricklass = R8; /* * TemplateTable Interpreter Register Usage */ // Temporary registers -#define R0_tmp R0 -#define R1_tmp R1 -#define R2_tmp R2 -#define R3_tmp R3 -#define R4_tmp R4 -#define R5_tmp R5 -#define R12_tmp R12 -#define LR_tmp LR +constexpr Register R0_tmp = R0; +constexpr Register R1_tmp = R1; +constexpr Register R2_tmp = R2; +constexpr Register R3_tmp = R3; +constexpr Register R4_tmp = R4; +constexpr Register R5_tmp = R5; +constexpr Register R12_tmp = R12; +constexpr Register LR_tmp = LR; -#define S0_tmp S0 -#define S1_tmp S1_reg +constexpr FloatRegister S0_tmp = S0; +constexpr FloatRegister S1_tmp = S1_reg; -#define D0_tmp D0 -#define D1_tmp D1 +constexpr FloatRegister D0_tmp = D0; +constexpr FloatRegister D1_tmp = D1; // Temporary registers saved across VM calls (according to C calling conventions) -#define Rtmp_save0 R4 -#define Rtmp_save1 R5 +constexpr Register Rtmp_save0 = R4; +constexpr Register Rtmp_save1 = R5; // Cached TOS value -#define R0_tos R0 +constexpr Register R0_tos = R0; -#define R0_tos_lo R0 -#define R1_tos_hi R1 +constexpr Register R0_tos_lo = R0; +constexpr Register R1_tos_hi = R1; -#define S0_tos S0 -#define D0_tos D0 +constexpr FloatRegister S0_tos = S0; +constexpr FloatRegister D0_tos = D0; // Dispatch table -#define RdispatchTable R6 +constexpr Register RdispatchTable = R6; // Bytecode pointer -#define Rbcp altFP_7_11 +constexpr Register Rbcp = altFP_7_11; // Pre-loaded next bytecode for the dispatch -#define R3_bytecode R3 +constexpr Register R3_bytecode = R3; // Conventions between bytecode templates and stubs -#define R2_ClassCastException_obj R2 -#define R4_ArrayIndexOutOfBounds_index R4 +constexpr Register R2_ClassCastException_obj = R2; +constexpr Register R4_ArrayIndexOutOfBounds_index = R4; // Interpreter expression stack top -#define Rstack_top SP +constexpr Register Rstack_top = SP; /* * Linux 32-bit ARM C ABI Register calling conventions @@ -444,10 +526,11 @@ class VFPSystemRegisterImpl : public AbstractRegisterImpl { * R14 (LR) Link register * R15 (PC) Program Counter */ -#define c_rarg0 R0 -#define c_rarg1 R1 -#define c_rarg2 R2 -#define c_rarg3 R3 + +constexpr Register c_rarg0 = R0; +constexpr Register c_rarg1 = R1; +constexpr Register c_rarg2 = R2; +constexpr Register c_rarg3 = R3; #define GPR_PARAMS 4 @@ -455,10 +538,10 @@ class VFPSystemRegisterImpl : public AbstractRegisterImpl { // Java ABI // XXX Is this correct? -#define j_rarg0 c_rarg0 -#define j_rarg1 c_rarg1 -#define j_rarg2 c_rarg2 -#define j_rarg3 c_rarg3 +constexpr Register j_rarg0 = c_rarg0; +constexpr Register j_rarg1 = c_rarg1; +constexpr Register j_rarg2 = c_rarg2; +constexpr Register j_rarg3 = c_rarg3; #endif // CPU_ARM_REGISTER_ARM_HPP diff --git a/src/hotspot/cpu/arm/runtime_arm.cpp b/src/hotspot/cpu/arm/runtime_arm.cpp index 8d48de5795a..29fd0aa0a10 100644 --- a/src/hotspot/cpu/arm/runtime_arm.cpp +++ b/src/hotspot/cpu/arm/runtime_arm.cpp @@ -182,8 +182,6 @@ UncommonTrapBlob* OptoRuntime::generate_uncommon_trap_blob() { //------------------------------ generate_exception_blob --------------------------- // creates exception blob at the end -// Using exception blob, this code is jumped from a compiled method. -// (see emit_exception_handler in sparc.ad file) // // Given an exception pc at a call we call into the runtime for the // handler in this method. This handler might merely restore state diff --git a/src/hotspot/cpu/arm/sharedRuntime_arm.cpp b/src/hotspot/cpu/arm/sharedRuntime_arm.cpp index 76e38d29478..13e1f4493ff 100644 --- a/src/hotspot/cpu/arm/sharedRuntime_arm.cpp +++ b/src/hotspot/cpu/arm/sharedRuntime_arm.cpp @@ -70,7 +70,7 @@ public: enum RegisterLayout { - fpu_save_size = FloatRegisterImpl::number_of_registers, + fpu_save_size = FloatRegister::number_of_registers, #ifndef __SOFTFP__ D0_offset = 0, #endif @@ -139,8 +139,8 @@ OopMap* RegisterSaver::save_live_registers(MacroAssembler* masm, if (VM_Version::has_vfp3_32()) { __ fpush(FloatRegisterSet(D16, 16)); } else { - if (FloatRegisterImpl::number_of_registers > 32) { - assert(FloatRegisterImpl::number_of_registers == 64, "nb fp registers should be 64"); + if (FloatRegister::number_of_registers > 32) { + assert(FloatRegister::number_of_registers == 64, "nb fp registers should be 64"); __ sub(SP, SP, 32 * wordSize); } } @@ -182,8 +182,8 @@ void RegisterSaver::restore_live_registers(MacroAssembler* masm, bool restore_lr if (VM_Version::has_vfp3_32()) { __ fpop(FloatRegisterSet(D16, 16)); } else { - if (FloatRegisterImpl::number_of_registers > 32) { - assert(FloatRegisterImpl::number_of_registers == 64, "nb fp registers should be 64"); + if (FloatRegister::number_of_registers > 32) { + assert(FloatRegister::number_of_registers == 64, "nb fp registers should be 64"); __ add(SP, SP, 32 * wordSize); } } diff --git a/src/hotspot/cpu/arm/vmreg_arm.cpp b/src/hotspot/cpu/arm/vmreg_arm.cpp index 4ce1dd0be20..efaf38ef729 100644 --- a/src/hotspot/cpu/arm/vmreg_arm.cpp +++ b/src/hotspot/cpu/arm/vmreg_arm.cpp @@ -30,14 +30,14 @@ void VMRegImpl::set_regName() { Register reg = ::as_Register(0); int i; for (i = 0; i < ConcreteRegisterImpl::max_gpr; reg = reg->successor()) { - for (int j = 0; j < (1 << ConcreteRegisterImpl::log_vmregs_per_gpr); j++) { + for (int j = 0; j < Register::max_slots_per_register; j++) { regName[i++] = reg->name(); } } #ifndef __SOFTFP__ FloatRegister freg = ::as_FloatRegister(0); for ( ; i < ConcreteRegisterImpl::max_fpr ; ) { - for (int j = 0; j < (1 << ConcreteRegisterImpl::log_vmregs_per_fpr); j++) { + for (int j = 0; j < Register::max_slots_per_register; j++) { regName[i++] = freg->name(); } freg = freg->successor(); diff --git a/src/hotspot/cpu/arm/vmreg_arm.hpp b/src/hotspot/cpu/arm/vmreg_arm.hpp index c13f443b804..f1dfd09a1e6 100644 --- a/src/hotspot/cpu/arm/vmreg_arm.hpp +++ b/src/hotspot/cpu/arm/vmreg_arm.hpp @@ -36,20 +36,20 @@ inline Register as_Register() { assert(is_Register(), "must be"); assert(is_concrete(), "concrete register expected"); - return ::as_Register(value() >> ConcreteRegisterImpl::log_vmregs_per_gpr); + return ::as_Register(value() / Register::max_slots_per_register); } inline FloatRegister as_FloatRegister() { assert(is_FloatRegister(), "must be"); assert(is_concrete(), "concrete register expected"); - return ::as_FloatRegister((value() - ConcreteRegisterImpl::max_gpr) >> ConcreteRegisterImpl::log_vmregs_per_fpr); + return ::as_FloatRegister((value() - ConcreteRegisterImpl::max_gpr) / FloatRegister::max_slots_per_register); } inline bool is_concrete() { if (is_Register()) { - return ((value() & right_n_bits(ConcreteRegisterImpl::log_vmregs_per_gpr)) == 0); + return (value() % Register::max_slots_per_register == 0); } else if (is_FloatRegister()) { - return (((value() - ConcreteRegisterImpl::max_gpr) & right_n_bits(ConcreteRegisterImpl::log_vmregs_per_fpr)) == 0); + return (value() % FloatRegister::max_slots_per_register == 0); // Single slot } else { return false; } diff --git a/src/hotspot/cpu/arm/vmreg_arm.inline.hpp b/src/hotspot/cpu/arm/vmreg_arm.inline.hpp index f122b9ede70..3e5c18dbda0 100644 --- a/src/hotspot/cpu/arm/vmreg_arm.inline.hpp +++ b/src/hotspot/cpu/arm/vmreg_arm.inline.hpp @@ -25,11 +25,11 @@ #ifndef CPU_ARM_VMREG_ARM_INLINE_HPP #define CPU_ARM_VMREG_ARM_INLINE_HPP -inline VMReg RegisterImpl::as_VMReg() { - return VMRegImpl::as_VMReg(encoding() << ConcreteRegisterImpl::log_vmregs_per_gpr); +inline VMReg Register::RegisterImpl::as_VMReg() const { + return VMRegImpl::as_VMReg(encoding() * Register::max_slots_per_register); } -inline VMReg FloatRegisterImpl::as_VMReg() { - return VMRegImpl::as_VMReg((encoding() << ConcreteRegisterImpl::log_vmregs_per_fpr) + ConcreteRegisterImpl::max_gpr); +inline VMReg FloatRegister::FloatRegisterImpl::as_VMReg() const { + return VMRegImpl::as_VMReg((encoding() * FloatRegister::max_slots_per_register) + ConcreteRegisterImpl::max_gpr); } #endif // CPU_ARM_VMREG_ARM_INLINE_HPP diff --git a/src/hotspot/cpu/ppc/atomicAccess_ppc.hpp b/src/hotspot/cpu/ppc/atomicAccess_ppc.hpp index a0ff19e6171..c4529b0eb1a 100644 --- a/src/hotspot/cpu/ppc/atomicAccess_ppc.hpp +++ b/src/hotspot/cpu/ppc/atomicAccess_ppc.hpp @@ -157,6 +157,9 @@ inline D AtomicAccess::PlatformAdd<8>::add_then_fetch(D volatile* dest, I add_va return result; } +template<> +struct AtomicAccess::PlatformXchg<1> : AtomicAccess::XchgUsingCmpxchg<1> {}; + template<> template inline T AtomicAccess::PlatformXchg<4>::operator()(T volatile* dest, diff --git a/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp b/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp index 108da2039f6..0b48653ae64 100644 --- a/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp +++ b/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp @@ -264,12 +264,19 @@ int LIR_Assembler::emit_deopt_handler() { } int offset = code_offset(); + Label start; + + __ bind(start); __ bl64_patchable(SharedRuntime::deopt_blob()->unpack(), relocInfo::runtime_call_type); + int entry_offset = __ offset(); + __ b(start); guarantee(code_offset() - offset <= deopt_handler_size(), "overflow"); + assert(code_offset() - entry_offset >= NativePostCallNop::first_check_size, + "out of bounds read in post-call NOP check"); __ end_a_stub(); - return offset; + return entry_offset; } diff --git a/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.hpp b/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.hpp index e4de2eb5c46..6a2f6264850 100644 --- a/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.hpp +++ b/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.hpp @@ -63,7 +63,7 @@ enum { _static_call_stub_size = 4 * BytesPerInstWord + MacroAssembler::b64_patchable_size, // or smaller _call_stub_size = _static_call_stub_size + MacroAssembler::trampoline_stub_size, // or smaller _exception_handler_size = MacroAssembler::b64_patchable_size, // or smaller - _deopt_handler_size = MacroAssembler::bl64_patchable_size + _deopt_handler_size = MacroAssembler::bl64_patchable_size + BytesPerInstWord }; // '_static_call_stub_size' is only used on ppc (see LIR_Assembler::emit_static_call_stub() diff --git a/src/hotspot/cpu/ppc/nativeInst_ppc.hpp b/src/hotspot/cpu/ppc/nativeInst_ppc.hpp index dcb5c2bb3cb..75ca50674bf 100644 --- a/src/hotspot/cpu/ppc/nativeInst_ppc.hpp +++ b/src/hotspot/cpu/ppc/nativeInst_ppc.hpp @@ -51,8 +51,6 @@ class NativeInstruction { friend class Relocation; public: - bool is_post_call_nop() const { return MacroAssembler::is_post_call_nop(long_at(0)); } - bool is_jump() const { return Assembler::is_b(long_at(0)); } // See NativeGeneralJump. bool is_sigtrap_ic_miss_check() { @@ -531,6 +529,14 @@ class NativePostCallNop: public NativeInstruction { }; public: + enum ppc_specific_constants { + // If the check is adjusted to read beyond size of the instruction at the deopt handler stub + // code entry point, it has to happen in two stages - to prevent out of bounds access in case + // the return address points to the entry point which could be at the end of page. + first_check_size = BytesPerInstWord + }; + + bool is_post_call_nop() const { return MacroAssembler::is_post_call_nop(long_at(0)); } bool check() const { return is_post_call_nop(); } bool decode(int32_t& oopmap_slot, int32_t& cb_offset) const { uint32_t instr_bits = long_at(0); diff --git a/src/hotspot/cpu/ppc/ppc.ad b/src/hotspot/cpu/ppc/ppc.ad index c169d673aaf..87fcf112756 100644 --- a/src/hotspot/cpu/ppc/ppc.ad +++ b/src/hotspot/cpu/ppc/ppc.ad @@ -2088,17 +2088,11 @@ class HandlerImpl { public: - static int emit_exception_handler(C2_MacroAssembler *masm); static int emit_deopt_handler(C2_MacroAssembler* masm); - static uint size_exception_handler() { - // The exception_handler is a b64_patchable. - return MacroAssembler::b64_patchable_size; - } - static uint size_deopt_handler() { // The deopt_handler is a bl64_patchable. - return MacroAssembler::bl64_patchable_size; + return MacroAssembler::bl64_patchable_size + BytesPerInstWord; } }; @@ -2114,22 +2108,6 @@ public: source %{ -int HandlerImpl::emit_exception_handler(C2_MacroAssembler *masm) { - address base = __ start_a_stub(size_exception_handler()); - if (base == nullptr) { - ciEnv::current()->record_failure("CodeCache is full"); - return 0; // CodeBuffer::expand failed - } - - int offset = __ offset(); - __ b64_patchable((address)OptoRuntime::exception_blob()->content_begin(), - relocInfo::runtime_call_type); - assert(__ offset() - offset == (int)size_exception_handler(), "must be fixed size"); - __ end_a_stub(); - - return offset; -} - // The deopt_handler is like the exception handler, but it calls to // the deoptimization blob instead of jumping to the exception blob. int HandlerImpl::emit_deopt_handler(C2_MacroAssembler* masm) { @@ -2140,12 +2118,23 @@ int HandlerImpl::emit_deopt_handler(C2_MacroAssembler* masm) { } int offset = __ offset(); + + Label start; + __ bind(start); + __ bl64_patchable((address)SharedRuntime::deopt_blob()->unpack(), relocInfo::runtime_call_type); + + int entry_offset = __ offset(); + + __ b(start); + assert(__ offset() - offset == (int) size_deopt_handler(), "must be fixed size"); + assert(__ offset() - entry_offset >= NativePostCallNop::first_check_size, + "out of bounds read in post-call NOP check"); __ end_a_stub(); - return offset; + return entry_offset; } //============================================================================= @@ -2394,6 +2383,10 @@ bool Matcher::is_reg2reg_move(MachNode* m) { return false; } +bool Matcher::is_register_biasing_candidate(const MachNode* mdef, int oper_index) { + return false; +} + bool Matcher::is_generic_vector(MachOper* opnd) { ShouldNotReachHere(); // generic vector operands not supported return false; diff --git a/src/hotspot/cpu/ppc/runtime_ppc.cpp b/src/hotspot/cpu/ppc/runtime_ppc.cpp index 2654075f702..ab658e9de58 100644 --- a/src/hotspot/cpu/ppc/runtime_ppc.cpp +++ b/src/hotspot/cpu/ppc/runtime_ppc.cpp @@ -46,7 +46,6 @@ //------------------------------generate_exception_blob--------------------------- // Creates exception blob at the end. -// Using exception blob, this code is jumped from a compiled method. // // Given an exception pc at a call we call into the runtime for the // handler in this method. This handler might merely restore state diff --git a/src/hotspot/cpu/ppc/sharedRuntime_ppc.cpp b/src/hotspot/cpu/ppc/sharedRuntime_ppc.cpp index db45a2fa4c8..4e427ace404 100644 --- a/src/hotspot/cpu/ppc/sharedRuntime_ppc.cpp +++ b/src/hotspot/cpu/ppc/sharedRuntime_ppc.cpp @@ -83,7 +83,6 @@ class RegisterSaver { static OopMap* push_frame_reg_args_and_save_live_registers(MacroAssembler* masm, int* out_frame_size_in_bytes, bool generate_oop_map, - int return_pc_adjustment, ReturnPCLocation return_pc_location, bool save_vectors = false); static void restore_live_registers_and_pop_frame(MacroAssembler* masm, @@ -262,7 +261,6 @@ static const RegisterSaver::LiveRegType RegisterSaver_LiveVecRegs[] = { OopMap* RegisterSaver::push_frame_reg_args_and_save_live_registers(MacroAssembler* masm, int* out_frame_size_in_bytes, bool generate_oop_map, - int return_pc_adjustment, ReturnPCLocation return_pc_location, bool save_vectors) { // Push an abi_reg_args-frame and store all registers which may be live. @@ -271,7 +269,6 @@ OopMap* RegisterSaver::push_frame_reg_args_and_save_live_registers(MacroAssemble // propagated to the RegisterMap of the caller frame during // StackFrameStream construction (needed for deoptimization; see // compiledVFrame::create_stack_value). - // If return_pc_adjustment != 0 adjust the return pc by return_pc_adjustment. // Updated return pc is returned in R31 (if not return_pc_is_pre_saved). // calculate frame size @@ -305,14 +302,11 @@ OopMap* RegisterSaver::push_frame_reg_args_and_save_live_registers(MacroAssemble // Do the save_LR by hand and adjust the return pc if requested. switch (return_pc_location) { case return_pc_is_lr: __ mflr(R31); break; - case return_pc_is_pre_saved: assert(return_pc_adjustment == 0, "unsupported"); break; + case return_pc_is_pre_saved: break; case return_pc_is_thread_saved_exception_pc: __ ld(R31, thread_(saved_exception_pc)); break; default: ShouldNotReachHere(); } if (return_pc_location != return_pc_is_pre_saved) { - if (return_pc_adjustment != 0) { - __ addi(R31, R31, return_pc_adjustment); - } __ std(R31, frame_size_in_bytes + _abi0(lr), R1_SP); } @@ -2907,22 +2901,15 @@ void SharedRuntime::generate_deopt_blob() { // deopt_handler: call_deopt_stub // cur. return pc --> ... // - // So currently SR_LR points behind the call in the deopt handler. - // We adjust it such that it points to the start of the deopt handler. // The return_pc has been stored in the frame of the deoptee and // will replace the address of the deopt_handler in the call // to Deoptimization::fetch_unroll_info below. - // We can't grab a free register here, because all registers may - // contain live values, so let the RegisterSaver do the adjustment - // of the return pc. - const int return_pc_adjustment_no_exception = -MacroAssembler::bl64_patchable_size; // Push the "unpack frame" // Save everything in sight. map = RegisterSaver::push_frame_reg_args_and_save_live_registers(masm, &first_frame_size_in_bytes, /*generate_oop_map=*/ true, - return_pc_adjustment_no_exception, RegisterSaver::return_pc_is_lr); assert(map != nullptr, "OopMap must have been created"); @@ -2957,7 +2944,6 @@ void SharedRuntime::generate_deopt_blob() { RegisterSaver::push_frame_reg_args_and_save_live_registers(masm, &first_frame_size_in_bytes, /*generate_oop_map=*/ false, - /*return_pc_adjustment_exception=*/ 0, RegisterSaver::return_pc_is_pre_saved); // Deopt during an exception. Save exec mode for unpack_frames. @@ -2975,7 +2961,6 @@ void SharedRuntime::generate_deopt_blob() { RegisterSaver::push_frame_reg_args_and_save_live_registers(masm, &first_frame_size_in_bytes, /*generate_oop_map=*/ false, - /*return_pc_adjustment_reexecute=*/ 0, RegisterSaver::return_pc_is_pre_saved); __ li(exec_mode_reg, Deoptimization::Unpack_reexecute); #endif @@ -3266,7 +3251,6 @@ SafepointBlob* SharedRuntime::generate_handler_blob(StubId id, address call_ptr) map = RegisterSaver::push_frame_reg_args_and_save_live_registers(masm, &frame_size_in_bytes, /*generate_oop_map=*/ true, - /*return_pc_adjustment=*/0, return_pc_location, save_vectors); // The following is basically a call_VM. However, we need the precise @@ -3367,7 +3351,6 @@ RuntimeStub* SharedRuntime::generate_resolve_blob(StubId id, address destination map = RegisterSaver::push_frame_reg_args_and_save_live_registers(masm, &frame_size_in_bytes, /*generate_oop_map*/ true, - /*return_pc_adjustment*/ 0, RegisterSaver::return_pc_is_lr); // Use noreg as last_Java_pc, the return pc will be reconstructed diff --git a/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp b/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp index 9d8ae770ccf..e77a2067e89 100644 --- a/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp @@ -377,12 +377,20 @@ int LIR_Assembler::emit_deopt_handler() { int offset = code_offset(); - __ auipc(ra, 0); - __ far_jump(RuntimeAddress(SharedRuntime::deopt_blob()->unpack())); + Label start; + __ bind(start); + + __ far_call(RuntimeAddress(SharedRuntime::deopt_blob()->unpack())); + + int entry_offset = __ offset(); + __ j(start); + guarantee(code_offset() - offset <= deopt_handler_size(), "overflow"); + assert(code_offset() - entry_offset >= NativePostCallNop::first_check_size, + "out of bounds read in post-call NOP check"); __ end_a_stub(); - return offset; + return entry_offset; } void LIR_Assembler::return_op(LIR_Opr result, C1SafepointPollStub* code_stub) { diff --git a/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.hpp b/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.hpp index e4efb2c171d..ed2ab0c4861 100644 --- a/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.hpp +++ b/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.hpp @@ -72,7 +72,7 @@ private: // See emit_exception_handler for detail _exception_handler_size = DEBUG_ONLY(256) NOT_DEBUG(32), // or smaller // See emit_deopt_handler for detail - // auipc (1) + far_jump (2) + // far_call (2) + j (1) _deopt_handler_size = 1 * MacroAssembler::instruction_size + 2 * MacroAssembler::instruction_size }; diff --git a/src/hotspot/cpu/riscv/nativeInst_riscv.hpp b/src/hotspot/cpu/riscv/nativeInst_riscv.hpp index d990cfbc50d..b28e33759b2 100644 --- a/src/hotspot/cpu/riscv/nativeInst_riscv.hpp +++ b/src/hotspot/cpu/riscv/nativeInst_riscv.hpp @@ -311,12 +311,19 @@ inline bool NativeInstruction::is_jump_or_nop() { // can store an offset from the initial nop to the nmethod. class NativePostCallNop: public NativeInstruction { public: + enum RISCV_specific_constants { + // The two parts should be checked separately to prevent out of bounds access in + // case the return address points to the deopt handler stub code entry point + // which could be at the end of page. + first_check_size = instruction_size + }; + bool check() const { // Check for two instructions: nop; lui zr, hi20 // These instructions only ever appear together in a post-call // NOP, so it's unnecessary to check that the third instruction is // an addiw as well. - return is_nop() && MacroAssembler::is_lui_to_zr_at(addr_at(4)); + return is_nop() && MacroAssembler::is_lui_to_zr_at(addr_at(first_check_size)); } bool decode(int32_t& oopmap_slot, int32_t& cb_offset) const; bool patch(int32_t oopmap_slot, int32_t cb_offset); diff --git a/src/hotspot/cpu/riscv/riscv.ad b/src/hotspot/cpu/riscv/riscv.ad index 7acbb5a478b..3f5dd4ad0ee 100644 --- a/src/hotspot/cpu/riscv/riscv.ad +++ b/src/hotspot/cpu/riscv/riscv.ad @@ -1049,15 +1049,10 @@ class HandlerImpl { public: - static int emit_exception_handler(C2_MacroAssembler *masm); static int emit_deopt_handler(C2_MacroAssembler* masm); - static uint size_exception_handler() { - return MacroAssembler::far_branch_size(); - } - static uint size_deopt_handler() { - // count auipc + far branch + // count far call + j return NativeInstruction::instruction_size + MacroAssembler::far_branch_size(); } }; @@ -1838,25 +1833,6 @@ uint MachUEPNode::size(PhaseRegAlloc* ra_) const //============================================================================= -// Emit exception handler code. -int HandlerImpl::emit_exception_handler(C2_MacroAssembler* masm) -{ - // auipc t1, #exception_blob_entry_point - // jr (offset)t1 - // Note that the code buffer's insts_mark is always relative to insts. - // That's why we must use the macroassembler to generate a handler. - address base = __ start_a_stub(size_exception_handler()); - if (base == nullptr) { - ciEnv::current()->record_failure("CodeCache is full"); - return 0; // CodeBuffer::expand failed - } - int offset = __ offset(); - __ far_jump(RuntimeAddress(OptoRuntime::exception_blob()->entry_point())); - assert(__ offset() - offset <= (int) size_exception_handler(), "overflow"); - __ end_a_stub(); - return offset; -} - // Emit deopt handler code. int HandlerImpl::emit_deopt_handler(C2_MacroAssembler* masm) { @@ -1867,12 +1843,19 @@ int HandlerImpl::emit_deopt_handler(C2_MacroAssembler* masm) } int offset = __ offset(); - __ auipc(ra, 0); - __ far_jump(RuntimeAddress(SharedRuntime::deopt_blob()->unpack())); + Label start; + __ bind(start); + + __ far_call(RuntimeAddress(SharedRuntime::deopt_blob()->unpack())); + + int entry_offset = __ offset(); + __ j(start); assert(__ offset() - offset <= (int) size_deopt_handler(), "overflow"); + assert(__ offset() - entry_offset >= NativePostCallNop::first_check_size, + "out of bounds read in post-call NOP check"); __ end_a_stub(); - return offset; + return entry_offset; } // REQUIRED MATCHER CODE @@ -2070,6 +2053,10 @@ bool Matcher::is_reg2reg_move(MachNode* m) { return false; } +bool Matcher::is_register_biasing_candidate(const MachNode* mdef, int oper_index) { + return false; +} + bool Matcher::is_generic_vector(MachOper* opnd) { ShouldNotReachHere(); // generic vector operands not supported return false; diff --git a/src/hotspot/cpu/riscv/runtime_riscv.cpp b/src/hotspot/cpu/riscv/runtime_riscv.cpp index e1add8dbb82..c52d5a31066 100644 --- a/src/hotspot/cpu/riscv/runtime_riscv.cpp +++ b/src/hotspot/cpu/riscv/runtime_riscv.cpp @@ -249,8 +249,6 @@ UncommonTrapBlob* OptoRuntime::generate_uncommon_trap_blob() { //------------------------------generate_exception_blob--------------------------- // creates exception blob at the end -// Using exception blob, this code is jumped from a compiled method. -// (see emit_exception_handler in riscv.ad file) // // Given an exception pc at a call we call into the runtime for the // handler in this method. This handler might merely restore state diff --git a/src/hotspot/cpu/riscv/vm_version_riscv.hpp b/src/hotspot/cpu/riscv/vm_version_riscv.hpp index 16f2e5d8f5b..168a3a576d0 100644 --- a/src/hotspot/cpu/riscv/vm_version_riscv.hpp +++ b/src/hotspot/cpu/riscv/vm_version_riscv.hpp @@ -89,11 +89,12 @@ class VM_Version : public Abstract_VM_Version { FLAG_SET_DEFAULT(flag, true); \ } else { \ FLAG_SET_DEFAULT(flag, false); \ - stringStream ss; \ - deps_string(ss, dep0, ##__VA_ARGS__); \ - warning("Cannot enable " #flag ", it's missing dependent extension(s) %s", ss.as_string(true)); \ /* Sync CPU features with flags */ \ disable_feature(); \ + stringStream ss; \ + ss.print("missing dependent extension(s): "); \ + deps_string(ss, dep0, ##__VA_ARGS__); \ + log_disabled(ss.as_string(true)); \ } \ } else { \ /* Sync CPU features with flags */ \ @@ -101,11 +102,12 @@ class VM_Version : public Abstract_VM_Version { disable_feature(); \ } else if (!deps_all_enabled(dep0, ##__VA_ARGS__)) { \ FLAG_SET_DEFAULT(flag, false); \ - stringStream ss; \ - deps_string(ss, dep0, ##__VA_ARGS__); \ - warning("Cannot enable " #flag ", it's missing dependent extension(s) %s", ss.as_string(true)); \ /* Sync CPU features with flags */ \ disable_feature(); \ + stringStream ss; \ + ss.print("missing dependent extension(s): "); \ + deps_string(ss, dep0, ##__VA_ARGS__); \ + log_disabled(ss.as_string(true)); \ } \ } \ } \ @@ -136,6 +138,7 @@ class VM_Version : public Abstract_VM_Version { RVExtFeatures::current()->clear_feature(_cpu_feature_index); } void log_enabled(); + void log_disabled(const char* reason); protected: bool deps_all_enabled(RVExtFeatureValue* dep0, ...) { diff --git a/src/hotspot/cpu/s390/c1_LIRAssembler_s390.cpp b/src/hotspot/cpu/s390/c1_LIRAssembler_s390.cpp index 298234156c3..93d6051aa76 100644 --- a/src/hotspot/cpu/s390/c1_LIRAssembler_s390.cpp +++ b/src/hotspot/cpu/s390/c1_LIRAssembler_s390.cpp @@ -272,14 +272,27 @@ int LIR_Assembler::emit_deopt_handler() { // Not enough space left for the handler. bailout("deopt handler overflow"); return -1; - } int offset = code_offset(); + } + + int offset = code_offset(); + + Label start; + __ bind(start); + // Size must be constant (see HandlerImpl::emit_deopt_handler). __ load_const(Z_R1_scratch, SharedRuntime::deopt_blob()->unpack()); __ call(Z_R1_scratch); + + int entry_offset = __ offset(); + + __ z_bru(start); + guarantee(code_offset() - offset <= deopt_handler_size(), "overflow"); + assert(code_offset() - entry_offset >= NativePostCallNop::first_check_size, + "out of bounds read in post-call NOP check"); __ end_a_stub(); - return offset; + return entry_offset; } void LIR_Assembler::jobject2reg(jobject o, Register reg) { diff --git a/src/hotspot/cpu/s390/nativeInst_s390.hpp b/src/hotspot/cpu/s390/nativeInst_s390.hpp index 16400df3f26..9852bc410b1 100644 --- a/src/hotspot/cpu/s390/nativeInst_s390.hpp +++ b/src/hotspot/cpu/s390/nativeInst_s390.hpp @@ -649,6 +649,13 @@ class NativeGeneralJump: public NativeInstruction { class NativePostCallNop: public NativeInstruction { public: + enum z_specific_constants { + // Once the check is implemented, this has to specify number of bytes checked on the first + // read. If the check would read beyond size of the instruction at the deopt handler stub + // code entry point, then it has to happen in two stages - to prevent out of bounds access + // in case the return address points to the entry point which could be at the end of page. + first_check_size = 0 // check is unimplemented + }; bool check() const { Unimplemented(); return false; } bool decode(int32_t& oopmap_slot, int32_t& cb_offset) const { return false; } bool patch(int32_t oopmap_slot, int32_t cb_offset) { Unimplemented(); return false; } diff --git a/src/hotspot/cpu/s390/runtime_s390.cpp b/src/hotspot/cpu/s390/runtime_s390.cpp index 314c407af91..658fba069b4 100644 --- a/src/hotspot/cpu/s390/runtime_s390.cpp +++ b/src/hotspot/cpu/s390/runtime_s390.cpp @@ -43,8 +43,6 @@ //------------------------------generate_exception_blob--------------------------- // creates exception blob at the end -// Using exception blob, this code is jumped from a compiled method. -// (see emit_exception_handler in s390.ad file) // // Given an exception pc at a call we call into the runtime for the // handler in this method. This handler might merely restore state diff --git a/src/hotspot/cpu/s390/s390.ad b/src/hotspot/cpu/s390/s390.ad index cab3965ecfa..7d3e963a108 100644 --- a/src/hotspot/cpu/s390/s390.ad +++ b/src/hotspot/cpu/s390/s390.ad @@ -1649,15 +1649,10 @@ source_hpp %{ // Header information of the source block. class HandlerImpl { public: - static int emit_exception_handler(C2_MacroAssembler *masm); static int emit_deopt_handler(C2_MacroAssembler* masm); - static uint size_exception_handler() { - return NativeJump::max_instruction_size(); - } - static uint size_deopt_handler() { - return NativeCall::max_instruction_size(); + return NativeCall::max_instruction_size() + MacroAssembler::jump_pcrelative_size(); } }; @@ -1672,43 +1667,6 @@ public: source %{ -// This exception handler code snippet is placed after the method's -// code. It is the return point if an exception occurred. it jumps to -// the exception blob. -// -// If the method gets deoptimized, the method and this code snippet -// get patched. -// -// 1) Trampoline code gets patched into the end of this exception -// handler. the trampoline code jumps to the deoptimization blob. -// -// 2) The return address in the method's code will get patched such -// that it jumps to the trampoline. -// -// 3) The handler will get patched such that it does not jump to the -// exception blob, but to an entry in the deoptimization blob being -// aware of the exception. -int HandlerImpl::emit_exception_handler(C2_MacroAssembler *masm) { - Register temp_reg = Z_R1; - - address base = __ start_a_stub(size_exception_handler()); - if (base == nullptr) { - ciEnv::current()->record_failure("CodeCache is full"); - return 0; // CodeBuffer::expand failed - } - - int offset = __ offset(); - // Use unconditional pc-relative jump with 32-bit range here. - __ load_const_optimized(temp_reg, (address)OptoRuntime::exception_blob()->content_begin()); - __ z_br(temp_reg); - - assert(__ offset() - offset <= (int) size_exception_handler(), "overflow"); - - __ end_a_stub(); - - return offset; -} - // Emit deopt handler code. int HandlerImpl::emit_deopt_handler(C2_MacroAssembler* masm) { address base = __ start_a_stub(size_deopt_handler()); @@ -1720,14 +1678,24 @@ int HandlerImpl::emit_deopt_handler(C2_MacroAssembler* masm) { int offset = __ offset(); + Label start; + __ bind(start); + // Size_deopt_handler() must be exact on zarch, so for simplicity // we do not use load_const_opt here. __ load_const(Z_R1, SharedRuntime::deopt_blob()->unpack()); __ call(Z_R1); + + int entry_offset = __ offset(); + + __ z_bru(start); + assert(__ offset() - offset == (int) size_deopt_handler(), "must be fixed size"); + assert(__ offset() - entry_offset >= NativePostCallNop::first_check_size, + "out of bounds read in post-call NOP check"); __ end_a_stub(); - return offset; + return entry_offset; } //============================================================================= @@ -1897,6 +1865,10 @@ bool Matcher::is_reg2reg_move(MachNode* m) { return false; } +bool Matcher::is_register_biasing_candidate(const MachNode* mdef, int oper_index) { + return false; +} + bool Matcher::is_generic_vector(MachOper* opnd) { ShouldNotReachHere(); // generic vector operands not supported return false; diff --git a/src/hotspot/cpu/s390/sharedRuntime_s390.cpp b/src/hotspot/cpu/s390/sharedRuntime_s390.cpp index a3605f649cc..5b6f7dcd984 100644 --- a/src/hotspot/cpu/s390/sharedRuntime_s390.cpp +++ b/src/hotspot/cpu/s390/sharedRuntime_s390.cpp @@ -2544,14 +2544,10 @@ void SharedRuntime::generate_deopt_blob() { // Normal entry (non-exception case) // // We have been called from the deopt handler of the deoptee. - // Z_R14 points behind the call in the deopt handler. We adjust - // it such that it points to the start of the deopt handler. + // Z_R14 points to the entry point of the deopt handler. // The return_pc has been stored in the frame of the deoptee and // will replace the address of the deopt_handler in the call // to Deoptimization::fetch_unroll_info below. - // The (int) cast is necessary, because -((unsigned int)14) - // is an unsigned int. - __ add2reg(Z_R14, -(int)NativeCall::max_instruction_size()); const Register exec_mode_reg = Z_tmp_1; diff --git a/src/hotspot/cpu/x86/assembler_x86.cpp b/src/hotspot/cpu/x86/assembler_x86.cpp index e3ba0ebb56a..cbc5c6988d4 100644 --- a/src/hotspot/cpu/x86/assembler_x86.cpp +++ b/src/hotspot/cpu/x86/assembler_x86.cpp @@ -3860,6 +3860,46 @@ void Assembler::evmovdquq(Address dst, KRegister mask, XMMRegister src, bool mer emit_operand(src, dst, 0); } +void Assembler::vmovsldup(XMMRegister dst, XMMRegister src, int vector_len) { + assert(vector_len == AVX_512bit ? VM_Version::supports_evex() : VM_Version::supports_avx(), ""); + InstructionAttr attributes(vector_len, /* vex_w */ false, /* legacy_mode */ false, /* no_mask_reg */ true, /* uses_vl */ true); + int encode = vex_prefix_and_encode(dst->encoding(), 0, src->encoding(), VEX_SIMD_F3, VEX_OPCODE_0F, &attributes); + emit_int16(0x12, (0xC0 | encode)); +} + +void Assembler::vmovshdup(XMMRegister dst, XMMRegister src, int vector_len) { + assert(vector_len == AVX_512bit ? VM_Version::supports_evex() : VM_Version::supports_avx(), ""); + InstructionAttr attributes(vector_len, /* vex_w */ false, /* legacy_mode */ false, /* no_mask_reg */ true, /* uses_vl */ true); + int encode = vex_prefix_and_encode(dst->encoding(), 0, src->encoding(), VEX_SIMD_F3, VEX_OPCODE_0F, &attributes); + emit_int16(0x16, (0xC0 | encode)); +} + +void Assembler::evmovsldup(XMMRegister dst, KRegister mask, XMMRegister src, bool merge, int vector_len) { + assert(VM_Version::supports_evex(), ""); + assert(vector_len == AVX_512bit || VM_Version::supports_avx512vl(), ""); + InstructionAttr attributes(vector_len, /* vex_w */ false, /* legacy_mode */ false, /* no_mask_reg */ false, /* uses_vl */ true); + attributes.set_embedded_opmask_register_specifier(mask); + attributes.set_is_evex_instruction(); + if (merge) { + attributes.reset_is_clear_context(); + } + int encode = vex_prefix_and_encode(dst->encoding(), 0, src->encoding(), VEX_SIMD_F3, VEX_OPCODE_0F, &attributes); + emit_int16(0x12, (0xC0 | encode)); +} + +void Assembler::evmovshdup(XMMRegister dst, KRegister mask, XMMRegister src, bool merge, int vector_len) { + assert(VM_Version::supports_evex(), ""); + assert(vector_len == AVX_512bit || VM_Version::supports_avx512vl(), ""); + InstructionAttr attributes(vector_len, /* vex_w */ false, /* legacy_mode */ false, /* no_mask_reg */ false, /* uses_vl */ true); + attributes.set_embedded_opmask_register_specifier(mask); + attributes.set_is_evex_instruction(); + if (merge) { + attributes.reset_is_clear_context(); + } + int encode = vex_prefix_and_encode(dst->encoding(), 0, src->encoding(), VEX_SIMD_F3, VEX_OPCODE_0F, &attributes); + emit_int16(0x16, (0xC0 | encode)); +} + // Uses zero extension on 64bit void Assembler::movl(Register dst, int32_t imm32) { diff --git a/src/hotspot/cpu/x86/assembler_x86.hpp b/src/hotspot/cpu/x86/assembler_x86.hpp index c863191df4c..43471a88391 100644 --- a/src/hotspot/cpu/x86/assembler_x86.hpp +++ b/src/hotspot/cpu/x86/assembler_x86.hpp @@ -1664,6 +1664,11 @@ private: void evmovdqaq(XMMRegister dst, Address src, int vector_len); void evmovdqaq(XMMRegister dst, KRegister mask, Address src, bool merge, int vector_len); + void vmovsldup(XMMRegister dst, XMMRegister src, int vector_len); + void vmovshdup(XMMRegister dst, XMMRegister src, int vector_len); + void evmovsldup(XMMRegister dst, KRegister mask, XMMRegister src, bool merge, int vector_len); + void evmovshdup(XMMRegister dst, KRegister mask, XMMRegister src, bool merge, int vector_len); + // Move lower 64bit to high 64bit in 128bit register void movlhps(XMMRegister dst, XMMRegister src); diff --git a/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp b/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp index edeb0baea0e..a2ea7af606d 100644 --- a/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp @@ -450,14 +450,22 @@ int LIR_Assembler::emit_deopt_handler() { } int offset = code_offset(); - InternalAddress here(__ pc()); - __ pushptr(here.addr(), rscratch1); - __ jump(RuntimeAddress(SharedRuntime::deopt_blob()->unpack())); + Label start; + __ bind(start); + + __ call(RuntimeAddress(SharedRuntime::deopt_blob()->unpack())); + + int entry_offset = __ offset(); + + __ jmp(start); + guarantee(code_offset() - offset <= deopt_handler_size(), "overflow"); + assert(code_offset() - entry_offset >= NativePostCallNop::first_check_size, + "out of bounds read in post-call NOP check"); __ end_a_stub(); - return offset; + return entry_offset; } void LIR_Assembler::return_op(LIR_Opr result, C1SafepointPollStub* code_stub) { diff --git a/src/hotspot/cpu/x86/c1_LIRAssembler_x86.hpp b/src/hotspot/cpu/x86/c1_LIRAssembler_x86.hpp index 8524dc90276..33f7b063e77 100644 --- a/src/hotspot/cpu/x86/c1_LIRAssembler_x86.hpp +++ b/src/hotspot/cpu/x86/c1_LIRAssembler_x86.hpp @@ -48,7 +48,7 @@ enum { _call_stub_size = 28, _exception_handler_size = DEBUG_ONLY(1*K) NOT_DEBUG(175), - _deopt_handler_size = 17 + _deopt_handler_size = 7 }; public: diff --git a/src/hotspot/cpu/x86/gc/g1/g1BarrierSetAssembler_x86.cpp b/src/hotspot/cpu/x86/gc/g1/g1BarrierSetAssembler_x86.cpp index 586135fcebc..34de9403ccf 100644 --- a/src/hotspot/cpu/x86/gc/g1/g1BarrierSetAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/gc/g1/g1BarrierSetAssembler_x86.cpp @@ -89,10 +89,10 @@ void G1BarrierSetAssembler::gen_write_ref_array_pre_barrier(MacroAssembler* masm void G1BarrierSetAssembler::gen_write_ref_array_post_barrier(MacroAssembler* masm, DecoratorSet decorators, Register addr, Register count, Register tmp) { - Label done; + Label L_done; __ testptr(count, count); - __ jcc(Assembler::zero, done); + __ jccb(Assembler::zero, L_done); // Calculate end address in "count". Address::ScaleFactor scale = UseCompressedOops ? Address::times_4 : Address::times_8; @@ -111,31 +111,31 @@ void G1BarrierSetAssembler::gen_write_ref_array_post_barrier(MacroAssembler* mas __ shrptr(count, CardTable::card_shift()); __ addptr(count, tmp); - Label loop; + Label L_loop; // Iterate from start card to end card (inclusive). - __ bind(loop); + __ bind(L_loop); - Label is_clean_card; + Label L_is_clean_card; if (UseCondCardMark) { __ cmpb(Address(addr, 0), G1CardTable::clean_card_val()); - __ jcc(Assembler::equal, is_clean_card); + __ jccb(Assembler::equal, L_is_clean_card); } else { __ movb(Address(addr, 0), G1CardTable::dirty_card_val()); } - Label next_card; - __ bind(next_card); + Label L_next_card; + __ bind(L_next_card); __ addptr(addr, sizeof(CardTable::CardValue)); __ cmpptr(addr, count); - __ jcc(Assembler::belowEqual, loop); - __ jmp(done); + __ jccb(Assembler::belowEqual, L_loop); + __ jmpb(L_done); - __ bind(is_clean_card); - // Card was clean. Dirty card and go to next.. + __ bind(L_is_clean_card); + // Card was clean. Dirty card and go to next. __ movb(Address(addr, 0), G1CardTable::dirty_card_val()); - __ jmp(next_card); + __ jmpb(L_next_card); - __ bind(done); + __ bind(L_done); } void G1BarrierSetAssembler::load_at(MacroAssembler* masm, DecoratorSet decorators, BasicType type, @@ -157,22 +157,6 @@ void G1BarrierSetAssembler::load_at(MacroAssembler* masm, DecoratorSet decorator } } -static void generate_queue_insertion(MacroAssembler* masm, ByteSize index_offset, ByteSize buffer_offset, Label& runtime, - const Register thread, const Register value, const Register temp) { - // This code assumes that buffer index is pointer sized. - STATIC_ASSERT(in_bytes(SATBMarkQueue::byte_width_of_index()) == sizeof(intptr_t)); - // Can we store a value in the given thread's buffer? - // (The index field is typed as size_t.) - __ movptr(temp, Address(thread, in_bytes(index_offset))); // temp := *(index address) - __ testptr(temp, temp); // index == 0? - __ jcc(Assembler::zero, runtime); // jump to runtime if index == 0 (full buffer) - // The buffer is not full, store value into it. - __ subptr(temp, wordSize); // temp := next index - __ movptr(Address(thread, in_bytes(index_offset)), temp); // *(index address) := next index - __ addptr(temp, Address(thread, in_bytes(buffer_offset))); // temp := buffer address + next index - __ movptr(Address(temp, 0), value); // *(buffer address + next index) := value -} - static void generate_pre_barrier_fast_path(MacroAssembler* masm, const Register thread) { Address in_progress(thread, in_bytes(G1ThreadLocalData::satb_mark_queue_active_offset())); @@ -190,21 +174,40 @@ static void generate_pre_barrier_slow_path(MacroAssembler* masm, const Register pre_val, const Register thread, const Register tmp, - Label& done, - Label& runtime) { + Label& L_done) { + Address index_addr(thread, in_bytes(G1ThreadLocalData::satb_mark_queue_index_offset())); + Address buffer_addr(thread, in_bytes(G1ThreadLocalData::satb_mark_queue_buffer_offset())); + + // This code assumes that buffer index is pointer sized. + STATIC_ASSERT(in_bytes(SATBMarkQueue::byte_width_of_index()) == sizeof(intptr_t)); + + Label L_runtime; + // Do we need to load the previous value? if (obj != noreg) { __ load_heap_oop(pre_val, Address(obj, 0), noreg, AS_RAW); } + // Is the previous value null? - __ cmpptr(pre_val, NULL_WORD); - __ jcc(Assembler::equal, done); - generate_queue_insertion(masm, - G1ThreadLocalData::satb_mark_queue_index_offset(), - G1ThreadLocalData::satb_mark_queue_buffer_offset(), - runtime, - thread, pre_val, tmp); - __ jmp(done); + __ testptr(pre_val, pre_val); + __ jcc(Assembler::equal, L_done); + + // Can we store a value in the given thread's buffer? + // (The index field is typed as size_t.) + __ movptr(tmp, index_addr); // temp := *(index address) + __ testptr(tmp, tmp); // index == 0? + __ jccb(Assembler::zero, L_runtime); // jump to runtime if index == 0 (full buffer) + + // The buffer is not full, store value into it. + __ subptr(tmp, wordSize); // temp := next index + __ movptr(index_addr, tmp); // *(index address) := next index + __ addptr(tmp, buffer_addr); // temp := buffer address + next index + __ movptr(Address(tmp, 0), pre_val); // *(buffer address + next index) := value + + // Jump out if done, or fall-through to runtime. + // "L_done" is far away, so jump cannot be short. + __ jmp(L_done); + __ bind(L_runtime); } void G1BarrierSetAssembler::g1_write_barrier_pre(MacroAssembler* masm, @@ -219,7 +222,6 @@ void G1BarrierSetAssembler::g1_write_barrier_pre(MacroAssembler* masm, const Register thread = r15_thread; Label done; - Label runtime; assert(pre_val != noreg, "check this code"); @@ -231,9 +233,7 @@ void G1BarrierSetAssembler::g1_write_barrier_pre(MacroAssembler* masm, generate_pre_barrier_fast_path(masm, thread); // If marking is not active (*(mark queue active address) == 0), jump to done __ jcc(Assembler::equal, done); - generate_pre_barrier_slow_path(masm, obj, pre_val, thread, tmp, done, runtime); - - __ bind(runtime); + generate_pre_barrier_slow_path(masm, obj, pre_val, thread, tmp, done); // Determine and save the live input values __ push_call_clobbered_registers(); @@ -272,23 +272,23 @@ static void generate_post_barrier(MacroAssembler* masm, const Register store_addr, const Register new_val, const Register tmp1, - Label& done, bool new_val_may_be_null) { assert_different_registers(store_addr, new_val, tmp1, noreg); Register thread = r15_thread; + Label L_done; // Does store cross heap regions? __ movptr(tmp1, store_addr); // tmp1 := store address __ xorptr(tmp1, new_val); // tmp1 := store address ^ new value __ shrptr(tmp1, G1HeapRegion::LogOfHRGrainBytes); // ((store address ^ new value) >> LogOfHRGrainBytes) == 0? - __ jcc(Assembler::equal, done); + __ jccb(Assembler::equal, L_done); // Crosses regions, storing null? if (new_val_may_be_null) { - __ cmpptr(new_val, NULL_WORD); // new value == null? - __ jcc(Assembler::equal, done); + __ testptr(new_val, new_val); // new value == null? + __ jccb(Assembler::equal, L_done); } __ movptr(tmp1, store_addr); // tmp1 := store address @@ -298,20 +298,19 @@ static void generate_post_barrier(MacroAssembler* masm, __ addptr(tmp1, card_table_addr); // tmp1 := card address if (UseCondCardMark) { __ cmpb(Address(tmp1, 0), G1CardTable::clean_card_val()); // *(card address) == clean_card_val? - __ jcc(Assembler::notEqual, done); + __ jccb(Assembler::notEqual, L_done); } // Storing a region crossing, non-null oop, card is clean. // Dirty card. __ movb(Address(tmp1, 0), G1CardTable::dirty_card_val()); // *(card address) := dirty_card_val + __ bind(L_done); } void G1BarrierSetAssembler::g1_write_barrier_post(MacroAssembler* masm, Register store_addr, Register new_val, Register tmp) { - Label done; - generate_post_barrier(masm, store_addr, new_val, tmp, done, true /* new_val_may_be_null */); - __ bind(done); + generate_post_barrier(masm, store_addr, new_val, tmp, true /* new_val_may_be_null */); } #if defined(COMPILER2) @@ -354,7 +353,6 @@ void G1BarrierSetAssembler::g1_write_barrier_pre_c2(MacroAssembler* masm, void G1BarrierSetAssembler::generate_c2_pre_barrier_stub(MacroAssembler* masm, G1PreBarrierStubC2* stub) const { Assembler::InlineSkippedInstructionsCounter skip_counter(masm); - Label runtime; Register obj = stub->obj(); Register pre_val = stub->pre_val(); Register thread = stub->thread(); @@ -362,9 +360,8 @@ void G1BarrierSetAssembler::generate_c2_pre_barrier_stub(MacroAssembler* masm, assert(stub->tmp2() == noreg, "not needed in this platform"); __ bind(*stub->entry()); - generate_pre_barrier_slow_path(masm, obj, pre_val, thread, tmp, *stub->continuation(), runtime); + generate_pre_barrier_slow_path(masm, obj, pre_val, thread, tmp, *stub->continuation()); - __ bind(runtime); generate_c2_barrier_runtime_call(masm, stub, pre_val, CAST_FROM_FN_PTR(address, G1BarrierSetRuntime::write_ref_field_pre_entry)); __ jmp(*stub->continuation()); } @@ -374,9 +371,7 @@ void G1BarrierSetAssembler::g1_write_barrier_post_c2(MacroAssembler* masm, Register new_val, Register tmp, bool new_val_may_be_null) { - Label done; - generate_post_barrier(masm, store_addr, new_val, tmp, done, new_val_may_be_null); - __ bind(done); + generate_post_barrier(masm, store_addr, new_val, tmp, new_val_may_be_null); } #endif // COMPILER2 @@ -449,7 +444,7 @@ void G1BarrierSetAssembler::gen_pre_barrier_stub(LIR_Assembler* ce, G1PreBarrier ce->mem2reg(stub->addr(), stub->pre_val(), T_OBJECT, stub->patch_code(), stub->info(), false /*wide*/); } - __ cmpptr(pre_val_reg, NULL_WORD); + __ testptr(pre_val_reg, pre_val_reg); __ jcc(Assembler::equal, *stub->continuation()); ce->store_parameter(stub->pre_val()->as_register(), 0); __ call(RuntimeAddress(bs->pre_barrier_c1_runtime_code_blob()->code_begin())); @@ -465,9 +460,7 @@ void G1BarrierSetAssembler::g1_write_barrier_post_c1(MacroAssembler* masm, Register thread, Register tmp1, Register tmp2 /* unused on x86 */) { - Label done; - generate_post_barrier(masm, store_addr, new_val, tmp1, done, true /* new_val_may_be_null */); - masm->bind(done); + generate_post_barrier(masm, store_addr, new_val, tmp1, true /* new_val_may_be_null */); } #define __ sasm-> @@ -490,8 +483,7 @@ void G1BarrierSetAssembler::generate_c1_pre_barrier_runtime_stub(StubAssembler* Address queue_index(thread, in_bytes(G1ThreadLocalData::satb_mark_queue_index_offset())); Address buffer(thread, in_bytes(G1ThreadLocalData::satb_mark_queue_buffer_offset())); - Label done; - Label runtime; + Label L_done, L_runtime; // Is marking still active? if (in_bytes(SATBMarkQueue::byte_width_of_active()) == 4) { @@ -500,13 +492,13 @@ void G1BarrierSetAssembler::generate_c1_pre_barrier_runtime_stub(StubAssembler* assert(in_bytes(SATBMarkQueue::byte_width_of_active()) == 1, "Assumption"); __ cmpb(queue_active, 0); } - __ jcc(Assembler::equal, done); + __ jcc(Assembler::equal, L_done); // Can we store original value in the thread's buffer? __ movptr(tmp, queue_index); __ testptr(tmp, tmp); - __ jcc(Assembler::zero, runtime); + __ jccb(Assembler::zero, L_runtime); __ subptr(tmp, wordSize); __ movptr(queue_index, tmp); __ addptr(tmp, buffer); @@ -514,9 +506,9 @@ void G1BarrierSetAssembler::generate_c1_pre_barrier_runtime_stub(StubAssembler* // prev_val (rax) __ load_parameter(0, pre_val); __ movptr(Address(tmp, 0), pre_val); - __ jmp(done); + __ jmp(L_done); - __ bind(runtime); + __ bind(L_runtime); __ push_call_clobbered_registers(); @@ -526,7 +518,7 @@ void G1BarrierSetAssembler::generate_c1_pre_barrier_runtime_stub(StubAssembler* __ pop_call_clobbered_registers(); - __ bind(done); + __ bind(L_done); __ pop_ppx(rdx); __ pop_ppx(rax); diff --git a/src/hotspot/cpu/x86/macroAssembler_x86.hpp b/src/hotspot/cpu/x86/macroAssembler_x86.hpp index 4cecaa55345..695eea6ad03 100644 --- a/src/hotspot/cpu/x86/macroAssembler_x86.hpp +++ b/src/hotspot/cpu/x86/macroAssembler_x86.hpp @@ -1368,6 +1368,7 @@ public: void vpcmpeqw(XMMRegister dst, XMMRegister nds, Address src, int vector_len); void vpcmpeqw(XMMRegister dst, XMMRegister nds, XMMRegister src, int vector_len); + using Assembler::evpcmpeqd; void evpcmpeqd(KRegister kdst, KRegister mask, XMMRegister nds, AddressLiteral src, int vector_len, Register rscratch = noreg); // Vector compares diff --git a/src/hotspot/cpu/x86/nativeInst_x86.hpp b/src/hotspot/cpu/x86/nativeInst_x86.hpp index 3e767006480..ec7fc3b154a 100644 --- a/src/hotspot/cpu/x86/nativeInst_x86.hpp +++ b/src/hotspot/cpu/x86/nativeInst_x86.hpp @@ -73,6 +73,7 @@ class NativeInstruction { s_char sbyte_at(int offset) const { return *(s_char*) addr_at(offset); } u_char ubyte_at(int offset) const { return *(u_char*) addr_at(offset); } + jshort short_at(int offset) const { return *(jshort*) addr_at(offset); } jint int_at(int offset) const { return *(jint*) addr_at(offset); } intptr_t ptr_at(int offset) const { return *(intptr_t*) addr_at(offset); } @@ -578,10 +579,15 @@ public: instruction_code = 0x0f, instruction_size = 8, instruction_offset = 0, - displacement_offset = 4 + displacement_offset = 4, + + // The two parts should be checked separately to prevent out of bounds access in case + // the return address points to the deopt handler stub code entry point which could be + // at the end of page. + first_check_size = 2 }; - bool check() const { return int_at(0) == 0x841f0f; } + bool check() const { return short_at(0) == 0x1f0f && short_at(first_check_size) == 0x0084; } bool decode(int32_t& oopmap_slot, int32_t& cb_offset) const { int32_t data = int_at(displacement_offset); if (data == 0) { diff --git a/src/hotspot/cpu/x86/runtime_x86_64.cpp b/src/hotspot/cpu/x86/runtime_x86_64.cpp index 7b98cf4fad7..5bf65299a0c 100644 --- a/src/hotspot/cpu/x86/runtime_x86_64.cpp +++ b/src/hotspot/cpu/x86/runtime_x86_64.cpp @@ -242,8 +242,6 @@ UncommonTrapBlob* OptoRuntime::generate_uncommon_trap_blob() { //------------------------------generate_exception_blob--------------------------- // creates exception blob at the end -// Using exception blob, this code is jumped from a compiled method. -// (see emit_exception_handler in x86_64.ad file) // // Given an exception pc at a call we call into the runtime for the // handler in this method. This handler might merely restore state diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_dilithium.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_dilithium.cpp index 9555d60c8a4..b9590939468 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64_dilithium.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_dilithium.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, Intel 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 @@ -30,8 +31,6 @@ #define __ _masm-> -#define xmm(i) as_XMMRegister(i) - #ifdef PRODUCT #define BLOCK_COMMENT(str) /* nothing */ #else @@ -40,15 +39,13 @@ #define BIND(label) bind(label); BLOCK_COMMENT(#label ":") -#define XMMBYTES 64 - // Constants // ATTRIBUTE_ALIGNED(64) static const uint32_t dilithiumAvx512Consts[] = { 58728449, // montQInvModR - 8380417, // dilithium_q - 2365951, // montRSquareModQ - 5373807 // Barrett addend for modular reduction + 8380417, // dilithium_q + 2365951, // montRSquareModQ + 5373807 // Barrett addend for modular reduction }; const int montQInvModRIdx = 0; @@ -60,392 +57,590 @@ static address dilithiumAvx512ConstsAddr(int offset) { return ((address) dilithiumAvx512Consts) + offset; } -const Register scratch = r10; -const XMMRegister montMulPerm = xmm28; -const XMMRegister montQInvModR = xmm30; -const XMMRegister dilithium_q = xmm31; +ATTRIBUTE_ALIGNED(64) static const uint32_t unshufflePerms[] = { + // Shuffle for the 128-bit element swap (uint64_t) + 0, 0, 1, 0, 8, 0, 9, 0, 4, 0, 5, 0, 12, 0, 13, 0, + 10, 0, 11, 0, 2, 0, 3, 0, 14, 0, 15, 0, 6, 0, 7, 0, + // Final shuffle for AlmostNtt + 0, 16, 1, 17, 2, 18, 3, 19, 4, 20, 5, 21, 6, 22, 7, 23, + 24, 8, 25, 9, 26, 10, 27, 11, 28, 12, 29, 13, 30, 14, 31, 15, -ATTRIBUTE_ALIGNED(64) static const uint32_t dilithiumAvx512Perms[] = { - // collect montmul results into the destination register - 17, 1, 19, 3, 21, 5, 23, 7, 25, 9, 27, 11, 29, 13, 31, 15, - // ntt - // level 4 - 0, 1, 2, 3, 4, 5, 6, 7, 16, 17, 18, 19, 20, 21, 22, 23, - 8, 9, 10, 11, 12, 13, 14, 15, 24, 25, 26, 27, 28, 29, 30, 31, - // level 5 - 0, 1, 2, 3, 16, 17, 18, 19, 8, 9, 10, 11, 24, 25, 26, 27, - 4, 5, 6, 7, 20, 21, 22, 23, 12, 13, 14, 15, 28, 29, 30, 31, - // level 6 - 0, 1, 16, 17, 4, 5, 20, 21, 8, 9, 24, 25, 12, 13, 28, 29, - 2, 3, 18, 19, 6, 7, 22, 23, 10, 11, 26, 27, 14, 15, 30, 31, - // level 7 - 0, 16, 2, 18, 4, 20, 6, 22, 8, 24, 10, 26, 12, 28, 14, 30, - 1, 17, 3, 19, 5, 21, 7, 23, 9, 25, 11, 27, 13, 29, 15, 31, - 0, 16, 1, 17, 2, 18, 3, 19, 4, 20, 5, 21, 6, 22, 7, 23, - 8, 24, 9, 25, 10, 26, 11, 27, 12, 28, 13, 29, 14, 30, 15, 31, - - // ntt inverse - // level 0 - 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, - 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, - // level 1 - 0, 16, 2, 18, 4, 20, 6, 22, 8, 24, 10, 26, 12, 28, 14, 30, - 1, 17, 3, 19, 5, 21, 7, 23, 9, 25, 11, 27, 13, 29, 15, 31, - // level 2 - 0, 1, 16, 17, 4, 5, 20, 21, 8, 9, 24, 25, 12, 13, 28, 29, - 2, 3, 18, 19, 6, 7, 22, 23, 10, 11, 26, 27, 14, 15, 30, 31, - // level 3 - 0, 1, 2, 3, 16, 17, 18, 19, 8, 9, 10, 11, 24, 25, 26, 27, - 4, 5, 6, 7, 20, 21, 22, 23, 12, 13, 14, 15, 28, 29, 30, 31, - // level 4 - 0, 1, 2, 3, 4, 5, 6, 7, 16, 17, 18, 19, 20, 21, 22, 23, - 8, 9, 10, 11, 12, 13, 14, 15, 24, 25, 26, 27, 28, 29, 30, 31 + // Initial shuffle for AlmostInverseNtt + 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, + 17, 19, 21, 23, 25, 27, 29, 31, 1, 3, 5, 7, 9, 11, 13, 15 }; -const int montMulPermsIdx = 0; -const int nttL4PermsIdx = 64; -const int nttL5PermsIdx = 192; -const int nttL6PermsIdx = 320; -const int nttL7PermsIdx = 448; -const int nttInvL0PermsIdx = 704; -const int nttInvL1PermsIdx = 832; -const int nttInvL2PermsIdx = 960; -const int nttInvL3PermsIdx = 1088; -const int nttInvL4PermsIdx = 1216; - -static address dilithiumAvx512PermsAddr() { - return (address) dilithiumAvx512Perms; +static address unshufflePermsAddr(int offset) { + return ((address) unshufflePerms) + offset*64; } -// We do Montgomery multiplications of two vectors of 16 ints each in 4 steps: -// 1. Do the multiplications of the corresponding even numbered slots into -// the odd numbered slots of a third register. -// 2. Swap the even and odd numbered slots of the original input registers. -// 3. Similar to step 1, but into a different output register. -// 4. Combine the outputs of step 1 and step 3 into the output of the Montgomery -// multiplication. -// (For levels 0-6 in the Ntt and levels 1-7 of the inverse Ntt we only swap the -// odd-even slots of the first multiplicand as in the second (zetas) the -// odd slots contain the same number as the corresponding even one.) -// The indexes of the registers to be multiplied -// are in inputRegs1[] and inputRegs[2]. -// The results go to the registers whose indexes are in outputRegs. -// scratchRegs should contain 12 different register indexes. -// The set in outputRegs should not overlap with the set of the middle four -// scratch registers. -// The sets in inputRegs1 and inputRegs2 cannot overlap with the set of the -// first eight scratch registers. -// In most of the cases, the odd and the corresponding even slices of the -// registers indexed by the numbers in inputRegs2 will contain the same number, -// this should be indicated by calling this function with -// input2NeedsShuffle=false . +// The following function swaps elements A<->B, C<->D, and so forth. +// input1[] is shuffled in place; shuffle of input2[] is copied to output2[]. +// Element size (in bits) is specified by size parameter. +// +-----+-----+-----+-----+----- +// | | A | | C | ... +// +-----+-----+-----+-----+----- +// +-----+-----+-----+-----+----- +// | B | | D | | ... +// +-----+-----+-----+-----+----- // -static void montMul64(int outputRegs[], int inputRegs1[], int inputRegs2[], - int scratchRegs[], bool input2NeedsShuffle, - MacroAssembler *_masm) { +// NOTE: size 0 and 1 are used for initial and final shuffles respectively of +// dilithiumAlmostInverseNtt and dilithiumAlmostNtt. For size 0 and 1, input1[] +// and input2[] are modified in-place (and output2 is used as a temporary) +// +// Using C++ lambdas for improved readability (to hide parameters that always repeat) +static auto whole_shuffle(Register scratch, KRegister mergeMask1, KRegister mergeMask2, + const XMMRegister unshuffle1, const XMMRegister unshuffle2, int vector_len, MacroAssembler *_masm) { - for (int i = 0; i < 4; i++) { - __ vpmuldq(xmm(scratchRegs[i]), xmm(inputRegs1[i]), xmm(inputRegs2[i]), - Assembler::AVX_512bit); - } - for (int i = 0; i < 4; i++) { - __ vpmulld(xmm(scratchRegs[i + 4]), xmm(scratchRegs[i]), montQInvModR, - Assembler::AVX_512bit); - } - for (int i = 0; i < 4; i++) { - __ vpmuldq(xmm(scratchRegs[i + 4]), xmm(scratchRegs[i + 4]), dilithium_q, - Assembler::AVX_512bit); - } - for (int i = 0; i < 4; i++) { - __ evpsubd(xmm(scratchRegs[i + 4]), k0, xmm(scratchRegs[i]), - xmm(scratchRegs[i + 4]), false, Assembler::AVX_512bit); + int regCnt = 4; + if (vector_len == Assembler::AVX_256bit) { + regCnt = 2; } - for (int i = 0; i < 4; i++) { - __ vpshufd(xmm(inputRegs1[i]), xmm(inputRegs1[i]), 0xB1, - Assembler::AVX_512bit); - if (input2NeedsShuffle) { - __ vpshufd(xmm(inputRegs2[i]), xmm(inputRegs2[i]), 0xB1, - Assembler::AVX_512bit); + return [=](const XMMRegister output2[], const XMMRegister input1[], + const XMMRegister input2[], int size) { + if (vector_len == Assembler::AVX_256bit) { + switch (size) { + case 128: + for (int i = 0; i < regCnt; i++) { + __ vperm2i128(output2[i], input1[i], input2[i], 0b110001); + } + for (int i = 0; i < regCnt; i++) { + __ vinserti128(input1[i], input1[i], input2[i], 1); + } + break; + case 64: + for (int i = 0; i < regCnt; i++) { + __ vshufpd(output2[i], input1[i], input2[i], 0b11111111, vector_len); + } + for (int i = 0; i < regCnt; i++) { + __ vshufpd(input1[i], input1[i], input2[i], 0b00000000, vector_len); + } + break; + case 32: + for (int i = 0; i < regCnt; i++) { + __ vmovshdup(output2[i], input1[i], vector_len); + } + for (int i = 0; i < regCnt; i++) { + __ vpblendd(output2[i], output2[i], input2[i], 0b10101010, vector_len); + } + for (int i = 0; i < regCnt; i++) { + __ vmovsldup(input2[i], input2[i], vector_len); + } + for (int i = 0; i < regCnt; i++) { + __ vpblendd(input1[i], input1[i], input2[i], 0b10101010, vector_len); + } + break; + // Special cases + case 1: // initial shuffle for dilithiumAlmostInverseNtt + // shuffle all even 32bit columns to input1, and odd to input2 + for (int i = 0; i < regCnt; i++) { + // 0b-3-1-3-1 + __ vshufps(output2[i], input1[i], input2[i], 0b11011101, vector_len); + } + for (int i = 0; i < regCnt; i++) { + // 0b-2-0-2-0 + __ vshufps(input1[i], input1[i], input2[i], 0b10001000, vector_len); + } + for (int i = 0; i < regCnt; i++) { + __ vpermq(input2[i], output2[i], 0b11011000, vector_len); + } + for (int i = 0; i < regCnt; i++) { + // 0b-3-1-2-0 + __ vpermq(input1[i], input1[i], 0b11011000, vector_len); + } + break; + case 0: // final unshuffle for dilithiumAlmostNtt + // reverse case 1: all even are in input1 and odd in input2, put back + for (int i = 0; i < regCnt; i++) { + __ vpunpckhdq(output2[i], input1[i], input2[i], vector_len); + } + for (int i = 0; i < regCnt; i++) { + __ vpunpckldq(input1[i], input1[i], input2[i], vector_len); + } + for (int i = 0; i < regCnt; i++) { + __ vperm2i128(input2[i], input1[i], output2[i], 0b110001); + } + for (int i = 0; i < regCnt; i++) { + __ vinserti128(input1[i], input1[i], output2[i], 1); + } + break; + default: + assert(false, "Don't call here"); + } + } else { + switch (size) { + case 256: + for (int i = 0; i < regCnt; i++) { + // 0b-3-2-3-2 + __ evshufi64x2(output2[i], input1[i], input2[i], 0b11101110, vector_len); + } + for (int i = 0; i < regCnt; i++) { + __ vinserti64x4(input1[i], input1[i], input2[i], 1); + } + break; + case 128: + for (int i = 0; i < regCnt; i++) { + __ vmovdqu(output2[i], input2[i], vector_len); + } + for (int i = 0; i < regCnt; i++) { + __ evpermt2q(output2[i], unshuffle2, input1[i], vector_len); + } + for (int i = 0; i < regCnt; i++) { + __ evpermt2q(input1[i], unshuffle1, input2[i], vector_len); + } + + break; + case 64: + for (int i = 0; i < regCnt; i++) { + __ vshufpd(output2[i], input1[i], input2[i], 0b11111111, vector_len); + } + for (int i = 0; i < regCnt; i++) { + __ vshufpd(input1[i], input1[i], input2[i], 0b00000000, vector_len); + } + break; + case 32: + for (int i = 0; i < regCnt; i++) { + __ vmovdqu(output2[i], input2[i], vector_len); + } + for (int i = 0; i < regCnt; i++) { + __ evmovshdup(output2[i], mergeMask2, input1[i], true, vector_len); + } + for (int i = 0; i < regCnt; i++) { + __ evmovsldup(input1[i], mergeMask1, input2[i], true, vector_len); + } + break; + // Special cases + case 1: // initial shuffle for dilithiumAlmostInverseNtt + // shuffle all even 32bit columns to input1, and odd to input2 + for (int i = 0; i < regCnt; i++) { + __ vmovdqu(output2[i], input2[i], vector_len); + } + for (int i = 0; i < regCnt; i++) { + __ evpermt2d(input2[i], unshuffle2, input1[i], vector_len); + } + for (int i = 0; i < regCnt; i++) { + __ evpermt2d(input1[i], unshuffle1, output2[i], vector_len); + } + break; + case 0: // final unshuffle for dilithiumAlmostNtt + // reverse case 1: all even are in input1 and odd in input2, put back + for (int i = 0; i < regCnt; i++) { + __ vmovdqu(output2[i], input2[i], vector_len); + } + for (int i = 0; i < regCnt; i++) { + __ evpermt2d(input2[i], unshuffle2, input1[i], vector_len); + } + for (int i = 0; i < regCnt; i++) { + __ evpermt2d(input1[i], unshuffle1, output2[i], vector_len); + } + break; + default: + assert(false, "Don't call here"); + } } - } - - for (int i = 0; i < 4; i++) { - __ vpmuldq(xmm(scratchRegs[i]), xmm(inputRegs1[i]), xmm(inputRegs2[i]), - Assembler::AVX_512bit); - } - for (int i = 0; i < 4; i++) { - __ vpmulld(xmm(scratchRegs[i + 8]), xmm(scratchRegs[i]), montQInvModR, - Assembler::AVX_512bit); - } - for (int i = 0; i < 4; i++) { - __ vpmuldq(xmm(scratchRegs[i + 8]), xmm(scratchRegs[i + 8]), dilithium_q, - Assembler::AVX_512bit); - } - for (int i = 0; i < 4; i++) { - __ evpsubd(xmm(outputRegs[i]), k0, xmm(scratchRegs[i]), - xmm(scratchRegs[i + 8]), false, Assembler::AVX_512bit); - } - - for (int i = 0; i < 4; i++) { - __ evpermt2d(xmm(outputRegs[i]), montMulPerm, xmm(scratchRegs[i + 4]), - Assembler::AVX_512bit); - } + }; // return } -static void montMul64(int outputRegs[], int inputRegs1[], int inputRegs2[], - int scratchRegs[], MacroAssembler *_masm) { - montMul64(outputRegs, inputRegs1, inputRegs2, scratchRegs, false, _masm); -} - -static void sub_add(int subResult[], int addResult[], - int input1[], int input2[], MacroAssembler *_masm) { - - for (int i = 0; i < 4; i++) { - __ evpsubd(xmm(subResult[i]), k0, xmm(input1[i]), xmm(input2[i]), false, - Assembler::AVX_512bit); +// We do Montgomery multiplications of two AVX registers in 4 steps: +// 1. Do the multiplications of the corresponding even numbered slots into +// the odd numbered slots of the scratch2 register. +// 2. Swap the even and odd numbered slots of the original input registers.(*Note) +// 3. Similar to step 1, but multiplication result is placed into output register. +// 4. Combine odd/even slots respectively from the scratch2 and output registers +// into the output register for the final result of the Montgomery multiplication. +// (*Note: For levels 0-6 in the Ntt and levels 1-7 of the inverse Ntt, need NOT +// swap the second operand (zetas) since the odd slots contain the same number +// as the corresponding even one. This is indicated by input2NeedsShuffle=false) +// +// The registers to be multiplied are in input1[] and inputs2[]. The results go +// into output[]. Two scratch[] register arrays are expected. input1[] can +// overlap with either output[] or scratch1[] +// - If AVX512, all register arrays are of length 4 +// - If AVX2, first two registers of each array are in xmm0-xmm15 range +// Constants montQInvModR, dilithium_q and mergeMask expected to have already +// been loaded. +// +// Using C++ lambdas for improved readability (to hide parameters that always repeat) +static auto whole_montMul(XMMRegister montQInvModR, XMMRegister dilithium_q, + KRegister mergeMask, int vector_len, MacroAssembler *_masm) { + int regCnt = 4; + int regSize = 64; + if (vector_len == Assembler::AVX_256bit) { + regCnt = 2; + regSize = 32; } - for (int i = 0; i < 4; i++) { - __ evpaddd(xmm(addResult[i]), k0, xmm(input1[i]), xmm(input2[i]), false, - Assembler::AVX_512bit); - } -} + return [=](const XMMRegister output[], const XMMRegister input1[], + const XMMRegister input2[], const XMMRegister scratch1[], + const XMMRegister scratch2[], bool input2NeedsShuffle = false) { + // (Register overloading) Can't always use scratch1 (could override input1). + // If so, use output: + const XMMRegister* scratch = scratch1 == input1 ? output: scratch1; -static void loadPerm(int destinationRegs[], Register perms, - int offset, MacroAssembler *_masm) { - __ evmovdqul(xmm(destinationRegs[0]), Address(perms, offset), - Assembler::AVX_512bit); - for (int i = 1; i < 4; i++) { - __ evmovdqul(xmm(destinationRegs[i]), xmm(destinationRegs[0]), - Assembler::AVX_512bit); + // scratch = input1_even * intput2_even + for (int i = 0; i < regCnt; i++) { + __ vpmuldq(scratch[i], input1[i], input2[i], vector_len); } + + // scratch2_low = scratch_low * montQInvModR + for (int i = 0; i < regCnt; i++) { + __ vpmuldq(scratch2[i], scratch[i], montQInvModR, vector_len); + } + + // scratch2 = scratch2_low * dilithium_q + for (int i = 0; i < regCnt; i++) { + __ vpmuldq(scratch2[i], scratch2[i], dilithium_q, vector_len); + } + + // scratch2_high = scratch2_high - scratch_high + for (int i = 0; i < regCnt; i++) { + __ vpsubd(scratch2[i], scratch[i], scratch2[i], vector_len); + } + + // input1_even = input1_odd + // input2_even = input2_odd + for (int i = 0; i < regCnt; i++) { + __ vpshufd(input1[i], input1[i], 0xB1, vector_len); + if (input2NeedsShuffle) { + __ vpshufd(input2[i], input2[i], 0xB1, vector_len); + } + } + + // scratch1 = input1_even*intput2_even + for (int i = 0; i < regCnt; i++) { + __ vpmuldq(scratch1[i], input1[i], input2[i], vector_len); + } + + // output = scratch1_low * montQInvModR + for (int i = 0; i < regCnt; i++) { + __ vpmuldq(output[i], scratch1[i], montQInvModR, vector_len); + } + + // output = output * dilithium_q + for (int i = 0; i < regCnt; i++) { + __ vpmuldq(output[i], output[i], dilithium_q, vector_len); + } + + // output_high = scratch1_high - output_high + for (int i = 0; i < regCnt; i++) { + __ vpsubd(output[i], scratch1[i], output[i], vector_len); + } + + // output = select(output_high, scratch2_high) + if (vector_len == Assembler::AVX_256bit) { + for (int i = 0; i < regCnt; i++) { + __ vmovshdup(scratch2[i], scratch2[i], vector_len); + } + for (int i = 0; i < regCnt; i++) { + __ vpblendd(output[i], output[i], scratch2[i], 0b01010101, vector_len); + } + } else { + for (int i = 0; i < regCnt; i++) { + __ evmovshdup(output[i], mergeMask, scratch2[i], true, vector_len); + } + } + }; // return } -static void load4Xmms(int destinationRegs[], Register source, int offset, - MacroAssembler *_masm) { - for (int i = 0; i < 4; i++) { - __ evmovdqul(xmm(destinationRegs[i]), Address(source, offset + i * XMMBYTES), - Assembler::AVX_512bit); +static void sub_add(const XMMRegister subResult[], const XMMRegister addResult[], + const XMMRegister input1[], const XMMRegister input2[], + int vector_len, MacroAssembler *_masm) { + int regCnt = 4; + if (vector_len == Assembler::AVX_256bit) { + regCnt = 2; + } + + for (int i = 0; i < regCnt; i++) { + __ vpsubd(subResult[i], input1[i], input2[i], vector_len); + } + + for (int i = 0; i < regCnt; i++) { + __ vpaddd(addResult[i], input1[i], input2[i], vector_len); } } -static void loadXmm29(Register source, int offset, MacroAssembler *_masm) { - __ evmovdqul(xmm29, Address(source, offset), Assembler::AVX_512bit); -} +static void loadXmms(const XMMRegister destinationRegs[], Register source, int offset, + int vector_len, MacroAssembler *_masm, int regCnt = -1, int memStep = -1) { -static void store4Xmms(Register destination, int offset, int xmmRegs[], - MacroAssembler *_masm) { - for (int i = 0; i < 4; i++) { - __ evmovdqul(Address(destination, offset + i * XMMBYTES), xmm(xmmRegs[i]), - Assembler::AVX_512bit); + if (vector_len == Assembler::AVX_256bit) { + regCnt = regCnt == -1 ? 2 : regCnt; + memStep = memStep == -1 ? 32 : memStep; + } else { + regCnt = 4; + memStep = 64; + } + + for (int i = 0; i < regCnt; i++) { + __ vmovdqu(destinationRegs[i], Address(source, offset + i * memStep), vector_len); } } -static int xmm0_3[] = {0, 1, 2, 3}; -static int xmm0145[] = {0, 1, 4, 5}; -static int xmm0246[] = {0, 2, 4, 6}; -static int xmm0426[] = {0, 4, 2, 6}; -static int xmm1357[] = {1, 3, 5, 7}; -static int xmm1537[] = {1, 5, 3, 7}; -static int xmm2367[] = {2, 3, 6, 7}; -static int xmm4_7[] = {4, 5, 6, 7}; -static int xmm8_11[] = {8, 9, 10, 11}; -static int xmm12_15[] = {12, 13, 14, 15}; -static int xmm16_19[] = {16, 17, 18, 19}; -static int xmm20_23[] = {20, 21, 22, 23}; -static int xmm20222426[] = {20, 22, 24, 26}; -static int xmm21232527[] = {21, 23, 25, 27}; -static int xmm24_27[] = {24, 25, 26, 27}; -static int xmm4_20_24[] = {4, 5, 6, 7, 20, 21, 22, 23, 24, 25, 26, 27}; -static int xmm16_27[] = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27}; -static int xmm29_29[] = {29, 29, 29, 29}; +static void storeXmms(Register destination, int offset, const XMMRegister xmmRegs[], + int vector_len, MacroAssembler *_masm, int regCnt = -1, int memStep = -1) { + if (vector_len == Assembler::AVX_256bit) { + regCnt = regCnt == -1 ? 2 : regCnt; + memStep = memStep == -1 ? 32 : memStep; + } else { + regCnt = 4; + memStep = 64; + } + + for (int i = 0; i < regCnt; i++) { + __ vmovdqu(Address(destination, offset + i * memStep), xmmRegs[i], vector_len); + } +} // Dilithium NTT function except for the final "normalization" to |coeff| < Q. // Implements // static int implDilithiumAlmostNtt(int[] coeffs, int zetas[]) {} // // coeffs (int[256]) = c_rarg0 -// zetas (int[256]) = c_rarg1 +// zetas (int[128*8]) = c_rarg1 // -// -static address generate_dilithiumAlmostNtt_avx512(StubGenerator *stubgen, - MacroAssembler *_masm) { - +static address generate_dilithiumAlmostNtt_avx(StubGenerator *stubgen, + int vector_len, MacroAssembler *_masm) { __ align(CodeEntryAlignment); StubId stub_id = StubId::stubgen_dilithiumAlmostNtt_id; StubCodeMark mark(stubgen, stub_id); address start = __ pc(); __ enter(); - Label L_loop, L_end; - const Register coeffs = c_rarg0; const Register zetas = c_rarg1; - const Register iterations = c_rarg2; - - const Register perms = r11; - - __ lea(perms, ExternalAddress(dilithiumAvx512PermsAddr())); - - __ evmovdqul(montMulPerm, Address(perms, montMulPermsIdx), Assembler::AVX_512bit); + const Register scratch = r10; // Each level represents one iteration of the outer for loop of the Java version // In each of these iterations half of the coefficients are (Montgomery) // multiplied by a zeta corresponding to the coefficient and then these // products will be added to and subtracted from the other half of the - // coefficients. In each level we just collect the coefficients (using - // evpermi2d() instructions where necessary, i.e. in levels 4-7) that need to + // coefficients. In each level we just shuffle the coefficients that need to // be multiplied by the zetas in one set, the rest to another set of vector // registers, then redistribute the addition/substraction results. // For levels 0 and 1 the zetas are not different within the 4 xmm registers - // that we would use for them, so we use only one, xmm29. - loadXmm29(zetas, 0, _masm); + // that we would use for them, so we use only one register. + + // AVX2 version uses the first half of these arrays + const XMMRegister Coeffs1[] = {xmm0, xmm1, xmm16, xmm17}; + const XMMRegister Coeffs2[] = {xmm2, xmm3, xmm18, xmm19}; + const XMMRegister Coeffs3[] = {xmm4, xmm5, xmm20, xmm21}; + const XMMRegister Coeffs4[] = {xmm6, xmm7, xmm22, xmm23}; + const XMMRegister Scratch1[] = {xmm8, xmm9, xmm24, xmm25}; + const XMMRegister Scratch2[] = {xmm10, xmm11, xmm26, xmm27}; + const XMMRegister Zetas1[] = {xmm12, xmm12, xmm12, xmm12}; + const XMMRegister Zetas2[] = {xmm12, xmm12, xmm13, xmm13}; + const XMMRegister Zetas3[] = {xmm12, xmm13, xmm28, xmm29}; + const XMMRegister montQInvModR = xmm14; + const XMMRegister dilithium_q = xmm15; + const XMMRegister unshuffle1 = xmm30; + const XMMRegister unshuffle2 = xmm31; + KRegister mergeMask1 = k1; + KRegister mergeMask2 = k2; + // lambdas to hide repeated parameters + auto shuffle = whole_shuffle(scratch, mergeMask1, mergeMask2, unshuffle1, unshuffle2, vector_len, _masm); + auto montMul64 = whole_montMul(montQInvModR, dilithium_q, mergeMask2, vector_len, _masm); + __ vpbroadcastd(montQInvModR, ExternalAddress(dilithiumAvx512ConstsAddr(montQInvModRIdx)), - Assembler::AVX_512bit, scratch); // q^-1 mod 2^32 + vector_len, scratch); // q^-1 mod 2^32 __ vpbroadcastd(dilithium_q, ExternalAddress(dilithiumAvx512ConstsAddr(dilithium_qIdx)), - Assembler::AVX_512bit, scratch); // q + vector_len, scratch); // q - // load all coefficients into the vector registers Zmm_0-Zmm_15, - // 16 coefficients into each - load4Xmms(xmm0_3, coeffs, 0, _masm); - load4Xmms(xmm4_7, coeffs, 4 * XMMBYTES, _masm); - load4Xmms(xmm8_11, coeffs, 8 * XMMBYTES, _masm); - load4Xmms(xmm12_15, coeffs, 12 * XMMBYTES, _masm); + if (vector_len == Assembler::AVX_512bit) { + // levels 0-3, register shuffles: + const XMMRegister Coeffs1_1[] = {xmm0, xmm1, xmm2, xmm3}; + const XMMRegister Coeffs2_1[] = {xmm16, xmm17, xmm18, xmm19}; + const XMMRegister Coeffs3_1[] = {xmm4, xmm5, xmm6, xmm7}; + const XMMRegister Coeffs4_1[] = {xmm20, xmm21, xmm22, xmm23}; + const XMMRegister Coeffs1_2[] = {xmm0, xmm16, xmm2, xmm18}; + const XMMRegister Coeffs2_2[] = {xmm1, xmm17, xmm3, xmm19}; + const XMMRegister Coeffs3_2[] = {xmm4, xmm20, xmm6, xmm22}; + const XMMRegister Coeffs4_2[] = {xmm5, xmm21, xmm7, xmm23}; - // level 0 and 1 can be done entirely in registers as the zetas on these - // levels are the same for all the montmuls that we can do in parallel + // Constants for shuffle and montMul64 + __ mov64(scratch, 0b1010101010101010); + __ kmovwl(mergeMask1, scratch); + __ knotwl(mergeMask2, mergeMask1); + __ vmovdqu(unshuffle1, ExternalAddress(unshufflePermsAddr(0)), vector_len, scratch); + __ vmovdqu(unshuffle2, ExternalAddress(unshufflePermsAddr(1)), vector_len, scratch); - // level 0 - montMul64(xmm16_19, xmm8_11, xmm29_29, xmm16_27, _masm); - sub_add(xmm8_11, xmm0_3, xmm0_3, xmm16_19, _masm); - montMul64(xmm16_19, xmm12_15, xmm29_29, xmm16_27, _masm); - loadXmm29(zetas, 512, _masm); // for level 1 - sub_add(xmm12_15, xmm4_7, xmm4_7, xmm16_19, _masm); + int memStep = 4 * 64; // 4*64-byte registers + loadXmms(Coeffs1, coeffs, 0*memStep, vector_len, _masm); + loadXmms(Coeffs2, coeffs, 1*memStep, vector_len, _masm); + loadXmms(Coeffs3, coeffs, 2*memStep, vector_len, _masm); + loadXmms(Coeffs4, coeffs, 3*memStep, vector_len, _masm); - // level 1 + // level 0-3 can be done by shuffling registers (also notice fewer zetas loads, they repeat) + // level 0 - 128 + // scratch1 = coeffs3 * zetas1 + // coeffs3, coeffs1 = coeffs1 ± scratch1 + // scratch1 = coeffs4 * zetas1 + // coeffs4, coeffs2 = coeffs2 ± scratch1 + __ vmovdqu(Zetas1[0], Address(zetas, 0), vector_len); + montMul64(Scratch1, Coeffs3, Zetas1, Coeffs3, Scratch2); + sub_add(Coeffs3, Coeffs1, Coeffs1, Scratch1, vector_len, _masm); + montMul64(Scratch1, Coeffs4, Zetas1, Coeffs4, Scratch2); + sub_add(Coeffs4, Coeffs2, Coeffs2, Scratch1, vector_len, _masm); - montMul64(xmm16_19, xmm4_7, xmm29_29, xmm16_27, _masm); - loadXmm29(zetas, 768, _masm); - sub_add(xmm4_7, xmm0_3, xmm0_3, xmm16_19, _masm); - montMul64(xmm16_19, xmm12_15, xmm29_29, xmm16_27, _masm); - sub_add(xmm12_15, xmm8_11, xmm8_11, xmm16_19, _masm); + // level 1 - 64 + __ vmovdqu(Zetas1[0], Address(zetas, 512), vector_len); + montMul64(Scratch1, Coeffs2, Zetas1, Coeffs2, Scratch2); + sub_add(Coeffs2, Coeffs1, Coeffs1, Scratch1, vector_len, _masm); - // levels 2 to 7 are done in 2 batches, by first saving half of the coefficients - // from level 1 into memory, doing all the level 2 to level 7 computations - // on the remaining half in the vector registers, saving the result to - // memory after level 7, then loading back the coefficients that we saved after - // level 1 and do the same computation with those + __ vmovdqu(Zetas1[0], Address(zetas, 4*64 + 512), vector_len); + montMul64(Scratch1, Coeffs4, Zetas1, Coeffs4, Scratch2); + sub_add(Coeffs4, Coeffs3, Coeffs3, Scratch1, vector_len, _masm); - store4Xmms(coeffs, 8 * XMMBYTES, xmm8_11, _masm); - store4Xmms(coeffs, 12 * XMMBYTES, xmm12_15, _masm); + // level 2 - 32 + __ vmovdqu(Zetas2[0], Address(zetas, 2 * 512), vector_len); + __ vmovdqu(Zetas2[2], Address(zetas, 2*64 + 2 * 512), vector_len); + montMul64(Scratch1, Coeffs2_1, Zetas2, Coeffs2_1, Scratch2); + sub_add(Coeffs2_1, Coeffs1_1, Coeffs1_1, Scratch1, vector_len, _masm); - __ movl(iterations, 2); + __ vmovdqu(Zetas2[0], Address(zetas, 4*64 + 2 * 512), vector_len); + __ vmovdqu(Zetas2[2], Address(zetas, 6*64 + 2 * 512), vector_len); + montMul64(Scratch1, Coeffs4_1, Zetas2, Coeffs4_1, Scratch2); + sub_add(Coeffs4_1, Coeffs3_1, Coeffs3_1, Scratch1, vector_len, _masm); - __ align(OptoLoopAlignment); - __ BIND(L_loop); + // level 3 - 16 + loadXmms(Zetas3, zetas, 3 * 512, vector_len, _masm); + montMul64(Scratch1, Coeffs2_2, Zetas3, Coeffs2_2, Scratch2); + sub_add(Coeffs2_2, Coeffs1_2, Coeffs1_2, Scratch1, vector_len, _masm); - __ subl(iterations, 1); + loadXmms(Zetas3, zetas, 4*64 + 3 * 512, vector_len, _masm); + montMul64(Scratch1, Coeffs4_2, Zetas3, Coeffs4_2, Scratch2); + sub_add(Coeffs4_2, Coeffs3_2, Coeffs3_2, Scratch1, vector_len, _masm); - // level 2 - load4Xmms(xmm12_15, zetas, 2 * 512, _masm); - montMul64(xmm16_19, xmm2367, xmm12_15, xmm16_27, _masm); - load4Xmms(xmm12_15, zetas, 3 * 512, _masm); // for level 3 - sub_add(xmm2367, xmm0145, xmm0145, xmm16_19, _masm); + for (int level = 4, distance = 8; level<8; level++, distance /= 2) { + // zetas = load(level * 512) + // coeffs1_2, scratch1 = shuffle(coeffs1_2, coeffs2_2) + // scratch1 = scratch1 * zetas + // coeffs2_2 = coeffs1_2 - scratch1 + // coeffs1_2 = coeffs1_2 + scratch1 + loadXmms(Zetas3, zetas, level * 512, vector_len, _masm); + shuffle(Scratch1, Coeffs1_2, Coeffs2_2, distance * 32); // Coeffs2_2 freed + montMul64(Scratch1, Scratch1, Zetas3, Coeffs2_2, Scratch2, level==7); + sub_add(Coeffs2_2, Coeffs1_2, Coeffs1_2, Scratch1, vector_len, _masm); - // level 3 + loadXmms(Zetas3, zetas, 4*64 + level * 512, vector_len, _masm); + shuffle(Scratch1, Coeffs3_2, Coeffs4_2, distance * 32); // Coeffs4_2 freed + montMul64(Scratch1, Scratch1, Zetas3, Coeffs4_2, Scratch2, level==7); + sub_add(Coeffs4_2, Coeffs3_2, Coeffs3_2, Scratch1, vector_len, _masm); + } - montMul64(xmm16_19, xmm1357, xmm12_15, xmm16_27, _masm); - sub_add(xmm1357, xmm0246, xmm0246, xmm16_19, _masm); + // Constants for final unshuffle + __ vmovdqu(unshuffle1, ExternalAddress(unshufflePermsAddr(2)), vector_len, scratch); + __ vmovdqu(unshuffle2, ExternalAddress(unshufflePermsAddr(3)), vector_len, scratch); + shuffle(Scratch1, Coeffs1_2, Coeffs2_2, 0); + shuffle(Scratch1, Coeffs3_2, Coeffs4_2, 0); - // level 4 - loadPerm(xmm16_19, perms, nttL4PermsIdx, _masm); - loadPerm(xmm12_15, perms, nttL4PermsIdx + 64, _masm); - load4Xmms(xmm24_27, zetas, 4 * 512, _masm); + storeXmms(coeffs, 0*memStep, Coeffs1, vector_len, _masm); + storeXmms(coeffs, 1*memStep, Coeffs2, vector_len, _masm); + storeXmms(coeffs, 2*memStep, Coeffs3, vector_len, _masm); + storeXmms(coeffs, 3*memStep, Coeffs4, vector_len, _masm); + } else { // Assembler::AVX_256bit + // levels 0-4, register shuffles: + const XMMRegister Coeffs1_1[] = {xmm0, xmm2}; + const XMMRegister Coeffs2_1[] = {xmm1, xmm3}; + const XMMRegister Coeffs3_1[] = {xmm4, xmm6}; + const XMMRegister Coeffs4_1[] = {xmm5, xmm7}; - for (int i = 0; i < 8; i += 2) { - __ evpermi2d(xmm(i/2 + 16), xmm(i), xmm(i + 1), Assembler::AVX_512bit); + const XMMRegister Coeffs1_2[] = {xmm0, xmm1, xmm2, xmm3}; + const XMMRegister Coeffs2_2[] = {xmm4, xmm5, xmm6, xmm7}; + + // Since we cannot fit the entire payload into registers, we process the + // input in two stages. For the first half, load 8 registers, each 32 integers + // apart. With one load, we can process level 0-2 (128-, 64- and 32-integers + // apart). For the remaining levels, load 8 registers from consecutive memory + // (16-, 8-, 4-, 2-, 1-integer apart) + // Levels 5, 6, 7 (4-, 2-, 1-integer apart) require shuffles within registers. + // On the other levels, shuffles can be done by rearranging the register order + + // Four batches of 8 registers each, 128 bytes apart + for (int i=0; i<4; i++) { + loadXmms(Coeffs1_2, coeffs, i*32 + 0*128, vector_len, _masm, 4, 128); + loadXmms(Coeffs2_2, coeffs, i*32 + 4*128, vector_len, _masm, 4, 128); + + // level 0-2 can be done by shuffling registers (also notice fewer zetas loads, they repeat) + // level 0 - 128 + __ vmovdqu(Zetas1[0], Address(zetas, 0), vector_len); + montMul64(Scratch1, Coeffs3, Zetas1, Coeffs3, Scratch2); + sub_add(Coeffs3, Coeffs1, Coeffs1, Scratch1, vector_len, _masm); + montMul64(Scratch1, Coeffs4, Zetas1, Coeffs4, Scratch2); + sub_add(Coeffs4, Coeffs2, Coeffs2, Scratch1, vector_len, _masm); + + // level 1 - 64 + __ vmovdqu(Zetas1[0], Address(zetas, 512), vector_len); + montMul64(Scratch1, Coeffs2, Zetas1, Coeffs2, Scratch2); + sub_add(Coeffs2, Coeffs1, Coeffs1, Scratch1, vector_len, _masm); + + __ vmovdqu(Zetas1[0], Address(zetas, 4*64 + 512), vector_len); + montMul64(Scratch1, Coeffs4, Zetas1, Coeffs4, Scratch2); + sub_add(Coeffs4, Coeffs3, Coeffs3, Scratch1, vector_len, _masm); + + // level 2 - 32 + loadXmms(Zetas3, zetas, 2 * 512, vector_len, _masm, 2, 128); + montMul64(Scratch1, Coeffs2_1, Zetas3, Coeffs2_1, Scratch2); + sub_add(Coeffs2_1, Coeffs1_1, Coeffs1_1, Scratch1, vector_len, _masm); + + loadXmms(Zetas3, zetas, 4*64 + 2 * 512, vector_len, _masm, 2, 128); + montMul64(Scratch1, Coeffs4_1, Zetas3, Coeffs4_1, Scratch2); + sub_add(Coeffs4_1, Coeffs3_1, Coeffs3_1, Scratch1, vector_len, _masm); + + storeXmms(coeffs, i*32 + 0*128, Coeffs1_2, vector_len, _masm, 4, 128); + storeXmms(coeffs, i*32 + 4*128, Coeffs2_2, vector_len, _masm, 4, 128); + } + + // Four batches of 8 registers, consecutive loads + for (int i=0; i<4; i++) { + loadXmms(Coeffs1_2, coeffs, i*256, vector_len, _masm, 4); + loadXmms(Coeffs2_2, coeffs, 128 + i*256, vector_len, _masm, 4); + + // level 3 - 16 + __ vmovdqu(Zetas1[0], Address(zetas, i*128 + 3 * 512), vector_len); + montMul64(Scratch1, Coeffs2, Zetas1, Coeffs2, Scratch2); + sub_add(Coeffs2, Coeffs1, Coeffs1, Scratch1, vector_len, _masm); + + __ vmovdqu(Zetas1[0], Address(zetas, i*128 + 64 + 3 * 512), vector_len); + montMul64(Scratch1, Coeffs4, Zetas1, Coeffs4, Scratch2); + sub_add(Coeffs4, Coeffs3, Coeffs3, Scratch1, vector_len, _masm); + + // level 4 - 8 + loadXmms(Zetas3, zetas, i*128 + 4 * 512, vector_len, _masm); + montMul64(Scratch1, Coeffs2_1, Zetas3, Coeffs2_1, Scratch2); + sub_add(Coeffs2_1, Coeffs1_1, Coeffs1_1, Scratch1, vector_len, _masm); + + loadXmms(Zetas3, zetas, i*128 + 64 + 4 * 512, vector_len, _masm); + montMul64(Scratch1, Coeffs4_1, Zetas3, Coeffs4_1, Scratch2); + sub_add(Coeffs4_1, Coeffs3_1, Coeffs3_1, Scratch1, vector_len, _masm); + + for (int level = 5, distance = 4; level<8; level++, distance /= 2) { + // zetas = load(level * 512) + // coeffs1_2, scratch1 = shuffle(coeffs1_2, coeffs2_2) + // scratch1 = scratch1 * zetas + // coeffs2_2 = coeffs1_2 - scratch1 + // coeffs1_2 = coeffs1_2 + scratch1 + loadXmms(Zetas3, zetas, i*128 + level * 512, vector_len, _masm); + shuffle(Scratch1, Coeffs1_1, Coeffs2_1, distance * 32); //Coeffs2_2 freed + montMul64(Scratch1, Scratch1, Zetas3, Coeffs2_1, Scratch2, level==7); + sub_add(Coeffs2_1, Coeffs1_1, Coeffs1_1, Scratch1, vector_len, _masm); + + loadXmms(Zetas3, zetas, i*128 + 64 + level * 512, vector_len, _masm); + shuffle(Scratch1, Coeffs3_1, Coeffs4_1, distance * 32); //Coeffs4_2 freed + montMul64(Scratch1, Scratch1, Zetas3, Coeffs4_1, Scratch2, level==7); + sub_add(Coeffs4_1, Coeffs3_1, Coeffs3_1, Scratch1, vector_len, _masm); + } + + shuffle(Scratch1, Coeffs1_1, Coeffs2_1, 0); + shuffle(Scratch1, Coeffs3_1, Coeffs4_1, 0); + + storeXmms(coeffs, i*256, Coeffs1_2, vector_len, _masm, 4); + storeXmms(coeffs, 128 + i*256, Coeffs2_2, vector_len, _masm, 4); + } } - for (int i = 0; i < 8; i += 2) { - __ evpermi2d(xmm(i / 2 + 12), xmm(i), xmm(i + 1), Assembler::AVX_512bit); - } - - montMul64(xmm12_15, xmm12_15, xmm24_27, xmm4_20_24, _masm); - sub_add(xmm1357, xmm0246, xmm16_19, xmm12_15, _masm); - - // level 5 - loadPerm(xmm16_19, perms, nttL5PermsIdx, _masm); - loadPerm(xmm12_15, perms, nttL5PermsIdx + 64, _masm); - load4Xmms(xmm24_27, zetas, 5 * 512, _masm); - - for (int i = 0; i < 8; i += 2) { - __ evpermi2d(xmm(i/2 + 16), xmm(i), xmm(i + 1), Assembler::AVX_512bit); - } - for (int i = 0; i < 8; i += 2) { - __ evpermi2d(xmm(i / 2 + 12), xmm(i), xmm(i + 1), Assembler::AVX_512bit); - } - - montMul64(xmm12_15, xmm12_15, xmm24_27, xmm4_20_24, _masm); - sub_add(xmm1357, xmm0246, xmm16_19, xmm12_15, _masm); - - // level 6 - loadPerm(xmm16_19, perms, nttL6PermsIdx, _masm); - loadPerm(xmm12_15, perms, nttL6PermsIdx + 64, _masm); - load4Xmms(xmm24_27, zetas, 6 * 512, _masm); - - for (int i = 0; i < 8; i += 2) { - __ evpermi2d(xmm(i/2 + 16), xmm(i), xmm(i + 1), Assembler::AVX_512bit); - } - for (int i = 0; i < 8; i += 2) { - __ evpermi2d(xmm(i / 2 + 12), xmm(i), xmm(i + 1), Assembler::AVX_512bit); - } - - montMul64(xmm12_15, xmm12_15, xmm24_27, xmm4_20_24, _masm); - sub_add(xmm1357, xmm0246, xmm16_19, xmm12_15, _masm); - - // level 7 - loadPerm(xmm16_19, perms, nttL7PermsIdx, _masm); - loadPerm(xmm12_15, perms, nttL7PermsIdx + 64, _masm); - load4Xmms(xmm24_27, zetas, 7 * 512, _masm); - - for (int i = 0; i < 8; i += 2) { - __ evpermi2d(xmm(i / 2 + 16), xmm(i), xmm(i + 1), Assembler::AVX_512bit); - } - for (int i = 0; i < 8; i += 2) { - __ evpermi2d(xmm(i / 2 + 12), xmm(i), xmm(i + 1), Assembler::AVX_512bit); - } - - montMul64(xmm12_15, xmm12_15, xmm24_27, xmm4_20_24, true, _masm); - loadPerm(xmm0246, perms, nttL7PermsIdx + 2 * XMMBYTES, _masm); - loadPerm(xmm1357, perms, nttL7PermsIdx + 3 * XMMBYTES, _masm); - sub_add(xmm21232527, xmm20222426, xmm16_19, xmm12_15, _masm); - - for (int i = 0; i < 8; i += 2) { - __ evpermi2d(xmm(i), xmm(i + 20), xmm(i + 21), Assembler::AVX_512bit); - __ evpermi2d(xmm(i + 1), xmm(i + 20), xmm(i + 21), Assembler::AVX_512bit); - } - - __ cmpl(iterations, 0); - __ jcc(Assembler::equal, L_end); - - store4Xmms(coeffs, 0, xmm0_3, _masm); - store4Xmms(coeffs, 4 * XMMBYTES, xmm4_7, _masm); - - load4Xmms(xmm0_3, coeffs, 8 * XMMBYTES, _masm); - load4Xmms(xmm4_7, coeffs, 12 * XMMBYTES, _masm); - - __ addptr(zetas, 4 * XMMBYTES); - - __ jmp(L_loop); - - __ BIND(L_end); - - store4Xmms(coeffs, 8 * XMMBYTES, xmm0_3, _masm); - store4Xmms(coeffs, 12 * XMMBYTES, xmm4_7, _masm); __ leave(); // required for proper stackwalking of RuntimeStub frame __ mov64(rax, 0); // return 0 @@ -459,173 +654,234 @@ static address generate_dilithiumAlmostNtt_avx512(StubGenerator *stubgen, // static int implDilithiumAlmostInverseNtt(int[] coeffs, int[] zetas) {} // // coeffs (int[256]) = c_rarg0 -// zetas (int[256]) = c_rarg1 -static address generate_dilithiumAlmostInverseNtt_avx512(StubGenerator *stubgen, - MacroAssembler *_masm) { - +// zetas (int[128*8]) = c_rarg1 +static address generate_dilithiumAlmostInverseNtt_avx(StubGenerator *stubgen, + int vector_len, MacroAssembler *_masm) { __ align(CodeEntryAlignment); StubId stub_id = StubId::stubgen_dilithiumAlmostInverseNtt_id; StubCodeMark mark(stubgen, stub_id); address start = __ pc(); __ enter(); - Label L_loop, L_end; - const Register coeffs = c_rarg0; const Register zetas = c_rarg1; + const Register scratch = r10; - const Register iterations = c_rarg2; + // AVX2 version uses the first half of these arrays + const XMMRegister Coeffs1[] = {xmm0, xmm1, xmm16, xmm17}; + const XMMRegister Coeffs2[] = {xmm2, xmm3, xmm18, xmm19}; + const XMMRegister Coeffs3[] = {xmm4, xmm5, xmm20, xmm21}; + const XMMRegister Coeffs4[] = {xmm6, xmm7, xmm22, xmm23}; + const XMMRegister Scratch1[] = {xmm8, xmm9, xmm24, xmm25}; + const XMMRegister Scratch2[] = {xmm10, xmm11, xmm26, xmm27}; + const XMMRegister Zetas1[] = {xmm12, xmm12, xmm12, xmm12}; + const XMMRegister Zetas2[] = {xmm12, xmm12, xmm13, xmm13}; + const XMMRegister Zetas3[] = {xmm12, xmm13, xmm28, xmm29}; + const XMMRegister montQInvModR = xmm14; + const XMMRegister dilithium_q = xmm15; + const XMMRegister unshuffle1 = xmm30; + const XMMRegister unshuffle2 = xmm31; + KRegister mergeMask1 = k1; + KRegister mergeMask2 = k2; + // lambdas to hide repeated parameters + auto shuffle = whole_shuffle(scratch, mergeMask1, mergeMask2, unshuffle1, unshuffle2, vector_len, _masm); + auto montMul64 = whole_montMul(montQInvModR, dilithium_q, mergeMask2, vector_len, _masm); - const Register perms = r11; - - __ lea(perms, ExternalAddress(dilithiumAvx512PermsAddr())); - - __ evmovdqul(montMulPerm, Address(perms, montMulPermsIdx), Assembler::AVX_512bit); __ vpbroadcastd(montQInvModR, ExternalAddress(dilithiumAvx512ConstsAddr(montQInvModRIdx)), - Assembler::AVX_512bit, scratch); // q^-1 mod 2^32 + vector_len, scratch); // q^-1 mod 2^32 __ vpbroadcastd(dilithium_q, ExternalAddress(dilithiumAvx512ConstsAddr(dilithium_qIdx)), - Assembler::AVX_512bit, scratch); // q + vector_len, scratch); // q // Each level represents one iteration of the outer for loop of the // Java version. // In each of these iterations half of the coefficients are added to and // subtracted from the other half of the coefficients then the result of - // the substartion is (Montgomery) multiplied by the corresponding zetas. - // In each level we just collect the coefficients (using evpermi2d() - // instructions where necessary, i.e. on levels 0-4) so that the results of + // the subtraction is (Montgomery) multiplied by the corresponding zetas. + // In each level we just shuffle the coefficients so that the results of // the additions and subtractions go to the vector registers so that they // align with each other and the zetas. - // We do levels 0-6 in two batches, each batch entirely in the vector registers - load4Xmms(xmm0_3, coeffs, 0, _masm); - load4Xmms(xmm4_7, coeffs, 4 * XMMBYTES, _masm); + if (vector_len == Assembler::AVX_512bit) { + // levels 4-7, register shuffles: + const XMMRegister Coeffs1_1[] = {xmm0, xmm1, xmm2, xmm3}; + const XMMRegister Coeffs2_1[] = {xmm16, xmm17, xmm18, xmm19}; + const XMMRegister Coeffs3_1[] = {xmm4, xmm5, xmm6, xmm7}; + const XMMRegister Coeffs4_1[] = {xmm20, xmm21, xmm22, xmm23}; + const XMMRegister Coeffs1_2[] = {xmm0, xmm16, xmm2, xmm18}; + const XMMRegister Coeffs2_2[] = {xmm1, xmm17, xmm3, xmm19}; + const XMMRegister Coeffs3_2[] = {xmm4, xmm20, xmm6, xmm22}; + const XMMRegister Coeffs4_2[] = {xmm5, xmm21, xmm7, xmm23}; - __ movl(iterations, 2); + // Constants for shuffle and montMul64 + __ mov64(scratch, 0b1010101010101010); + __ kmovwl(mergeMask1, scratch); + __ knotwl(mergeMask2, mergeMask1); + __ vmovdqu(unshuffle1, ExternalAddress(unshufflePermsAddr(4)), vector_len, scratch); + __ vmovdqu(unshuffle2, ExternalAddress(unshufflePermsAddr(5)), vector_len, scratch); - __ align(OptoLoopAlignment); - __ BIND(L_loop); + int memStep = 4 * 64; + loadXmms(Coeffs1, coeffs, 0*memStep, vector_len, _masm); + loadXmms(Coeffs2, coeffs, 1*memStep, vector_len, _masm); + loadXmms(Coeffs3, coeffs, 2*memStep, vector_len, _masm); + loadXmms(Coeffs4, coeffs, 3*memStep, vector_len, _masm); - __ subl(iterations, 1); + shuffle(Scratch1, Coeffs1_2, Coeffs2_2, 1); + shuffle(Scratch1, Coeffs3_2, Coeffs4_2, 1); - // level 0 - loadPerm(xmm8_11, perms, nttInvL0PermsIdx, _masm); - loadPerm(xmm12_15, perms, nttInvL0PermsIdx + 64, _masm); + // Constants for shuffle(128) + __ vmovdqu(unshuffle1, ExternalAddress(unshufflePermsAddr(0)), vector_len, scratch); + __ vmovdqu(unshuffle2, ExternalAddress(unshufflePermsAddr(1)), vector_len, scratch); + for (int level = 0, distance = 1; level<4; level++, distance *= 2) { + // zetas = load(level * 512) + // coeffs1_2 = coeffs1_2 + coeffs2_2 + // scratch1 = coeffs1_2 - coeffs2_2 + // scratch1 = scratch1 * zetas + // coeffs1_2, coeffs2_2 = shuffle(coeffs1_2, scratch1) + loadXmms(Zetas3, zetas, level * 512, vector_len, _masm); + sub_add(Scratch1, Coeffs1_2, Coeffs1_2, Coeffs2_2, vector_len, _masm); // Coeffs2_2 freed + montMul64(Scratch1, Scratch1, Zetas3, Coeffs2_2, Scratch2, level==0); + shuffle(Coeffs2_2, Coeffs1_2, Scratch1, distance * 32); - for (int i = 0; i < 8; i += 2) { - __ evpermi2d(xmm(i / 2 + 8), xmm(i), xmm(i + 1), Assembler::AVX_512bit); - __ evpermi2d(xmm(i / 2 + 12), xmm(i), xmm(i + 1), Assembler::AVX_512bit); + loadXmms(Zetas3, zetas, 4*64 + level * 512, vector_len, _masm); + sub_add(Scratch1, Coeffs3_2, Coeffs3_2, Coeffs4_2, vector_len, _masm); // Coeffs4_2 freed + montMul64(Scratch1, Scratch1, Zetas3, Coeffs4_2, Scratch2, level==0); + shuffle(Coeffs4_2, Coeffs3_2, Scratch1, distance * 32); + } + + // level 4 + loadXmms(Zetas3, zetas, 4 * 512, vector_len, _masm); + sub_add(Scratch1, Coeffs1_2, Coeffs1_2, Coeffs2_2, vector_len, _masm); // Coeffs2_2 freed + montMul64(Coeffs2_2, Scratch1, Zetas3, Scratch1, Scratch2); + + loadXmms(Zetas3, zetas, 4*64 + 4 * 512, vector_len, _masm); + sub_add(Scratch1, Coeffs3_2, Coeffs3_2, Coeffs4_2, vector_len, _masm); // Coeffs4_2 freed + montMul64(Coeffs4_2, Scratch1, Zetas3, Scratch1, Scratch2); + + // level 5 + __ vmovdqu(Zetas2[0], Address(zetas, 5 * 512), vector_len); + __ vmovdqu(Zetas2[2], Address(zetas, 2*64 + 5 * 512), vector_len); + sub_add(Scratch1, Coeffs1_1, Coeffs1_1, Coeffs2_1, vector_len, _masm); // Coeffs2_1 freed + montMul64(Coeffs2_1, Scratch1, Zetas2, Scratch1, Scratch2); + + __ vmovdqu(Zetas2[0], Address(zetas, 4*64 + 5 * 512), vector_len); + __ vmovdqu(Zetas2[2], Address(zetas, 6*64 + 5 * 512), vector_len); + sub_add(Scratch1, Coeffs3_1, Coeffs3_1, Coeffs4_1, vector_len, _masm); // Coeffs4_1 freed + montMul64(Coeffs4_1, Scratch1, Zetas2, Scratch1, Scratch2); + + // level 6 + __ vmovdqu(Zetas1[0], Address(zetas, 6 * 512), vector_len); + sub_add(Scratch1, Coeffs1, Coeffs1, Coeffs2, vector_len, _masm); // Coeffs2 freed + montMul64(Coeffs2, Scratch1, Zetas1, Scratch1, Scratch2); + + __ vmovdqu(Zetas1[0], Address(zetas, 4*64 + 6 * 512), vector_len); + sub_add(Scratch1, Coeffs3, Coeffs3, Coeffs4, vector_len, _masm); // Coeffs4 freed + montMul64(Coeffs4, Scratch1, Zetas1, Scratch1, Scratch2); + + // level 7 + __ vmovdqu(Zetas1[0], Address(zetas, 7 * 512), vector_len); + sub_add(Scratch1, Coeffs1, Coeffs1, Coeffs3, vector_len, _masm); // Coeffs3 freed + montMul64(Coeffs3, Scratch1, Zetas1, Scratch1, Scratch2); + sub_add(Scratch1, Coeffs2, Coeffs2, Coeffs4, vector_len, _masm); // Coeffs4 freed + montMul64(Coeffs4, Scratch1, Zetas1, Scratch1, Scratch2); + + storeXmms(coeffs, 0*memStep, Coeffs1, vector_len, _masm); + storeXmms(coeffs, 1*memStep, Coeffs2, vector_len, _masm); + storeXmms(coeffs, 2*memStep, Coeffs3, vector_len, _masm); + storeXmms(coeffs, 3*memStep, Coeffs4, vector_len, _masm); + } else { // Assembler::AVX_256bit + // Permutations of Coeffs1, Coeffs2, Coeffs3 and Coeffs4 + const XMMRegister Coeffs1_1[] = {xmm0, xmm2}; + const XMMRegister Coeffs2_1[] = {xmm1, xmm3}; + const XMMRegister Coeffs3_1[] = {xmm4, xmm6}; + const XMMRegister Coeffs4_1[] = {xmm5, xmm7}; + + const XMMRegister Coeffs1_2[] = {xmm0, xmm1, xmm2, xmm3}; + const XMMRegister Coeffs2_2[] = {xmm4, xmm5, xmm6, xmm7}; + + // Four batches of 8 registers, consecutive loads + for (int i=0; i<4; i++) { + loadXmms(Coeffs1_2, coeffs, i*256, vector_len, _masm, 4); + loadXmms(Coeffs2_2, coeffs, 128 + i*256, vector_len, _masm, 4); + + shuffle(Scratch1, Coeffs1_1, Coeffs2_1, 1); + shuffle(Scratch1, Coeffs3_1, Coeffs4_1, 1); + + for (int level = 0, distance = 1; level <= 2; level++, distance *= 2) { + // zetas = load(level * 512) + // coeffs1_2 = coeffs1_2 + coeffs2_2 + // scratch1 = coeffs1_2 - coeffs2_2 + // scratch1 = scratch1 * zetas + // coeffs1_2, coeffs2_2 = shuffle(coeffs1_2, scratch1) + loadXmms(Zetas3, zetas, i*128 + level * 512, vector_len, _masm); + sub_add(Scratch1, Coeffs1_1, Coeffs1_1, Coeffs2_1, vector_len, _masm); // Coeffs2_1 freed + montMul64(Scratch1, Scratch1, Zetas3, Coeffs2_1, Scratch2, level==0); + shuffle(Coeffs2_1, Coeffs1_1, Scratch1, distance * 32); + + loadXmms(Zetas3, zetas, i*128 + 64 + level * 512, vector_len, _masm); + sub_add(Scratch1, Coeffs3_1, Coeffs3_1, Coeffs4_1, vector_len, _masm); // Coeffs4_1 freed + montMul64(Scratch1, Scratch1, Zetas3, Coeffs4_1, Scratch2, level==0); + shuffle(Coeffs4_1, Coeffs3_1, Scratch1, distance * 32); + } + + // level 3 + loadXmms(Zetas3, zetas, i*128 + 3 * 512, vector_len, _masm); + sub_add(Scratch1, Coeffs1_1, Coeffs1_1, Coeffs2_1, vector_len, _masm); // Coeffs2_1 freed + montMul64(Coeffs2_1, Scratch1, Zetas3, Scratch1, Scratch2); + + loadXmms(Zetas3, zetas, i*128 + 64 + 3 * 512, vector_len, _masm); + sub_add(Scratch1, Coeffs3_1, Coeffs3_1, Coeffs4_1, vector_len, _masm); // Coeffs4_1 freed + montMul64(Coeffs4_1, Scratch1, Zetas3, Scratch1, Scratch2); + + // level 4 + __ vmovdqu(Zetas1[0], Address(zetas, i*128 + 4 * 512), vector_len); + sub_add(Scratch1, Coeffs1, Coeffs1, Coeffs2, vector_len, _masm); // Coeffs2 freed + montMul64(Coeffs2, Scratch1, Zetas1, Scratch1, Scratch2); + + __ vmovdqu(Zetas1[0], Address(zetas, i*128 + 64 + 4 * 512), vector_len); + sub_add(Scratch1, Coeffs3, Coeffs3, Coeffs4, vector_len, _masm); // Coeffs4 freed + montMul64(Coeffs4, Scratch1, Zetas1, Scratch1, Scratch2); + + storeXmms(coeffs, i*256, Coeffs1_2, vector_len, _masm, 4); + storeXmms(coeffs, 128 + i*256, Coeffs2_2, vector_len, _masm, 4); + } + + // Four batches of 8 registers each, 128 bytes apart + for (int i=0; i<4; i++) { + loadXmms(Coeffs1_2, coeffs, i*32 + 0*128, vector_len, _masm, 4, 128); + loadXmms(Coeffs2_2, coeffs, i*32 + 4*128, vector_len, _masm, 4, 128); + + // level 5 + loadXmms(Zetas3, zetas, 5 * 512, vector_len, _masm, 2, 128); + sub_add(Scratch1, Coeffs1_1, Coeffs1_1, Coeffs2_1, vector_len, _masm); // Coeffs2_1 freed + montMul64(Coeffs2_1, Scratch1, Zetas3, Scratch1, Scratch2); + + loadXmms(Zetas3, zetas, 4*64 + 5 * 512, vector_len, _masm, 2, 128); + sub_add(Scratch1, Coeffs3_1, Coeffs3_1, Coeffs4_1, vector_len, _masm); // Coeffs4_1 freed + montMul64(Coeffs4_1, Scratch1, Zetas3, Scratch1, Scratch2); + + // level 6 + __ vmovdqu(Zetas1[0], Address(zetas, 6 * 512), vector_len); + sub_add(Scratch1, Coeffs1, Coeffs1, Coeffs2, vector_len, _masm); // Coeffs2 freed + montMul64(Coeffs2, Scratch1, Zetas1, Scratch1, Scratch2); + + __ vmovdqu(Zetas1[0], Address(zetas, 4*64 + 6 * 512), vector_len); + sub_add(Scratch1, Coeffs3, Coeffs3, Coeffs4, vector_len, _masm); // Coeffs4 freed + montMul64(Coeffs4, Scratch1, Zetas1, Scratch1, Scratch2); + + // level 7 + __ vmovdqu(Zetas1[0], Address(zetas, 7 * 512), vector_len); + sub_add(Scratch1, Coeffs1, Coeffs1, Coeffs3, vector_len, _masm); // Coeffs3 freed + montMul64(Coeffs3, Scratch1, Zetas1, Scratch1, Scratch2); + sub_add(Scratch1, Coeffs2, Coeffs2, Coeffs4, vector_len, _masm); // Coeffs4 freed + montMul64(Coeffs4, Scratch1, Zetas1, Scratch1, Scratch2); + + storeXmms(coeffs, i*32 + 0*128, Coeffs1_2, vector_len, _masm, 4, 128); + storeXmms(coeffs, i*32 + 4*128, Coeffs2_2, vector_len, _masm, 4, 128); + } } - load4Xmms(xmm4_7, zetas, 0, _masm); - sub_add(xmm24_27, xmm0_3, xmm8_11, xmm12_15, _masm); - montMul64(xmm4_7, xmm4_7, xmm24_27, xmm16_27, true, _masm); - - // level 1 - loadPerm(xmm8_11, perms, nttInvL1PermsIdx, _masm); - loadPerm(xmm12_15, perms, nttInvL1PermsIdx + 64, _masm); - - for (int i = 0; i < 4; i++) { - __ evpermi2d(xmm(i + 8), xmm(i), xmm(i + 4), Assembler::AVX_512bit); - __ evpermi2d(xmm(i + 12), xmm(i), xmm(i + 4), Assembler::AVX_512bit); - } - - load4Xmms(xmm4_7, zetas, 512, _masm); - sub_add(xmm24_27, xmm0_3, xmm8_11, xmm12_15, _masm); - montMul64(xmm4_7, xmm24_27, xmm4_7, xmm16_27, _masm); - - // level 2 - loadPerm(xmm8_11, perms, nttInvL2PermsIdx, _masm); - loadPerm(xmm12_15, perms, nttInvL2PermsIdx + 64, _masm); - - for (int i = 0; i < 4; i++) { - __ evpermi2d(xmm(i + 8), xmm(i), xmm(i + 4), Assembler::AVX_512bit); - __ evpermi2d(xmm(i + 12), xmm(i), xmm(i + 4), Assembler::AVX_512bit); - } - - load4Xmms(xmm4_7, zetas, 2 * 512, _masm); - sub_add(xmm24_27, xmm0_3, xmm8_11, xmm12_15, _masm); - montMul64(xmm4_7, xmm24_27, xmm4_7, xmm16_27, _masm); - - // level 3 - loadPerm(xmm8_11, perms, nttInvL3PermsIdx, _masm); - loadPerm(xmm12_15, perms, nttInvL3PermsIdx + 64, _masm); - - for (int i = 0; i < 4; i++) { - __ evpermi2d(xmm(i + 8), xmm(i), xmm(i + 4), Assembler::AVX_512bit); - __ evpermi2d(xmm(i + 12), xmm(i), xmm(i + 4), Assembler::AVX_512bit); - } - - load4Xmms(xmm4_7, zetas, 3 * 512, _masm); - sub_add(xmm24_27, xmm0_3, xmm8_11, xmm12_15, _masm); - montMul64(xmm4_7, xmm24_27, xmm4_7, xmm16_27, _masm); - - // level 4 - loadPerm(xmm8_11, perms, nttInvL4PermsIdx, _masm); - loadPerm(xmm12_15, perms, nttInvL4PermsIdx + 64, _masm); - - for (int i = 0; i < 4; i++) { - __ evpermi2d(xmm(i + 8), xmm(i), xmm(i + 4), Assembler::AVX_512bit); - __ evpermi2d(xmm(i + 12), xmm(i), xmm(i + 4), Assembler::AVX_512bit); - } - - load4Xmms(xmm4_7, zetas, 4 * 512, _masm); - sub_add(xmm24_27, xmm0_3, xmm8_11, xmm12_15, _masm); - montMul64(xmm4_7, xmm24_27, xmm4_7, xmm16_27, _masm); - - // level 5 - load4Xmms(xmm12_15, zetas, 5 * 512, _masm); - sub_add(xmm8_11, xmm0_3, xmm0426, xmm1537, _masm); - montMul64(xmm4_7, xmm8_11, xmm12_15, xmm16_27, _masm); - - // level 6 - load4Xmms(xmm12_15, zetas, 6 * 512, _masm); - sub_add(xmm8_11, xmm0_3, xmm0145, xmm2367, _masm); - montMul64(xmm4_7, xmm8_11, xmm12_15, xmm16_27, _masm); - - __ cmpl(iterations, 0); - __ jcc(Assembler::equal, L_end); - - // save the coefficients of the first batch, adjust the zetas - // and load the second batch of coefficients - store4Xmms(coeffs, 0, xmm0_3, _masm); - store4Xmms(coeffs, 4 * XMMBYTES, xmm4_7, _masm); - - __ addptr(zetas, 4 * XMMBYTES); - - load4Xmms(xmm0_3, coeffs, 8 * XMMBYTES, _masm); - load4Xmms(xmm4_7, coeffs, 12 * XMMBYTES, _masm); - - __ jmp(L_loop); - - __ BIND(L_end); - - // load the coeffs of the first batch of coefficients that were saved after - // level 6 into Zmm_8-Zmm_15 and do the last level entirely in the vector - // registers - load4Xmms(xmm8_11, coeffs, 0, _masm); - load4Xmms(xmm12_15, coeffs, 4 * XMMBYTES, _masm); - - // level 7 - - loadXmm29(zetas, 7 * 512, _masm); - - for (int i = 0; i < 8; i++) { - __ evpaddd(xmm(i + 16), k0, xmm(i), xmm(i + 8), false, Assembler::AVX_512bit); - } - - for (int i = 0; i < 8; i++) { - __ evpsubd(xmm(i), k0, xmm(i + 8), xmm(i), false, Assembler::AVX_512bit); - } - - store4Xmms(coeffs, 0, xmm16_19, _masm); - store4Xmms(coeffs, 4 * XMMBYTES, xmm20_23, _masm); - montMul64(xmm0_3, xmm0_3, xmm29_29, xmm16_27, _masm); - montMul64(xmm4_7, xmm4_7, xmm29_29, xmm16_27, _masm); - store4Xmms(coeffs, 8 * XMMBYTES, xmm0_3, _masm); - store4Xmms(coeffs, 12 * XMMBYTES, xmm4_7, _masm); - __ leave(); // required for proper stackwalking of RuntimeStub frame __ mov64(rax, 0); // return 0 __ ret(0); @@ -641,8 +897,8 @@ static address generate_dilithiumAlmostInverseNtt_avx512(StubGenerator *stubgen, // result (int[256]) = c_rarg0 // poly1 (int[256]) = c_rarg1 // poly2 (int[256]) = c_rarg2 -static address generate_dilithiumNttMult_avx512(StubGenerator *stubgen, - MacroAssembler *_masm) { +static address generate_dilithiumNttMult_avx(StubGenerator *stubgen, + int vector_len, MacroAssembler *_masm) { __ align(CodeEntryAlignment); StubId stub_id = StubId::stubgen_dilithiumNttMult_id; @@ -655,40 +911,60 @@ static address generate_dilithiumNttMult_avx512(StubGenerator *stubgen, const Register result = c_rarg0; const Register poly1 = c_rarg1; const Register poly2 = c_rarg2; - - const Register perms = r10; // scratch reused after not needed any more + const Register scratch = r10; const Register len = r11; - const XMMRegister montRSquareModQ = xmm29; + const XMMRegister montQInvModR = xmm8; + const XMMRegister dilithium_q = xmm9; + + const XMMRegister Poly1[] = {xmm0, xmm1, xmm16, xmm17}; + const XMMRegister Poly2[] = {xmm2, xmm3, xmm18, xmm19}; + const XMMRegister Scratch1[] = {xmm4, xmm5, xmm20, xmm21}; + const XMMRegister Scratch2[] = {xmm6, xmm7, xmm22, xmm23}; + const XMMRegister MontRSquareModQ[] = {xmm10, xmm10, xmm10, xmm10}; + KRegister mergeMask = k1; + // lambda to hide repeated parameters + auto montMul64 = whole_montMul(montQInvModR, dilithium_q, mergeMask, vector_len, _masm); __ vpbroadcastd(montQInvModR, ExternalAddress(dilithiumAvx512ConstsAddr(montQInvModRIdx)), - Assembler::AVX_512bit, scratch); // q^-1 mod 2^32 + vector_len, scratch); // q^-1 mod 2^32 __ vpbroadcastd(dilithium_q, ExternalAddress(dilithiumAvx512ConstsAddr(dilithium_qIdx)), - Assembler::AVX_512bit, scratch); // q - __ vpbroadcastd(montRSquareModQ, + vector_len, scratch); // q + __ vpbroadcastd(MontRSquareModQ[0], ExternalAddress(dilithiumAvx512ConstsAddr(montRSquareModQIdx)), - Assembler::AVX_512bit, scratch); // 2^64 mod q + vector_len, scratch); // 2^64 mod q + if (vector_len == Assembler::AVX_512bit) { + __ mov64(scratch, 0b0101010101010101); + __ kmovwl(mergeMask, scratch); + } - __ lea(perms, ExternalAddress(dilithiumAvx512PermsAddr())); - __ evmovdqul(montMulPerm, Address(perms, montMulPermsIdx), Assembler::AVX_512bit); + // Total payload is 256*int32s. + // - memStep is number of bytes one iteration processes. + // - loopCnt is number of iterations it will take to process entire payload. + int loopCnt = 4; + int memStep = 4 * 64; + if (vector_len == Assembler::AVX_256bit) { + loopCnt = 16; + memStep = 2 * 32; + } - __ movl(len, 4); + __ movl(len, loopCnt); __ align(OptoLoopAlignment); __ BIND(L_loop); - load4Xmms(xmm4_7, poly2, 0, _masm); - load4Xmms(xmm0_3, poly1, 0, _masm); - montMul64(xmm4_7, xmm4_7, xmm29_29, xmm16_27, _masm); - montMul64(xmm0_3, xmm0_3, xmm4_7, xmm16_27, true, _masm); - store4Xmms(result, 0, xmm0_3, _masm); + loadXmms(Poly2, poly2, 0, vector_len, _masm); + loadXmms(Poly1, poly1, 0, vector_len, _masm); + montMul64(Poly2, Poly2, MontRSquareModQ, Scratch1, Scratch2); + montMul64(Poly1, Poly1, Poly2, Scratch1, Scratch2, true); + storeXmms(result, 0, Poly1, vector_len, _masm); __ subl(len, 1); - __ addptr(poly1, 4 * XMMBYTES); - __ addptr(poly2, 4 * XMMBYTES); - __ addptr(result, 4 * XMMBYTES); + __ addptr(poly1, memStep); + __ addptr(poly2, memStep); + __ addptr(result, memStep); __ cmpl(len, 0); __ jcc(Assembler::notEqual, L_loop); @@ -705,8 +981,8 @@ static address generate_dilithiumNttMult_avx512(StubGenerator *stubgen, // // coeffs (int[256]) = c_rarg0 // constant (int) = c_rarg1 -static address generate_dilithiumMontMulByConstant_avx512(StubGenerator *stubgen, - MacroAssembler *_masm) { +static address generate_dilithiumMontMulByConstant_avx(StubGenerator *stubgen, + int vector_len, MacroAssembler *_masm) { __ align(CodeEntryAlignment); StubId stub_id = StubId::stubgen_dilithiumMontMulByConstant_id; @@ -718,38 +994,64 @@ static address generate_dilithiumMontMulByConstant_avx512(StubGenerator *stubgen const Register coeffs = c_rarg0; const Register rConstant = c_rarg1; - - const Register perms = c_rarg2; // not used for argument + const Register scratch = r10; const Register len = r11; - const XMMRegister constant = xmm29; + const XMMRegister montQInvModR = xmm8; + const XMMRegister dilithium_q = xmm9; - __ lea(perms, ExternalAddress(dilithiumAvx512PermsAddr())); + const XMMRegister Coeffs1[] = {xmm0, xmm1, xmm16, xmm17}; + const XMMRegister Coeffs2[] = {xmm2, xmm3, xmm18, xmm19}; + const XMMRegister Scratch1[] = {xmm4, xmm5, xmm20, xmm21}; + const XMMRegister Scratch2[] = {xmm6, xmm7, xmm22, xmm23}; + const XMMRegister Constant[] = {xmm10, xmm10, xmm10, xmm10}; + XMMRegister constant = Constant[0]; + KRegister mergeMask = k1; + // lambda to hide repeated parameters + auto montMul64 = whole_montMul(montQInvModR, dilithium_q, mergeMask, vector_len, _masm); - // the following four vector registers are used in montMul64 + // load constants for montMul64 __ vpbroadcastd(montQInvModR, ExternalAddress(dilithiumAvx512ConstsAddr(montQInvModRIdx)), - Assembler::AVX_512bit, scratch); // q^-1 mod 2^32 + vector_len, scratch); // q^-1 mod 2^32 __ vpbroadcastd(dilithium_q, ExternalAddress(dilithiumAvx512ConstsAddr(dilithium_qIdx)), - Assembler::AVX_512bit, scratch); // q - __ evmovdqul(montMulPerm, Address(perms, montMulPermsIdx), Assembler::AVX_512bit); - __ evpbroadcastd(constant, rConstant, Assembler::AVX_512bit); // constant multiplier + vector_len, scratch); // q + if (vector_len == Assembler::AVX_256bit) { + __ movdl(constant, rConstant); + __ vpbroadcastd(constant, constant, vector_len); // constant multiplier + } else { + __ evpbroadcastd(constant, rConstant, Assembler::AVX_512bit); // constant multiplier - __ movl(len, 2); + __ mov64(scratch, 0b0101010101010101); //dw-mask + __ kmovwl(mergeMask, scratch); + } + + // Total payload is 256*int32s. + // - memStep is number of bytes one montMul64 processes. + // - loopCnt is number of iterations it will take to process entire payload. + // - (two memSteps per loop) + int memStep = 4 * 64; + int loopCnt = 2; + if (vector_len == Assembler::AVX_256bit) { + memStep = 2 * 32; + loopCnt = 8; + } + + __ movl(len, loopCnt); __ align(OptoLoopAlignment); __ BIND(L_loop); - load4Xmms(xmm0_3, coeffs, 0, _masm); - load4Xmms(xmm4_7, coeffs, 4 * XMMBYTES, _masm); - montMul64(xmm0_3, xmm0_3, xmm29_29, xmm16_27, _masm); - montMul64(xmm4_7, xmm4_7, xmm29_29, xmm16_27, _masm); - store4Xmms(coeffs, 0, xmm0_3, _masm); - store4Xmms(coeffs, 4 * XMMBYTES, xmm4_7, _masm); + loadXmms(Coeffs1, coeffs, 0, vector_len, _masm); + loadXmms(Coeffs2, coeffs, memStep, vector_len, _masm); + montMul64(Coeffs1, Coeffs1, Constant, Scratch1, Scratch2); + montMul64(Coeffs2, Coeffs2, Constant, Scratch1, Scratch2); + storeXmms(coeffs, 0, Coeffs1, vector_len, _masm); + storeXmms(coeffs, memStep, Coeffs2, vector_len, _masm); __ subl(len, 1); - __ addptr(coeffs, 512); + __ addptr(coeffs, 2 * memStep); __ cmpl(len, 0); __ jcc(Assembler::notEqual, L_loop); @@ -769,9 +1071,8 @@ static address generate_dilithiumMontMulByConstant_avx512(StubGenerator *stubgen // highPart (int[256]) = c_rarg2 // twoGamma2 (int) = c_rarg3 // multiplier (int) = c_rarg4 -static address generate_dilithiumDecomposePoly_avx512(StubGenerator *stubgen, - MacroAssembler *_masm) { - +static address generate_dilithiumDecomposePoly_avx(StubGenerator *stubgen, + int vector_len, MacroAssembler *_masm) { __ align(CodeEntryAlignment); StubId stub_id = StubId::stubgen_dilithiumDecomposePoly_id; StubCodeMark mark(stubgen, stub_id); @@ -785,26 +1086,45 @@ static address generate_dilithiumDecomposePoly_avx512(StubGenerator *stubgen, const Register highPart = c_rarg2; const Register rTwoGamma2 = c_rarg3; + const Register scratch = r10; const Register len = r11; - const XMMRegister zero = xmm24; - const XMMRegister one = xmm25; - const XMMRegister qMinus1 = xmm26; - const XMMRegister gamma2 = xmm27; - const XMMRegister twoGamma2 = xmm28; - const XMMRegister barrettMultiplier = xmm29; - const XMMRegister barrettAddend = xmm30; - __ vpxor(zero, zero, zero, Assembler::AVX_512bit); // 0 - __ vpternlogd(xmm0, 0xff, xmm0, xmm0, Assembler::AVX_512bit); // -1 - __ vpsubd(one, zero, xmm0, Assembler::AVX_512bit); // 1 + const XMMRegister one = xmm0; + const XMMRegister gamma2 = xmm1; + const XMMRegister twoGamma2 = xmm2; + const XMMRegister barrettMultiplier = xmm3; + const XMMRegister barrettAddend = xmm4; + const XMMRegister dilithium_q = xmm5; + const XMMRegister zero = xmm29; // AVX512-only + const XMMRegister minusOne = xmm30; // AVX512-only + const XMMRegister qMinus1 = xmm31; // AVX512-only + + XMMRegister RPlus[] = {xmm6, xmm7, xmm16, xmm17}; + XMMRegister Quotient[] = {xmm8, xmm9, xmm18, xmm19}; + XMMRegister R0[] = {xmm10, xmm11, xmm20, xmm21}; + XMMRegister Mask[] = {xmm12, xmm13, xmm22, xmm23}; + XMMRegister Tmp1[] = {xmm14, xmm15, xmm24, xmm25}; + __ vpbroadcastd(dilithium_q, ExternalAddress(dilithiumAvx512ConstsAddr(dilithium_qIdx)), - Assembler::AVX_512bit, scratch); // q + vector_len, scratch); // q __ vpbroadcastd(barrettAddend, ExternalAddress(dilithiumAvx512ConstsAddr(barrettAddendIdx)), - Assembler::AVX_512bit, scratch); // addend for Barrett reduction + vector_len, scratch); // addend for Barrett reduction + if (vector_len == Assembler::AVX_512bit) { + __ vpxor(zero, zero, zero, vector_len); // 0 + __ vpternlogd(minusOne, 0xff, minusOne, minusOne, vector_len); // -1 + __ vpsrld(one, minusOne, 31, vector_len); + __ vpsubd(qMinus1, dilithium_q, one, vector_len); // q - 1 + __ evpbroadcastd(twoGamma2, rTwoGamma2, vector_len); // 2 * gamma2 + } else { + __ vpcmpeqd(one, one, one, vector_len); + __ vpsrld(one, one, 31, vector_len); + __ movdl(twoGamma2, rTwoGamma2); + __ vpbroadcastd(twoGamma2, twoGamma2, vector_len); // 2 * gamma2 + } - __ evpbroadcastd(twoGamma2, rTwoGamma2, Assembler::AVX_512bit); // 2 * gamma2 + __ vpsrad(gamma2, twoGamma2, 1, vector_len); // gamma2 #ifndef _WIN64 const Register rMultiplier = c_rarg4; @@ -813,201 +1133,185 @@ static address generate_dilithiumDecomposePoly_avx512(StubGenerator *stubgen, const Register rMultiplier = c_rarg3; // arg3 is already consumed, reused here __ movptr(rMultiplier, multiplier_mem); #endif - __ evpbroadcastd(barrettMultiplier, rMultiplier, - Assembler::AVX_512bit); // multiplier for mod 2 * gamma2 reduce + if (vector_len == Assembler::AVX_512bit) { + __ evpbroadcastd(barrettMultiplier, rMultiplier, + vector_len); // multiplier for mod 2 * gamma2 reduce + } else { + __ movdl(barrettMultiplier, rMultiplier); + __ vpbroadcastd(barrettMultiplier, barrettMultiplier, vector_len); + } - __ evpsubd(qMinus1, k0, dilithium_q, one, false, Assembler::AVX_512bit); // q - 1 - __ evpsrad(gamma2, k0, twoGamma2, 1, false, Assembler::AVX_512bit); // gamma2 + // Total payload is 1024 bytes + int memStep = 4 * 64; // Number of bytes per loop iteration + int regCnt = 4; // Register array length + if (vector_len == Assembler::AVX_256bit) { + memStep = 2 * 32; + regCnt = 2; + } __ movl(len, 1024); __ align(OptoLoopAlignment); __ BIND(L_loop); - load4Xmms(xmm0_3, input, 0, _masm); + loadXmms(RPlus, input, 0, vector_len, _masm); - __ addptr(input, 4 * XMMBYTES); + __ addptr(input, memStep); - // rplus in xmm0 // rplus = rplus - ((rplus + 5373807) >> 23) * dilithium_q; - __ evpaddd(xmm4, k0, xmm0, barrettAddend, false, Assembler::AVX_512bit); - __ evpaddd(xmm5, k0, xmm1, barrettAddend, false, Assembler::AVX_512bit); - __ evpaddd(xmm6, k0, xmm2, barrettAddend, false, Assembler::AVX_512bit); - __ evpaddd(xmm7, k0, xmm3, barrettAddend, false, Assembler::AVX_512bit); + for (int i = 0; i < regCnt; i++) { + __ vpaddd(Tmp1[i], RPlus[i], barrettAddend, vector_len); + } - __ evpsrad(xmm4, k0, xmm4, 23, false, Assembler::AVX_512bit); - __ evpsrad(xmm5, k0, xmm5, 23, false, Assembler::AVX_512bit); - __ evpsrad(xmm6, k0, xmm6, 23, false, Assembler::AVX_512bit); - __ evpsrad(xmm7, k0, xmm7, 23, false, Assembler::AVX_512bit); + for (int i = 0; i < regCnt; i++) { + __ vpsrad(Tmp1[i], Tmp1[i], 23, vector_len); + } - __ evpmulld(xmm4, k0, xmm4, dilithium_q, false, Assembler::AVX_512bit); - __ evpmulld(xmm5, k0, xmm5, dilithium_q, false, Assembler::AVX_512bit); - __ evpmulld(xmm6, k0, xmm6, dilithium_q, false, Assembler::AVX_512bit); - __ evpmulld(xmm7, k0, xmm7, dilithium_q, false, Assembler::AVX_512bit); + for (int i = 0; i < regCnt; i++) { + __ vpmulld(Tmp1[i], Tmp1[i], dilithium_q, vector_len); + } + + for (int i = 0; i < regCnt; i++) { + __ vpsubd(RPlus[i], RPlus[i], Tmp1[i], vector_len); + } - __ evpsubd(xmm0, k0, xmm0, xmm4, false, Assembler::AVX_512bit); - __ evpsubd(xmm1, k0, xmm1, xmm5, false, Assembler::AVX_512bit); - __ evpsubd(xmm2, k0, xmm2, xmm6, false, Assembler::AVX_512bit); - __ evpsubd(xmm3, k0, xmm3, xmm7, false, Assembler::AVX_512bit); - // rplus in xmm0 // rplus = rplus + ((rplus >> 31) & dilithium_q); - __ evpsrad(xmm4, k0, xmm0, 31, false, Assembler::AVX_512bit); - __ evpsrad(xmm5, k0, xmm1, 31, false, Assembler::AVX_512bit); - __ evpsrad(xmm6, k0, xmm2, 31, false, Assembler::AVX_512bit); - __ evpsrad(xmm7, k0, xmm3, 31, false, Assembler::AVX_512bit); + for (int i = 0; i < regCnt; i++) { + __ vpsrad(Tmp1[i], RPlus[i], 31, vector_len); + } - __ evpandd(xmm4, k0, xmm4, dilithium_q, false, Assembler::AVX_512bit); - __ evpandd(xmm5, k0, xmm5, dilithium_q, false, Assembler::AVX_512bit); - __ evpandd(xmm6, k0, xmm6, dilithium_q, false, Assembler::AVX_512bit); - __ evpandd(xmm7, k0, xmm7, dilithium_q, false, Assembler::AVX_512bit); + for (int i = 0; i < regCnt; i++) { + __ vpand(Tmp1[i], Tmp1[i], dilithium_q, vector_len); + } + + for (int i = 0; i < regCnt; i++) { + __ vpaddd(RPlus[i], RPlus[i], Tmp1[i], vector_len); + } - __ evpaddd(xmm0, k0, xmm0, xmm4, false, Assembler::AVX_512bit); - __ evpaddd(xmm1, k0, xmm1, xmm5, false, Assembler::AVX_512bit); - __ evpaddd(xmm2, k0, xmm2, xmm6, false, Assembler::AVX_512bit); - __ evpaddd(xmm3, k0, xmm3, xmm7, false, Assembler::AVX_512bit); - // rplus in xmm0 // int quotient = (rplus * barrettMultiplier) >> 22; - __ evpmulld(xmm4, k0, xmm0, barrettMultiplier, false, Assembler::AVX_512bit); - __ evpmulld(xmm5, k0, xmm1, barrettMultiplier, false, Assembler::AVX_512bit); - __ evpmulld(xmm6, k0, xmm2, barrettMultiplier, false, Assembler::AVX_512bit); - __ evpmulld(xmm7, k0, xmm3, barrettMultiplier, false, Assembler::AVX_512bit); + for (int i = 0; i < regCnt; i++) { + __ vpmulld(Quotient[i], RPlus[i], barrettMultiplier, vector_len); + } + + for (int i = 0; i < regCnt; i++) { + __ vpsrad(Quotient[i], Quotient[i], 22, vector_len); + } - __ evpsrad(xmm4, k0, xmm4, 22, false, Assembler::AVX_512bit); - __ evpsrad(xmm5, k0, xmm5, 22, false, Assembler::AVX_512bit); - __ evpsrad(xmm6, k0, xmm6, 22, false, Assembler::AVX_512bit); - __ evpsrad(xmm7, k0, xmm7, 22, false, Assembler::AVX_512bit); - // quotient in xmm4 // int r0 = rplus - quotient * twoGamma2; - __ evpmulld(xmm8, k0, xmm4, twoGamma2, false, Assembler::AVX_512bit); - __ evpmulld(xmm9, k0, xmm5, twoGamma2, false, Assembler::AVX_512bit); - __ evpmulld(xmm10, k0, xmm6, twoGamma2, false, Assembler::AVX_512bit); - __ evpmulld(xmm11, k0, xmm7, twoGamma2, false, Assembler::AVX_512bit); + for (int i = 0; i < regCnt; i++) { + __ vpmulld(R0[i], Quotient[i], twoGamma2, vector_len); + } + + for (int i = 0; i < regCnt; i++) { + __ vpsubd(R0[i], RPlus[i], R0[i], vector_len); + } - __ evpsubd(xmm8, k0, xmm0, xmm8, false, Assembler::AVX_512bit); - __ evpsubd(xmm9, k0, xmm1, xmm9, false, Assembler::AVX_512bit); - __ evpsubd(xmm10, k0, xmm2, xmm10, false, Assembler::AVX_512bit); - __ evpsubd(xmm11, k0, xmm3, xmm11, false, Assembler::AVX_512bit); - // r0 in xmm8 // int mask = (twoGamma2 - r0) >> 22; - __ evpsubd(xmm12, k0, twoGamma2, xmm8, false, Assembler::AVX_512bit); - __ evpsubd(xmm13, k0, twoGamma2, xmm9, false, Assembler::AVX_512bit); - __ evpsubd(xmm14, k0, twoGamma2, xmm10, false, Assembler::AVX_512bit); - __ evpsubd(xmm15, k0, twoGamma2, xmm11, false, Assembler::AVX_512bit); + for (int i = 0; i < regCnt; i++) { + __ vpsubd(Mask[i], twoGamma2, R0[i], vector_len); + } + + for (int i = 0; i < regCnt; i++) { + __ vpsrad(Mask[i], Mask[i], 22, vector_len); + } - __ evpsrad(xmm12, k0, xmm12, 22, false, Assembler::AVX_512bit); - __ evpsrad(xmm13, k0, xmm13, 22, false, Assembler::AVX_512bit); - __ evpsrad(xmm14, k0, xmm14, 22, false, Assembler::AVX_512bit); - __ evpsrad(xmm15, k0, xmm15, 22, false, Assembler::AVX_512bit); - // mask in xmm12 // r0 -= (mask & twoGamma2); - __ evpandd(xmm16, k0, xmm12, twoGamma2, false, Assembler::AVX_512bit); - __ evpandd(xmm17, k0, xmm13, twoGamma2, false, Assembler::AVX_512bit); - __ evpandd(xmm18, k0, xmm14, twoGamma2, false, Assembler::AVX_512bit); - __ evpandd(xmm19, k0, xmm15, twoGamma2, false, Assembler::AVX_512bit); + for (int i = 0; i < regCnt; i++) { + __ vpand(Tmp1[i], Mask[i], twoGamma2, vector_len); + } + + for (int i = 0; i < regCnt; i++) { + __ vpsubd(R0[i], R0[i], Tmp1[i], vector_len); + } - __ evpsubd(xmm8, k0, xmm8, xmm16, false, Assembler::AVX_512bit); - __ evpsubd(xmm9, k0, xmm9, xmm17, false, Assembler::AVX_512bit); - __ evpsubd(xmm10, k0, xmm10, xmm18, false, Assembler::AVX_512bit); - __ evpsubd(xmm11, k0, xmm11, xmm19, false, Assembler::AVX_512bit); - // r0 in xmm8 // quotient += (mask & 1); - __ evpandd(xmm16, k0, xmm12, one, false, Assembler::AVX_512bit); - __ evpandd(xmm17, k0, xmm13, one, false, Assembler::AVX_512bit); - __ evpandd(xmm18, k0, xmm14, one, false, Assembler::AVX_512bit); - __ evpandd(xmm19, k0, xmm15, one, false, Assembler::AVX_512bit); + for (int i = 0; i < regCnt; i++) { + __ vpand(Tmp1[i], Mask[i], one, vector_len); + } - __ evpaddd(xmm4, k0, xmm4, xmm16, false, Assembler::AVX_512bit); - __ evpaddd(xmm5, k0, xmm5, xmm17, false, Assembler::AVX_512bit); - __ evpaddd(xmm6, k0, xmm6, xmm18, false, Assembler::AVX_512bit); - __ evpaddd(xmm7, k0, xmm7, xmm19, false, Assembler::AVX_512bit); + for (int i = 0; i < regCnt; i++) { + __ vpaddd(Quotient[i], Quotient[i], Tmp1[i], vector_len); + } // mask = (twoGamma2 / 2 - r0) >> 31; - __ evpsubd(xmm12, k0, gamma2, xmm8, false, Assembler::AVX_512bit); - __ evpsubd(xmm13, k0, gamma2, xmm9, false, Assembler::AVX_512bit); - __ evpsubd(xmm14, k0, gamma2, xmm10, false, Assembler::AVX_512bit); - __ evpsubd(xmm15, k0, gamma2, xmm11, false, Assembler::AVX_512bit); + for (int i = 0; i < regCnt; i++) { + __ vpsubd(Mask[i], gamma2, R0[i], vector_len); + } - __ evpsrad(xmm12, k0, xmm12, 31, false, Assembler::AVX_512bit); - __ evpsrad(xmm13, k0, xmm13, 31, false, Assembler::AVX_512bit); - __ evpsrad(xmm14, k0, xmm14, 31, false, Assembler::AVX_512bit); - __ evpsrad(xmm15, k0, xmm15, 31, false, Assembler::AVX_512bit); + for (int i = 0; i < regCnt; i++) { + __ vpsrad(Mask[i], Mask[i], 31, vector_len); + } // r0 -= (mask & twoGamma2); - __ evpandd(xmm16, k0, xmm12, twoGamma2, false, Assembler::AVX_512bit); - __ evpandd(xmm17, k0, xmm13, twoGamma2, false, Assembler::AVX_512bit); - __ evpandd(xmm18, k0, xmm14, twoGamma2, false, Assembler::AVX_512bit); - __ evpandd(xmm19, k0, xmm15, twoGamma2, false, Assembler::AVX_512bit); + for (int i = 0; i < regCnt; i++) { + __ vpand(Tmp1[i], Mask[i], twoGamma2, vector_len); + } + + for (int i = 0; i < regCnt; i++) { + __ vpsubd(R0[i], R0[i], Tmp1[i], vector_len); + } - __ evpsubd(xmm8, k0, xmm8, xmm16, false, Assembler::AVX_512bit); - __ evpsubd(xmm9, k0, xmm9, xmm17, false, Assembler::AVX_512bit); - __ evpsubd(xmm10, k0, xmm10, xmm18, false, Assembler::AVX_512bit); - __ evpsubd(xmm11, k0, xmm11, xmm19, false, Assembler::AVX_512bit); - // r0 in xmm8 // quotient += (mask & 1); - __ evpandd(xmm16, k0, xmm12, one, false, Assembler::AVX_512bit); - __ evpandd(xmm17, k0, xmm13, one, false, Assembler::AVX_512bit); - __ evpandd(xmm18, k0, xmm14, one, false, Assembler::AVX_512bit); - __ evpandd(xmm19, k0, xmm15, one, false, Assembler::AVX_512bit); + for (int i = 0; i < regCnt; i++) { + __ vpand(Tmp1[i], Mask[i], one, vector_len); + } - __ evpaddd(xmm4, k0, xmm4, xmm16, false, Assembler::AVX_512bit); - __ evpaddd(xmm5, k0, xmm5, xmm17, false, Assembler::AVX_512bit); - __ evpaddd(xmm6, k0, xmm6, xmm18, false, Assembler::AVX_512bit); - __ evpaddd(xmm7, k0, xmm7, xmm19, false, Assembler::AVX_512bit); - // quotient in xmm4 + for (int i = 0; i < regCnt; i++) { + __ vpaddd(Quotient[i], Quotient[i], Tmp1[i], vector_len); + } + // r1 in RPlus // int r1 = rplus - r0 - (dilithium_q - 1); - __ evpsubd(xmm16, k0, xmm0, xmm8, false, Assembler::AVX_512bit); - __ evpsubd(xmm17, k0, xmm1, xmm9, false, Assembler::AVX_512bit); - __ evpsubd(xmm18, k0, xmm2, xmm10, false, Assembler::AVX_512bit); - __ evpsubd(xmm19, k0, xmm3, xmm11, false, Assembler::AVX_512bit); - - __ evpsubd(xmm16, k0, xmm16, xmm26, false, Assembler::AVX_512bit); - __ evpsubd(xmm17, k0, xmm17, xmm26, false, Assembler::AVX_512bit); - __ evpsubd(xmm18, k0, xmm18, xmm26, false, Assembler::AVX_512bit); - __ evpsubd(xmm19, k0, xmm19, xmm26, false, Assembler::AVX_512bit); - // r1 in xmm16 // r1 = (r1 | (-r1)) >> 31; // 0 if rplus - r0 == (dilithium_q - 1), -1 otherwise - __ evpsubd(xmm20, k0, zero, xmm16, false, Assembler::AVX_512bit); - __ evpsubd(xmm21, k0, zero, xmm17, false, Assembler::AVX_512bit); - __ evpsubd(xmm22, k0, zero, xmm18, false, Assembler::AVX_512bit); - __ evpsubd(xmm23, k0, zero, xmm19, false, Assembler::AVX_512bit); + for (int i = 0; i < regCnt; i++) { + __ vpsubd(RPlus[i], RPlus[i], R0[i], vector_len); + } - __ evporq(xmm16, k0, xmm16, xmm20, false, Assembler::AVX_512bit); - __ evporq(xmm17, k0, xmm17, xmm21, false, Assembler::AVX_512bit); - __ evporq(xmm18, k0, xmm18, xmm22, false, Assembler::AVX_512bit); - __ evporq(xmm19, k0, xmm19, xmm23, false, Assembler::AVX_512bit); + if (vector_len == Assembler::AVX_512bit) { + KRegister EqMsk[] = {k1, k2, k3, k4}; + for (int i = 0; i < regCnt; i++) { + __ evpcmpeqd(EqMsk[i], k0, RPlus[i], qMinus1, vector_len); + } - __ evpsubd(xmm12, k0, zero, one, false, Assembler::AVX_512bit); // -1 + // r0 += ~r1; // add -1 or keep as is, using EqMsk as filter + for (int i = 0; i < regCnt; i++) { + __ evpaddd(R0[i], EqMsk[i], R0[i], minusOne, true, vector_len); + } - __ evpsrad(xmm0, k0, xmm16, 31, false, Assembler::AVX_512bit); - __ evpsrad(xmm1, k0, xmm17, 31, false, Assembler::AVX_512bit); - __ evpsrad(xmm2, k0, xmm18, 31, false, Assembler::AVX_512bit); - __ evpsrad(xmm3, k0, xmm19, 31, false, Assembler::AVX_512bit); - // r1 in xmm0 - // r0 += ~r1; - __ evpxorq(xmm20, k0, xmm0, xmm12, false, Assembler::AVX_512bit); - __ evpxorq(xmm21, k0, xmm1, xmm12, false, Assembler::AVX_512bit); - __ evpxorq(xmm22, k0, xmm2, xmm12, false, Assembler::AVX_512bit); - __ evpxorq(xmm23, k0, xmm3, xmm12, false, Assembler::AVX_512bit); + // r1 in Quotient + // r1 = r1 & quotient; // copy 0 or keep as is, using EqMsk as filter + for (int i = 0; i < regCnt; i++) { + __ evpandd(Quotient[i], EqMsk[i], Quotient[i], zero, true, vector_len); + } + } else { + const XMMRegister qMinus1 = Tmp1[0]; + __ vpsubd(qMinus1, dilithium_q, one, vector_len); // q - 1 - __ evpaddd(xmm8, k0, xmm8, xmm20, false, Assembler::AVX_512bit); - __ evpaddd(xmm9, k0, xmm9, xmm21, false, Assembler::AVX_512bit); - __ evpaddd(xmm10, k0, xmm10, xmm22, false, Assembler::AVX_512bit); - __ evpaddd(xmm11, k0, xmm11, xmm23, false, Assembler::AVX_512bit); - // r0 in xmm8 - // r1 = r1 & quotient; - __ evpandd(xmm0, k0, xmm4, xmm0, false, Assembler::AVX_512bit); - __ evpandd(xmm1, k0, xmm5, xmm1, false, Assembler::AVX_512bit); - __ evpandd(xmm2, k0, xmm6, xmm2, false, Assembler::AVX_512bit); - __ evpandd(xmm3, k0, xmm7, xmm3, false, Assembler::AVX_512bit); - // r1 in xmm0 + for (int i = 0; i < regCnt; i++) { + __ vpcmpeqd(Mask[i], RPlus[i], qMinus1, vector_len); + } + + // r0 += ~r1; + // Mask already negated + for (int i = 0; i < regCnt; i++) { + __ vpaddd(R0[i], R0[i], Mask[i], vector_len); + } + + // r1 in Quotient + // r1 = r1 & quotient; + for (int i = 0; i < regCnt; i++) { + __ vpandn(Quotient[i], Mask[i], Quotient[i], vector_len); + } + } + + // r1 in Quotient // lowPart[m] = r0; // highPart[m] = r1; - store4Xmms(highPart, 0, xmm0_3, _masm); - store4Xmms(lowPart, 0, xmm8_11, _masm); + storeXmms(highPart, 0, Quotient, vector_len, _masm); + storeXmms(lowPart, 0, R0, vector_len, _masm); - __ addptr(highPart, 4 * XMMBYTES); - __ addptr(lowPart, 4 * XMMBYTES); - __ subl(len, 4 * XMMBYTES); + __ addptr(highPart, memStep); + __ addptr(lowPart, memStep); + __ subl(len, memStep); __ jcc(Assembler::notEqual, L_loop); __ leave(); // required for proper stackwalking of RuntimeStub frame @@ -1018,17 +1322,21 @@ static address generate_dilithiumDecomposePoly_avx512(StubGenerator *stubgen, } void StubGenerator::generate_dilithium_stubs() { + int vector_len = Assembler::AVX_256bit; + if (VM_Version::supports_evex() && VM_Version::supports_avx512bw()) { + vector_len = Assembler::AVX_512bit; + } // Generate Dilithium intrinsics code if (UseDilithiumIntrinsics) { - StubRoutines::_dilithiumAlmostNtt = - generate_dilithiumAlmostNtt_avx512(this, _masm); - StubRoutines::_dilithiumAlmostInverseNtt = - generate_dilithiumAlmostInverseNtt_avx512(this, _masm); - StubRoutines::_dilithiumNttMult = - generate_dilithiumNttMult_avx512(this, _masm); - StubRoutines::_dilithiumMontMulByConstant = - generate_dilithiumMontMulByConstant_avx512(this, _masm); - StubRoutines::_dilithiumDecomposePoly = - generate_dilithiumDecomposePoly_avx512(this, _masm); + StubRoutines::_dilithiumAlmostNtt = + generate_dilithiumAlmostNtt_avx(this, vector_len, _masm); + StubRoutines::_dilithiumAlmostInverseNtt = + generate_dilithiumAlmostInverseNtt_avx(this, vector_len, _masm); + StubRoutines::_dilithiumNttMult = + generate_dilithiumNttMult_avx(this, vector_len, _masm); + StubRoutines::_dilithiumMontMulByConstant = + generate_dilithiumMontMulByConstant_avx(this, vector_len, _masm); + StubRoutines::_dilithiumDecomposePoly = + generate_dilithiumDecomposePoly_avx(this, vector_len, _masm); } } diff --git a/src/hotspot/cpu/x86/vm_version_x86.cpp b/src/hotspot/cpu/x86/vm_version_x86.cpp index 4961aed61c3..747daefd51d 100644 --- a/src/hotspot/cpu/x86/vm_version_x86.cpp +++ b/src/hotspot/cpu/x86/vm_version_x86.cpp @@ -1271,8 +1271,7 @@ void VM_Version::get_processor_features() { } // Dilithium Intrinsics - // Currently we only have them for AVX512 - if (supports_evex() && supports_avx512bw()) { + if (UseAVX > 1) { if (FLAG_IS_DEFAULT(UseDilithiumIntrinsics)) { UseDilithiumIntrinsics = true; } diff --git a/src/hotspot/cpu/x86/x86.ad b/src/hotspot/cpu/x86/x86.ad index a9748617e1f..1d393897bca 100644 --- a/src/hotspot/cpu/x86/x86.ad +++ b/src/hotspot/cpu/x86/x86.ad @@ -2633,6 +2633,70 @@ bool Matcher::supports_vector_calling_convention(void) { return EnableVectorSupport; } +static bool is_ndd_demotable(const MachNode* mdef) { + return ((mdef->flags() & Node::PD::Flag_ndd_demotable) != 0); +} + +static bool is_ndd_demotable_commutative(const MachNode* mdef) { + return ((mdef->flags() & Node::PD::Flag_ndd_demotable_commutative) != 0); +} + +static bool is_demotion_candidate(const MachNode* mdef) { + return (is_ndd_demotable(mdef) || is_ndd_demotable_commutative(mdef)); +} + +bool Matcher::is_register_biasing_candidate(const MachNode* mdef, + int oper_index) { + if (mdef == nullptr) { + return false; + } + + if (mdef->num_opnds() <= oper_index || mdef->operand_index(oper_index) < 0 || + mdef->in(mdef->operand_index(oper_index)) == nullptr) { + assert(oper_index != 1 || !is_demotion_candidate(mdef), "%s", mdef->Name()); + assert(oper_index != 2 || !is_ndd_demotable_commutative(mdef), "%s", mdef->Name()); + return false; + } + + // Complex memory operand covers multiple incoming edges needed for + // address computation. Biasing def towards any address component will not + // result in NDD demotion by assembler. + if (mdef->operand_num_edges(oper_index) != 1) { + assert(!is_ndd_demotable(mdef), "%s", mdef->Name()); + return false; + } + + // Demotion candidate must be register mask compatible with definition. + const RegMask& oper_mask = mdef->in_RegMask(mdef->operand_index(oper_index)); + if (!oper_mask.overlap(mdef->out_RegMask())) { + assert(!is_demotion_candidate(mdef), "%s", mdef->Name()); + return false; + } + + switch (oper_index) { + // First operand of MachNode corresponding to Intel APX NDD selection + // pattern can share its assigned register with definition operand if + // their live ranges do not overlap. In such a scenario we can demote + // it to legacy map0/map1 instruction by replacing its 4-byte extended + // EVEX prefix with shorter REX/REX2 encoding. Demotion candidates + // are decorated with a special flag by instruction selector. + case 1: + return is_demotion_candidate(mdef); + + // Definition operand of commutative operation can be biased towards second + // operand. + case 2: + return is_ndd_demotable_commutative(mdef); + + // Current scheme only selects up to two biasing candidates + default: + assert(false, "unhandled operand index: %s", mdef->Name()); + break; + } + + return false; +} + OptoRegPair Matcher::vector_return_value(uint ideal_reg) { assert(EnableVectorSupport, "sanity"); int lo = XMM0_num; @@ -2767,21 +2831,11 @@ class HandlerImpl { public: - static int emit_exception_handler(C2_MacroAssembler *masm); static int emit_deopt_handler(C2_MacroAssembler* masm); - static uint size_exception_handler() { - // NativeCall instruction size is the same as NativeJump. - // exception handler starts out as jump and can be patched to - // a call be deoptimization. (4932387) - // Note that this value is also credited (in output.cpp) to - // the size of the code section. - return NativeJump::instruction_size; - } - static uint size_deopt_handler() { - // three 5 byte instructions plus one move for unreachable address. - return 15+3; + // one call and one jmp. + return 7; } }; @@ -2822,7 +2876,7 @@ static inline bool is_clz_non_subword_predicate_evex(BasicType bt, int vlen_byte class Node::PD { public: - enum NodeFlags { + enum NodeFlags : uint64_t { Flag_intel_jcc_erratum = Node::_last_flag << 1, Flag_sets_carry_flag = Node::_last_flag << 2, Flag_sets_parity_flag = Node::_last_flag << 3, @@ -2834,7 +2888,9 @@ public: Flag_clears_zero_flag = Node::_last_flag << 9, Flag_clears_overflow_flag = Node::_last_flag << 10, Flag_clears_sign_flag = Node::_last_flag << 11, - _last_flag = Flag_clears_sign_flag + Flag_ndd_demotable = Node::_last_flag << 12, + Flag_ndd_demotable_commutative = Node::_last_flag << 13, + _last_flag = Flag_ndd_demotable_commutative }; }; @@ -2873,24 +2929,6 @@ int MachNode::compute_padding(int current_offset) const { } } -// Emit exception handler code. -// Stuff framesize into a register and call a VM stub routine. -int HandlerImpl::emit_exception_handler(C2_MacroAssembler* masm) { - - // Note that the code buffer's insts_mark is always relative to insts. - // That's why we must use the macroassembler to generate a handler. - address base = __ start_a_stub(size_exception_handler()); - if (base == nullptr) { - ciEnv::current()->record_failure("CodeCache is full"); - return 0; // CodeBuffer::expand failed - } - int offset = __ offset(); - __ jump(RuntimeAddress(OptoRuntime::exception_blob()->entry_point())); - assert(__ offset() - offset <= (int) size_exception_handler(), "overflow"); - __ end_a_stub(); - return offset; -} - // Emit deopt handler code. int HandlerImpl::emit_deopt_handler(C2_MacroAssembler* masm) { @@ -2903,21 +2941,20 @@ int HandlerImpl::emit_deopt_handler(C2_MacroAssembler* masm) { } int offset = __ offset(); - address the_pc = (address) __ pc(); - Label next; - // push a "the_pc" on the stack without destroying any registers - // as they all may be live. + Label start; + __ bind(start); - // push address of "next" - __ call(next, relocInfo::none); // reloc none is fine since it is a disp32 - __ bind(next); - // adjust it so it matches "the_pc" - __ subptr(Address(rsp, 0), __ offset() - offset); + __ call(RuntimeAddress(SharedRuntime::deopt_blob()->unpack())); + + int entry_offset = __ offset(); + + __ jmp(start); - __ jump(RuntimeAddress(SharedRuntime::deopt_blob()->unpack())); assert(__ offset() - offset <= (int) size_deopt_handler(), "overflow %d", (__ offset() - offset)); + assert(__ offset() - entry_offset >= NativePostCallNop::first_check_size, + "out of bounds read in post-call NOP check"); __ end_a_stub(); - return offset; + return entry_offset; } static Assembler::Width widthForType(BasicType bt) { @@ -9830,7 +9867,7 @@ instruct addI_rReg_ndd(rRegI dst, rRegI src1, rRegI src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (AddI src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag); + flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable_commutative); format %{ "eaddl $dst, $src1, $src2\t# int ndd" %} ins_encode %{ @@ -9858,7 +9895,7 @@ instruct addI_rReg_rReg_imm_ndd(rRegI dst, rRegI src1, immI src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (AddI src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag); + flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable); format %{ "eaddl $dst, $src1, $src2\t# int ndd" %} ins_encode %{ @@ -9901,7 +9938,7 @@ instruct addI_rReg_rReg_mem_ndd(rRegI dst, rRegI src1, memory src2, rFlagsReg cr predicate(UseAPX); match(Set dst (AddI src1 (LoadI src2))); effect(KILL cr); - flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag); + flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable_commutative); ins_cost(150); format %{ "eaddl $dst, $src1, $src2\t# int ndd" %} @@ -9958,6 +9995,7 @@ instruct incI_rReg_ndd(rRegI dst, rRegI src, immI_1 val, rFlagsReg cr) predicate(UseAPX && UseIncDec); match(Set dst (AddI src val)); effect(KILL cr); + flag(PD::Flag_ndd_demotable); format %{ "eincl $dst, $src\t# int ndd" %} ins_encode %{ @@ -10012,6 +10050,7 @@ instruct decI_rReg_ndd(rRegI dst, rRegI src, immI_M1 val, rFlagsReg cr) predicate(UseAPX && UseIncDec); match(Set dst (AddI src val)); effect(KILL cr); + flag(PD::Flag_ndd_demotable); format %{ "edecl $dst, $src\t# int ndd" %} ins_encode %{ @@ -10118,7 +10157,7 @@ instruct addL_rReg_ndd(rRegL dst, rRegL src1, rRegL src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (AddL src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag); + flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable_commutative); format %{ "eaddq $dst, $src1, $src2\t# long ndd" %} ins_encode %{ @@ -10146,7 +10185,7 @@ instruct addL_rReg_rReg_imm_ndd(rRegL dst, rRegL src1, immL32 src2, rFlagsReg cr predicate(UseAPX); match(Set dst (AddL src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag); + flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable); format %{ "eaddq $dst, $src1, $src2\t# long ndd" %} ins_encode %{ @@ -10189,7 +10228,7 @@ instruct addL_rReg_rReg_mem_ndd(rRegL dst, rRegL src1, memory src2, rFlagsReg cr predicate(UseAPX); match(Set dst (AddL src1 (LoadL src2))); effect(KILL cr); - flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag); + flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable_commutative); ins_cost(150); format %{ "eaddq $dst, $src1, $src2\t# long ndd" %} @@ -10245,6 +10284,7 @@ instruct incL_rReg_ndd(rRegL dst, rRegI src, immL1 val, rFlagsReg cr) predicate(UseAPX && UseIncDec); match(Set dst (AddL src val)); effect(KILL cr); + flag(PD::Flag_ndd_demotable); format %{ "eincq $dst, $src\t# long ndd" %} ins_encode %{ @@ -10299,6 +10339,7 @@ instruct decL_rReg_ndd(rRegL dst, rRegL src, immL_M1 val, rFlagsReg cr) predicate(UseAPX && UseIncDec); match(Set dst (AddL src val)); effect(KILL cr); + flag(PD::Flag_ndd_demotable); format %{ "edecq $dst, $src\t# long ndd" %} ins_encode %{ @@ -11013,7 +11054,7 @@ instruct subI_rReg_ndd(rRegI dst, rRegI src1, rRegI src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (SubI src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag); + flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable); format %{ "esubl $dst, $src1, $src2\t# int ndd" %} ins_encode %{ @@ -11027,7 +11068,7 @@ instruct subI_rReg_rReg_imm_ndd(rRegI dst, rRegI src1, immI src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (SubI src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag); + flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable); format %{ "esubl $dst, $src1, $src2\t# int ndd" %} ins_encode %{ @@ -11070,7 +11111,7 @@ instruct subI_rReg_rReg_mem_ndd(rRegI dst, rRegI src1, memory src2, rFlagsReg cr predicate(UseAPX); match(Set dst (SubI src1 (LoadI src2))); effect(KILL cr); - flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag); + flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable); ins_cost(150); format %{ "esubl $dst, $src1, $src2\t# int ndd" %} @@ -11128,7 +11169,7 @@ instruct subL_rReg_ndd(rRegL dst, rRegL src1, rRegL src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (SubL src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag); + flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable); format %{ "esubq $dst, $src1, $src2\t# long ndd" %} ins_encode %{ @@ -11142,7 +11183,7 @@ instruct subL_rReg_rReg_imm_ndd(rRegL dst, rRegL src1, immL32 src2, rFlagsReg cr predicate(UseAPX); match(Set dst (SubL src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag); + flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable); format %{ "esubq $dst, $src1, $src2\t# long ndd" %} ins_encode %{ @@ -11185,7 +11226,7 @@ instruct subL_rReg_rReg_mem_ndd(rRegL dst, rRegL src1, memory src2, rFlagsReg cr predicate(UseAPX); match(Set dst (SubL src1 (LoadL src2))); effect(KILL cr); - flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag); + flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable); ins_cost(150); format %{ "esubq $dst, $src1, $src2\t# long ndd" %} @@ -11257,7 +11298,7 @@ instruct negI_rReg_ndd(rRegI dst, rRegI src, immI_0 zero, rFlagsReg cr) predicate(UseAPX); match(Set dst (SubI zero src)); effect(KILL cr); - flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag); + flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable); format %{ "enegl $dst, $src\t# int ndd" %} ins_encode %{ @@ -11285,7 +11326,7 @@ instruct negI_rReg_2_ndd(rRegI dst, rRegI src, rFlagsReg cr) predicate(UseAPX); match(Set dst (NegI src)); effect(KILL cr); - flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag); + flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable); format %{ "enegl $dst, $src\t# int ndd" %} ins_encode %{ @@ -11326,7 +11367,7 @@ instruct negL_rReg_ndd(rRegL dst, rRegL src, immL0 zero, rFlagsReg cr) predicate(UseAPX); match(Set dst (SubL zero src)); effect(KILL cr); - flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag); + flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable); format %{ "enegq $dst, $src\t# long ndd" %} ins_encode %{ @@ -11354,7 +11395,7 @@ instruct negL_rReg_2_ndd(rRegL dst, rRegL src, rFlagsReg cr) predicate(UseAPX); match(Set dst (NegL src)); effect(KILL cr); - flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag); + flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable); format %{ "enegq $dst, $src\t# long ndd" %} ins_encode %{ @@ -11399,6 +11440,7 @@ instruct mulI_rReg_ndd(rRegI dst, rRegI src1, rRegI src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (MulI src1 src2)); effect(KILL cr); + flag(PD::Flag_ndd_demotable_commutative); ins_cost(300); format %{ "eimull $dst, $src1, $src2\t# int ndd" %} @@ -11440,6 +11482,7 @@ instruct mulI_rReg_rReg_mem_ndd(rRegI dst, rRegI src1, memory src2, rFlagsReg cr predicate(UseAPX); match(Set dst (MulI src1 (LoadI src2))); effect(KILL cr); + flag(PD::Flag_ndd_demotable); ins_cost(350); format %{ "eimull $dst, $src1, $src2\t# int ndd" %} @@ -11491,6 +11534,7 @@ instruct mulL_rReg_ndd(rRegL dst, rRegL src1, rRegL src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (MulL src1 src2)); effect(KILL cr); + flag(PD::Flag_ndd_demotable_commutative); ins_cost(300); format %{ "eimulq $dst, $src1, $src2\t# long ndd" %} @@ -11532,6 +11576,7 @@ instruct mulL_rReg_rReg_mem_ndd(rRegL dst, rRegL src1, memory src2, rFlagsReg cr predicate(UseAPX); match(Set dst (MulL src1 (LoadL src2))); effect(KILL cr); + flag(PD::Flag_ndd_demotable_commutative); ins_cost(350); format %{ "eimulq $dst, $src1, $src2 \t# long" %} @@ -11806,6 +11851,7 @@ instruct salI_rReg_immI2_ndd(rRegI dst, rRegI src, immI2 shift, rFlagsReg cr) predicate(UseAPX); match(Set dst (LShiftI src shift)); effect(KILL cr); + flag(PD::Flag_ndd_demotable); format %{ "esall $dst, $src, $shift\t# int(ndd)" %} ins_encode %{ @@ -11834,6 +11880,7 @@ instruct salI_rReg_imm_ndd(rRegI dst, rRegI src, immI8 shift, rFlagsReg cr) predicate(UseAPX); match(Set dst (LShiftI src shift)); effect(KILL cr); + flag(PD::Flag_ndd_demotable); format %{ "esall $dst, $src, $shift\t# int (ndd)" %} ins_encode %{ @@ -11940,6 +11987,7 @@ instruct sarI_rReg_imm_ndd(rRegI dst, rRegI src, immI8 shift, rFlagsReg cr) predicate(UseAPX); match(Set dst (RShiftI src shift)); effect(KILL cr); + flag(PD::Flag_ndd_demotable); format %{ "esarl $dst, $src, $shift\t# int (ndd)" %} ins_encode %{ @@ -12046,6 +12094,7 @@ instruct shrI_rReg_imm_ndd(rRegI dst, rRegI src, immI8 shift, rFlagsReg cr) predicate(UseAPX); match(Set dst (URShiftI src shift)); effect(KILL cr); + flag(PD::Flag_ndd_demotable); format %{ "eshrl $dst, $src, $shift\t # int (ndd)" %} ins_encode %{ @@ -12153,6 +12202,7 @@ instruct salL_rReg_immI2_ndd(rRegL dst, rRegL src, immI2 shift, rFlagsReg cr) predicate(UseAPX); match(Set dst (LShiftL src shift)); effect(KILL cr); + flag(PD::Flag_ndd_demotable); format %{ "esalq $dst, $src, $shift\t# long (ndd)" %} ins_encode %{ @@ -12181,6 +12231,7 @@ instruct salL_rReg_imm_ndd(rRegL dst, rRegL src, immI8 shift, rFlagsReg cr) predicate(UseAPX); match(Set dst (LShiftL src shift)); effect(KILL cr); + flag(PD::Flag_ndd_demotable); format %{ "esalq $dst, $src, $shift\t# long (ndd)" %} ins_encode %{ @@ -12287,6 +12338,7 @@ instruct sarL_rReg_imm_ndd(rRegL dst, rRegL src, immI shift, rFlagsReg cr) predicate(UseAPX); match(Set dst (RShiftL src shift)); effect(KILL cr); + flag(PD::Flag_ndd_demotable); format %{ "esarq $dst, $src, $shift\t# long (ndd)" %} ins_encode %{ @@ -12393,6 +12445,7 @@ instruct shrL_rReg_imm_ndd(rRegL dst, rRegL src, immI8 shift, rFlagsReg cr) predicate(UseAPX); match(Set dst (URShiftL src shift)); effect(KILL cr); + flag(PD::Flag_ndd_demotable); format %{ "eshrq $dst, $src, $shift\t# long (ndd)" %} ins_encode %{ @@ -12564,6 +12617,7 @@ instruct rolI_rReg_Var_ndd(rRegI dst, rRegI src, rcx_RegI shift, rFlagsReg cr) predicate(UseAPX && n->bottom_type()->basic_type() == T_INT); match(Set dst (RotateLeft src shift)); effect(KILL cr); + flag(PD::Flag_ndd_demotable); format %{ "eroll $dst, $src, $shift\t# rotate left (int ndd)" %} ins_encode %{ @@ -12628,6 +12682,7 @@ instruct rorI_rReg_Var_ndd(rRegI dst, rRegI src, rcx_RegI shift, rFlagsReg cr) predicate(UseAPX && n->bottom_type()->basic_type() == T_INT); match(Set dst (RotateRight src shift)); effect(KILL cr); + flag(PD::Flag_ndd_demotable); format %{ "erorl $dst, $src, $shift\t# rotate right(int ndd)" %} ins_encode %{ @@ -12680,6 +12735,7 @@ instruct rolL_rReg_Var(rRegL dst, rcx_RegI shift, rFlagsReg cr) predicate(!UseAPX && n->bottom_type()->basic_type() == T_LONG); match(Set dst (RotateLeft dst shift)); effect(KILL cr); + format %{ "rolq $dst, $shift" %} ins_encode %{ __ rolq($dst$$Register); @@ -12693,6 +12749,7 @@ instruct rolL_rReg_Var_ndd(rRegL dst, rRegL src, rcx_RegI shift, rFlagsReg cr) predicate(UseAPX && n->bottom_type()->basic_type() == T_LONG); match(Set dst (RotateLeft src shift)); effect(KILL cr); + flag(PD::Flag_ndd_demotable); format %{ "erolq $dst, $src, $shift\t# rotate left(long ndd)" %} ins_encode %{ @@ -12757,6 +12814,7 @@ instruct rorL_rReg_Var_ndd(rRegL dst, rRegL src, rcx_RegI shift, rFlagsReg cr) predicate(UseAPX && n->bottom_type()->basic_type() == T_LONG); match(Set dst (RotateRight src shift)); effect(KILL cr); + flag(PD::Flag_ndd_demotable); format %{ "erorq $dst, $src, $shift\t# rotate right(long ndd)" %} ins_encode %{ @@ -12834,7 +12892,7 @@ instruct andI_rReg_ndd(rRegI dst, rRegI src1, rRegI src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (AndI src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag); + flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable_commutative); format %{ "eandl $dst, $src1, $src2\t# int ndd" %} ins_encode %{ @@ -12927,7 +12985,7 @@ instruct andI_rReg_rReg_imm_ndd(rRegI dst, rRegI src1, immI src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (AndI src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag); + flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable); format %{ "eandl $dst, $src1, $src2\t# int ndd" %} ins_encode %{ @@ -12971,7 +13029,7 @@ instruct andI_rReg_rReg_mem_ndd(rRegI dst, rRegI src1, memory src2, rFlagsReg cr predicate(UseAPX); match(Set dst (AndI src1 (LoadI src2))); effect(KILL cr); - flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag); + flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable_commutative); ins_cost(150); format %{ "eandl $dst, $src1, $src2\t# int ndd" %} @@ -13171,7 +13229,7 @@ instruct orI_rReg_ndd(rRegI dst, rRegI src1, rRegI src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (OrI src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag); + flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable_commutative); format %{ "eorl $dst, $src1, $src2\t# int ndd" %} ins_encode %{ @@ -13200,7 +13258,7 @@ instruct orI_rReg_rReg_imm_ndd(rRegI dst, rRegI src1, immI src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (OrI src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag); + flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable); format %{ "eorl $dst, $src1, $src2\t# int ndd" %} ins_encode %{ @@ -13214,7 +13272,7 @@ instruct orI_rReg_imm_rReg_ndd(rRegI dst, immI src1, rRegI src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (OrI src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag); + flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable); format %{ "eorl $dst, $src2, $src1\t# int ndd" %} ins_encode %{ @@ -13258,7 +13316,7 @@ instruct orI_rReg_rReg_mem_ndd(rRegI dst, rRegI src1, memory src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (OrI src1 (LoadI src2))); effect(KILL cr); - flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag); + flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable); ins_cost(150); format %{ "eorl $dst, $src1, $src2\t# int ndd" %} @@ -13334,7 +13392,7 @@ instruct xorI_rReg_ndd(rRegI dst, rRegI src1, rRegI src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (XorI src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag); + flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable_commutative); format %{ "exorl $dst, $src1, $src2\t# int ndd" %} ins_encode %{ @@ -13360,6 +13418,7 @@ instruct xorI_rReg_im1_ndd(rRegI dst, rRegI src, immI_M1 imm) %{ match(Set dst (XorI src imm)); predicate(UseAPX); + flag(PD::Flag_ndd_demotable); format %{ "enotl $dst, $src" %} ins_encode %{ @@ -13390,7 +13449,7 @@ instruct xorI_rReg_rReg_imm_ndd(rRegI dst, rRegI src1, immI src2, rFlagsReg cr) predicate(UseAPX && n->in(2)->bottom_type()->is_int()->get_con() != -1); match(Set dst (XorI src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag); + flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable); format %{ "exorl $dst, $src1, $src2\t# int ndd" %} ins_encode %{ @@ -13436,7 +13495,7 @@ instruct xorI_rReg_rReg_mem_ndd(rRegI dst, rRegI src1, memory src2, rFlagsReg cr predicate(UseAPX); match(Set dst (XorI src1 (LoadI src2))); effect(KILL cr); - flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag); + flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable); ins_cost(150); format %{ "exorl $dst, $src1, $src2\t# int ndd" %} @@ -13515,7 +13574,7 @@ instruct andL_rReg_ndd(rRegL dst, rRegL src1, rRegL src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (AndL src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag); + flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable_commutative); format %{ "eandq $dst, $src1, $src2\t# long ndd" %} ins_encode %{ @@ -13571,7 +13630,7 @@ instruct andL_rReg_rReg_imm_ndd(rRegL dst, rRegL src1, immL32 src2, rFlagsReg cr predicate(UseAPX); match(Set dst (AndL src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag); + flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable); format %{ "eandq $dst, $src1, $src2\t# long ndd" %} ins_encode %{ @@ -13615,7 +13674,7 @@ instruct andL_rReg_rReg_mem_ndd(rRegL dst, rRegL src1, memory src2, rFlagsReg cr predicate(UseAPX); match(Set dst (AndL src1 (LoadL src2))); effect(KILL cr); - flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag); + flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable_commutative); ins_cost(150); format %{ "eandq $dst, $src1, $src2\t# long ndd" %} @@ -13818,7 +13877,7 @@ instruct orL_rReg_ndd(rRegL dst, rRegL src1, rRegL src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (OrL src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag); + flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable_commutative); format %{ "eorq $dst, $src1, $src2\t# long ndd" %} ins_encode %{ @@ -13873,7 +13932,7 @@ instruct orL_rReg_rReg_imm_ndd(rRegL dst, rRegL src1, immL32 src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (OrL src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag); + flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable); format %{ "eorq $dst, $src1, $src2\t# long ndd" %} ins_encode %{ @@ -13887,7 +13946,7 @@ instruct orL_rReg_imm_rReg_ndd(rRegL dst, immL32 src1, rRegL src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (OrL src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag); + flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable); format %{ "eorq $dst, $src2, $src1\t# long ndd" %} ins_encode %{ @@ -13932,7 +13991,7 @@ instruct orL_rReg_rReg_mem_ndd(rRegL dst, rRegL src1, memory src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (OrL src1 (LoadL src2))); effect(KILL cr); - flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag); + flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable_commutative); ins_cost(150); format %{ "eorq $dst, $src1, $src2\t# long ndd" %} @@ -14011,7 +14070,7 @@ instruct xorL_rReg_ndd(rRegL dst, rRegL src1, rRegL src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (XorL src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag); + flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable_commutative); format %{ "exorq $dst, $src1, $src2\t# long ndd" %} ins_encode %{ @@ -14037,6 +14096,7 @@ instruct xorL_rReg_im1_ndd(rRegL dst,rRegL src, immL_M1 imm) %{ predicate(UseAPX); match(Set dst (XorL src imm)); + flag(PD::Flag_ndd_demotable); format %{ "enotq $dst, $src" %} ins_encode %{ @@ -14067,7 +14127,7 @@ instruct xorL_rReg_rReg_imm(rRegL dst, rRegL src1, immL32 src2, rFlagsReg cr) predicate(UseAPX && n->in(2)->bottom_type()->is_long()->get_con() != -1L); match(Set dst (XorL src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag); + flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable); format %{ "exorq $dst, $src1, $src2\t# long ndd" %} ins_encode %{ @@ -14113,7 +14173,7 @@ instruct xorL_rReg_rReg_mem_ndd(rRegL dst, rRegL src1, memory src2, rFlagsReg cr predicate(UseAPX); match(Set dst (XorL src1 (LoadL src2))); effect(KILL cr); - flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag); + flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable_commutative); ins_cost(150); format %{ "exorq $dst, $src1, $src2\t# long ndd" %} @@ -16568,6 +16628,7 @@ instruct minI_rReg_ndd(rRegI dst, rRegI src1, rRegI src2) predicate(UseAPX); match(Set dst (MinI src1 src2)); effect(DEF dst, USE src1, USE src2); + flag(PD::Flag_ndd_demotable); ins_cost(200); expand %{ @@ -16619,6 +16680,7 @@ instruct maxI_rReg_ndd(rRegI dst, rRegI src1, rRegI src2) predicate(UseAPX); match(Set dst (MaxI src1 src2)); effect(DEF dst, USE src1, USE src2); + flag(PD::Flag_ndd_demotable); ins_cost(200); expand %{ diff --git a/src/hotspot/os/aix/os_aix.cpp b/src/hotspot/os/aix/os_aix.cpp index 5f81912c0d6..48bd5e05816 100644 --- a/src/hotspot/os/aix/os_aix.cpp +++ b/src/hotspot/os/aix/os_aix.cpp @@ -1747,6 +1747,9 @@ size_t os::pd_pretouch_memory(void* first, void* last, size_t page_size) { return page_size; } +void os::numa_set_thread_affinity(Thread *thread, int node) { +} + void os::numa_make_global(char *addr, size_t bytes) { } diff --git a/src/hotspot/os/bsd/os_bsd.cpp b/src/hotspot/os/bsd/os_bsd.cpp index 3e5fa8b84e1..0b37cb100f6 100644 --- a/src/hotspot/os/bsd/os_bsd.cpp +++ b/src/hotspot/os/bsd/os_bsd.cpp @@ -1581,6 +1581,9 @@ size_t os::pd_pretouch_memory(void* first, void* last, size_t page_size) { return page_size; } +void os::numa_set_thread_affinity(Thread *thread, int node) { +} + void os::numa_make_global(char *addr, size_t bytes) { } diff --git a/src/hotspot/os/linux/cgroupV1Subsystem_linux.hpp b/src/hotspot/os/linux/cgroupV1Subsystem_linux.hpp index 8aeb64ef18c..f556bc57f26 100644 --- a/src/hotspot/os/linux/cgroupV1Subsystem_linux.hpp +++ b/src/hotspot/os/linux/cgroupV1Subsystem_linux.hpp @@ -209,14 +209,14 @@ class CgroupV1Subsystem: public CgroupSubsystem { bool pids_max(uint64_t& result) override; bool pids_current(uint64_t& result) override; - bool is_containerized(); + bool is_containerized() override; - const char * container_type() { + const char * container_type() override { return "cgroupv1"; } - CachingCgroupController* memory_controller() { return _memory; } - CachingCgroupController* cpu_controller() { return _cpu; } - CgroupCpuacctController* cpuacct_controller() { return _cpuacct; } + CachingCgroupController* memory_controller() override { return _memory; } + CachingCgroupController* cpu_controller() override { return _cpu; } + CgroupCpuacctController* cpuacct_controller() override { return _cpuacct; } private: /* controllers */ diff --git a/src/hotspot/os/linux/os_linux.cpp b/src/hotspot/os/linux/os_linux.cpp index a345663dd5b..a1d957eb77d 100644 --- a/src/hotspot/os/linux/os_linux.cpp +++ b/src/hotspot/os/linux/os_linux.cpp @@ -159,9 +159,7 @@ physical_memory_size_type os::Linux::_physical_memory = 0; address os::Linux::_initial_thread_stack_bottom = nullptr; uintptr_t os::Linux::_initial_thread_stack_size = 0; -int (*os::Linux::_pthread_getcpuclockid)(pthread_t, clockid_t *) = nullptr; pthread_t os::Linux::_main_thread; -bool os::Linux::_supports_fast_thread_cpu_time = false; const char * os::Linux::_libc_version = nullptr; const char * os::Linux::_libpthread_version = nullptr; @@ -1475,29 +1473,6 @@ void os::Linux::capture_initial_stack(size_t max_size) { //////////////////////////////////////////////////////////////////////////////// // time support -void os::Linux::fast_thread_clock_init() { - clockid_t clockid; - struct timespec tp; - int (*pthread_getcpuclockid_func)(pthread_t, clockid_t *) = - (int(*)(pthread_t, clockid_t *)) dlsym(RTLD_DEFAULT, "pthread_getcpuclockid"); - - // Switch to using fast clocks for thread cpu time if - // the clock_getres() returns 0 error code. - // Note, that some kernels may support the current thread - // clock (CLOCK_THREAD_CPUTIME_ID) but not the clocks - // returned by the pthread_getcpuclockid(). - // If the fast POSIX clocks are supported then the clock_getres() - // must return at least tp.tv_sec == 0 which means a resolution - // better than 1 sec. This is extra check for reliability. - - if (pthread_getcpuclockid_func && - pthread_getcpuclockid_func(_main_thread, &clockid) == 0 && - clock_getres(clockid, &tp) == 0 && tp.tv_sec == 0) { - _supports_fast_thread_cpu_time = true; - _pthread_getcpuclockid = pthread_getcpuclockid_func; - } -} - // thread_id is kernel thread id (similar to Solaris LWP id) intx os::current_thread_id() { return os::Linux::gettid(); } int os::current_process_id() { @@ -1770,7 +1745,9 @@ void * os::dll_load(const char *filename, char *ebuf, int ebuflen) { {EM_LOONGARCH, EM_LOONGARCH, ELFCLASS64, ELFDATA2LSB, (char*)"LoongArch"}, }; -#if (defined AMD64) +#if (defined IA32) + static Elf32_Half running_arch_code=EM_386; +#elif (defined AMD64) || (defined X32) static Elf32_Half running_arch_code=EM_X86_64; #elif (defined __sparc) && (defined _LP64) static Elf32_Half running_arch_code=EM_SPARCV9; @@ -1804,7 +1781,7 @@ void * os::dll_load(const char *filename, char *ebuf, int ebuflen) { static Elf32_Half running_arch_code=EM_LOONGARCH; #else #error Method os::dll_load requires that one of following is defined:\ - AARCH64, ALPHA, ARM, AMD64, LOONGARCH64, M68K, MIPS, MIPSEL, PARISC, __powerpc__, __powerpc64__, RISCV, S390, SH, __sparc + AARCH64, ALPHA, ARM, AMD64, IA32, LOONGARCH64, M68K, MIPS, MIPSEL, PARISC, __powerpc__, __powerpc64__, RISCV, S390, SH, __sparc #endif // Identify compatibility class for VM's architecture and library's architecture @@ -1866,6 +1843,7 @@ void * os::dll_load(const char *filename, char *ebuf, int ebuflen) { } void * os::Linux::dlopen_helper(const char *filename, char *ebuf, int ebuflen) { +#ifndef IA32 bool ieee_handling = IEEE_subnormal_handling_OK(); if (!ieee_handling) { Events::log_dll_message(nullptr, "IEEE subnormal handling check failed before loading %s", filename); @@ -1888,9 +1866,14 @@ void * os::Linux::dlopen_helper(const char *filename, char *ebuf, int ebuflen) { // numerical "accuracy", but we need to protect Java semantics first // and foremost. See JDK-8295159. + // This workaround is ineffective on IA32 systems because the MXCSR + // register (which controls flush-to-zero mode) is not stored in the + // legacy fenv. + fenv_t default_fenv; int rtn = fegetenv(&default_fenv); assert(rtn == 0, "fegetenv must succeed"); +#endif // IA32 void* result; JFR_ONLY(NativeLibraryLoadEvent load_event(filename, &result);) @@ -1910,6 +1893,7 @@ void * os::Linux::dlopen_helper(const char *filename, char *ebuf, int ebuflen) { } else { Events::log_dll_message(nullptr, "Loaded shared library %s", filename); log_info(os)("shared library load of %s was successful", filename); +#ifndef IA32 // Quickly test to make sure subnormals are correctly handled. if (! IEEE_subnormal_handling_OK()) { // We just dlopen()ed a library that mangled the floating-point flags. @@ -1935,6 +1919,7 @@ void * os::Linux::dlopen_helper(const char *filename, char *ebuf, int ebuflen) { assert(false, "fesetenv didn't work"); } } +#endif // IA32 } return result; } @@ -2433,6 +2418,7 @@ void os::Linux::print_uptime_info(outputStream* st) { if (ret == 0) { os::print_dhm(st, "OS uptime:", (long) sinfo.uptime); } + assert(ret == 0, "sysinfo failed: %s", os::strerror(errno)); } bool os::Linux::print_container_info(outputStream* st) { @@ -2597,7 +2583,8 @@ void os::print_memory_info(outputStream* st) { // values in struct sysinfo are "unsigned long" struct sysinfo si; - sysinfo(&si); + int ret = sysinfo(&si); + assert(ret == 0, "sysinfo failed: %s", os::strerror(errno)); physical_memory_size_type phys_mem = physical_memory(); st->print(", physical " PHYS_MEM_TYPE_FORMAT "k", phys_mem >> 10); @@ -2605,10 +2592,12 @@ void os::print_memory_info(outputStream* st) { (void)os::available_memory(avail_mem); st->print("(" PHYS_MEM_TYPE_FORMAT "k free)", avail_mem >> 10); - st->print(", swap " UINT64_FORMAT "k", - ((jlong)si.totalswap * si.mem_unit) >> 10); - st->print("(" UINT64_FORMAT "k free)", - ((jlong)si.freeswap * si.mem_unit) >> 10); + if (ret == 0) { + st->print(", swap " UINT64_FORMAT "k", + ((jlong)si.totalswap * si.mem_unit) >> 10); + st->print("(" UINT64_FORMAT "k free)", + ((jlong)si.freeswap * si.mem_unit) >> 10); + } st->cr(); st->print("Page Sizes: "); _page_sizes.print_on(st); @@ -2991,6 +2980,10 @@ size_t os::pd_pretouch_memory(void* first, void* last, size_t page_size) { return page_size; } +void os::numa_set_thread_affinity(Thread* thread, int node) { + Linux::numa_set_thread_affinity(thread->osthread()->thread_id(), node); +} + void os::numa_make_global(char *addr, size_t bytes) { Linux::numa_interleave_memory(addr, bytes); } @@ -3173,6 +3166,8 @@ bool os::Linux::libnuma_init() { libnuma_dlsym(handle, "numa_set_bind_policy"))); set_numa_bitmask_isbitset(CAST_TO_FN_PTR(numa_bitmask_isbitset_func_t, libnuma_dlsym(handle, "numa_bitmask_isbitset"))); + set_numa_bitmask_clearbit(CAST_TO_FN_PTR(numa_bitmask_clearbit_func_t, + libnuma_dlsym(handle, "numa_bitmask_clearbit"))); set_numa_bitmask_equal(CAST_TO_FN_PTR(numa_bitmask_equal_func_t, libnuma_dlsym(handle, "numa_bitmask_equal"))); set_numa_distance(CAST_TO_FN_PTR(numa_distance_func_t, @@ -3187,20 +3182,32 @@ bool os::Linux::libnuma_init() { libnuma_dlsym(handle, "numa_set_preferred"))); set_numa_get_run_node_mask(CAST_TO_FN_PTR(numa_get_run_node_mask_func_t, libnuma_v2_dlsym(handle, "numa_get_run_node_mask"))); + set_numa_sched_setaffinity(CAST_TO_FN_PTR(numa_sched_setaffinity_func_t, + libnuma_v2_dlsym(handle, "numa_sched_setaffinity"))); + set_numa_allocate_cpumask(CAST_TO_FN_PTR(numa_allocate_cpumask_func_t, + libnuma_v2_dlsym(handle, "numa_allocate_cpumask"))); if (numa_available() != -1) { set_numa_all_nodes((unsigned long*)libnuma_dlsym(handle, "numa_all_nodes")); set_numa_all_nodes_ptr((struct bitmask **)libnuma_dlsym(handle, "numa_all_nodes_ptr")); set_numa_nodes_ptr((struct bitmask **)libnuma_dlsym(handle, "numa_nodes_ptr")); + set_numa_all_cpus_ptr((struct bitmask **)libnuma_dlsym(handle, "numa_all_cpus_ptr")); set_numa_interleave_bitmask(_numa_get_interleave_mask()); set_numa_membind_bitmask(_numa_get_membind()); set_numa_cpunodebind_bitmask(_numa_get_run_node_mask()); + // Create an index -> node mapping, since nodes are not always consecutive _nindex_to_node = new (mtInternal) GrowableArray(0, mtInternal); rebuild_nindex_to_node_map(); + // Create a cpu -> node mapping _cpu_to_node = new (mtInternal) GrowableArray(0, mtInternal); rebuild_cpu_to_node_map(); + + // Create a node -> CPUs mapping + _numa_affinity_masks = new (mtInternal) GrowableArray(0, mtInternal); + build_numa_affinity_masks(); + return true; } } @@ -3236,6 +3243,42 @@ size_t os::Linux::default_guard_size(os::ThreadType thr_type) { return ((thr_type == java_thread || thr_type == compiler_thread) ? 0 : os::vm_page_size()); } +void os::Linux::build_numa_affinity_masks() { + // We only build the affinity masks if running libnuma v2 (_numa_node_to_cpus_v2 + // is available) and we have the affinity mask of the process when it started. + if (_numa_node_to_cpus_v2 == nullptr || _numa_all_cpus_ptr == nullptr) { + return; + } + + // It's important that we respect any user configuration by removing the + // CPUs we're not allowed to run on from the affinity mask. For example, + // if the user runs the JVM with "numactl -C 0-1,4-5" on a machine with + // the following NUMA setup: + // NUMA 0: CPUs 0-3, NUMA 1: CPUs 4-7 + // We expect to get the following affinity masks: + // Affinity masks: idx 0 = (0, 1), idx 1 = (4, 5) + + const int num_nodes = get_existing_num_nodes(); + const unsigned num_cpus = (unsigned)os::processor_count(); + + for (int i = 0; i < num_nodes; i++) { + struct bitmask* affinity_mask = _numa_allocate_cpumask(); + + // Fill the affinity mask with all CPUs belonging to NUMA node i + _numa_node_to_cpus_v2(i, affinity_mask); + + // Clear the bits of all CPUs that the process is not allowed to + // execute tasks on + for (unsigned j = 0; j < num_cpus; j++) { + if (!_numa_bitmask_isbitset(_numa_all_cpus_ptr, j)) { + _numa_bitmask_clearbit(affinity_mask, j); + } + } + + _numa_affinity_masks->push(affinity_mask); + } +} + void os::Linux::rebuild_nindex_to_node_map() { int highest_node_number = Linux::numa_max_node(); @@ -3351,6 +3394,25 @@ int os::Linux::numa_node_to_cpus(int node, unsigned long *buffer, int bufferlen) return -1; } +void os::Linux::numa_set_thread_affinity(pid_t tid, int node) { + // We only set affinity if running libnuma v2 (_numa_sched_setaffinity + // is available) and we have all affinity mask + if (_numa_sched_setaffinity == nullptr || + _numa_all_cpus_ptr == nullptr || + _numa_affinity_masks->is_empty()) { + return; + } + + if (node == -1) { + // If the node is -1, the affinity is reverted to the original affinity + // of the thread when the VM was started + _numa_sched_setaffinity(tid, _numa_all_cpus_ptr); + } else { + // Normal case, set the affinity to the corresponding affinity mask + _numa_sched_setaffinity(tid, _numa_affinity_masks->at(node)); + } +} + int os::Linux::get_node_by_cpu(int cpu_id) { if (cpu_to_node() != nullptr && cpu_id >= 0 && cpu_id < cpu_to_node()->length()) { return cpu_to_node()->at(cpu_id); @@ -3360,6 +3422,7 @@ int os::Linux::get_node_by_cpu(int cpu_id) { GrowableArray* os::Linux::_cpu_to_node; GrowableArray* os::Linux::_nindex_to_node; +GrowableArray* os::Linux::_numa_affinity_masks; os::Linux::sched_getcpu_func_t os::Linux::_sched_getcpu; os::Linux::numa_node_to_cpus_func_t os::Linux::_numa_node_to_cpus; os::Linux::numa_node_to_cpus_v2_func_t os::Linux::_numa_node_to_cpus_v2; @@ -3371,17 +3434,21 @@ os::Linux::numa_interleave_memory_func_t os::Linux::_numa_interleave_memory; os::Linux::numa_interleave_memory_v2_func_t os::Linux::_numa_interleave_memory_v2; os::Linux::numa_set_bind_policy_func_t os::Linux::_numa_set_bind_policy; os::Linux::numa_bitmask_isbitset_func_t os::Linux::_numa_bitmask_isbitset; +os::Linux::numa_bitmask_clearbit_func_t os::Linux::_numa_bitmask_clearbit; os::Linux::numa_bitmask_equal_func_t os::Linux::_numa_bitmask_equal; os::Linux::numa_distance_func_t os::Linux::_numa_distance; os::Linux::numa_get_membind_func_t os::Linux::_numa_get_membind; os::Linux::numa_get_interleave_mask_func_t os::Linux::_numa_get_interleave_mask; os::Linux::numa_get_run_node_mask_func_t os::Linux::_numa_get_run_node_mask; +os::Linux::numa_sched_setaffinity_func_t os::Linux::_numa_sched_setaffinity; +os::Linux::numa_allocate_cpumask_func_t os::Linux::_numa_allocate_cpumask; os::Linux::numa_move_pages_func_t os::Linux::_numa_move_pages; os::Linux::numa_set_preferred_func_t os::Linux::_numa_set_preferred; os::Linux::NumaAllocationPolicy os::Linux::_current_numa_policy; unsigned long* os::Linux::_numa_all_nodes; struct bitmask* os::Linux::_numa_all_nodes_ptr; struct bitmask* os::Linux::_numa_nodes_ptr; +struct bitmask* os::Linux::_numa_all_cpus_ptr; struct bitmask* os::Linux::_numa_interleave_bitmask; struct bitmask* os::Linux::_numa_membind_bitmask; struct bitmask* os::Linux::_numa_cpunodebind_bitmask; @@ -4236,7 +4303,7 @@ OSReturn os::get_native_priority(const Thread* const thread, // For reference, please, see IEEE Std 1003.1-2004: // http://www.unix.org/single_unix_specification -jlong os::Linux::fast_thread_cpu_time(clockid_t clockid) { +jlong os::Linux::total_thread_cpu_time(clockid_t clockid) { struct timespec tp; int status = clock_gettime(clockid, &tp); assert(status == 0, "clock_gettime error: %s", os::strerror(errno)); @@ -4464,8 +4531,6 @@ jint os::init_2(void) { os::Posix::init_2(); - Linux::fast_thread_clock_init(); - if (PosixSignals::init() == JNI_ERR) { return JNI_ERR; } @@ -4893,14 +4958,14 @@ int os::open(const char *path, int oflag, int mode) { return fd; } -static jlong slow_thread_cpu_time(Thread *thread, bool user_sys_cpu_time); +static jlong user_thread_cpu_time(Thread *thread); -static jlong fast_cpu_time(Thread *thread) { +static jlong total_thread_cpu_time(Thread *thread) { clockid_t clockid; - int rc = os::Linux::pthread_getcpuclockid(thread->osthread()->pthread_id(), + int rc = pthread_getcpuclockid(thread->osthread()->pthread_id(), &clockid); if (rc == 0) { - return os::Linux::fast_thread_cpu_time(clockid); + return os::Linux::total_thread_cpu_time(clockid); } else { // It's possible to encounter a terminated native thread that failed // to detach itself from the VM - which should result in ESRCH. @@ -4917,41 +4982,31 @@ static jlong fast_cpu_time(Thread *thread) { // the fast estimate available on the platform. jlong os::current_thread_cpu_time() { - if (os::Linux::supports_fast_thread_cpu_time()) { - return os::Linux::fast_thread_cpu_time(CLOCK_THREAD_CPUTIME_ID); - } else { - // return user + sys since the cost is the same - return slow_thread_cpu_time(Thread::current(), true /* user + sys */); - } + return os::Linux::total_thread_cpu_time(CLOCK_THREAD_CPUTIME_ID); } jlong os::thread_cpu_time(Thread* thread) { - // consistent with what current_thread_cpu_time() returns - if (os::Linux::supports_fast_thread_cpu_time()) { - return fast_cpu_time(thread); - } else { - return slow_thread_cpu_time(thread, true /* user + sys */); - } + return total_thread_cpu_time(thread); } jlong os::current_thread_cpu_time(bool user_sys_cpu_time) { - if (user_sys_cpu_time && os::Linux::supports_fast_thread_cpu_time()) { - return os::Linux::fast_thread_cpu_time(CLOCK_THREAD_CPUTIME_ID); + if (user_sys_cpu_time) { + return os::Linux::total_thread_cpu_time(CLOCK_THREAD_CPUTIME_ID); } else { - return slow_thread_cpu_time(Thread::current(), user_sys_cpu_time); + return user_thread_cpu_time(Thread::current()); } } jlong os::thread_cpu_time(Thread *thread, bool user_sys_cpu_time) { - if (user_sys_cpu_time && os::Linux::supports_fast_thread_cpu_time()) { - return fast_cpu_time(thread); + if (user_sys_cpu_time) { + return total_thread_cpu_time(thread); } else { - return slow_thread_cpu_time(thread, user_sys_cpu_time); + return user_thread_cpu_time(thread); } } // -1 on error. -static jlong slow_thread_cpu_time(Thread *thread, bool user_sys_cpu_time) { +static jlong user_thread_cpu_time(Thread *thread) { pid_t tid = thread->osthread()->thread_id(); char *s; char stat[2048]; @@ -4988,11 +5043,8 @@ static jlong slow_thread_cpu_time(Thread *thread, bool user_sys_cpu_time) { &ldummy, &ldummy, &ldummy, &ldummy, &ldummy, &user_time, &sys_time); if (count != 13) return -1; - if (user_sys_cpu_time) { - return ((jlong)sys_time + (jlong)user_time) * (1000000000 / os::Posix::clock_tics_per_second()); - } else { - return (jlong)user_time * (1000000000 / os::Posix::clock_tics_per_second()); - } + + return (jlong)user_time * (1000000000 / os::Posix::clock_tics_per_second()); } void os::current_thread_cpu_time_info(jvmtiTimerInfo *info_ptr) { @@ -5071,7 +5123,7 @@ int os::get_core_path(char* buffer, size_t bufferSize) { if (core_pattern[0] == '|') { written = jio_snprintf(buffer, bufferSize, - "\"%s\" (or dumping to %s/core.%d)", + "\"%s\" (alternatively, falling back to %s/core.%d)", &core_pattern[1], p, current_process_id()); } else if (pid_pos != nullptr) { *pid_pos = '\0'; diff --git a/src/hotspot/os/linux/os_linux.hpp b/src/hotspot/os/linux/os_linux.hpp index df96a17d8e9..dd07cb600b9 100644 --- a/src/hotspot/os/linux/os_linux.hpp +++ b/src/hotspot/os/linux/os_linux.hpp @@ -32,19 +32,19 @@ class os::Linux { friend class os; - static int (*_pthread_getcpuclockid)(pthread_t, clockid_t *); - static address _initial_thread_stack_bottom; static uintptr_t _initial_thread_stack_size; static const char *_libc_version; static const char *_libpthread_version; - static bool _supports_fast_thread_cpu_time; - static GrowableArray* _cpu_to_node; static GrowableArray* _nindex_to_node; + static GrowableArray* _numa_affinity_masks; + + static void build_numa_affinity_masks(); + protected: static physical_memory_size_type _physical_memory; @@ -142,18 +142,7 @@ class os::Linux { static bool manually_expand_stack(JavaThread * t, address addr); static void expand_stack_to(address bottom); - // fast POSIX clocks support - static void fast_thread_clock_init(void); - - static int pthread_getcpuclockid(pthread_t tid, clockid_t *clock_id) { - return _pthread_getcpuclockid ? _pthread_getcpuclockid(tid, clock_id) : -1; - } - - static bool supports_fast_thread_cpu_time() { - return _supports_fast_thread_cpu_time; - } - - static jlong fast_thread_cpu_time(clockid_t clockid); + static jlong total_thread_cpu_time(clockid_t clockid); static jlong sendfile(int out_fd, int in_fd, jlong* offset, jlong count); @@ -230,8 +219,11 @@ class os::Linux { typedef void (*numa_set_preferred_func_t)(int node); typedef void (*numa_set_bind_policy_func_t)(int policy); typedef int (*numa_bitmask_isbitset_func_t)(struct bitmask *bmp, unsigned int n); + typedef int (*numa_bitmask_clearbit_func_t)(struct bitmask *bmp, unsigned int n); typedef int (*numa_bitmask_equal_func_t)(struct bitmask *bmp1, struct bitmask *bmp2); typedef int (*numa_distance_func_t)(int node1, int node2); + typedef int (*numa_sched_setaffinity_func_t)(pid_t pid, struct bitmask* mask); + typedef struct bitmask* (*numa_allocate_cpumask_func_t)(void); static sched_getcpu_func_t _sched_getcpu; static numa_node_to_cpus_func_t _numa_node_to_cpus; @@ -244,6 +236,7 @@ class os::Linux { static numa_interleave_memory_v2_func_t _numa_interleave_memory_v2; static numa_set_bind_policy_func_t _numa_set_bind_policy; static numa_bitmask_isbitset_func_t _numa_bitmask_isbitset; + static numa_bitmask_clearbit_func_t _numa_bitmask_clearbit; static numa_bitmask_equal_func_t _numa_bitmask_equal; static numa_distance_func_t _numa_distance; static numa_get_membind_func_t _numa_get_membind; @@ -251,9 +244,12 @@ class os::Linux { static numa_get_interleave_mask_func_t _numa_get_interleave_mask; static numa_move_pages_func_t _numa_move_pages; static numa_set_preferred_func_t _numa_set_preferred; + static numa_sched_setaffinity_func_t _numa_sched_setaffinity; + static numa_allocate_cpumask_func_t _numa_allocate_cpumask; static unsigned long* _numa_all_nodes; static struct bitmask* _numa_all_nodes_ptr; static struct bitmask* _numa_nodes_ptr; + static struct bitmask* _numa_all_cpus_ptr; static struct bitmask* _numa_interleave_bitmask; static struct bitmask* _numa_membind_bitmask; static struct bitmask* _numa_cpunodebind_bitmask; @@ -269,6 +265,7 @@ class os::Linux { static void set_numa_interleave_memory_v2(numa_interleave_memory_v2_func_t func) { _numa_interleave_memory_v2 = func; } static void set_numa_set_bind_policy(numa_set_bind_policy_func_t func) { _numa_set_bind_policy = func; } static void set_numa_bitmask_isbitset(numa_bitmask_isbitset_func_t func) { _numa_bitmask_isbitset = func; } + static void set_numa_bitmask_clearbit(numa_bitmask_clearbit_func_t func) { _numa_bitmask_clearbit = func; } static void set_numa_bitmask_equal(numa_bitmask_equal_func_t func) { _numa_bitmask_equal = func; } static void set_numa_distance(numa_distance_func_t func) { _numa_distance = func; } static void set_numa_get_membind(numa_get_membind_func_t func) { _numa_get_membind = func; } @@ -279,9 +276,12 @@ class os::Linux { static void set_numa_all_nodes(unsigned long* ptr) { _numa_all_nodes = ptr; } static void set_numa_all_nodes_ptr(struct bitmask **ptr) { _numa_all_nodes_ptr = (ptr == nullptr ? nullptr : *ptr); } static void set_numa_nodes_ptr(struct bitmask **ptr) { _numa_nodes_ptr = (ptr == nullptr ? nullptr : *ptr); } + static void set_numa_all_cpus_ptr(struct bitmask **ptr) { _numa_all_cpus_ptr = (ptr == nullptr ? nullptr : *ptr); } static void set_numa_interleave_bitmask(struct bitmask* ptr) { _numa_interleave_bitmask = ptr ; } static void set_numa_membind_bitmask(struct bitmask* ptr) { _numa_membind_bitmask = ptr ; } static void set_numa_cpunodebind_bitmask(struct bitmask* ptr) { _numa_cpunodebind_bitmask = ptr ; } + static void set_numa_sched_setaffinity(numa_sched_setaffinity_func_t func) { _numa_sched_setaffinity = func; } + static void set_numa_allocate_cpumask(numa_allocate_cpumask_func_t func) { _numa_allocate_cpumask = func; } static int sched_getcpu_syscall(void); enum NumaAllocationPolicy{ @@ -292,6 +292,8 @@ class os::Linux { static NumaAllocationPolicy _current_numa_policy; public: + static void numa_set_thread_affinity(pid_t tid, int node); + static int sched_getcpu() { return _sched_getcpu != nullptr ? _sched_getcpu() : -1; } static int numa_node_to_cpus(int node, unsigned long *buffer, int bufferlen); static int numa_max_node() { return _numa_max_node != nullptr ? _numa_max_node() : -1; } diff --git a/src/hotspot/os/posix/os_posix.cpp b/src/hotspot/os/posix/os_posix.cpp index 1a04cbba0de..8f1f07dd055 100644 --- a/src/hotspot/os/posix/os_posix.cpp +++ b/src/hotspot/os/posix/os_posix.cpp @@ -108,41 +108,60 @@ size_t os::_os_min_stack_allowed = PTHREAD_STACK_MIN; // Check core dump limit and report possible place where core can be found void os::check_core_dump_prerequisites(char* buffer, size_t bufferSize, bool check_only) { + stringStream buf(buffer, bufferSize); if (!FLAG_IS_DEFAULT(CreateCoredumpOnCrash) && !CreateCoredumpOnCrash) { - jio_snprintf(buffer, bufferSize, "CreateCoredumpOnCrash is disabled from command line"); - VMError::record_coredump_status(buffer, false); + buf.print("CreateCoredumpOnCrash is disabled from command line"); + VMError::record_coredump_status(buf.freeze(), false); } else { struct rlimit rlim; bool success = true; bool warn = true; char core_path[PATH_MAX]; if (get_core_path(core_path, PATH_MAX) <= 0) { - jio_snprintf(buffer, bufferSize, "core.%d (may not exist)", current_process_id()); + // In the warning message, let the user know. + if (check_only) { + buf.print("the core path couldn't be determined. It commonly defaults to "); + } + buf.print("core.%d%s", current_process_id(), check_only ? "" : " (may not exist)"); #ifdef LINUX } else if (core_path[0] == '"') { // redirect to user process - jio_snprintf(buffer, bufferSize, "Core dumps may be processed with %s", core_path); + if (check_only) { + buf.print("core dumps may be further processed by the following: "); + } else { + buf.print("Determined by the following: "); + } + buf.print("%s", core_path); #endif } else if (getrlimit(RLIMIT_CORE, &rlim) != 0) { - jio_snprintf(buffer, bufferSize, "%s (may not exist)", core_path); + if (check_only) { + buf.print("the rlimit couldn't be determined. If resource limits permit, the core dump will be located at "); + } + buf.print("%s%s", core_path, check_only ? "" : " (may not exist)"); } else { switch(rlim.rlim_cur) { case RLIM_INFINITY: - jio_snprintf(buffer, bufferSize, "%s", core_path); + buf.print("%s", core_path); warn = false; break; case 0: - jio_snprintf(buffer, bufferSize, "Core dumps have been disabled. To enable core dumping, try \"ulimit -c unlimited\" before starting Java again"); + buf.print("%s dumps have been disabled. To enable core dumping, try \"ulimit -c unlimited\" before starting Java again", check_only ? "core" : "Core"); success = false; break; default: - jio_snprintf(buffer, bufferSize, "%s (max size " UINT64_FORMAT " k). To ensure a full core dump, try \"ulimit -c unlimited\" before starting Java again", core_path, uint64_t(rlim.rlim_cur) / K); + if (check_only) { + buf.print("core dumps are constrained "); + } else { + buf.print( "%s ", core_path); + } + buf.print( "(max size " UINT64_FORMAT " k). To ensure a full core dump, try \"ulimit -c unlimited\" before starting Java again", uint64_t(rlim.rlim_cur) / K); break; } } + const char* result = buf.freeze(); if (!check_only) { - VMError::record_coredump_status(buffer, success); + VMError::record_coredump_status(result, success); } else if (warn) { - warning("CreateCoredumpOnCrash specified, but %s", buffer); + warning("CreateCoredumpOnCrash specified, but %s", result); } } } diff --git a/src/hotspot/os/posix/signals_posix.cpp b/src/hotspot/os/posix/signals_posix.cpp index 5833e324070..625eb63445a 100644 --- a/src/hotspot/os/posix/signals_posix.cpp +++ b/src/hotspot/os/posix/signals_posix.cpp @@ -621,7 +621,7 @@ int JVM_HANDLE_XXX_SIGNAL(int sig, siginfo_t* info, if (cb != nullptr && cb->is_nmethod()) { nmethod* nm = cb->as_nmethod(); assert(nm->insts_contains_inclusive(pc), ""); - address deopt = nm->deopt_handler_begin(); + address deopt = nm->deopt_handler_entry(); assert(deopt != nullptr, ""); frame fr = os::fetch_frame_from_context(uc); diff --git a/src/hotspot/os/windows/os_windows.cpp b/src/hotspot/os/windows/os_windows.cpp index ce2baeaf46c..8a450a291d3 100644 --- a/src/hotspot/os/windows/os_windows.cpp +++ b/src/hotspot/os/windows/os_windows.cpp @@ -2795,7 +2795,7 @@ LONG WINAPI topLevelExceptionFilter(struct _EXCEPTION_POINTERS* exceptionInfo) { if (cb != nullptr && cb->is_nmethod()) { nmethod* nm = cb->as_nmethod(); frame fr = os::fetch_frame_from_context((void*)exceptionInfo->ContextRecord); - address deopt = nm->deopt_handler_begin(); + address deopt = nm->deopt_handler_entry(); assert(nm->insts_contains_inclusive(pc), ""); nm->set_original_pc(&fr, pc); // Set pc to handler @@ -3752,6 +3752,7 @@ size_t os::pd_pretouch_memory(void* first, void* last, size_t page_size) { return page_size; } +void os::numa_set_thread_affinity(Thread *thread, int node) { } void os::numa_make_global(char *addr, size_t bytes) { } void os::numa_make_local(char *addr, size_t bytes, int lgrp_hint) { } size_t os::numa_get_groups_num() { return MAX2(numa_node_list_holder.get_count(), 1); } diff --git a/src/hotspot/os_cpu/bsd_aarch64/atomicAccess_bsd_aarch64.hpp b/src/hotspot/os_cpu/bsd_aarch64/atomicAccess_bsd_aarch64.hpp index 3d2c632ace8..67701775f94 100644 --- a/src/hotspot/os_cpu/bsd_aarch64/atomicAccess_bsd_aarch64.hpp +++ b/src/hotspot/os_cpu/bsd_aarch64/atomicAccess_bsd_aarch64.hpp @@ -52,12 +52,16 @@ struct AtomicAccess::PlatformAdd { } }; +template<> +struct AtomicAccess::PlatformXchg<1> : AtomicAccess::XchgUsingCmpxchg<1> {}; + template template inline T AtomicAccess::PlatformXchg::operator()(T volatile* dest, T exchange_value, atomic_memory_order order) const { STATIC_ASSERT(byte_size == sizeof(T)); + STATIC_ASSERT(byte_size == 4 || byte_size == 8); T res = __atomic_exchange_n(dest, exchange_value, __ATOMIC_RELEASE); FULL_MEM_BARRIER; return res; diff --git a/src/hotspot/os_cpu/bsd_x86/atomicAccess_bsd_x86.hpp b/src/hotspot/os_cpu/bsd_x86/atomicAccess_bsd_x86.hpp index 1024c6b1418..29471300f3d 100644 --- a/src/hotspot/os_cpu/bsd_x86/atomicAccess_bsd_x86.hpp +++ b/src/hotspot/os_cpu/bsd_x86/atomicAccess_bsd_x86.hpp @@ -52,6 +52,9 @@ inline D AtomicAccess::PlatformAdd<4>::fetch_then_add(D volatile* dest, I add_va return old_value; } +template<> +struct AtomicAccess::PlatformXchg<1> : AtomicAccess::XchgUsingCmpxchg<1> {}; + template<> template inline T AtomicAccess::PlatformXchg<4>::operator()(T volatile* dest, diff --git a/src/hotspot/os_cpu/bsd_zero/atomicAccess_bsd_zero.hpp b/src/hotspot/os_cpu/bsd_zero/atomicAccess_bsd_zero.hpp index 6a720dac54e..6c8684718fc 100644 --- a/src/hotspot/os_cpu/bsd_zero/atomicAccess_bsd_zero.hpp +++ b/src/hotspot/os_cpu/bsd_zero/atomicAccess_bsd_zero.hpp @@ -66,6 +66,9 @@ inline D AtomicAccess::PlatformAdd<8>::add_then_fetch(D volatile* dest, I add_va return res; } +template<> +struct AtomicAccess::PlatformXchg<1> : AtomicAccess::XchgUsingCmpxchg<1> {}; + template<> template inline T AtomicAccess::PlatformXchg<4>::operator()(T volatile* dest, diff --git a/src/hotspot/os_cpu/linux_aarch64/atomicAccess_linux_aarch64.hpp b/src/hotspot/os_cpu/linux_aarch64/atomicAccess_linux_aarch64.hpp index 6e5f53edfa3..4ddb2b758b4 100644 --- a/src/hotspot/os_cpu/linux_aarch64/atomicAccess_linux_aarch64.hpp +++ b/src/hotspot/os_cpu/linux_aarch64/atomicAccess_linux_aarch64.hpp @@ -113,6 +113,9 @@ inline D AtomicAccess::PlatformAdd<8>::fetch_then_add(D volatile* dest, I add_va return atomic_fastcall(stub, dest, add_value); } +template<> +struct AtomicAccess::PlatformXchg<1> : AtomicAccess::XchgUsingCmpxchg<1> {}; + template<> template inline T AtomicAccess::PlatformXchg<4>::operator()(T volatile* dest, diff --git a/src/hotspot/os_cpu/linux_arm/atomicAccess_linux_arm.hpp b/src/hotspot/os_cpu/linux_arm/atomicAccess_linux_arm.hpp index 5b5f9da51a6..390207f9e5e 100644 --- a/src/hotspot/os_cpu/linux_arm/atomicAccess_linux_arm.hpp +++ b/src/hotspot/os_cpu/linux_arm/atomicAccess_linux_arm.hpp @@ -118,6 +118,8 @@ inline D AtomicAccess::PlatformAdd<4>::add_then_fetch(D volatile* dest, I add_va return add_using_helper(ARMAtomicFuncs::_add_func, dest, add_value); } +template<> +struct AtomicAccess::PlatformXchg<1> : AtomicAccess::XchgUsingCmpxchg<1> {}; template<> template diff --git a/src/hotspot/os_cpu/linux_arm/macroAssembler_linux_arm_32.cpp b/src/hotspot/os_cpu/linux_arm/macroAssembler_linux_arm_32.cpp index e74daaa6d66..e4737191cfc 100644 --- a/src/hotspot/os_cpu/linux_arm/macroAssembler_linux_arm_32.cpp +++ b/src/hotspot/os_cpu/linux_arm/macroAssembler_linux_arm_32.cpp @@ -246,9 +246,9 @@ void MacroAssembler::atomic_cas64(Register memval_lo, Register memval_hi, Regist Label loop; assert_different_registers(memval_lo, memval_hi, result, oldval_lo, oldval_hi, newval_lo, newval_hi, base); - assert(memval_hi == memval_lo + 1 && memval_lo < R9, "cmpxchg_long: illegal registers"); - assert(oldval_hi == oldval_lo + 1 && oldval_lo < R9, "cmpxchg_long: illegal registers"); - assert(newval_hi == newval_lo + 1 && newval_lo < R9, "cmpxchg_long: illegal registers"); + assert(memval_hi == as_Register(memval_lo->encoding() + 1) && memval_lo->encoding() < R9->encoding(), "cmpxchg_long: illegal registers"); + assert(oldval_hi == as_Register(oldval_lo->encoding() + 1) && oldval_lo->encoding() < R9->encoding(), "cmpxchg_long: illegal registers"); + assert(newval_hi == as_Register(newval_lo->encoding() + 1) && newval_lo->encoding() < R9->encoding(), "cmpxchg_long: illegal registers"); assert(result != R10, "cmpxchg_long: illegal registers"); assert(base != R10, "cmpxchg_long: illegal registers"); diff --git a/src/hotspot/os_cpu/linux_riscv/atomicAccess_linux_riscv.hpp b/src/hotspot/os_cpu/linux_riscv/atomicAccess_linux_riscv.hpp index 6d57ea55a83..bdbc0b8ac7f 100644 --- a/src/hotspot/os_cpu/linux_riscv/atomicAccess_linux_riscv.hpp +++ b/src/hotspot/os_cpu/linux_riscv/atomicAccess_linux_riscv.hpp @@ -152,6 +152,9 @@ inline T AtomicAccess::PlatformCmpxchg<4>::operator()(T volatile* dest __attribu } #endif +template<> +struct AtomicAccess::PlatformXchg<1> : AtomicAccess::XchgUsingCmpxchg<1> {}; + template template inline T AtomicAccess::PlatformXchg::operator()(T volatile* dest, @@ -164,6 +167,7 @@ inline T AtomicAccess::PlatformXchg::operator()(T volatile* dest, #endif STATIC_ASSERT(byte_size == sizeof(T)); + STATIC_ASSERT(byte_size == 4 || byte_size == 8); if (order != memory_order_relaxed) { FULL_MEM_BARRIER; diff --git a/src/hotspot/os_cpu/linux_riscv/vm_version_linux_riscv.cpp b/src/hotspot/os_cpu/linux_riscv/vm_version_linux_riscv.cpp index 0799de014a9..35cbb75e8ff 100644 --- a/src/hotspot/os_cpu/linux_riscv/vm_version_linux_riscv.cpp +++ b/src/hotspot/os_cpu/linux_riscv/vm_version_linux_riscv.cpp @@ -104,11 +104,15 @@ uint32_t VM_Version::cpu_vector_length() { } void VM_Version::RVExtFeatureValue::log_enabled() { - log_debug(os, cpu)("Enabled RV64 feature \"%s\"", pretty()); + log_info(os, cpu)("Enabled RV64 feature \"%s\"", pretty()); +} + +void VM_Version::RVExtFeatureValue::log_disabled(const char* reason) { + log_info(os, cpu)("Disabled RV64 feature \"%s\" (%s)", pretty(), reason); } void VM_Version::RVNonExtFeatureValue::log_enabled() { - log_debug(os, cpu)("Enabled RV64 feature \"%s\" (%ld)", pretty(), value()); + log_info(os, cpu)("Enabled RV64 feature \"%s\" (%ld)", pretty(), value()); } void VM_Version::setup_cpu_available_features() { @@ -193,7 +197,7 @@ void VM_Version::setup_cpu_available_features() { // via PR_RISCV_SCOPE_PER_THREAD, i.e. on VM attach/deattach. int ret = prctl(PR_RISCV_SET_ICACHE_FLUSH_CTX, PR_RISCV_CTX_SW_FENCEI_ON, PR_RISCV_SCOPE_PER_PROCESS); if (ret == 0) { - log_debug(os, cpu)("UseCtxFencei (PR_RISCV_CTX_SW_FENCEI_ON) enabled."); + log_info(os, cpu)("UseCtxFencei (PR_RISCV_CTX_SW_FENCEI_ON) enabled."); } else { FLAG_SET_ERGO(UseCtxFencei, false); log_info(os, cpu)("UseCtxFencei (PR_RISCV_CTX_SW_FENCEI_ON) disabled, unsupported by kernel."); diff --git a/src/hotspot/os_cpu/linux_s390/atomicAccess_linux_s390.hpp b/src/hotspot/os_cpu/linux_s390/atomicAccess_linux_s390.hpp index 5849d69ae2f..f3c1e8f1a2c 100644 --- a/src/hotspot/os_cpu/linux_s390/atomicAccess_linux_s390.hpp +++ b/src/hotspot/os_cpu/linux_s390/atomicAccess_linux_s390.hpp @@ -209,6 +209,9 @@ inline D AtomicAccess::PlatformAdd<8>::add_then_fetch(D volatile* dest, I inc, // // The return value is the (unchanged) value from memory as it was when the // replacement succeeded. +template<> +struct AtomicAccess::PlatformXchg<1> : AtomicAccess::XchgUsingCmpxchg<1> {}; + template<> template inline T AtomicAccess::PlatformXchg<4>::operator()(T volatile* dest, diff --git a/src/hotspot/os_cpu/linux_x86/atomicAccess_linux_x86.hpp b/src/hotspot/os_cpu/linux_x86/atomicAccess_linux_x86.hpp index dd91444d0a3..6b43b5e8e09 100644 --- a/src/hotspot/os_cpu/linux_x86/atomicAccess_linux_x86.hpp +++ b/src/hotspot/os_cpu/linux_x86/atomicAccess_linux_x86.hpp @@ -52,6 +52,9 @@ inline D AtomicAccess::PlatformAdd<4>::fetch_then_add(D volatile* dest, I add_va return old_value; } +template<> +struct AtomicAccess::PlatformXchg<1> : AtomicAccess::XchgUsingCmpxchg<1> {}; + template<> template inline T AtomicAccess::PlatformXchg<4>::operator()(T volatile* dest, diff --git a/src/hotspot/os_cpu/linux_zero/atomicAccess_linux_zero.hpp b/src/hotspot/os_cpu/linux_zero/atomicAccess_linux_zero.hpp index 376ef7a9dc9..96c46c6f59a 100644 --- a/src/hotspot/os_cpu/linux_zero/atomicAccess_linux_zero.hpp +++ b/src/hotspot/os_cpu/linux_zero/atomicAccess_linux_zero.hpp @@ -65,6 +65,9 @@ inline D AtomicAccess::PlatformAdd<8>::add_then_fetch(D volatile* dest, I add_va return res; } +template<> +struct AtomicAccess::PlatformXchg<1> : AtomicAccess::XchgUsingCmpxchg<1> {}; + template<> template inline T AtomicAccess::PlatformXchg<4>::operator()(T volatile* dest, diff --git a/src/hotspot/os_cpu/windows_aarch64/atomicAccess_windows_aarch64.hpp b/src/hotspot/os_cpu/windows_aarch64/atomicAccess_windows_aarch64.hpp index 62b6e3f87ec..f8119654c50 100644 --- a/src/hotspot/os_cpu/windows_aarch64/atomicAccess_windows_aarch64.hpp +++ b/src/hotspot/os_cpu/windows_aarch64/atomicAccess_windows_aarch64.hpp @@ -68,6 +68,9 @@ DEFINE_INTRINSIC_ADD(InterlockedAdd64, __int64) #undef DEFINE_INTRINSIC_ADD +template<> +struct AtomicAccess::PlatformXchg<1> : AtomicAccess::XchgUsingCmpxchg<1> {}; + #define DEFINE_INTRINSIC_XCHG(IntrinsicName, IntrinsicType) \ template<> \ template \ @@ -75,6 +78,8 @@ DEFINE_INTRINSIC_ADD(InterlockedAdd64, __int64) T exchange_value, \ atomic_memory_order order) const { \ STATIC_ASSERT(sizeof(IntrinsicType) == sizeof(T)); \ + STATIC_ASSERT(sizeof(IntrinsicType) == 4 || \ + sizeof(IntrinsicType) == 8); \ return PrimitiveConversions::cast( \ IntrinsicName(reinterpret_cast(dest), \ PrimitiveConversions::cast(exchange_value))); \ diff --git a/src/hotspot/os_cpu/windows_x86/atomicAccess_windows_x86.hpp b/src/hotspot/os_cpu/windows_x86/atomicAccess_windows_x86.hpp index a95da151688..aa78a401235 100644 --- a/src/hotspot/os_cpu/windows_x86/atomicAccess_windows_x86.hpp +++ b/src/hotspot/os_cpu/windows_x86/atomicAccess_windows_x86.hpp @@ -70,6 +70,9 @@ DEFINE_INTRINSIC_ADD(InterlockedAdd64, __int64) #undef DEFINE_INTRINSIC_ADD +template<> +struct AtomicAccess::PlatformXchg<1> : AtomicAccess::XchgUsingCmpxchg<1> {}; + #define DEFINE_INTRINSIC_XCHG(IntrinsicName, IntrinsicType) \ template<> \ template \ @@ -77,6 +80,8 @@ DEFINE_INTRINSIC_ADD(InterlockedAdd64, __int64) T exchange_value, \ atomic_memory_order order) const { \ STATIC_ASSERT(sizeof(IntrinsicType) == sizeof(T)); \ + STATIC_ASSERT(sizeof(IntrinsicType) == 4 || \ + sizeof(IntrinsicType) == 8); \ return PrimitiveConversions::cast( \ IntrinsicName(reinterpret_cast(dest), \ PrimitiveConversions::cast(exchange_value))); \ diff --git a/src/hotspot/share/cds/aotConstantPoolResolver.cpp b/src/hotspot/share/cds/aotConstantPoolResolver.cpp index ddf7d32ed70..c4bb26f6fb1 100644 --- a/src/hotspot/share/cds/aotConstantPoolResolver.cpp +++ b/src/hotspot/share/cds/aotConstantPoolResolver.cpp @@ -449,7 +449,7 @@ bool AOTConstantPoolResolver::check_lambda_metafactory_signature(ConstantPool* c } bool AOTConstantPoolResolver::check_lambda_metafactory_methodtype_arg(ConstantPool* cp, int bsms_attribute_index, int arg_i) { - int mt_index = cp->bsm_attribute_entry(bsms_attribute_index)->argument_index(arg_i); + int mt_index = cp->bsm_attribute_entry(bsms_attribute_index)->argument(arg_i); if (!cp->tag_at(mt_index).is_method_type()) { // malformed class? return false; @@ -465,7 +465,7 @@ bool AOTConstantPoolResolver::check_lambda_metafactory_methodtype_arg(ConstantPo } bool AOTConstantPoolResolver::check_lambda_metafactory_methodhandle_arg(ConstantPool* cp, int bsms_attribute_index, int arg_i) { - int mh_index = cp->bsm_attribute_entry(bsms_attribute_index)->argument_index(arg_i); + int mh_index = cp->bsm_attribute_entry(bsms_attribute_index)->argument(arg_i); if (!cp->tag_at(mh_index).is_method_handle()) { // malformed class? return false; diff --git a/src/hotspot/share/cds/aotMetaspace.cpp b/src/hotspot/share/cds/aotMetaspace.cpp index 42d41e6ae89..f56050d4d31 100644 --- a/src/hotspot/share/cds/aotMetaspace.cpp +++ b/src/hotspot/share/cds/aotMetaspace.cpp @@ -114,6 +114,7 @@ intx AOTMetaspace::_relocation_delta; char* AOTMetaspace::_requested_base_address; Array* AOTMetaspace::_archived_method_handle_intrinsics = nullptr; bool AOTMetaspace::_use_optimized_module_handling = true; +int volatile AOTMetaspace::_preimage_static_archive_dumped = 0; FileMapInfo* AOTMetaspace::_output_mapinfo = nullptr; // The CDS archive is divided into the following regions: @@ -1056,7 +1057,21 @@ void AOTMetaspace::exercise_runtime_cds_code(TRAPS) { CDSProtectionDomain::to_file_URL("dummy.jar", Handle(), CHECK); } +bool AOTMetaspace::preimage_static_archive_dumped() { + assert(CDSConfig::is_dumping_preimage_static_archive(), "Required"); + return AtomicAccess::load_acquire(&_preimage_static_archive_dumped) == 1; +} + void AOTMetaspace::dump_static_archive_impl(StaticArchiveBuilder& builder, TRAPS) { + if (CDSConfig::is_dumping_preimage_static_archive()) { + // When dumping to the AOT configuration file ensure this function is only executed once. + // Multiple invocations may happen via JCmd, during VM exit or other means (in the future) + // from different threads and possibly concurrently. + if (AtomicAccess::cmpxchg(&_preimage_static_archive_dumped, 0, 1) != 0) { + return; + } + } + if (CDSConfig::is_dumping_classic_static_archive()) { // We are running with -Xshare:dump load_classes(CHECK); @@ -1355,8 +1370,11 @@ bool AOTMetaspace::try_link_class(JavaThread* current, InstanceKlass* ik) { ik->link_class(THREAD); if (HAS_PENDING_EXCEPTION) { ResourceMark rm(THREAD); - aot_log_warning(aot)("Preload Warning: Verification failed for %s", - ik->external_name()); + oop message = java_lang_Throwable::message(current->pending_exception()); + aot_log_warning(aot)("Preload Warning: Verification failed for %s because a %s was thrown: %s", + ik->external_name(), + current->pending_exception()->klass()->external_name(), + message == nullptr ? "(no message)" : java_lang_String::as_utf8_string(message)); CLEAR_PENDING_EXCEPTION; SystemDictionaryShared::set_class_has_failed_verification(ik); } else { diff --git a/src/hotspot/share/cds/aotMetaspace.hpp b/src/hotspot/share/cds/aotMetaspace.hpp index 1712a7865ad..ab78787288f 100644 --- a/src/hotspot/share/cds/aotMetaspace.hpp +++ b/src/hotspot/share/cds/aotMetaspace.hpp @@ -60,6 +60,7 @@ class AOTMetaspace : AllStatic { static char* _requested_base_address; static bool _use_optimized_module_handling; static Array* _archived_method_handle_intrinsics; + static int volatile _preimage_static_archive_dumped; static FileMapInfo* _output_mapinfo; public: @@ -115,6 +116,8 @@ public: // inside the metaspace of the dynamic static CDS archive static bool in_aot_cache_dynamic_region(void* p) NOT_CDS_RETURN_(false); + static bool preimage_static_archive_dumped() NOT_CDS_RETURN_(false); + static void unrecoverable_loading_error(const char* message = "unrecoverable error"); static void report_loading_error(const char* format, ...) ATTRIBUTE_PRINTF(1, 0); static void unrecoverable_writing_error(const char* message = nullptr); diff --git a/src/hotspot/share/ci/ciEnv.cpp b/src/hotspot/share/ci/ciEnv.cpp index 79ab881e7f6..92bacc4c2c3 100644 --- a/src/hotspot/share/ci/ciEnv.cpp +++ b/src/hotspot/share/ci/ciEnv.cpp @@ -1057,7 +1057,9 @@ void ciEnv::register_method(ciMethod* target, } assert(offsets->value(CodeOffsets::Deopt) != -1, "must have deopt entry"); - assert(offsets->value(CodeOffsets::Exceptions) != -1, "must have exception entry"); + + assert(compiler->type() == compiler_c2 || + offsets->value(CodeOffsets::Exceptions) != -1, "must have exception entry"); nm = nmethod::new_nmethod(method, compile_id(), diff --git a/src/hotspot/share/classfile/classFileParser.cpp b/src/hotspot/share/classfile/classFileParser.cpp index eb8a2a389b9..68890775051 100644 --- a/src/hotspot/share/classfile/classFileParser.cpp +++ b/src/hotspot/share/classfile/classFileParser.cpp @@ -47,6 +47,7 @@ #include "memory/resourceArea.hpp" #include "memory/universe.hpp" #include "oops/annotations.hpp" +#include "oops/bsmAttribute.inline.hpp" #include "oops/constantPool.inline.hpp" #include "oops/fieldInfo.hpp" #include "oops/fieldStreams.inline.hpp" @@ -3298,8 +3299,9 @@ void ClassFileParser::parse_classfile_bootstrap_methods_attribute(const ClassFil TRAPS) { assert(cfs != nullptr, "invariant"); assert(cp != nullptr, "invariant"); + const int cp_size = cp->length(); - const u1* const current_start = cfs->current(); + const u1* const current_before_parsing = cfs->current(); guarantee_property(attribute_byte_length >= sizeof(u2), "Invalid BootstrapMethods attribute length %u in class file %s", @@ -3308,57 +3310,40 @@ void ClassFileParser::parse_classfile_bootstrap_methods_attribute(const ClassFil cfs->guarantee_more(attribute_byte_length, CHECK); - const int attribute_array_length = cfs->get_u2_fast(); + const int num_bootstrap_methods = cfs->get_u2_fast(); - guarantee_property(_max_bootstrap_specifier_index < attribute_array_length, + guarantee_property(_max_bootstrap_specifier_index < num_bootstrap_methods, "Short length on BootstrapMethods in class file %s", CHECK); + const u4 bootstrap_methods_u2_len = (attribute_byte_length - sizeof(u2)) / sizeof(u2); - // The attribute contains a counted array of counted tuples of shorts, - // represending bootstrap specifiers: - // length*{bootstrap_method_index, argument_count*{argument_index}} - const unsigned int operand_count = (attribute_byte_length - (unsigned)sizeof(u2)) / (unsigned)sizeof(u2); - // operand_count = number of shorts in attr, except for leading length - - // The attribute is copied into a short[] array. - // The array begins with a series of short[2] pairs, one for each tuple. - const int index_size = (attribute_array_length * 2); - - Array* const operands = - MetadataFactory::new_array(_loader_data, index_size + operand_count, CHECK); - - // Eagerly assign operands so they will be deallocated with the constant + // Eagerly assign the arrays so that they will be deallocated with the constant // pool if there is an error. - cp->set_operands(operands); + BSMAttributeEntries::InsertionIterator iter = + cp->bsm_entries().start_extension(num_bootstrap_methods, + bootstrap_methods_u2_len, + _loader_data, + CHECK); - int operand_fill_index = index_size; - const int cp_size = cp->length(); - - for (int n = 0; n < attribute_array_length; n++) { - // Store a 32-bit offset into the header of the operand array. - ConstantPool::operand_offset_at_put(operands, n, operand_fill_index); - - // Read a bootstrap specifier. + for (int i = 0; i < num_bootstrap_methods; i++) { cfs->guarantee_more(sizeof(u2) * 2, CHECK); // bsm, argc - const u2 bootstrap_method_index = cfs->get_u2_fast(); - const u2 argument_count = cfs->get_u2_fast(); + u2 bootstrap_method_ref = cfs->get_u2_fast(); + u2 num_bootstrap_arguments = cfs->get_u2_fast(); guarantee_property( - valid_cp_range(bootstrap_method_index, cp_size) && - cp->tag_at(bootstrap_method_index).is_method_handle(), - "bootstrap_method_index %u has bad constant type in class file %s", - bootstrap_method_index, - CHECK); + valid_cp_range(bootstrap_method_ref, cp_size) && + cp->tag_at(bootstrap_method_ref).is_method_handle(), + "bootstrap_method_index %u has bad constant type in class file %s", + bootstrap_method_ref, + CHECK); + cfs->guarantee_more(sizeof(u2) * num_bootstrap_arguments, CHECK); // argv[argc] - guarantee_property((operand_fill_index + 1 + argument_count) < operands->length(), - "Invalid BootstrapMethods num_bootstrap_methods or num_bootstrap_arguments value in class file %s", - CHECK); + BSMAttributeEntry* entry = iter.reserve_new_entry(bootstrap_method_ref, num_bootstrap_arguments); + guarantee_property(entry != nullptr, + "Invalid BootstrapMethods num_bootstrap_methods." + " The total amount of space reserved for the BootstrapMethod attribute was not sufficient", CHECK); - operands->at_put(operand_fill_index++, bootstrap_method_index); - operands->at_put(operand_fill_index++, argument_count); - - cfs->guarantee_more(sizeof(u2) * argument_count, CHECK); // argv[argc] - for (int j = 0; j < argument_count; j++) { + for (int argi = 0; argi < num_bootstrap_arguments; argi++) { const u2 argument_index = cfs->get_u2_fast(); guarantee_property( valid_cp_range(argument_index, cp_size) && @@ -3366,10 +3351,11 @@ void ClassFileParser::parse_classfile_bootstrap_methods_attribute(const ClassFil "argument_index %u has bad constant type in class file %s", argument_index, CHECK); - operands->at_put(operand_fill_index++, argument_index); + entry->set_argument(argi, argument_index); } } - guarantee_property(current_start + attribute_byte_length == cfs->current(), + cp->bsm_entries().end_extension(iter, _loader_data, CHECK); + guarantee_property(current_before_parsing + attribute_byte_length == cfs->current(), "Bad length on BootstrapMethods in class file %s", CHECK); } diff --git a/src/hotspot/share/code/nmethod.cpp b/src/hotspot/share/code/nmethod.cpp index d91af9b4991..c2f8b46f00e 100644 --- a/src/hotspot/share/code/nmethod.cpp +++ b/src/hotspot/share/code/nmethod.cpp @@ -1302,7 +1302,7 @@ nmethod::nmethod( } // Native wrappers do not have deopt handlers. Make the values // something that will never match a pc like the nmethod vtable entry - _deopt_handler_offset = 0; + _deopt_handler_entry_offset = 0; _unwind_handler_offset = 0; CHECKED_CAST(_oops_size, uint16_t, align_up(code_buffer->total_oop_size(), oopSize)); @@ -1442,7 +1442,7 @@ nmethod::nmethod(const nmethod &nm) : CodeBlob(nm._name, nm._kind, nm._size, nm. _skipped_instructions_size = nm._skipped_instructions_size; _stub_offset = nm._stub_offset; _exception_offset = nm._exception_offset; - _deopt_handler_offset = nm._deopt_handler_offset; + _deopt_handler_entry_offset = nm._deopt_handler_entry_offset; _unwind_handler_offset = nm._unwind_handler_offset; _num_stack_arg_slots = nm._num_stack_arg_slots; _oops_size = nm._oops_size; @@ -1704,19 +1704,26 @@ nmethod::nmethod( _exception_offset = -1; } if (offsets->value(CodeOffsets::Deopt) != -1) { - _deopt_handler_offset = code_offset() + offsets->value(CodeOffsets::Deopt); + _deopt_handler_entry_offset = code_offset() + offsets->value(CodeOffsets::Deopt); } else { - _deopt_handler_offset = -1; + _deopt_handler_entry_offset = -1; } } else #endif { // Exception handler and deopt handler are in the stub section - assert(offsets->value(CodeOffsets::Exceptions) != -1, "must be set"); assert(offsets->value(CodeOffsets::Deopt ) != -1, "must be set"); - _exception_offset = _stub_offset + offsets->value(CodeOffsets::Exceptions); - _deopt_handler_offset = _stub_offset + offsets->value(CodeOffsets::Deopt); + bool has_exception_handler = (offsets->value(CodeOffsets::Exceptions) != -1); + assert(has_exception_handler == (compiler->type() != compiler_c2), + "C2 compiler doesn't provide exception handler stub code."); + if (has_exception_handler) { + _exception_offset = _stub_offset + offsets->value(CodeOffsets::Exceptions); + } else { + _exception_offset = -1; + } + + _deopt_handler_entry_offset = _stub_offset + offsets->value(CodeOffsets::Deopt); } if (offsets->value(CodeOffsets::UnwindHandler) != -1) { // C1 generates UnwindHandler at the end of instructions section. @@ -4024,7 +4031,7 @@ const char* nmethod::nmethod_section_label(address pos) const { // Check stub_code before checking exception_handler or deopt_handler. if (pos == this->stub_begin()) label = "[Stub Code]"; if (JVMCI_ONLY(_exception_offset >= 0 &&) pos == exception_begin()) label = "[Exception Handler]"; - if (JVMCI_ONLY(_deopt_handler_offset != -1 &&) pos == deopt_handler_begin()) label = "[Deopt Handler Code]"; + if (JVMCI_ONLY(_deopt_handler_entry_offset != -1 &&) pos == deopt_handler_entry()) label = "[Deopt Handler Entry Point]"; return label; } diff --git a/src/hotspot/share/code/nmethod.hpp b/src/hotspot/share/code/nmethod.hpp index 34accf428b6..0fa9d7fda9e 100644 --- a/src/hotspot/share/code/nmethod.hpp +++ b/src/hotspot/share/code/nmethod.hpp @@ -229,7 +229,7 @@ class nmethod : public CodeBlob { int _exception_offset; // All deoptee's will resume execution at this location described by // this offset. - int _deopt_handler_offset; + int _deopt_handler_entry_offset; // Offset (from insts_end) of the unwind handler if it exists int16_t _unwind_handler_offset; // Number of arguments passed on the stack @@ -617,7 +617,7 @@ public: address stub_begin () const { return header_begin() + _stub_offset ; } address stub_end () const { return code_end() ; } address exception_begin () const { return header_begin() + _exception_offset ; } - address deopt_handler_begin () const { return header_begin() + _deopt_handler_offset ; } + address deopt_handler_entry () const { return header_begin() + _deopt_handler_entry_offset ; } address unwind_handler_begin () const { return _unwind_handler_offset != -1 ? (insts_end() - _unwind_handler_offset) : nullptr; } oop* oops_begin () const { return (oop*) data_begin(); } oop* oops_end () const { return (oop*) data_end(); } diff --git a/src/hotspot/share/code/nmethod.inline.hpp b/src/hotspot/share/code/nmethod.inline.hpp index 44331db669c..ecee3c0c31a 100644 --- a/src/hotspot/share/code/nmethod.inline.hpp +++ b/src/hotspot/share/code/nmethod.inline.hpp @@ -34,7 +34,7 @@ inline bool nmethod::is_deopt_pc(address pc) { return is_deopt_entry(pc); } inline bool nmethod::is_deopt_entry(address pc) { - return pc == deopt_handler_begin(); + return pc == deopt_handler_entry(); } // class ExceptionCache methods diff --git a/src/hotspot/share/compiler/compilationMemoryStatistic.cpp b/src/hotspot/share/compiler/compilationMemoryStatistic.cpp index d1e2f6f34a0..1951fd066fc 100644 --- a/src/hotspot/share/compiler/compilationMemoryStatistic.cpp +++ b/src/hotspot/share/compiler/compilationMemoryStatistic.cpp @@ -1010,8 +1010,10 @@ void CompilationMemoryStatistic::print_error_report(outputStream* st) { oom_stats->print_peak_state_on(st); st->cr(); } - st->print_cr("Compiler Memory Statistic, 10 most expensive compilations:"); - print_all_by_size(st, false, false, 0, 10); + if (Thread::current_or_null_safe() != nullptr) { + st->print_cr("Compiler Memory Statistic, 10 most expensive compilations:"); + print_all_by_size(st, false, false, 0, 10); + } } void CompilationMemoryStatistic::print_final_report(outputStream* st) { diff --git a/src/hotspot/share/cppstdlib/new.hpp b/src/hotspot/share/cppstdlib/new.hpp index 3536ac13288..ea9d6c88c87 100644 --- a/src/hotspot/share/cppstdlib/new.hpp +++ b/src/hotspot/share/cppstdlib/new.hpp @@ -79,11 +79,10 @@ class [[deprecated]] bad_array_new_length; // version to decide whether to redeclare deprecated. #if defined(__clang__) -#if __clang_major__ >= 19 -// clang18 and earlier may accept the declaration but go wrong with uses. -// Different warnings and link-time failures are both possible. -#define CAN_DEPRECATE_HARDWARE_INTERFERENCE_SIZES 1 -#endif // restrict clang version +// Some versions of clang with some stdlibs reject the declaration. Others may +// accept the declaration but go wrong with uses. Different warnings and +// link-time failures are both possible. +// Known to have problems at least through clang19. #elif defined(__GNUC__) #if (__GNUC__ > 13) || (__GNUC__ == 13 && __GNUC_MINOR__ >= 2) diff --git a/src/hotspot/share/gc/g1/g1AllocRegion.cpp b/src/hotspot/share/gc/g1/g1AllocRegion.cpp index 7e748cf7e9f..1af7638102a 100644 --- a/src/hotspot/share/gc/g1/g1AllocRegion.cpp +++ b/src/hotspot/share/gc/g1/g1AllocRegion.cpp @@ -33,10 +33,10 @@ #include "utilities/align.hpp" G1CollectedHeap* G1AllocRegion::_g1h = nullptr; -G1HeapRegion* G1AllocRegion::_dummy_region = nullptr; +Atomic G1AllocRegion::_dummy_region; void G1AllocRegion::setup(G1CollectedHeap* g1h, G1HeapRegion* dummy_region) { - assert(_dummy_region == nullptr, "should be set once"); + assert(_dummy_region.load_relaxed() == nullptr, "should be set once"); assert(dummy_region != nullptr, "pre-condition"); assert(dummy_region->free() == 0, "pre-condition"); @@ -46,11 +46,11 @@ void G1AllocRegion::setup(G1CollectedHeap* g1h, G1HeapRegion* dummy_region) { assert(dummy_region->par_allocate(1, 1, &assert_tmp) == nullptr, "should fail"); _g1h = g1h; - _dummy_region = dummy_region; + _dummy_region.release_store(dummy_region); } size_t G1AllocRegion::fill_up_remaining_space(G1HeapRegion* alloc_region) { - assert(alloc_region != nullptr && alloc_region != _dummy_region, + assert(alloc_region != nullptr && alloc_region != _dummy_region.load_relaxed(), "pre-condition"); size_t result = 0; @@ -111,13 +111,13 @@ size_t G1AllocRegion::retire_internal(G1HeapRegion* alloc_region, bool fill_up) } size_t G1AllocRegion::retire(bool fill_up) { - assert_alloc_region(_alloc_region != nullptr, "not initialized properly"); + assert_alloc_region(_alloc_region.load_relaxed() != nullptr, "not initialized properly"); size_t waste = 0; trace("retiring"); - G1HeapRegion* alloc_region = _alloc_region; - if (alloc_region != _dummy_region) { + G1HeapRegion* alloc_region = _alloc_region.load_acquire(); + if (alloc_region != _dummy_region.load_relaxed()) { waste = retire_internal(alloc_region, fill_up); reset_alloc_region(); } @@ -127,7 +127,7 @@ size_t G1AllocRegion::retire(bool fill_up) { } HeapWord* G1AllocRegion::new_alloc_region_and_allocate(size_t word_size) { - assert_alloc_region(_alloc_region == _dummy_region, "pre-condition"); + assert_alloc_region(_alloc_region.load_relaxed() == _dummy_region.load_relaxed(), "pre-condition"); trace("attempting region allocation"); G1HeapRegion* new_alloc_region = allocate_new_region(word_size); @@ -138,7 +138,6 @@ HeapWord* G1AllocRegion::new_alloc_region_and_allocate(size_t word_size) { HeapWord* result = new_alloc_region->allocate(word_size); assert_alloc_region(result != nullptr, "the allocation should succeeded"); - OrderAccess::storestore(); // Note that we first perform the allocation and then we store the // region in _alloc_region. This is the reason why an active region // can never be empty. @@ -154,16 +153,16 @@ HeapWord* G1AllocRegion::new_alloc_region_and_allocate(size_t word_size) { void G1AllocRegion::init() { trace("initializing"); - assert_alloc_region(_alloc_region == nullptr, "pre-condition"); - assert_alloc_region(_dummy_region != nullptr, "should have been set"); - _alloc_region = _dummy_region; + assert_alloc_region(_alloc_region.load_relaxed() == nullptr, "pre-condition"); + assert_alloc_region(_dummy_region.load_relaxed() != nullptr, "should have been set"); + _alloc_region.release_store(_dummy_region.load_relaxed()); _count = 0; trace("initialized"); } void G1AllocRegion::set(G1HeapRegion* alloc_region) { trace("setting"); - assert_alloc_region(_alloc_region == _dummy_region && _count == 0, "pre-condition"); + assert_alloc_region(_alloc_region.load_relaxed() == _dummy_region.load_relaxed() && _count == 0, "pre-condition"); update_alloc_region(alloc_region); trace("set"); @@ -175,19 +174,19 @@ void G1AllocRegion::update_alloc_region(G1HeapRegion* alloc_region) { // maintain the "the alloc region cannot be empty" invariant. assert_alloc_region(alloc_region != nullptr && !alloc_region->is_empty(), "pre-condition"); - _alloc_region = alloc_region; + _alloc_region.release_store(alloc_region); _count += 1; trace("updated"); } G1HeapRegion* G1AllocRegion::release() { trace("releasing"); - G1HeapRegion* alloc_region = _alloc_region; + G1HeapRegion* alloc_region = _alloc_region.load_acquire(); retire(false /* fill_up */); - assert_alloc_region(_alloc_region == _dummy_region, "post-condition of retire()"); - _alloc_region = nullptr; + assert_alloc_region(_alloc_region.load_relaxed() == _dummy_region.load_relaxed(), "post-condition of retire()"); + _alloc_region.store_relaxed(nullptr); trace("released"); - return (alloc_region == _dummy_region) ? nullptr : alloc_region; + return (alloc_region == _dummy_region.load_relaxed()) ? nullptr : alloc_region; } #ifndef PRODUCT @@ -211,12 +210,13 @@ void G1AllocRegion::trace(const char* str, size_t min_word_size, size_t desired_ out->print("%s: %u ", _name, _count); - if (_alloc_region == nullptr) { + G1HeapRegion* alloc_region = _alloc_region.load_acquire(); + if (alloc_region == nullptr) { out->print("null"); - } else if (_alloc_region == _dummy_region) { + } else if (alloc_region == _dummy_region.load_relaxed()) { out->print("DUMMY"); } else { - out->print(HR_FORMAT, HR_FORMAT_PARAMS(_alloc_region)); + out->print(HR_FORMAT, HR_FORMAT_PARAMS(alloc_region)); } out->print(" : %s", str); @@ -235,7 +235,7 @@ void G1AllocRegion::trace(const char* str, size_t min_word_size, size_t desired_ #endif // PRODUCT G1AllocRegion::G1AllocRegion(const char* name, uint node_index) - : _alloc_region(nullptr), + : _alloc_region(), _count(0), _name(name), _node_index(node_index) @@ -250,7 +250,7 @@ void MutatorAllocRegion::retire_region(G1HeapRegion* alloc_region) { } void MutatorAllocRegion::init() { - assert(_retained_alloc_region == nullptr, "Pre-condition"); + assert(_retained_alloc_region.load_relaxed() == nullptr, "Pre-condition"); G1AllocRegion::init(); _wasted_bytes = 0; } @@ -261,8 +261,9 @@ bool MutatorAllocRegion::should_retain(G1HeapRegion* region) { return false; } - if (_retained_alloc_region != nullptr && - free_bytes < _retained_alloc_region->free()) { + G1HeapRegion* retained_alloc_region = _retained_alloc_region.load_acquire(); + if (retained_alloc_region != nullptr && + free_bytes < retained_alloc_region->free()) { return false; } @@ -278,10 +279,11 @@ size_t MutatorAllocRegion::retire(bool fill_up) { // free than the currently retained region. if (should_retain(current_region)) { trace("mutator retained"); - if (_retained_alloc_region != nullptr) { - waste = retire_internal(_retained_alloc_region, true); + G1HeapRegion* retained_alloc_region = _retained_alloc_region.load_acquire(); + if (retained_alloc_region != nullptr) { + waste = retire_internal(retained_alloc_region, true); } - _retained_alloc_region = current_region; + _retained_alloc_region.release_store(current_region); } else { waste = retire_internal(current_region, fill_up); } @@ -300,7 +302,7 @@ size_t MutatorAllocRegion::used_in_alloc_regions() { used += hr->used(); } - hr = _retained_alloc_region; + hr = _retained_alloc_region.load_acquire(); if (hr != nullptr) { used += hr->used(); } @@ -313,9 +315,10 @@ G1HeapRegion* MutatorAllocRegion::release() { // The retained alloc region must be retired and this must be // done after the above call to release the mutator alloc region, // since it might update the _retained_alloc_region member. - if (_retained_alloc_region != nullptr) { - _wasted_bytes += retire_internal(_retained_alloc_region, false); - _retained_alloc_region = nullptr; + G1HeapRegion* retained_alloc_region = _retained_alloc_region.load_acquire(); + if (retained_alloc_region != nullptr) { + _wasted_bytes += retire_internal(retained_alloc_region, false); + _retained_alloc_region.store_relaxed(nullptr); } log_debug(gc, alloc, region)("Mutator Allocation stats, regions: %u, wasted size: %zu%s (%4.1f%%)", count(), diff --git a/src/hotspot/share/gc/g1/g1AllocRegion.hpp b/src/hotspot/share/gc/g1/g1AllocRegion.hpp index 3e38332ee6f..248aa0a9da0 100644 --- a/src/hotspot/share/gc/g1/g1AllocRegion.hpp +++ b/src/hotspot/share/gc/g1/g1AllocRegion.hpp @@ -29,6 +29,7 @@ #include "gc/g1/g1HeapRegion.hpp" #include "gc/g1/g1HeapRegionAttr.hpp" #include "gc/g1/g1NUMA.hpp" +#include "runtime/atomic.hpp" class G1CollectedHeap; @@ -40,8 +41,6 @@ class G1CollectedHeap; // replaced. class G1AllocRegion : public CHeapObj { - -private: // The active allocating region we are currently allocating out // of. The invariant is that if this object is initialized (i.e., // init() has been called and release() has not) then _alloc_region @@ -52,7 +51,7 @@ private: // then _alloc_region is null and this object should not be used to // satisfy allocation requests (it was done this way to force the // correct use of init() and release()). - G1HeapRegion* volatile _alloc_region; + Atomic _alloc_region; // It keeps track of the distinct number of regions that are used // for allocation in the active interval of this object, i.e., @@ -71,7 +70,7 @@ private: // == end()). When we don't have a valid active region we make // _alloc_region point to this. This allows us to skip checking // whether the _alloc_region is null or not. - static G1HeapRegion* _dummy_region; + static Atomic _dummy_region; // After a region is allocated by alloc_new_region, this // method is used to set it as the active alloc_region @@ -124,9 +123,9 @@ public: static void setup(G1CollectedHeap* g1h, G1HeapRegion* dummy_region); G1HeapRegion* get() const { - G1HeapRegion * hr = _alloc_region; + G1HeapRegion * hr = _alloc_region.load_acquire(); // Make sure that the dummy region does not escape this class. - return (hr == _dummy_region) ? nullptr : hr; + return (hr == _dummy_region.load_relaxed()) ? nullptr : hr; } uint count() { return _count; } @@ -177,7 +176,7 @@ private: // Retained allocation region. Used to lower the waste generated // during mutation by having two active regions if the free space // in a region about to be retired still could fit a TLAB. - G1HeapRegion* volatile _retained_alloc_region; + Atomic _retained_alloc_region; // Decide if the region should be retained, based on the free size // in it and the free size in the currently retained region, if any. diff --git a/src/hotspot/share/gc/g1/g1AllocRegion.inline.hpp b/src/hotspot/share/gc/g1/g1AllocRegion.inline.hpp index af9156163ac..e1d23867ea3 100644 --- a/src/hotspot/share/gc/g1/g1AllocRegion.inline.hpp +++ b/src/hotspot/share/gc/g1/g1AllocRegion.inline.hpp @@ -32,13 +32,13 @@ #define assert_alloc_region(p, message) \ do { \ assert((p), "[%s] %s c: %u r: " PTR_FORMAT, \ - _name, (message), _count, p2i(_alloc_region) \ + _name, (message), _count, p2i(_alloc_region.load_relaxed()) \ ); \ } while (0) inline void G1AllocRegion::reset_alloc_region() { - _alloc_region = _dummy_region; + _alloc_region.store_relaxed(_dummy_region.load_relaxed()); } inline HeapWord* G1AllocRegion::par_allocate(G1HeapRegion* alloc_region, size_t word_size) { @@ -51,7 +51,7 @@ inline HeapWord* G1AllocRegion::par_allocate(G1HeapRegion* alloc_region, size_t inline HeapWord* G1AllocRegion::attempt_allocation(size_t min_word_size, size_t desired_word_size, size_t* actual_word_size) { - G1HeapRegion* alloc_region = _alloc_region; + G1HeapRegion* alloc_region = _alloc_region.load_acquire(); assert_alloc_region(alloc_region != nullptr && !alloc_region->is_empty(), "not initialized properly"); HeapWord* result = alloc_region->par_allocate(min_word_size, desired_word_size, actual_word_size); @@ -97,8 +97,9 @@ inline HeapWord* G1AllocRegion::attempt_allocation_using_new_region(size_t min_w inline HeapWord* MutatorAllocRegion::attempt_retained_allocation(size_t min_word_size, size_t desired_word_size, size_t* actual_word_size) { - if (_retained_alloc_region != nullptr) { - HeapWord* result = _retained_alloc_region->par_allocate(min_word_size, desired_word_size, actual_word_size); + G1HeapRegion* retained_alloc_region = _retained_alloc_region.load_acquire(); + if (retained_alloc_region != nullptr) { + HeapWord* result = retained_alloc_region->par_allocate(min_word_size, desired_word_size, actual_word_size); if (result != nullptr) { trace("alloc retained", min_word_size, desired_word_size, *actual_word_size, result); return result; diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp index d18f61ff507..061241c24e2 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp @@ -478,11 +478,6 @@ HeapWord* G1CollectedHeap::attempt_allocation_slow(uint node_index, size_t word_ log_trace(gc, alloc)("%s: Unsuccessfully scheduled collection allocating %zu words", Thread::current()->name(), word_size); - if (is_shutting_down()) { - stall_for_vm_shutdown(); - return nullptr; - } - // Has the gc overhead limit been reached in the meantime? If so, this mutator // should receive null even when unsuccessfully scheduling a collection as well // for global consistency. @@ -738,11 +733,6 @@ HeapWord* G1CollectedHeap::attempt_allocation_humongous(size_t word_size) { log_trace(gc, alloc)("%s: Unsuccessfully scheduled collection allocating %zu", Thread::current()->name(), word_size); - if (is_shutting_down()) { - stall_for_vm_shutdown(); - return nullptr; - } - // Has the gc overhead limit been reached in the meantime? If so, this mutator // should receive null even when unsuccessfully scheduling a collection as well // for global consistency. @@ -1645,6 +1635,10 @@ jint G1CollectedHeap::initialize() { return JNI_OK; } +bool G1CollectedHeap::concurrent_mark_is_terminating() const { + return _cm_thread->should_terminate(); +} + void G1CollectedHeap::stop() { // Stop all concurrent threads. We do this to make sure these threads // do not continue to execute and access resources (e.g. logging) @@ -1965,8 +1959,8 @@ bool G1CollectedHeap::try_collect_concurrently(size_t allocation_word_size, } // If VMOp skipped initiating concurrent marking cycle because - // we're terminating, then we're done. - if (is_shutting_down()) { + // we're shutting down, then we're done. + if (op.is_shutting_down()) { LOG_COLLECT_CONCURRENTLY(cause, "skipped: terminating"); return false; } @@ -2361,7 +2355,8 @@ static void print_region_type(outputStream* st, const char* type, uint count, bo } void G1CollectedHeap::print_heap_on(outputStream* st) const { - size_t heap_used = Heap_lock->owned_by_self() ? used() : used_unlocked(); + size_t heap_used = (Thread::current_or_null_safe() != nullptr && + Heap_lock->owned_by_self()) ? used() : used_unlocked(); st->print("%-20s", "garbage-first heap"); st->print(" total reserved %zuK, committed %zuK, used %zuK", _hrm.reserved().byte_size()/K, capacity()/K, heap_used/K); diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp index 5dccf41e909..aff7166d391 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp @@ -917,6 +917,9 @@ public: // specified by the policy object. jint initialize() override; + // Returns whether concurrent mark threads (and the VM) are about to terminate. + bool concurrent_mark_is_terminating() const; + void safepoint_synchronize_begin() override; void safepoint_synchronize_end() override; diff --git a/src/hotspot/share/gc/g1/g1CollectionSetCandidates.cpp b/src/hotspot/share/gc/g1/g1CollectionSetCandidates.cpp index 47340fad768..d71108d4d0e 100644 --- a/src/hotspot/share/gc/g1/g1CollectionSetCandidates.cpp +++ b/src/hotspot/share/gc/g1/g1CollectionSetCandidates.cpp @@ -267,8 +267,6 @@ void G1CollectionSetCandidates::set_candidates_from_marking(G1HeapRegion** candi // the same MixedGC. uint group_limit = p->calc_min_old_cset_length(num_candidates); - uint num_added_to_group = 0; - G1CSetCandidateGroup::reset_next_group_id(); G1CSetCandidateGroup* current = nullptr; @@ -279,7 +277,7 @@ void G1CollectionSetCandidates::set_candidates_from_marking(G1HeapRegion** candi assert(!contains(r), "must not contain region %u", r->hrm_index()); _contains_map[r->hrm_index()] = CandidateOrigin::Marking; - if (num_added_to_group == group_limit) { + if (current->length() == group_limit) { if (group_limit != G1OldCSetGroupSize) { group_limit = G1OldCSetGroupSize; } @@ -287,10 +285,8 @@ void G1CollectionSetCandidates::set_candidates_from_marking(G1HeapRegion** candi _from_marking_groups.append(current); current = new G1CSetCandidateGroup(); - num_added_to_group = 0; } current->add(r); - num_added_to_group++; } _from_marking_groups.append(current); diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp index d37fe9ea7ba..456d543fa10 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp @@ -1883,7 +1883,7 @@ bool G1ConcurrentMark::concurrent_cycle_abort() { // nothing, but this situation should be extremely rare (a full gc after shutdown // has been signalled is already rare), and this work should be negligible compared // to actual full gc work. - if (!cm_thread()->in_progress() && !_g1h->is_shutting_down()) { + if (!cm_thread()->in_progress() && !_g1h->concurrent_mark_is_terminating()) { return false; } diff --git a/src/hotspot/share/gc/g1/g1HeapRegion.cpp b/src/hotspot/share/gc/g1/g1HeapRegion.cpp index b1eeb333d8d..361e19d4be5 100644 --- a/src/hotspot/share/gc/g1/g1HeapRegion.cpp +++ b/src/hotspot/share/gc/g1/g1HeapRegion.cpp @@ -307,10 +307,6 @@ void G1HeapRegion::add_code_root(nmethod* nm) { rem_set()->add_code_root(nm); } -void G1HeapRegion::remove_code_root(nmethod* nm) { - rem_set()->remove_code_root(nm); -} - void G1HeapRegion::code_roots_do(NMethodClosure* blk) const { rem_set()->code_roots_do(blk); } diff --git a/src/hotspot/share/gc/g1/g1HeapRegion.hpp b/src/hotspot/share/gc/g1/g1HeapRegion.hpp index 17ec3055b52..fe915b0dafe 100644 --- a/src/hotspot/share/gc/g1/g1HeapRegion.hpp +++ b/src/hotspot/share/gc/g1/g1HeapRegion.hpp @@ -543,7 +543,6 @@ public: // Routines for managing a list of code roots (attached to the // this region's RSet) that point into this heap region. void add_code_root(nmethod* nm); - void remove_code_root(nmethod* nm); // Applies blk->do_nmethod() to each of the entries in // the code roots list for this region diff --git a/src/hotspot/share/gc/g1/g1IHOPControl.cpp b/src/hotspot/share/gc/g1/g1IHOPControl.cpp index 34c8cd0366b..43698e9f12b 100644 --- a/src/hotspot/share/gc/g1/g1IHOPControl.cpp +++ b/src/hotspot/share/gc/g1/g1IHOPControl.cpp @@ -28,14 +28,63 @@ #include "gc/g1/g1Trace.hpp" #include "logging/log.hpp" -G1IHOPControl::G1IHOPControl(double initial_ihop_percent, - G1OldGenAllocationTracker const* old_gen_alloc_tracker) : - _initial_ihop_percent(initial_ihop_percent), - _target_occupancy(0), - _last_allocation_time_s(0.0), - _old_gen_alloc_tracker(old_gen_alloc_tracker) -{ - assert(_initial_ihop_percent >= 0.0 && _initial_ihop_percent <= 100.0, "Initial IHOP value must be between 0 and 100 but is %.3f", initial_ihop_percent); +double G1IHOPControl::predict(const TruncatedSeq* seq) const { + assert(_is_adaptive, "precondition"); + assert(_predictor != nullptr, "precondition"); + + return _predictor->predict_zero_bounded(seq); +} + +bool G1IHOPControl::have_enough_data_for_prediction() const { + assert(_is_adaptive, "precondition"); + + return ((size_t)_marking_times_s.num() >= G1AdaptiveIHOPNumInitialSamples) && + ((size_t)_allocation_rate_s.num() >= G1AdaptiveIHOPNumInitialSamples); +} + +double G1IHOPControl::last_marking_length_s() const { + return _marking_times_s.last(); +} + +size_t G1IHOPControl::actual_target_threshold() const { + assert(_is_adaptive, "precondition"); + + // The actual target threshold takes the heap reserve and the expected waste in + // free space into account. + // _heap_reserve is that part of the total heap capacity that is reserved for + // eventual promotion failure. + // _heap_waste is the amount of space will never be reclaimed in any + // heap, so can not be used for allocation during marking and must always be + // considered. + double safe_total_heap_percentage = + MIN2((double)(_heap_reserve_percent + _heap_waste_percent), 100.0); + + return (size_t)MIN2( + G1CollectedHeap::heap()->max_capacity() * (100.0 - safe_total_heap_percentage) / 100.0, + _target_occupancy * (100.0 - _heap_waste_percent) / 100.0 + ); +} + +G1IHOPControl::G1IHOPControl(double ihop_percent, + const G1OldGenAllocationTracker* old_gen_alloc_tracker, + bool adaptive, + const G1Predictions* predictor, + size_t heap_reserve_percent, + size_t heap_waste_percent) + : _is_adaptive(adaptive), + _initial_ihop_percent(ihop_percent), + _target_occupancy(0), + _heap_reserve_percent(heap_reserve_percent), + _heap_waste_percent(heap_waste_percent), + _last_allocation_time_s(0.0), + _old_gen_alloc_tracker(old_gen_alloc_tracker), + _predictor(predictor), + _marking_times_s(10, 0.05), + _allocation_rate_s(10, 0.05), + _last_unrestrained_young_size(0) { + assert(_initial_ihop_percent >= 0.0 && _initial_ihop_percent <= 100.0, + "IHOP percent out of range: %.3f", ihop_percent); + assert(!_is_adaptive || _predictor != nullptr, "precondition"); } void G1IHOPControl::update_target_occupancy(size_t new_target_occupancy) { @@ -50,9 +99,34 @@ void G1IHOPControl::report_statistics(G1NewTracer* new_tracer, size_t non_young_ } void G1IHOPControl::update_allocation_info(double allocation_time_s, size_t additional_buffer_size) { - assert(allocation_time_s >= 0.0, "Allocation time must be positive but is %.3f", allocation_time_s); - + assert(allocation_time_s > 0, "Invalid allocation time: %.3f", allocation_time_s); _last_allocation_time_s = allocation_time_s; + double alloc_rate = _old_gen_alloc_tracker->last_period_old_gen_growth() / allocation_time_s; + _allocation_rate_s.add(alloc_rate); + _last_unrestrained_young_size = additional_buffer_size; +} + +void G1IHOPControl::update_marking_length(double marking_length_s) { + assert(marking_length_s >= 0.0, "Invalid marking length: %.3f", marking_length_s); + _marking_times_s.add(marking_length_s); +} + +size_t G1IHOPControl::get_conc_mark_start_threshold() { + guarantee(_target_occupancy > 0, "Target occupancy must be initialized"); + + if (!_is_adaptive || !have_enough_data_for_prediction()) { + return (size_t)(_initial_ihop_percent * _target_occupancy / 100.0); + } + + double pred_marking_time = predict(&_marking_times_s); + double pred_rate = predict(&_allocation_rate_s); + size_t pred_bytes = (size_t)(pred_marking_time * pred_rate); + size_t predicted_needed = pred_bytes + _last_unrestrained_young_size; + size_t internal_threshold = actual_target_threshold(); + + return predicted_needed < internal_threshold + ? internal_threshold - predicted_needed + : 0; } void G1IHOPControl::print_log(size_t non_young_occupancy) { @@ -68,6 +142,23 @@ void G1IHOPControl::print_log(size_t non_young_occupancy) { _last_allocation_time_s * 1000.0, _last_allocation_time_s > 0.0 ? _old_gen_alloc_tracker->last_period_old_gen_bytes() / _last_allocation_time_s : 0.0, last_marking_length_s() * 1000.0); + + if (!_is_adaptive) { + return; + } + + size_t actual_threshold = actual_target_threshold(); + log_debug(gc, ihop)("Adaptive IHOP information (value update), threshold: %zuB (%1.2f), internal target threshold: %zuB, " + "non-young occupancy: %zuB, additional buffer size: %zuB, predicted old gen allocation rate: %1.2fB/s, " + "predicted marking phase length: %1.2fms, prediction active: %s", + cur_conc_mark_start_threshold, + percent_of(cur_conc_mark_start_threshold, actual_threshold), + actual_threshold, + non_young_occupancy, + _last_unrestrained_young_size, + predict(&_allocation_rate_s), + predict(&_marking_times_s) * 1000.0, + have_enough_data_for_prediction() ? "true" : "false"); } void G1IHOPControl::send_trace_event(G1NewTracer* tracer, size_t non_young_occupancy) { @@ -78,121 +169,14 @@ void G1IHOPControl::send_trace_event(G1NewTracer* tracer, size_t non_young_occup _old_gen_alloc_tracker->last_period_old_gen_bytes(), _last_allocation_time_s, last_marking_length_s()); -} -G1StaticIHOPControl::G1StaticIHOPControl(double ihop_percent, - G1OldGenAllocationTracker const* old_gen_alloc_tracker) : - G1IHOPControl(ihop_percent, old_gen_alloc_tracker), - _last_marking_length_s(0.0) { -} - -G1AdaptiveIHOPControl::G1AdaptiveIHOPControl(double ihop_percent, - G1OldGenAllocationTracker const* old_gen_alloc_tracker, - G1Predictions const* predictor, - size_t heap_reserve_percent, - size_t heap_waste_percent) : - G1IHOPControl(ihop_percent, old_gen_alloc_tracker), - _heap_reserve_percent(heap_reserve_percent), - _heap_waste_percent(heap_waste_percent), - _predictor(predictor), - _marking_times_s(10, 0.05), - _allocation_rate_s(10, 0.05), - _last_unrestrained_young_size(0) -{ -} - -size_t G1AdaptiveIHOPControl::actual_target_threshold() const { - guarantee(_target_occupancy > 0, "Target occupancy still not updated yet."); - // The actual target threshold takes the heap reserve and the expected waste in - // free space into account. - // _heap_reserve is that part of the total heap capacity that is reserved for - // eventual promotion failure. - // _heap_waste is the amount of space will never be reclaimed in any - // heap, so can not be used for allocation during marking and must always be - // considered. - - double safe_total_heap_percentage = MIN2((double)(_heap_reserve_percent + _heap_waste_percent), 100.0); - - return (size_t)MIN2( - G1CollectedHeap::heap()->max_capacity() * (100.0 - safe_total_heap_percentage) / 100.0, - _target_occupancy * (100.0 - _heap_waste_percent) / 100.0 - ); -} - -double G1AdaptiveIHOPControl::predict(TruncatedSeq const* seq) const { - return _predictor->predict_zero_bounded(seq); -} - -bool G1AdaptiveIHOPControl::have_enough_data_for_prediction() const { - return ((size_t)_marking_times_s.num() >= G1AdaptiveIHOPNumInitialSamples) && - ((size_t)_allocation_rate_s.num() >= G1AdaptiveIHOPNumInitialSamples); -} - -size_t G1AdaptiveIHOPControl::get_conc_mark_start_threshold() { - if (have_enough_data_for_prediction()) { - double pred_marking_time = predict(&_marking_times_s); - double pred_promotion_rate = predict(&_allocation_rate_s); - size_t pred_promotion_size = (size_t)(pred_marking_time * pred_promotion_rate); - - size_t predicted_needed_bytes_during_marking = - pred_promotion_size + - // In reality we would need the maximum size of the young gen during - // marking. This is a conservative estimate. - _last_unrestrained_young_size; - - size_t internal_threshold = actual_target_threshold(); - size_t predicted_initiating_threshold = predicted_needed_bytes_during_marking < internal_threshold ? - internal_threshold - predicted_needed_bytes_during_marking : - 0; - return predicted_initiating_threshold; - } else { - // Use the initial value. - return (size_t)(_initial_ihop_percent * _target_occupancy / 100.0); + if (_is_adaptive) { + tracer->report_adaptive_ihop_statistics(get_conc_mark_start_threshold(), + actual_target_threshold(), + non_young_occupancy, + _last_unrestrained_young_size, + predict(&_allocation_rate_s), + predict(&_marking_times_s), + have_enough_data_for_prediction()); } } - -double G1AdaptiveIHOPControl::last_mutator_period_old_allocation_rate() const { - assert(_last_allocation_time_s > 0, "This should not be called when the last GC is full"); - - return _old_gen_alloc_tracker->last_period_old_gen_growth() / _last_allocation_time_s; -} - -void G1AdaptiveIHOPControl::update_allocation_info(double allocation_time_s, - size_t additional_buffer_size) { - G1IHOPControl::update_allocation_info(allocation_time_s, additional_buffer_size); - _allocation_rate_s.add(last_mutator_period_old_allocation_rate()); - - _last_unrestrained_young_size = additional_buffer_size; -} - -void G1AdaptiveIHOPControl::update_marking_length(double marking_length_s) { - assert(marking_length_s >= 0.0, "Marking length must be larger than zero but is %.3f", marking_length_s); - _marking_times_s.add(marking_length_s); -} - -void G1AdaptiveIHOPControl::print_log(size_t non_young_occupancy) { - G1IHOPControl::print_log(non_young_occupancy); - size_t actual_threshold = actual_target_threshold(); - log_debug(gc, ihop)("Adaptive IHOP information (value update), threshold: %zuB (%1.2f), internal target threshold: %zuB, " - "non-young occupancy: %zuB, additional buffer size: %zuB, predicted old gen allocation rate: %1.2fB/s, " - "predicted marking phase length: %1.2fms, prediction active: %s", - get_conc_mark_start_threshold(), - percent_of(get_conc_mark_start_threshold(), actual_threshold), - actual_threshold, - non_young_occupancy, - _last_unrestrained_young_size, - predict(&_allocation_rate_s), - predict(&_marking_times_s) * 1000.0, - have_enough_data_for_prediction() ? "true" : "false"); -} - -void G1AdaptiveIHOPControl::send_trace_event(G1NewTracer* tracer, size_t non_young_occupancy) { - G1IHOPControl::send_trace_event(tracer, non_young_occupancy); - tracer->report_adaptive_ihop_statistics(get_conc_mark_start_threshold(), - actual_target_threshold(), - non_young_occupancy, - _last_unrestrained_young_size, - predict(&_allocation_rate_s), - predict(&_marking_times_s), - have_enough_data_for_prediction()); -} diff --git a/src/hotspot/share/gc/g1/g1IHOPControl.hpp b/src/hotspot/share/gc/g1/g1IHOPControl.hpp index 392a12a785a..b6e80d9b422 100644 --- a/src/hotspot/share/gc/g1/g1IHOPControl.hpp +++ b/src/hotspot/share/gc/g1/g1IHOPControl.hpp @@ -32,89 +32,32 @@ class G1Predictions; class G1NewTracer; -// Base class for algorithms that calculate the heap occupancy at which -// concurrent marking should start. This heap usage threshold should be relative -// to old gen size. +// Implements two strategies for calculating the concurrent mark starting occupancy threshold: +// - Static mode: Uses a fixed percentage of the target heap occupancy. +// - Adaptive mode: Predicts a threshold based on allocation rates and marking durations +// to ensure the target occupancy is never exceeded during marking. class G1IHOPControl : public CHeapObj { - protected: + private: + const bool _is_adaptive; + // The initial IHOP value relative to the target occupancy. double _initial_ihop_percent; + // The target maximum occupancy of the heap. The target occupancy is the number // of bytes when marking should be finished and reclaim started. size_t _target_occupancy; + // Percentage of maximum heap capacity we should avoid to touch + const size_t _heap_reserve_percent; + + // Percentage of free heap that should be considered as waste. + const size_t _heap_waste_percent; + // Most recent complete mutator allocation period in seconds. double _last_allocation_time_s; - const G1OldGenAllocationTracker* _old_gen_alloc_tracker; - // Initialize an instance with the old gen allocation tracker and the - // initial IHOP value in percent. The target occupancy will be updated - // at the first heap expansion. - G1IHOPControl(double ihop_percent, G1OldGenAllocationTracker const* old_gen_alloc_tracker); - - // Most recent time from the end of the concurrent start to the start of the first - // mixed gc. - virtual double last_marking_length_s() const = 0; - - virtual void print_log(size_t non_young_occupancy); - virtual void send_trace_event(G1NewTracer* tracer, size_t non_young_occupancy); - -public: - virtual ~G1IHOPControl() { } - - // Get the current non-young occupancy at which concurrent marking should start. - virtual size_t get_conc_mark_start_threshold() = 0; - - // Adjust target occupancy. - virtual void update_target_occupancy(size_t new_target_occupancy); - // Update information about time during which allocations in the Java heap occurred, - // how large these allocations were in bytes, and an additional buffer. - // The allocations should contain any amount of space made unusable for further - // allocation, e.g. any waste caused by TLAB allocation, space at the end of - // humongous objects that can not be used for allocation, etc. - // Together with the target occupancy, this additional buffer should contain the - // difference between old gen size and total heap size at the start of reclamation, - // and space required for that reclamation. - virtual void update_allocation_info(double allocation_time_s, size_t additional_buffer_size); - // Update the time spent in the mutator beginning from the end of concurrent start to - // the first mixed gc. - virtual void update_marking_length(double marking_length_s) = 0; - - void report_statistics(G1NewTracer* tracer, size_t non_young_occupancy); -}; - -// The returned concurrent mark starting occupancy threshold is a fixed value -// relative to the maximum heap size. -class G1StaticIHOPControl : public G1IHOPControl { - // Most recent mutator time between the end of concurrent mark to the start of the - // first mixed gc. - double _last_marking_length_s; - protected: - double last_marking_length_s() const { return _last_marking_length_s; } - public: - G1StaticIHOPControl(double ihop_percent, G1OldGenAllocationTracker const* old_gen_alloc_tracker); - - size_t get_conc_mark_start_threshold() { - guarantee(_target_occupancy > 0, "Target occupancy must have been initialized."); - return (size_t) (_initial_ihop_percent * _target_occupancy / 100.0); - } - - virtual void update_marking_length(double marking_length_s) { - assert(marking_length_s > 0.0, "Marking length must be larger than zero but is %.3f", marking_length_s); - _last_marking_length_s = marking_length_s; - } -}; - -// This algorithm tries to return a concurrent mark starting occupancy value that -// makes sure that during marking the given target occupancy is never exceeded, -// based on predictions of current allocation rate and time periods between -// concurrent start and the first mixed gc. -class G1AdaptiveIHOPControl : public G1IHOPControl { - size_t _heap_reserve_percent; // Percentage of maximum heap capacity we should avoid to touch - size_t _heap_waste_percent; // Percentage of free heap that should be considered as waste. - - const G1Predictions * _predictor; + const G1Predictions* _predictor; TruncatedSeq _marking_times_s; TruncatedSeq _allocation_rate_s; @@ -128,35 +71,48 @@ class G1AdaptiveIHOPControl : public G1IHOPControl { size_t _last_unrestrained_young_size; // Get a new prediction bounded below by zero from the given sequence. - double predict(TruncatedSeq const* seq) const; + double predict(const TruncatedSeq* seq) const; bool have_enough_data_for_prediction() const; + double last_marking_length_s() const; // The "actual" target threshold the algorithm wants to keep during and at the // end of marking. This is typically lower than the requested threshold, as the // algorithm needs to consider restrictions by the environment. size_t actual_target_threshold() const; - // This method calculates the old gen allocation rate based on the net survived - // bytes that are allocated in the old generation in the last mutator period. - double last_mutator_period_old_allocation_rate() const; - protected: - virtual double last_marking_length_s() const { return _marking_times_s.last(); } - - virtual void print_log(size_t non_young_occupancy); - virtual void send_trace_event(G1NewTracer* tracer, size_t non_young_occupancy); + void print_log(size_t non_young_occupancy); + void send_trace_event(G1NewTracer* tracer, size_t non_young_occupancy); public: - G1AdaptiveIHOPControl(double ihop_percent, - G1OldGenAllocationTracker const* old_gen_alloc_tracker, - G1Predictions const* predictor, - size_t heap_reserve_percent, // The percentage of total heap capacity that should not be tapped into. - size_t heap_waste_percent); // The percentage of the free space in the heap that we think is not usable for allocation. + G1IHOPControl(double ihop_percent, + const G1OldGenAllocationTracker* old_gen_alloc_tracker, + bool adaptive, + const G1Predictions* predictor, + size_t heap_reserve_percent, + size_t heap_waste_percent); - virtual size_t get_conc_mark_start_threshold(); + // Adjust target occupancy. + void update_target_occupancy(size_t new_target_occupancy); - virtual void update_allocation_info(double allocation_time_s, size_t additional_buffer_size); - virtual void update_marking_length(double marking_length_s); + // Update information about time during which allocations in the Java heap occurred, + // how large these allocations were in bytes, and an additional buffer. + // The allocations should contain any amount of space made unusable for further + // allocation, e.g. any waste caused by TLAB allocation, space at the end of + // humongous objects that can not be used for allocation, etc. + // Together with the target occupancy, this additional buffer should contain the + // difference between old gen size and total heap size at the start of reclamation, + // and space required for that reclamation. + void update_allocation_info(double allocation_time_s, size_t additional_buffer_size); + + // Update the time spent in the mutator beginning from the end of concurrent start to + // the first mixed gc. + void update_marking_length(double marking_length_s); + + // Get the current non-young occupancy at which concurrent marking should start. + size_t get_conc_mark_start_threshold(); + + void report_statistics(G1NewTracer* tracer, size_t non_young_occupancy); }; #endif // SHARE_GC_G1_G1IHOPCONTROL_HPP diff --git a/src/hotspot/share/gc/g1/g1OldGenAllocationTracker.hpp b/src/hotspot/share/gc/g1/g1OldGenAllocationTracker.hpp index 265c7029e14..aa5e3c6c942 100644 --- a/src/hotspot/share/gc/g1/g1OldGenAllocationTracker.hpp +++ b/src/hotspot/share/gc/g1/g1OldGenAllocationTracker.hpp @@ -28,8 +28,6 @@ #include "gc/g1/g1HeapRegion.hpp" #include "memory/allocation.hpp" -class G1AdaptiveIHOPControl; - // Track allocation details in the old generation. class G1OldGenAllocationTracker : public CHeapObj { // Total number of bytes allocated in the old generation at the end diff --git a/src/hotspot/share/gc/g1/g1Policy.cpp b/src/hotspot/share/gc/g1/g1Policy.cpp index 19573e11cd7..6eef6cbfa87 100644 --- a/src/hotspot/share/gc/g1/g1Policy.cpp +++ b/src/hotspot/share/gc/g1/g1Policy.cpp @@ -669,7 +669,6 @@ bool G1Policy::should_retain_evac_failed_region(uint index) const { } void G1Policy::record_pause_start_time() { - assert(!_g1h->is_shutting_down(), "Invariant!"); Ticks now = Ticks::now(); _cur_pause_start_sec = now.seconds(); @@ -1026,15 +1025,12 @@ void G1Policy::record_young_collection_end(bool concurrent_operation_is_full_mar G1IHOPControl* G1Policy::create_ihop_control(const G1OldGenAllocationTracker* old_gen_alloc_tracker, const G1Predictions* predictor) { - if (G1UseAdaptiveIHOP) { - return new G1AdaptiveIHOPControl(InitiatingHeapOccupancyPercent, - old_gen_alloc_tracker, - predictor, - G1ReservePercent, - G1HeapWastePercent); - } else { - return new G1StaticIHOPControl(InitiatingHeapOccupancyPercent, old_gen_alloc_tracker); - } + return new G1IHOPControl(InitiatingHeapOccupancyPercent, + old_gen_alloc_tracker, + G1UseAdaptiveIHOP, + predictor, + G1ReservePercent, + G1HeapWastePercent); } bool G1Policy::update_ihop_prediction(double mutator_time_s, @@ -1280,12 +1276,6 @@ void G1Policy::decide_on_concurrent_start_pause() { // concurrent start pause). assert(!collector_state()->in_concurrent_start_gc(), "pre-condition"); - // We should not be starting a concurrent start pause if the concurrent mark - // thread is terminating. - if (_g1h->is_shutting_down()) { - return; - } - if (collector_state()->initiate_conc_mark_if_possible()) { // We had noticed on a previous pause that the heap occupancy has // gone over the initiating threshold and we should start a diff --git a/src/hotspot/share/gc/g1/g1RemSet.cpp b/src/hotspot/share/gc/g1/g1RemSet.cpp index f0bacefd71c..d0633466f37 100644 --- a/src/hotspot/share/gc/g1/g1RemSet.cpp +++ b/src/hotspot/share/gc/g1/g1RemSet.cpp @@ -992,10 +992,11 @@ class G1MergeHeapRootsTask : public WorkerTask { } }; - // Closure to make sure that the marking bitmap is clear for any old region in - // the collection set. - // This is needed to be able to use the bitmap for evacuation failure handling. - class G1ClearBitmapClosure : public G1HeapRegionClosure { + // Closure to prepare the collection set regions for evacuation failure, i.e. make + // sure that the mark bitmap is clear for any old region in the collection set. + // + // These mark bitmaps record the evacuation failed objects. + class G1PrepareRegionsForEvacFailClosure : public G1HeapRegionClosure { G1CollectedHeap* _g1h; G1RemSetScanState* _scan_state; bool _initial_evacuation; @@ -1018,18 +1019,12 @@ class G1MergeHeapRootsTask : public WorkerTask { // the pause occurs during the Concurrent Cleanup for Next Mark phase. // Only at that point the region's bitmap may contain marks while being in the collection // set at the same time. - // - // There is one exception: shutdown might have aborted the Concurrent Cleanup for Next - // Mark phase midway, which might have also left stale marks in old generation regions. - // There might actually have been scheduled multiple collections, but at that point we do - // not care that much about performance and just do the work multiple times if needed. - return (_g1h->collector_state()->clear_bitmap_in_progress() || - _g1h->is_shutting_down()) && - hr->is_old(); + return _g1h->collector_state()->clear_bitmap_in_progress() && + hr->is_old(); } public: - G1ClearBitmapClosure(G1CollectedHeap* g1h, G1RemSetScanState* scan_state, bool initial_evacuation) : + G1PrepareRegionsForEvacFailClosure(G1CollectedHeap* g1h, G1RemSetScanState* scan_state, bool initial_evacuation) : _g1h(g1h), _scan_state(scan_state), _initial_evacuation(initial_evacuation) @@ -1178,8 +1173,8 @@ public: // Preparation for evacuation failure handling. { - G1ClearBitmapClosure clear(g1h, _scan_state, _initial_evacuation); - g1h->collection_set_iterate_increment_from(&clear, &_hr_claimer, worker_id); + G1PrepareRegionsForEvacFailClosure prepare_evac_failure(g1h, _scan_state, _initial_evacuation); + g1h->collection_set_iterate_increment_from(&prepare_evac_failure, &_hr_claimer, worker_id); } } }; diff --git a/src/hotspot/share/gc/parallel/parallelArguments.cpp b/src/hotspot/share/gc/parallel/parallelArguments.cpp index 629690a6258..be9673224f5 100644 --- a/src/hotspot/share/gc/parallel/parallelArguments.cpp +++ b/src/hotspot/share/gc/parallel/parallelArguments.cpp @@ -37,8 +37,45 @@ #include "utilities/defaultStream.hpp" #include "utilities/powerOfTwo.hpp" -size_t ParallelArguments::conservative_max_heap_alignment() { - return compute_heap_alignment(); +static size_t num_young_spaces() { + // When using NUMA, we create one MutableNUMASpace for each NUMA node + const size_t num_eden_spaces = UseNUMA ? os::numa_get_groups_num() : 1; + + // The young generation must have room for eden + two survivors + return num_eden_spaces + 2; +} + +static size_t num_old_spaces() { + return 1; +} + +void ParallelArguments::initialize_alignments() { + // Initialize card size before initializing alignments + CardTable::initialize_card_size(); + const size_t card_table_alignment = CardTable::ct_max_alignment_constraint(); + SpaceAlignment = ParallelScavengeHeap::default_space_alignment(); + + if (UseLargePages) { + const size_t total_spaces = num_young_spaces() + num_old_spaces(); + const size_t page_size = os::page_size_for_region_unaligned(MaxHeapSize, total_spaces); + ParallelScavengeHeap::set_desired_page_size(page_size); + + if (page_size == os::vm_page_size()) { + log_warning(gc, heap)("MaxHeapSize (%zu) must be large enough for %zu * page-size; Disabling UseLargePages for heap", + MaxHeapSize, total_spaces); + } + + if (page_size > SpaceAlignment) { + SpaceAlignment = page_size; + } + + HeapAlignment = lcm(page_size, card_table_alignment); + + } else { + assert(is_aligned(SpaceAlignment, os::vm_page_size()), ""); + ParallelScavengeHeap::set_desired_page_size(os::vm_page_size()); + HeapAlignment = card_table_alignment; + } } void ParallelArguments::initialize() { @@ -98,49 +135,36 @@ void ParallelArguments::initialize() { FullGCForwarding::initialize_flags(heap_reserved_size_bytes()); } -void ParallelArguments::initialize_alignments() { - // Initialize card size before initializing alignments - CardTable::initialize_card_size(); - SpaceAlignment = ParallelScavengeHeap::default_space_alignment(); - HeapAlignment = compute_heap_alignment(); -} +size_t ParallelArguments::conservative_max_heap_alignment() { + // The card marking array and the offset arrays for old generations are + // committed in os pages as well. Make sure they are entirely full (to + // avoid partial page problems), e.g. if 512 bytes heap corresponds to 1 + // byte entry and the os page size is 4096, the maximum heap size should + // be 512*4096 = 2MB aligned. -void ParallelArguments::initialize_heap_flags_and_sizes_one_pass() { - // Do basic sizing work - GenArguments::initialize_heap_flags_and_sizes(); -} + size_t alignment = CardTable::ct_max_alignment_constraint(); -void ParallelArguments::initialize_heap_flags_and_sizes() { - initialize_heap_flags_and_sizes_one_pass(); - - if (!UseLargePages) { - ParallelScavengeHeap::set_desired_page_size(os::vm_page_size()); - return; + if (UseLargePages) { + // In presence of large pages we have to make sure that our + // alignment is large page aware. + alignment = lcm(os::large_page_size(), alignment); } - // If using large-page, need to update SpaceAlignment so that spaces are page-size aligned. - const size_t min_pages = 4; // 1 for eden + 1 for each survivor + 1 for old - const size_t page_sz = os::page_size_for_region_aligned(MinHeapSize, min_pages); - ParallelScavengeHeap::set_desired_page_size(page_sz); - - if (page_sz == os::vm_page_size()) { - log_warning(gc, heap)("MinHeapSize (%zu) must be large enough for 4 * page-size; Disabling UseLargePages for heap", MinHeapSize); - return; - } - - // Space is largepage-aligned. - size_t new_alignment = page_sz; - if (new_alignment != SpaceAlignment) { - SpaceAlignment = new_alignment; - // Redo everything from the start - initialize_heap_flags_and_sizes_one_pass(); - } -} - -size_t ParallelArguments::heap_reserved_size_bytes() { - return MaxHeapSize; + return alignment; } CollectedHeap* ParallelArguments::create_heap() { return new ParallelScavengeHeap(); } + +size_t ParallelArguments::young_gen_size_lower_bound() { + return num_young_spaces() * SpaceAlignment; +} + +size_t ParallelArguments::old_gen_size_lower_bound() { + return num_old_spaces() * SpaceAlignment; +} + +size_t ParallelArguments::heap_reserved_size_bytes() { + return MaxHeapSize; +} diff --git a/src/hotspot/share/gc/parallel/parallelArguments.hpp b/src/hotspot/share/gc/parallel/parallelArguments.hpp index 159441be792..729fe43b879 100644 --- a/src/hotspot/share/gc/parallel/parallelArguments.hpp +++ b/src/hotspot/share/gc/parallel/parallelArguments.hpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2017, Red Hat, Inc. and/or its affiliates. + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,21 +26,16 @@ #ifndef SHARE_GC_PARALLEL_PARALLELARGUMENTS_HPP #define SHARE_GC_PARALLEL_PARALLELARGUMENTS_HPP -#include "gc/shared/gcArguments.hpp" #include "gc/shared/genArguments.hpp" -class CollectedHeap; - class ParallelArguments : public GenArguments { private: virtual void initialize_alignments(); - virtual void initialize_heap_flags_and_sizes(); - - void initialize_heap_flags_and_sizes_one_pass(); - virtual void initialize(); virtual size_t conservative_max_heap_alignment(); virtual CollectedHeap* create_heap(); + virtual size_t young_gen_size_lower_bound(); + virtual size_t old_gen_size_lower_bound(); public: static size_t heap_reserved_size_bytes(); diff --git a/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp b/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp index 747e2f3228c..3a13d0d0535 100644 --- a/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp +++ b/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp @@ -344,11 +344,6 @@ HeapWord* ParallelScavengeHeap::mem_allocate_work(size_t size, bool is_tlab) { assert(is_in_or_null(op.result()), "result not in heap"); return op.result(); } - - if (is_shutting_down()) { - stall_for_vm_shutdown(); - return nullptr; - } } // Was the gc-overhead reached inside the safepoint? If so, this mutator diff --git a/src/hotspot/share/gc/parallel/parallelScavengeHeap.hpp b/src/hotspot/share/gc/parallel/parallelScavengeHeap.hpp index 0221fd2a90e..5d8ddbcaaed 100644 --- a/src/hotspot/share/gc/parallel/parallelScavengeHeap.hpp +++ b/src/hotspot/share/gc/parallel/parallelScavengeHeap.hpp @@ -202,7 +202,6 @@ public: bool requires_barriers(stackChunkOop obj) const override; MemRegion reserved_region() const { return _reserved; } - HeapWord* base() const { return _reserved.start(); } // Memory allocation. HeapWord* mem_allocate(size_t size) override; diff --git a/src/hotspot/share/gc/serial/serialArguments.cpp b/src/hotspot/share/gc/serial/serialArguments.cpp index aed1c2353b4..efebec4fa38 100644 --- a/src/hotspot/share/gc/serial/serialArguments.cpp +++ b/src/hotspot/share/gc/serial/serialArguments.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2017, Red Hat, Inc. and/or its affiliates. + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,11 +28,49 @@ #include "gc/shared/fullGCForwarding.hpp" #include "gc/shared/gcArguments.hpp" +static size_t compute_heap_alignment() { + // The card marking array and the offset arrays for old generations are + // committed in os pages as well. Make sure they are entirely full (to + // avoid partial page problems), e.g. if 512 bytes heap corresponds to 1 + // byte entry and the os page size is 4096, the maximum heap size should + // be 512*4096 = 2MB aligned. + + size_t alignment = CardTable::ct_max_alignment_constraint(); + + if (UseLargePages) { + // In presence of large pages we have to make sure that our + // alignment is large page aware. + alignment = lcm(os::large_page_size(), alignment); + } + + return alignment; +} + +void SerialArguments::initialize_alignments() { + // Initialize card size before initializing alignments + CardTable::initialize_card_size(); + SpaceAlignment = (size_t)Generation::GenGrain; + HeapAlignment = compute_heap_alignment(); +} + void SerialArguments::initialize() { GCArguments::initialize(); FullGCForwarding::initialize_flags(MaxHeapSize); } +size_t SerialArguments::conservative_max_heap_alignment() { + return MAX2((size_t)Generation::GenGrain, compute_heap_alignment()); +} + CollectedHeap* SerialArguments::create_heap() { return new SerialHeap(); } + +size_t SerialArguments::young_gen_size_lower_bound() { + // The young generation must be aligned and have room for eden + two survivors + return 3 * SpaceAlignment; +} + +size_t SerialArguments::old_gen_size_lower_bound() { + return SpaceAlignment; +} diff --git a/src/hotspot/share/gc/serial/serialArguments.hpp b/src/hotspot/share/gc/serial/serialArguments.hpp index 90c3225ff8d..774168eb626 100644 --- a/src/hotspot/share/gc/serial/serialArguments.hpp +++ b/src/hotspot/share/gc/serial/serialArguments.hpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2017, Red Hat, Inc. and/or its affiliates. + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,12 +28,14 @@ #include "gc/shared/genArguments.hpp" -class CollectedHeap; - class SerialArguments : public GenArguments { private: + virtual void initialize_alignments(); virtual void initialize(); + virtual size_t conservative_max_heap_alignment(); virtual CollectedHeap* create_heap(); + virtual size_t young_gen_size_lower_bound(); + virtual size_t old_gen_size_lower_bound(); }; #endif // SHARE_GC_SERIAL_SERIALARGUMENTS_HPP diff --git a/src/hotspot/share/gc/serial/serialHeap.cpp b/src/hotspot/share/gc/serial/serialHeap.cpp index 00d74e691eb..03ad1282f5f 100644 --- a/src/hotspot/share/gc/serial/serialHeap.cpp +++ b/src/hotspot/share/gc/serial/serialHeap.cpp @@ -337,11 +337,6 @@ HeapWord* SerialHeap::mem_allocate_work(size_t size, bool is_tlab) { break; } - if (is_shutting_down()) { - stall_for_vm_shutdown(); - return nullptr; - } - // Give a warning if we seem to be looping forever. if ((QueuedAllocationWarningCount > 0) && (try_count % QueuedAllocationWarningCount == 0)) { diff --git a/src/hotspot/share/gc/shared/bufferNode.hpp b/src/hotspot/share/gc/shared/bufferNode.hpp index a453bbc964b..e4e2ff23fb1 100644 --- a/src/hotspot/share/gc/shared/bufferNode.hpp +++ b/src/hotspot/share/gc/shared/bufferNode.hpp @@ -27,6 +27,7 @@ #include "cppstdlib/limits.hpp" #include "gc/shared/freeListAllocator.hpp" +#include "runtime/atomic.hpp" #include "utilities/debug.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/lockFreeStack.hpp" @@ -38,7 +39,7 @@ class BufferNode { InternalSizeType _index; InternalSizeType _capacity; - BufferNode* volatile _next; + Atomic _next; void* _buffer[1]; // Pseudo flexible array member. BufferNode(InternalSizeType capacity) @@ -58,11 +59,11 @@ public: return std::numeric_limits::max(); } - static BufferNode* volatile* next_ptr(BufferNode& bn) { return &bn._next; } + static Atomic* next_ptr(BufferNode& bn) { return &bn._next; } typedef LockFreeStack Stack; - BufferNode* next() const { return _next; } - void set_next(BufferNode* n) { _next = n; } + BufferNode* next() const { return _next.load_relaxed(); } + void set_next(BufferNode* n) { _next.store_relaxed(n); } size_t index() const { return _index; } void set_index(size_t i) { diff --git a/src/hotspot/share/gc/shared/collectedHeap.cpp b/src/hotspot/share/gc/shared/collectedHeap.cpp index c8dd39e72be..a59ea3745ab 100644 --- a/src/hotspot/share/gc/shared/collectedHeap.cpp +++ b/src/hotspot/share/gc/shared/collectedHeap.cpp @@ -62,12 +62,14 @@ class ClassLoaderData; +bool CollectedHeap::_is_shutting_down = false; + size_t CollectedHeap::_lab_alignment_reserve = SIZE_MAX; Klass* CollectedHeap::_filler_object_klass = nullptr; size_t CollectedHeap::_filler_array_max_size = 0; size_t CollectedHeap::_stack_chunk_max_size = 0; -class GCLogMessage : public FormatBuffer<512> {}; +class GCLogMessage : public FormatBuffer<1024> {}; template <> void EventLogBase::print(outputStream* st, GCLogMessage& m) { @@ -377,8 +379,7 @@ MetaWord* CollectedHeap::satisfy_failed_metadata_allocation(ClassLoaderData* loa word_size, mdtype, gc_count, - full_gc_count, - GCCause::_metadata_GC_threshold); + full_gc_count); VMThread::execute(&op); @@ -386,11 +387,6 @@ MetaWord* CollectedHeap::satisfy_failed_metadata_allocation(ClassLoaderData* loa return op.result(); } - if (is_shutting_down()) { - stall_for_vm_shutdown(); - return nullptr; - } - loop_count++; if ((QueuedAllocationWarningCount > 0) && (loop_count % QueuedAllocationWarningCount == 0)) { @@ -605,30 +601,20 @@ void CollectedHeap::post_initialize() { initialize_serviceability(); } -bool CollectedHeap::is_shutting_down() const { - return Universe::is_shutting_down(); +bool CollectedHeap::is_shutting_down() { + assert(Heap_lock->owned_by_self(), "Protected by this lock"); + return _is_shutting_down; } -void CollectedHeap::stall_for_vm_shutdown() { - assert(is_shutting_down(), "Precondition"); - // Stall the thread (2 seconds) instead of an indefinite wait to avoid deadlock - // if the VM shutdown triggers a GC. - // The 2-seconds sleep is: - // - long enough to keep daemon threads stalled, while the shutdown - // sequence completes in the common case. - // - short enough to avoid excessive stall time if the shutdown itself - // triggers a GC. - JavaThread::current()->sleep(2 * MILLIUNITS); +void CollectedHeap::initiate_shutdown() { + { + // Acquire the Heap_lock to synchronize with VM_Heap_Sync_Operations, + // which may depend on the value of _is_shutting_down flag. + MutexLocker hl(Heap_lock); + _is_shutting_down = true; + } - ResourceMark rm; - log_warning(gc, alloc)("%s: Stall for VM-Shutdown timed out; allocation may fail with OOME", Thread::current()->name()); -} - -void CollectedHeap::before_exit() { print_tracing_info(); - - // Stop any on-going concurrent work and prepare for exit. - stop(); } size_t CollectedHeap::bootstrap_max_memory() const { diff --git a/src/hotspot/share/gc/shared/collectedHeap.hpp b/src/hotspot/share/gc/shared/collectedHeap.hpp index 6be0057480d..6f335b1cdf4 100644 --- a/src/hotspot/share/gc/shared/collectedHeap.hpp +++ b/src/hotspot/share/gc/shared/collectedHeap.hpp @@ -96,6 +96,8 @@ class CollectedHeap : public CHeapObj { friend class MemAllocator; private: + static bool _is_shutting_down; + GCHeapLog* _heap_log; GCMetaspaceLog* _metaspace_log; @@ -209,11 +211,10 @@ protected: // Default implementation does nothing. virtual void print_tracing_info() const = 0; + public: // Stop any onging concurrent work and prepare for exit. virtual void stop() = 0; - public: - static inline size_t filler_array_max_size() { return _filler_array_max_size; } @@ -245,14 +246,9 @@ protected: // This is the correct place to place such initialization methods. virtual void post_initialize(); - bool is_shutting_down() const; + static bool is_shutting_down(); - // If the VM is shutting down, we may have skipped VM_CollectForAllocation. - // In this case, stall the allocation request briefly in the hope that - // the VM shutdown completes before the allocation request returns. - void stall_for_vm_shutdown(); - - void before_exit(); + void initiate_shutdown(); // Stop and resume concurrent GC threads interfering with safepoint operations virtual void safepoint_synchronize_begin() {} diff --git a/src/hotspot/share/gc/shared/freeListAllocator.cpp b/src/hotspot/share/gc/shared/freeListAllocator.cpp index c6801c2be18..990bf88aade 100644 --- a/src/hotspot/share/gc/shared/freeListAllocator.cpp +++ b/src/hotspot/share/gc/shared/freeListAllocator.cpp @@ -41,26 +41,26 @@ FreeListAllocator::PendingList::PendingList() : size_t FreeListAllocator::PendingList::add(FreeNode* node) { assert(node->next() == nullptr, "precondition"); - FreeNode* old_head = AtomicAccess::xchg(&_head, node); + FreeNode* old_head = _head.exchange(node); if (old_head != nullptr) { node->set_next(old_head); } else { assert(_tail == nullptr, "invariant"); _tail = node; } - return AtomicAccess::add(&_count, size_t(1)); + return _count.add_then_fetch(1u); } typename FreeListAllocator::NodeList FreeListAllocator::PendingList::take_all() { - NodeList result{AtomicAccess::load(&_head), _tail, AtomicAccess::load(&_count)}; - AtomicAccess::store(&_head, (FreeNode*)nullptr); + NodeList result{_head.load_relaxed(), _tail, _count.load_relaxed()}; + _head.store_relaxed(nullptr); _tail = nullptr; - AtomicAccess::store(&_count, size_t(0)); + _count.store_relaxed(0u); return result; } size_t FreeListAllocator::PendingList::count() const { - return AtomicAccess::load(&_count); + return _count.load_relaxed(); } FreeListAllocator::FreeListAllocator(const char* name, FreeListConfig* config) : @@ -85,7 +85,7 @@ void FreeListAllocator::delete_list(FreeNode* list) { } FreeListAllocator::~FreeListAllocator() { - uint index = AtomicAccess::load(&_active_pending_list); + uint index = _active_pending_list.load_relaxed(); NodeList pending_list = _pending_lists[index].take_all(); delete_list(pending_list._head); delete_list(_free_list.pop_all()); @@ -93,18 +93,18 @@ FreeListAllocator::~FreeListAllocator() { // Drop existing nodes and reset all counters void FreeListAllocator::reset() { - uint index = AtomicAccess::load(&_active_pending_list); + uint index = _active_pending_list.load_relaxed(); _pending_lists[index].take_all(); _free_list.pop_all(); - _free_count = 0; + _free_count.store_relaxed(0u); } size_t FreeListAllocator::free_count() const { - return AtomicAccess::load(&_free_count); + return _free_count.load_relaxed(); } size_t FreeListAllocator::pending_count() const { - uint index = AtomicAccess::load(&_active_pending_list); + uint index = _active_pending_list.load_relaxed(); return _pending_lists[index].count(); } @@ -124,7 +124,7 @@ void* FreeListAllocator::allocate() { // Decrement count after getting buffer from free list. This, along // with incrementing count before adding to free list, ensures count // never underflows. - size_t count = AtomicAccess::sub(&_free_count, 1u); + size_t count = _free_count.sub_then_fetch(1u); assert((count + 1) != 0, "_free_count underflow"); return node; } else { @@ -149,7 +149,7 @@ void FreeListAllocator::release(void* free_node) { // we're done with what might be the pending list to be transferred. { GlobalCounter::CriticalSection cs(Thread::current()); - uint index = AtomicAccess::load_acquire(&_active_pending_list); + uint index = _active_pending_list.load_acquire(); size_t count = _pending_lists[index].add(node); if (count <= _config->transfer_threshold()) return; } @@ -164,17 +164,17 @@ void FreeListAllocator::release(void* free_node) { // in-progress transfer. bool FreeListAllocator::try_transfer_pending() { // Attempt to claim the lock. - if (AtomicAccess::load(&_transfer_lock) || // Skip CAS if likely to fail. - AtomicAccess::cmpxchg(&_transfer_lock, false, true)) { + if (_transfer_lock.load_relaxed() || // Skip CAS if likely to fail. + _transfer_lock.compare_exchange(false, true)) { return false; } // Have the lock; perform the transfer. // Change which pending list is active. Don't need an atomic RMW since // we have the lock and we're the only writer. - uint index = AtomicAccess::load(&_active_pending_list); + uint index = _active_pending_list.load_relaxed(); uint new_active = (index + 1) % ARRAY_SIZE(_pending_lists); - AtomicAccess::release_store(&_active_pending_list, new_active); + _active_pending_list.release_store(new_active); // Wait for all critical sections in the buffer life-cycle to complete. // This includes _free_list pops and adding to the now inactive pending @@ -186,11 +186,11 @@ bool FreeListAllocator::try_transfer_pending() { size_t count = transfer_list._entry_count; if (count > 0) { // Update count first so no underflow in allocate(). - AtomicAccess::add(&_free_count, count); + _free_count.add_then_fetch(count); _free_list.prepend(*transfer_list._head, *transfer_list._tail); log_trace(gc, freelist) ("Transferred %s pending to free: %zu", name(), count); } - AtomicAccess::release_store(&_transfer_lock, false); + _transfer_lock.release_store(false); return true; } diff --git a/src/hotspot/share/gc/shared/freeListAllocator.hpp b/src/hotspot/share/gc/shared/freeListAllocator.hpp index 07e075a6725..dd163f0fe67 100644 --- a/src/hotspot/share/gc/shared/freeListAllocator.hpp +++ b/src/hotspot/share/gc/shared/freeListAllocator.hpp @@ -27,7 +27,7 @@ #include "memory/allocation.hpp" #include "memory/padded.hpp" -#include "runtime/atomicAccess.hpp" +#include "runtime/atomic.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/lockFreeStack.hpp" @@ -62,15 +62,15 @@ public: // to the free list making them available for re-allocation. class FreeListAllocator { struct FreeNode { - FreeNode* volatile _next; + Atomic _next; FreeNode() : _next (nullptr) { } - FreeNode* next() { return AtomicAccess::load(&_next); } + FreeNode* next() { return _next.load_relaxed(); } - FreeNode* volatile* next_addr() { return &_next; } + Atomic* next_addr() { return &_next; } - void set_next(FreeNode* next) { AtomicAccess::store(&_next, next); } + void set_next(FreeNode* next) { _next.store_relaxed(next); } }; struct NodeList { @@ -85,8 +85,8 @@ class FreeListAllocator { class PendingList { FreeNode* _tail; - FreeNode* volatile _head; - volatile size_t _count; + Atomic _head; + Atomic _count; NONCOPYABLE(PendingList); @@ -105,20 +105,20 @@ class FreeListAllocator { NodeList take_all(); }; - static FreeNode* volatile* next_ptr(FreeNode& node) { return node.next_addr(); } - typedef LockFreeStack Stack; + static Atomic* next_ptr(FreeNode& node) { return node.next_addr(); } + using Stack = LockFreeStack; FreeListConfig* _config; char _name[DEFAULT_PADDING_SIZE - sizeof(FreeListConfig*)]; // Use name as padding. #define DECLARE_PADDED_MEMBER(Id, Type, Name) \ Type Name; DEFINE_PAD_MINUS_SIZE(Id, DEFAULT_PADDING_SIZE, sizeof(Type)) - DECLARE_PADDED_MEMBER(1, volatile size_t, _free_count); + DECLARE_PADDED_MEMBER(1, Atomic, _free_count); DECLARE_PADDED_MEMBER(2, Stack, _free_list); - DECLARE_PADDED_MEMBER(3, volatile bool, _transfer_lock); + DECLARE_PADDED_MEMBER(3, Atomic, _transfer_lock); #undef DECLARE_PADDED_MEMBER - volatile uint _active_pending_list; + Atomic _active_pending_list; PendingList _pending_lists[2]; void delete_list(FreeNode* list); diff --git a/src/hotspot/share/gc/shared/gcArguments.cpp b/src/hotspot/share/gc/shared/gcArguments.cpp index d45e6a9c7dd..424427c12b6 100644 --- a/src/hotspot/share/gc/shared/gcArguments.cpp +++ b/src/hotspot/share/gc/shared/gcArguments.cpp @@ -62,24 +62,6 @@ void GCArguments::initialize_heap_sizes() { initialize_size_info(); } -size_t GCArguments::compute_heap_alignment() { - // The card marking array and the offset arrays for old generations are - // committed in os pages as well. Make sure they are entirely full (to - // avoid partial page problems), e.g. if 512 bytes heap corresponds to 1 - // byte entry and the os page size is 4096, the maximum heap size should - // be 512*4096 = 2MB aligned. - - size_t alignment = CardTable::ct_max_alignment_constraint(); - - if (UseLargePages) { - // In presence of large pages we have to make sure that our - // alignment is large page aware. - alignment = lcm(os::large_page_size(), alignment); - } - - return alignment; -} - #ifdef ASSERT void GCArguments::assert_flags() { assert(InitialHeapSize <= MaxHeapSize, "Ergonomics decided on incompatible initial and maximum heap sizes"); diff --git a/src/hotspot/share/gc/shared/gcArguments.hpp b/src/hotspot/share/gc/shared/gcArguments.hpp index fff41e85d8c..d8a4901f887 100644 --- a/src/hotspot/share/gc/shared/gcArguments.hpp +++ b/src/hotspot/share/gc/shared/gcArguments.hpp @@ -45,6 +45,8 @@ protected: public: virtual void initialize(); + + // Return the (conservative) maximum heap alignment virtual size_t conservative_max_heap_alignment() = 0; // Used by heap size heuristics to determine max @@ -59,8 +61,6 @@ public: } void initialize_heap_sizes(); - - static size_t compute_heap_alignment(); }; #endif // SHARE_GC_SHARED_GCARGUMENTS_HPP diff --git a/src/hotspot/share/gc/shared/gcLogPrecious.cpp b/src/hotspot/share/gc/shared/gcLogPrecious.cpp index 43bd58db1aa..d556eed1b69 100644 --- a/src/hotspot/share/gc/shared/gcLogPrecious.cpp +++ b/src/hotspot/share/gc/shared/gcLogPrecious.cpp @@ -25,6 +25,7 @@ #include "runtime/mutex.hpp" #include "runtime/mutexLocker.hpp" #include "runtime/os.hpp" +#include "runtime/thread.hpp" #include "utilities/ostream.hpp" stringStream* GCLogPrecious::_lines = nullptr; @@ -83,7 +84,8 @@ void GCLogPrecious::print_on_error(outputStream* st) { return; } - if (!_lock->try_lock_without_rank_check()) { + if (Thread::current_or_null_safe() == nullptr || + !_lock->try_lock_without_rank_check()) { st->print_cr("\n"); return; } diff --git a/src/hotspot/share/gc/shared/gcVMOperations.cpp b/src/hotspot/share/gc/shared/gcVMOperations.cpp index 36aa0c9843d..6dbfd56b4e9 100644 --- a/src/hotspot/share/gc/shared/gcVMOperations.cpp +++ b/src/hotspot/share/gc/shared/gcVMOperations.cpp @@ -92,6 +92,22 @@ static bool should_use_gclocker() { return UseSerialGC || UseParallelGC; } +static void block_if_java_thread() { + Thread* thread = Thread::current(); + if (thread->is_Java_thread()) { + // Block here and allow the shutdown to complete + while (true) { + // The call to wait has a few important effects: + // 1) Block forever (minus spurious wake-ups, hence the loop) + // 2) Release the Heap_lock, which is taken by the shutdown code + // 3) Transition to blocked state so that the final VM_Exit operation can be scheduled + Heap_lock->wait(); + } + } else { + assert(thread->is_ConcurrentGC_thread(), "Unexpected thread type"); + } +} + bool VM_GC_Operation::doit_prologue() { assert(_gc_cause != GCCause::_no_gc, "Illegal GCCause"); @@ -110,8 +126,15 @@ bool VM_GC_Operation::doit_prologue() { } VM_Heap_Sync_Operation::doit_prologue(); + _is_shutting_down = CollectedHeap::is_shutting_down(); + if (_is_shutting_down) { + // Block forever if a Java thread is triggering a GC after + // the GC has started to shut down. + block_if_java_thread(); + } + // Check invocations - if (skip_operation() || Universe::is_shutting_down()) { + if (skip_operation() || _is_shutting_down) { // skip collection Heap_lock->unlock(); if (should_use_gclocker()) { @@ -197,9 +220,8 @@ VM_CollectForMetadataAllocation::VM_CollectForMetadataAllocation(ClassLoaderData size_t size, Metaspace::MetadataType mdtype, uint gc_count_before, - uint full_gc_count_before, - GCCause::Cause gc_cause) - : VM_GC_Collect_Operation(gc_count_before, gc_cause, full_gc_count_before, true), + uint full_gc_count_before) + : VM_GC_Collect_Operation(gc_count_before, GCCause::_metadata_GC_threshold, full_gc_count_before, true), _result(nullptr), _size(size), _mdtype(mdtype), _loader_data(loader_data) { assert(_size != 0, "An allocation should always be requested with this operation."); AllocTracer::send_allocation_requiring_gc_event(_size * HeapWordSize, GCId::peek()); @@ -208,8 +230,11 @@ VM_CollectForMetadataAllocation::VM_CollectForMetadataAllocation(ClassLoaderData void VM_CollectForMetadataAllocation::doit() { SvcGCMarker sgcm(SvcGCMarker::FULL); - CollectedHeap* heap = Universe::heap(); - GCCauseSetter gccs(heap, _gc_cause); + // Note: GCCauseSetter is intentionally not used here. + // The specific GC cause is set directly in downstream calls that initiate + // collections, allowing us to accurately reflect different situations: + // - A typical metadata allocation failure triggers a collection. + // - As a last resort, a collection clears soft references if prior attempts fail. // Check again if the space is available. Another thread // may have similarly failed a metadata allocation and induced @@ -232,8 +257,10 @@ void VM_CollectForMetadataAllocation::doit() { } #endif + CollectedHeap* heap = Universe::heap(); + // Don't clear the soft refs yet. - heap->collect_as_vm_thread(GCCause::_metadata_GC_threshold); + heap->collect_as_vm_thread(_gc_cause); // After a GC try to allocate without expanding. Could fail // and expansion will be tried below. _result = _loader_data->metaspace_non_null()->allocate(_size, _mdtype); diff --git a/src/hotspot/share/gc/shared/gcVMOperations.hpp b/src/hotspot/share/gc/shared/gcVMOperations.hpp index 5048bc3c1ed..a9aee2faf5d 100644 --- a/src/hotspot/share/gc/shared/gcVMOperations.hpp +++ b/src/hotspot/share/gc/shared/gcVMOperations.hpp @@ -110,23 +110,23 @@ class VM_GC_Operation: public VM_Heap_Sync_Operation { uint _full_gc_count_before; // full gc count before acquiring the Heap_lock bool _full; // whether a "full" collection bool _prologue_succeeded; // whether doit_prologue succeeded + bool _is_shutting_down; // whether the operation found that the GC is shutting down GCCause::Cause _gc_cause; // the putative cause for this gc op virtual bool skip_operation() const; public: VM_GC_Operation(uint gc_count_before, - GCCause::Cause _cause, + GCCause::Cause cause, uint full_gc_count_before, - bool full) : VM_Heap_Sync_Operation() { - _full = full; - _prologue_succeeded = false; - _gc_count_before = gc_count_before; - - _gc_cause = _cause; - - _full_gc_count_before = full_gc_count_before; - } + bool full) + : VM_Heap_Sync_Operation(), + _gc_count_before(gc_count_before), + _full_gc_count_before(full_gc_count_before), + _full(full), + _prologue_succeeded(false), + _is_shutting_down(false), + _gc_cause(cause) {} virtual const char* cause() const; @@ -139,6 +139,14 @@ class VM_GC_Operation: public VM_Heap_Sync_Operation { virtual bool allow_nested_vm_operations() const { return true; } virtual bool gc_succeeded() const { return _prologue_succeeded; } + // This function returns the value of CollectedHeap::is_shutting_down() that + // was recorded in the prologue. Unlike CollectedHeap::is_shutting_down(), + // this function can be called without acquiring the Heap_lock. + // + // This function exists so that code that tries to schedule a GC operation + // can check if it was refused because the JVM is about to shut down. + bool is_shutting_down() const { return _is_shutting_down; } + static void notify_gc_begin(bool full = false); static void notify_gc_end(); }; @@ -214,8 +222,7 @@ class VM_CollectForMetadataAllocation: public VM_GC_Collect_Operation { size_t size, Metaspace::MetadataType mdtype, uint gc_count_before, - uint full_gc_count_before, - GCCause::Cause gc_cause); + uint full_gc_count_before); virtual VMOp_Type type() const { return VMOp_CollectForMetadataAllocation; } virtual void doit(); diff --git a/src/hotspot/share/gc/shared/genArguments.cpp b/src/hotspot/share/gc/shared/genArguments.cpp index 9618c515b7d..5d5003f8d9f 100644 --- a/src/hotspot/share/gc/shared/genArguments.cpp +++ b/src/hotspot/share/gc/shared/genArguments.cpp @@ -42,17 +42,6 @@ size_t MaxOldSize = 0; // See more in JDK-8346005 size_t OldSize = ScaleForWordSize(4*M); -size_t GenArguments::conservative_max_heap_alignment() { return (size_t)Generation::GenGrain; } - -static size_t young_gen_size_lower_bound() { - // The young generation must be aligned and have room for eden + two survivors - return 3 * SpaceAlignment; -} - -static size_t old_gen_size_lower_bound() { - return SpaceAlignment; -} - size_t GenArguments::scale_by_NewRatio_aligned(size_t base_size, size_t alignment) { return align_down_bounded(base_size / (NewRatio + 1), alignment); } @@ -64,13 +53,6 @@ static size_t bound_minus_alignment(size_t desired_size, return MIN2(desired_size, max_minus); } -void GenArguments::initialize_alignments() { - // Initialize card size before initializing alignments - CardTable::initialize_card_size(); - SpaceAlignment = (size_t)Generation::GenGrain; - HeapAlignment = compute_heap_alignment(); -} - void GenArguments::initialize_heap_flags_and_sizes() { GCArguments::initialize_heap_flags_and_sizes(); diff --git a/src/hotspot/share/gc/shared/genArguments.hpp b/src/hotspot/share/gc/shared/genArguments.hpp index 80133bd1ec1..0ff9568575d 100644 --- a/src/hotspot/share/gc/shared/genArguments.hpp +++ b/src/hotspot/share/gc/shared/genArguments.hpp @@ -38,17 +38,16 @@ extern size_t OldSize; class GenArguments : public GCArguments { friend class TestGenCollectorPolicy; // Testing private: - virtual void initialize_alignments(); virtual void initialize_size_info(); - // Return the (conservative) maximum heap alignment - virtual size_t conservative_max_heap_alignment(); - DEBUG_ONLY(void assert_flags();) DEBUG_ONLY(void assert_size_info();) static size_t scale_by_NewRatio_aligned(size_t base_size, size_t alignment); + virtual size_t young_gen_size_lower_bound() = 0; + virtual size_t old_gen_size_lower_bound() = 0; + protected: virtual void initialize_heap_flags_and_sizes(); }; diff --git a/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.cpp b/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.cpp index 1ed3701fdab..ea3d644d105 100644 --- a/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.cpp +++ b/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.cpp @@ -250,7 +250,7 @@ static JVMFlag::Error MaxSizeForHeapAlignment(const char* name, size_t value, bo } else #endif { - heap_alignment = GCArguments::compute_heap_alignment(); + heap_alignment = Arguments::conservative_max_heap_alignment(); } return MaxSizeForAlignment(name, value, heap_alignment, verbose); @@ -285,7 +285,7 @@ JVMFlag::Error SoftMaxHeapSizeConstraintFunc(size_t value, bool verbose) { JVMFlag::Error HeapBaseMinAddressConstraintFunc(size_t value, bool verbose) { // If an overflow happened in Arguments::set_heap_size(), MaxHeapSize will have too large a value. // Check for this by ensuring that MaxHeapSize plus the requested min base address still fit within max_uintx. - if (UseCompressedOops && FLAG_IS_ERGO(MaxHeapSize) && (value > (max_uintx - MaxHeapSize))) { + if (value > (max_uintx - MaxHeapSize)) { JVMFlag::printError(verbose, "HeapBaseMinAddress (%zu) or MaxHeapSize (%zu) is too large. " "Sum of them must be less than or equal to maximum of size_t (%zu)\n", diff --git a/src/hotspot/share/gc/shared/partialArrayState.cpp b/src/hotspot/share/gc/shared/partialArrayState.cpp index 39c1fe4fc78..6f714d48a35 100644 --- a/src/hotspot/share/gc/shared/partialArrayState.cpp +++ b/src/hotspot/share/gc/shared/partialArrayState.cpp @@ -47,7 +47,7 @@ PartialArrayState::PartialArrayState(oop src, oop dst, } void PartialArrayState::add_references(size_t count) { - size_t new_count = AtomicAccess::add(&_refcount, count, memory_order_relaxed); + size_t new_count = _refcount.add_then_fetch(count, memory_order_relaxed); assert(new_count >= count, "reference count overflow"); } @@ -92,7 +92,7 @@ PartialArrayState* PartialArrayStateAllocator::allocate(oop src, oop dst, } void PartialArrayStateAllocator::release(PartialArrayState* state) { - size_t refcount = AtomicAccess::sub(&state->_refcount, size_t(1), memory_order_release); + size_t refcount = state->_refcount.sub_then_fetch(1u, memory_order_release); if (refcount != 0) { assert(refcount + 1 != 0, "refcount underflow"); } else { @@ -116,25 +116,25 @@ PartialArrayStateManager::~PartialArrayStateManager() { } Arena* PartialArrayStateManager::register_allocator() { - uint idx = AtomicAccess::fetch_then_add(&_registered_allocators, 1u, memory_order_relaxed); + uint idx = _registered_allocators.fetch_then_add(1u, memory_order_relaxed); assert(idx < _max_allocators, "exceeded configured max number of allocators"); return ::new (&_arenas[idx]) Arena(mtGC); } #ifdef ASSERT void PartialArrayStateManager::release_allocator() { - uint old = AtomicAccess::fetch_then_add(&_released_allocators, 1u, memory_order_relaxed); - assert(old < AtomicAccess::load(&_registered_allocators), "too many releases"); + uint old = _released_allocators.fetch_then_add(1u, memory_order_relaxed); + assert(old < _registered_allocators.load_relaxed(), "too many releases"); } #endif // ASSERT void PartialArrayStateManager::reset() { - uint count = AtomicAccess::load(&_registered_allocators); - assert(count == AtomicAccess::load(&_released_allocators), + uint count = _registered_allocators.load_relaxed(); + assert(count == _released_allocators.load_relaxed(), "some allocators still active"); for (uint i = 0; i < count; ++i) { _arenas[i].~Arena(); } - AtomicAccess::store(&_registered_allocators, 0u); - DEBUG_ONLY(AtomicAccess::store(&_released_allocators, 0u);) + _registered_allocators.store_relaxed(0u); + DEBUG_ONLY(_released_allocators.store_relaxed(0u);) } diff --git a/src/hotspot/share/gc/shared/partialArrayState.hpp b/src/hotspot/share/gc/shared/partialArrayState.hpp index 3208c6d6807..3dafeb0f14c 100644 --- a/src/hotspot/share/gc/shared/partialArrayState.hpp +++ b/src/hotspot/share/gc/shared/partialArrayState.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,6 +27,7 @@ #include "memory/allocation.hpp" #include "oops/oopsHierarchy.hpp" +#include "runtime/atomic.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/macros.hpp" @@ -60,8 +61,8 @@ class PartialArrayState { oop _source; oop _destination; size_t _length; - volatile size_t _index; - volatile size_t _refcount; + Atomic _index; + Atomic _refcount; friend class PartialArrayStateAllocator; @@ -90,7 +91,7 @@ public: // A pointer to the start index for the next segment to process, for atomic // update. - volatile size_t* index_addr() { return &_index; } + Atomic* index_addr() { return &_index; } }; // This class provides memory management for PartialArrayStates. @@ -178,8 +179,8 @@ class PartialArrayStateManager : public CHeapObj { // The number of allocators that have been registered/released. // Atomic to support concurrent registration, and concurrent release. // Phasing restriction forbids registration concurrent with release. - volatile uint _registered_allocators; - DEBUG_ONLY(volatile uint _released_allocators;) + Atomic _registered_allocators; + DEBUG_ONLY(Atomic _released_allocators;) // These are all for sole use of the befriended allocator class. Arena* register_allocator(); diff --git a/src/hotspot/share/gc/shared/partialArrayTaskStepper.hpp b/src/hotspot/share/gc/shared/partialArrayTaskStepper.hpp index a68d9bd3612..11499ca2ffe 100644 --- a/src/hotspot/share/gc/shared/partialArrayTaskStepper.hpp +++ b/src/hotspot/share/gc/shared/partialArrayTaskStepper.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,6 +26,7 @@ #define SHARE_GC_SHARED_PARTIALARRAYTASKSTEPPER_HPP #include "oops/arrayOop.hpp" +#include "runtime/atomic.hpp" #include "utilities/globalDefinitions.hpp" class PartialArrayState; @@ -73,7 +74,7 @@ private: uint _task_fanout; // For unit tests. - inline Step next_impl(size_t length, volatile size_t* index_addr) const; + inline Step next_impl(size_t length, Atomic* index_addr) const; }; #endif // SHARE_GC_SHARED_PARTIALARRAYTASKSTEPPER_HPP diff --git a/src/hotspot/share/gc/shared/partialArrayTaskStepper.inline.hpp b/src/hotspot/share/gc/shared/partialArrayTaskStepper.inline.hpp index 3693abaf8cf..aaa86e2de16 100644 --- a/src/hotspot/share/gc/shared/partialArrayTaskStepper.inline.hpp +++ b/src/hotspot/share/gc/shared/partialArrayTaskStepper.inline.hpp @@ -46,15 +46,13 @@ PartialArrayTaskStepper::start(size_t length) const { } PartialArrayTaskStepper::Step -PartialArrayTaskStepper::next_impl(size_t length, volatile size_t* index_addr) const { +PartialArrayTaskStepper::next_impl(size_t length, Atomic* index_addr) const { // The start of the next task is in the state's index. // Atomically increment by the chunk size to claim the associated chunk. // Because we limit the number of enqueued tasks to being no more than the // number of remaining chunks to process, we can use an atomic add for the // claim, rather than a CAS loop. - size_t start = AtomicAccess::fetch_then_add(index_addr, - _chunk_size, - memory_order_relaxed); + size_t start = index_addr->fetch_then_add(_chunk_size, memory_order_relaxed); assert(start < length, "invariant: start %zu, length %zu", start, length); assert(((length - start) % _chunk_size) == 0, diff --git a/src/hotspot/share/gc/shared/satbMarkQueue.cpp b/src/hotspot/share/gc/shared/satbMarkQueue.cpp index e6ffe39facf..93c52b499a0 100644 --- a/src/hotspot/share/gc/shared/satbMarkQueue.cpp +++ b/src/hotspot/share/gc/shared/satbMarkQueue.cpp @@ -27,7 +27,6 @@ #include "logging/log.hpp" #include "memory/allocation.inline.hpp" #include "oops/oop.inline.hpp" -#include "runtime/atomicAccess.hpp" #include "runtime/mutexLocker.hpp" #include "runtime/os.hpp" #include "runtime/safepoint.hpp" @@ -85,28 +84,28 @@ SATBMarkQueueSet::~SATBMarkQueueSet() { // remains set until the count is reduced to zero. // Increment count. If count > threshold, set flag, else maintain flag. -static void increment_count(volatile size_t* cfptr, size_t threshold) { +static void increment_count(Atomic* cfptr, size_t threshold) { size_t old; - size_t value = AtomicAccess::load(cfptr); + size_t value = cfptr->load_relaxed(); do { old = value; value += 2; assert(value > old, "overflow"); if (value > threshold) value |= 1; - value = AtomicAccess::cmpxchg(cfptr, old, value); + value = cfptr->compare_exchange(old, value); } while (value != old); } // Decrement count. If count == 0, clear flag, else maintain flag. -static void decrement_count(volatile size_t* cfptr) { +static void decrement_count(Atomic* cfptr) { size_t old; - size_t value = AtomicAccess::load(cfptr); + size_t value = cfptr->load_relaxed(); do { assert((value >> 1) != 0, "underflow"); old = value; value -= 2; if (value <= 1) value = 0; - value = AtomicAccess::cmpxchg(cfptr, old, value); + value = cfptr->compare_exchange(old, value); } while (value != old); } @@ -332,7 +331,7 @@ void SATBMarkQueueSet::print_all(const char* msg) { #endif // PRODUCT void SATBMarkQueueSet::abandon_completed_buffers() { - AtomicAccess::store(&_count_and_process_flag, size_t(0)); + _count_and_process_flag.store_relaxed(0u); BufferNode* buffers_to_delete = _list.pop_all(); while (buffers_to_delete != nullptr) { BufferNode* bn = buffers_to_delete; diff --git a/src/hotspot/share/gc/shared/satbMarkQueue.hpp b/src/hotspot/share/gc/shared/satbMarkQueue.hpp index e40b2a3ecf3..d2b14a3cc92 100644 --- a/src/hotspot/share/gc/shared/satbMarkQueue.hpp +++ b/src/hotspot/share/gc/shared/satbMarkQueue.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,6 +29,7 @@ #include "memory/allocation.hpp" #include "memory/padded.hpp" #include "oops/oopsHierarchy.hpp" +#include "runtime/atomic.hpp" class Thread; class Monitor; @@ -87,7 +88,7 @@ class SATBMarkQueueSet: public PtrQueueSet { DEFINE_PAD_MINUS_SIZE(1, DEFAULT_PADDING_SIZE, 0); PaddedEnd _list; - volatile size_t _count_and_process_flag; + Atomic _count_and_process_flag; // These are rarely (if ever) changed, so same cache line as count. size_t _process_completed_buffers_threshold; size_t _buffer_enqueue_threshold; @@ -148,12 +149,12 @@ public: // The number of buffers in the list. Racy and not updated atomically // with the set of completed buffers. size_t completed_buffers_num() const { - return _count_and_process_flag >> 1; + return _count_and_process_flag.load_relaxed() >> 1; } // Return true if completed buffers should be processed. bool process_completed_buffers() const { - return (_count_and_process_flag & 1) != 0; + return (_count_and_process_flag.load_relaxed() & 1) != 0; } #ifndef PRODUCT diff --git a/src/hotspot/share/gc/shared/taskqueue.hpp b/src/hotspot/share/gc/shared/taskqueue.hpp index 1c36e18894a..3a751852ab6 100644 --- a/src/hotspot/share/gc/shared/taskqueue.hpp +++ b/src/hotspot/share/gc/shared/taskqueue.hpp @@ -25,13 +25,16 @@ #ifndef SHARE_GC_SHARED_TASKQUEUE_HPP #define SHARE_GC_SHARED_TASKQUEUE_HPP +#include "cppstdlib/type_traits.hpp" #include "memory/allocation.hpp" #include "memory/padded.hpp" +#include "metaprogramming/primitiveConversions.hpp" #include "oops/oopsHierarchy.hpp" -#include "runtime/atomicAccess.hpp" +#include "runtime/atomic.hpp" #include "utilities/debug.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/ostream.hpp" +#include "utilities/powerOfTwo.hpp" #include "utilities/stack.hpp" #if TASKQUEUE_STATS @@ -100,76 +103,92 @@ void TaskQueueStats::reset() { } #endif // TASKQUEUE_STATS +// Helper for TaskQueueSuper, encoding {queue index, tag} pair in a form that +// supports atomic access to the pair. +class TaskQueueAge { + friend struct PrimitiveConversions::Translate; + +public: + // Internal type used for indexing the queue, and for the tag. + using idx_t = NOT_LP64(uint16_t) LP64_ONLY(uint32_t); + + explicit TaskQueueAge(size_t data = 0) : _data{data} {} + TaskQueueAge(idx_t top, idx_t tag) : _fields{top, tag} {} + + idx_t top() const { return _fields._top; } + idx_t tag() const { return _fields._tag; } + + bool operator==(const TaskQueueAge& other) const { return _data == other._data; } + +private: + struct Fields { + idx_t _top; + idx_t _tag; + }; + union { + size_t _data; // Provides access to _fields as a single integral value. + Fields _fields; + }; + // _data must be able to hold combined _fields. Must be equal to ensure + // there isn't any padding that could be uninitialized by 2-arg ctor. + static_assert(sizeof(_data) == sizeof(_fields)); +}; + +// Support for Atomic. +template<> +struct PrimitiveConversions::Translate : public std::true_type { + using Value = TaskQueueAge; + using Decayed = decltype(TaskQueueAge::_data); + + static Decayed decay(Value x) { return x._data; } + static Value recover(Decayed x) { return Value(x); } +}; + // TaskQueueSuper collects functionality common to all GenericTaskQueue instances. template class TaskQueueSuper: public CHeapObj { protected: - // Internal type for indexing the queue; also used for the tag. - typedef NOT_LP64(uint16_t) LP64_ONLY(uint32_t) idx_t; - STATIC_ASSERT(N == idx_t(N)); // Ensure N fits in an idx_t. + using Age = TaskQueueAge; + using idx_t = Age::idx_t; + static_assert(N == idx_t(N)); // Ensure N fits in an idx_t. // N must be a power of 2 for computing modulo via masking. // N must be >= 2 for the algorithm to work at all, though larger is better. - STATIC_ASSERT(N >= 2); - STATIC_ASSERT(is_power_of_2(N)); + static_assert(N >= 2); + static_assert(is_power_of_2(N)); static const uint MOD_N_MASK = N - 1; - class Age { - friend class TaskQueueSuper; - - public: - explicit Age(size_t data = 0) : _data(data) {} - Age(idx_t top, idx_t tag) { _fields._top = top; _fields._tag = tag; } - - idx_t top() const { return _fields._top; } - idx_t tag() const { return _fields._tag; } - - bool operator ==(const Age& other) const { return _data == other._data; } - - private: - struct fields { - idx_t _top; - idx_t _tag; - }; - union { - size_t _data; - fields _fields; - }; - STATIC_ASSERT(sizeof(size_t) >= sizeof(fields)); - }; - uint bottom_relaxed() const { - return AtomicAccess::load(&_bottom); + return _bottom.load_relaxed(); } uint bottom_acquire() const { - return AtomicAccess::load_acquire(&_bottom); + return _bottom.load_acquire(); } void set_bottom_relaxed(uint new_bottom) { - AtomicAccess::store(&_bottom, new_bottom); + _bottom.store_relaxed(new_bottom); } void release_set_bottom(uint new_bottom) { - AtomicAccess::release_store(&_bottom, new_bottom); + _bottom.release_store(new_bottom); } Age age_relaxed() const { - return Age(AtomicAccess::load(&_age._data)); + return _age.load_relaxed(); } void set_age_relaxed(Age new_age) { - AtomicAccess::store(&_age._data, new_age._data); + _age.store_relaxed(new_age); } Age cmpxchg_age(Age old_age, Age new_age) { - return Age(AtomicAccess::cmpxchg(&_age._data, old_age._data, new_age._data)); + return _age.compare_exchange(old_age, new_age); } idx_t age_top_relaxed() const { - // Atomically accessing a subfield of an "atomic" member. - return AtomicAccess::load(&_age._fields._top); + return _age.load_relaxed().top(); } // These both operate mod N. @@ -222,16 +241,16 @@ private: DEFINE_PAD_MINUS_SIZE(0, DEFAULT_PADDING_SIZE, 0); // Index of the first free element after the last one pushed (mod N). - volatile uint _bottom; - DEFINE_PAD_MINUS_SIZE(1, DEFAULT_PADDING_SIZE, sizeof(uint)); + Atomic _bottom; + DEFINE_PAD_MINUS_SIZE(1, DEFAULT_PADDING_SIZE, sizeof(_bottom)); // top() is the index of the oldest pushed element (mod N), and tag() // is the associated epoch, to distinguish different modifications of // the age. There is no available element if top() == _bottom or // (_bottom - top()) mod N == N-1; the latter indicates underflow // during concurrent pop_local/pop_global. - volatile Age _age; - DEFINE_PAD_MINUS_SIZE(2, DEFAULT_PADDING_SIZE, sizeof(Age)); + Atomic _age; + DEFINE_PAD_MINUS_SIZE(2, DEFAULT_PADDING_SIZE, sizeof(_age)); NONCOPYABLE(TaskQueueSuper); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAllocRequest.hpp b/src/hotspot/share/gc/shenandoah/shenandoahAllocRequest.hpp index 78ae78f4c24..05ecfb254a2 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahAllocRequest.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahAllocRequest.hpp @@ -31,15 +31,37 @@ class ShenandoahAllocRequest : StackObj { public: - enum Type { - _alloc_shared, // Allocate common, outside of TLAB - _alloc_shared_gc, // Allocate common, outside of GCLAB/PLAB - _alloc_cds, // Allocate for CDS - _alloc_tlab, // Allocate TLAB - _alloc_gclab, // Allocate GCLAB - _alloc_plab, // Allocate PLAB - _ALLOC_LIMIT - }; + // Alloc type is an int value with encoded bits in scheme as: + // [x|xx|xx|xx] + // ^---- Requester: + // 00 -- mutator + // 10 -- mutator (CDS) + // 01 -- GC + // ^------- Purpose: + // 00 -- shared + // 01 -- TLAB/GCLAB + // 11 -- PLAB + // ^---------- Affiliation: + // 00 -- YOUNG + // 01 -- OLD + // 11 -- OLD, promotion + typedef int Type; + + static constexpr int bit_gc_alloc = 1 << 0; + static constexpr int bit_cds_alloc = 1 << 1; + static constexpr int bit_lab_alloc = 1 << 2; + static constexpr int bit_plab_alloc = 1 << 3; + static constexpr int bit_old_alloc = 1 << 4; + static constexpr int bit_promotion_alloc = 1 << 5; + + static constexpr Type _alloc_shared = 0; + static constexpr Type _alloc_tlab = bit_lab_alloc; + static constexpr Type _alloc_cds = bit_cds_alloc; + static constexpr Type _alloc_shared_gc = bit_gc_alloc; + static constexpr Type _alloc_shared_gc_old = bit_gc_alloc | bit_old_alloc; + static constexpr Type _alloc_shared_gc_promotion = bit_gc_alloc | bit_old_alloc | bit_promotion_alloc; + static constexpr Type _alloc_gclab = bit_gc_alloc | bit_lab_alloc; + static constexpr Type _alloc_plab = bit_gc_alloc | bit_lab_alloc | bit_plab_alloc | bit_old_alloc; static const char* alloc_type_to_string(Type type) { switch (type) { @@ -47,6 +69,10 @@ public: return "Shared"; case _alloc_shared_gc: return "Shared GC"; + case _alloc_shared_gc_old: + return "Shared GC Old"; + case _alloc_shared_gc_promotion: + return "Shared GC Promotion"; case _alloc_cds: return "CDS"; case _alloc_tlab: @@ -80,20 +106,14 @@ private: // This is the type of the request. Type _alloc_type; - // This is the generation which the request is targeting. - ShenandoahAffiliation const _affiliation; - - // True if this request is trying to copy any object from young to old (promote). - bool _is_promotion; - #ifdef ASSERT // Check that this is set before being read. bool _actual_size_set; #endif - ShenandoahAllocRequest(size_t _min_size, size_t _requested_size, Type _alloc_type, ShenandoahAffiliation affiliation, bool is_promotion = false) : + ShenandoahAllocRequest(size_t _min_size, size_t _requested_size, Type _alloc_type) : _min_size(_min_size), _requested_size(_requested_size), - _actual_size(0), _waste(0), _alloc_type(_alloc_type), _affiliation(affiliation), _is_promotion(is_promotion) + _actual_size(0), _waste(0), _alloc_type(_alloc_type) #ifdef ASSERT , _actual_size_set(false) #endif @@ -101,31 +121,34 @@ private: public: static inline ShenandoahAllocRequest for_tlab(size_t min_size, size_t requested_size) { - return ShenandoahAllocRequest(min_size, requested_size, _alloc_tlab, ShenandoahAffiliation::YOUNG_GENERATION); + return ShenandoahAllocRequest(min_size, requested_size, _alloc_tlab); } static inline ShenandoahAllocRequest for_gclab(size_t min_size, size_t requested_size) { - return ShenandoahAllocRequest(min_size, requested_size, _alloc_gclab, ShenandoahAffiliation::YOUNG_GENERATION); + return ShenandoahAllocRequest(min_size, requested_size, _alloc_gclab); } static inline ShenandoahAllocRequest for_plab(size_t min_size, size_t requested_size) { - return ShenandoahAllocRequest(min_size, requested_size, _alloc_plab, ShenandoahAffiliation::OLD_GENERATION); + return ShenandoahAllocRequest(min_size, requested_size, _alloc_plab); } static inline ShenandoahAllocRequest for_shared_gc(size_t requested_size, ShenandoahAffiliation affiliation, bool is_promotion = false) { if (is_promotion) { - assert(affiliation == ShenandoahAffiliation::OLD_GENERATION, "Should only promote to old generation"); - return ShenandoahAllocRequest(0, requested_size, _alloc_shared_gc, affiliation, true); + assert(affiliation == OLD_GENERATION, "Should only promote to old generation"); + return ShenandoahAllocRequest(0, requested_size, _alloc_shared_gc_promotion); } - return ShenandoahAllocRequest(0, requested_size, _alloc_shared_gc, affiliation); + if (affiliation == OLD_GENERATION) { + return ShenandoahAllocRequest(0, requested_size, _alloc_shared_gc_old); + } + return ShenandoahAllocRequest(0, requested_size, _alloc_shared_gc); } static inline ShenandoahAllocRequest for_shared(size_t requested_size) { - return ShenandoahAllocRequest(0, requested_size, _alloc_shared, ShenandoahAffiliation::YOUNG_GENERATION); + return ShenandoahAllocRequest(0, requested_size, _alloc_shared); } static inline ShenandoahAllocRequest for_cds(size_t requested_size) { - return ShenandoahAllocRequest(0, requested_size, _alloc_cds, ShenandoahAffiliation::YOUNG_GENERATION); + return ShenandoahAllocRequest(0, requested_size, _alloc_cds); } inline size_t size() const { @@ -167,71 +190,35 @@ public: } inline bool is_mutator_alloc() const { - switch (_alloc_type) { - case _alloc_tlab: - case _alloc_shared: - case _alloc_cds: - return true; - case _alloc_gclab: - case _alloc_plab: - case _alloc_shared_gc: - return false; - default: - ShouldNotReachHere(); - return false; - } + return (_alloc_type & bit_gc_alloc) == 0; } inline bool is_gc_alloc() const { - switch (_alloc_type) { - case _alloc_tlab: - case _alloc_shared: - case _alloc_cds: - return false; - case _alloc_gclab: - case _alloc_plab: - case _alloc_shared_gc: - return true; - default: - ShouldNotReachHere(); - return false; - } + return (_alloc_type & bit_gc_alloc) != 0; } inline bool is_lab_alloc() const { - switch (_alloc_type) { - case _alloc_tlab: - case _alloc_gclab: - case _alloc_plab: - return true; - case _alloc_shared: - case _alloc_shared_gc: - case _alloc_cds: - return false; - default: - ShouldNotReachHere(); - return false; - } + return (_alloc_type & bit_lab_alloc) != 0; } - bool is_old() const { - return _affiliation == OLD_GENERATION; + inline bool is_old() const { + return (_alloc_type & bit_old_alloc) != 0; } - bool is_young() const { - return _affiliation == YOUNG_GENERATION; + inline bool is_young() const { + return (_alloc_type & bit_old_alloc) == 0; } - ShenandoahAffiliation affiliation() const { - return _affiliation; + inline ShenandoahAffiliation affiliation() const { + return (_alloc_type & bit_old_alloc) == 0 ? YOUNG_GENERATION : OLD_GENERATION ; } const char* affiliation_name() const { - return shenandoah_affiliation_name(_affiliation); + return shenandoah_affiliation_name(affiliation()); } - bool is_promotion() const { - return _is_promotion; + inline bool is_promotion() const { + return (_alloc_type & bit_promotion_alloc) != 0; } }; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahArguments.cpp b/src/hotspot/share/gc/shenandoah/shenandoahArguments.cpp index a7cf8e638dd..c1fa4b964b7 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahArguments.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahArguments.cpp @@ -37,6 +37,7 @@ #include "runtime/globals_extension.hpp" #include "runtime/java.hpp" #include "utilities/defaultStream.hpp" +#include "utilities/powerOfTwo.hpp" void ShenandoahArguments::initialize() { #if !(defined AARCH64 || defined AMD64 || defined PPC64 || defined RISCV64) @@ -205,7 +206,7 @@ void ShenandoahArguments::initialize() { } size_t ShenandoahArguments::conservative_max_heap_alignment() { - size_t align = ShenandoahMaxRegionSize; + size_t align = next_power_of_2(ShenandoahMaxRegionSize); if (UseLargePages) { align = MAX2(align, os::large_page_size()); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.hpp b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.hpp index 0d38cc757f4..2b5bc766a46 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.hpp @@ -129,7 +129,7 @@ public: private: template - inline void arraycopy_marking(T* src, T* dst, size_t count, bool is_old_marking); + inline void arraycopy_marking(T* dst, size_t count); template inline void arraycopy_evacuation(T* src, size_t count); template diff --git a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.inline.hpp index b176446452a..adeea8ebf96 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.inline.hpp @@ -387,13 +387,11 @@ template void ShenandoahBarrierSet::arraycopy_work(T* src, size_t count) { // Young cycles are allowed to run when old marking is in progress. When old marking is in progress, // this barrier will be called with ENQUEUE=true and HAS_FWD=false, even though the young generation - // may have forwarded objects. In this case, the `arraycopy_work` is first called with HAS_FWD=true and - // ENQUEUE=false. - assert(HAS_FWD == _heap->has_forwarded_objects() || _heap->is_concurrent_old_mark_in_progress(), - "Forwarded object status is sane"); + // may have forwarded objects. + assert(HAS_FWD == _heap->has_forwarded_objects() || _heap->is_concurrent_old_mark_in_progress(), "Forwarded object status is sane"); // This function cannot be called to handle marking and evacuation at the same time (they operate on // different sides of the copy). - assert((HAS_FWD || EVAC) != ENQUEUE, "Cannot evacuate and mark both sides of copy."); + static_assert((HAS_FWD || EVAC) != ENQUEUE, "Cannot evacuate and mark both sides of copy."); Thread* thread = Thread::current(); SATBMarkQueue& queue = ShenandoahThreadLocalData::satb_mark_queue(thread); @@ -412,7 +410,7 @@ void ShenandoahBarrierSet::arraycopy_work(T* src, size_t count) { shenandoah_assert_forwarded_except(elem_ptr, obj, _heap->cancelled_gc()); ShenandoahHeap::atomic_update_oop(fwd, elem_ptr, o); } - if (ENQUEUE && !ctx->is_marked_strong_or_old(obj)) { + if (ENQUEUE && !ctx->is_marked_strong(obj)) { _satb_mark_queue_set.enqueue_known_active(queue, obj); } } @@ -426,68 +424,29 @@ void ShenandoahBarrierSet::arraycopy_barrier(T* src, T* dst, size_t count) { return; } - char gc_state = ShenandoahThreadLocalData::gc_state(Thread::current()); - if ((gc_state & ShenandoahHeap::EVACUATION) != 0) { - arraycopy_evacuation(src, count); - } else if ((gc_state & ShenandoahHeap::UPDATE_REFS) != 0) { - arraycopy_update(src, count); + const char gc_state = ShenandoahThreadLocalData::gc_state(Thread::current()); + if ((gc_state & ShenandoahHeap::MARKING) != 0) { + // If marking old or young, we must evaluate the SATB barrier. This will be the only + // action if we are not marking old. If we are marking old, we must still evaluate the + // load reference barrier for a young collection. + arraycopy_marking(dst, count); } - if (_heap->mode()->is_generational()) { - assert(ShenandoahSATBBarrier, "Generational mode assumes SATB mode"); - if ((gc_state & ShenandoahHeap::YOUNG_MARKING) != 0) { - arraycopy_marking(src, dst, count, false); - } - if ((gc_state & ShenandoahHeap::OLD_MARKING) != 0) { - arraycopy_marking(src, dst, count, true); - } - } else if ((gc_state & ShenandoahHeap::MARKING) != 0) { - arraycopy_marking(src, dst, count, false); + if ((gc_state & ShenandoahHeap::EVACUATION) != 0) { + assert((gc_state & ShenandoahHeap::YOUNG_MARKING) == 0, "Cannot be marking young during evacuation"); + arraycopy_evacuation(src, count); + } else if ((gc_state & ShenandoahHeap::UPDATE_REFS) != 0) { + assert((gc_state & ShenandoahHeap::YOUNG_MARKING) == 0, "Cannot be marking young during update-refs"); + arraycopy_update(src, count); } } template -void ShenandoahBarrierSet::arraycopy_marking(T* src, T* dst, size_t count, bool is_old_marking) { +void ShenandoahBarrierSet::arraycopy_marking(T* dst, size_t count) { assert(_heap->is_concurrent_mark_in_progress(), "only during marking"); - /* - * Note that an old-gen object is considered live if it is live at the start of OLD marking or if it is promoted - * following the start of OLD marking. - * - * 1. Every object promoted following the start of OLD marking will be above TAMS within its old-gen region - * 2. Every object live at the start of OLD marking will be referenced from a "root" or it will be referenced from - * another live OLD-gen object. With regards to old-gen, roots include stack locations and all of live young-gen. - * All root references to old-gen are identified during a bootstrap young collection. All references from other - * old-gen objects will be marked during the traversal of all old objects, or will be marked by the SATB barrier. - * - * During old-gen marking (which is interleaved with young-gen collections), call arraycopy_work() if: - * - * 1. The overwritten array resides in old-gen and it is below TAMS within its old-gen region - * 2. Do not call arraycopy_work for any array residing in young-gen because young-gen collection is idle at this time - * - * During young-gen marking, call arraycopy_work() if: - * - * 1. The overwritten array resides in young-gen and is below TAMS within its young-gen region - * 2. Additionally, if array resides in old-gen, regardless of its relationship to TAMS because this old-gen array - * may hold references to young-gen - */ if (ShenandoahSATBBarrier) { - T* array = dst; - HeapWord* array_addr = reinterpret_cast(array); - ShenandoahHeapRegion* r = _heap->heap_region_containing(array_addr); - if (is_old_marking) { - // Generational, old marking - assert(_heap->mode()->is_generational(), "Invariant"); - if (r->is_old() && (array_addr < _heap->marking_context()->top_at_mark_start(r))) { - arraycopy_work(array, count); - } - } else if (_heap->mode()->is_generational()) { - // Generational, young marking - if (r->is_old() || (array_addr < _heap->marking_context()->top_at_mark_start(r))) { - arraycopy_work(array, count); - } - } else if (array_addr < _heap->marking_context()->top_at_mark_start(r)) { - // Non-generational, marking - arraycopy_work(array, count); + if (!_heap->marking_context()->allocated_after_mark_start(reinterpret_cast(dst))) { + arraycopy_work(dst, count); } } } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.cpp b/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.cpp index 0deb3b5ba4c..ab7985b3d34 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.cpp @@ -1311,19 +1311,11 @@ HeapWord* ShenandoahFreeSet::allocate_single(ShenandoahAllocRequest& req, bool& // Overwrite with non-zero (non-null) values only if necessary for allocation bookkeeping. - switch (req.type()) { - case ShenandoahAllocRequest::_alloc_tlab: - case ShenandoahAllocRequest::_alloc_shared: - case ShenandoahAllocRequest::_alloc_cds: - return allocate_for_mutator(req, in_new_region); - case ShenandoahAllocRequest::_alloc_gclab: - case ShenandoahAllocRequest::_alloc_plab: - case ShenandoahAllocRequest::_alloc_shared_gc: - return allocate_for_collector(req, in_new_region); - default: - ShouldNotReachHere(); + if (req.is_mutator_alloc()) { + return allocate_for_mutator(req, in_new_region); + } else { + return allocate_for_collector(req, in_new_region); } - return nullptr; } HeapWord* ShenandoahFreeSet::allocate_for_mutator(ShenandoahAllocRequest &req, bool &in_new_region) { @@ -1619,21 +1611,13 @@ HeapWord* ShenandoahFreeSet::try_allocate_in(ShenandoahHeapRegion* r, Shenandoah if (req.is_mutator_alloc()) { request_generation = _heap->mode()->is_generational()? _heap->young_generation(): _heap->global_generation(); orig_partition = ShenandoahFreeSetPartitionId::Mutator; - } else if (req.type() == ShenandoahAllocRequest::_alloc_gclab) { - request_generation = _heap->mode()->is_generational()? _heap->young_generation(): _heap->global_generation(); - orig_partition = ShenandoahFreeSetPartitionId::Collector; - } else if (req.type() == ShenandoahAllocRequest::_alloc_plab) { + } else if (req.is_old()) { request_generation = _heap->old_generation(); orig_partition = ShenandoahFreeSetPartitionId::OldCollector; } else { - assert(req.type() == ShenandoahAllocRequest::_alloc_shared_gc, "Unexpected allocation type"); - if (req.is_old()) { - request_generation = _heap->old_generation(); - orig_partition = ShenandoahFreeSetPartitionId::OldCollector; - } else { - request_generation = _heap->mode()->is_generational()? _heap->young_generation(): _heap->global_generation(); - orig_partition = ShenandoahFreeSetPartitionId::Collector; - } + // Not old collector alloc, so this is a young collector gclab or shared allocation + request_generation = _heap->mode()->is_generational()? _heap->young_generation(): _heap->global_generation(); + orig_partition = ShenandoahFreeSetPartitionId::Collector; } if (alloc_capacity(r) < PLAB::min_size() * HeapWordSize) { // Regardless of whether this allocation succeeded, if the remaining memory is less than PLAB:min_size(), retire this region. diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.inline.hpp index cad9dc0e932..636f65e2553 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.inline.hpp @@ -129,6 +129,8 @@ inline void ShenandoahHeapRegion::adjust_alloc_metadata(ShenandoahAllocRequest:: switch (type) { case ShenandoahAllocRequest::_alloc_shared: case ShenandoahAllocRequest::_alloc_shared_gc: + case ShenandoahAllocRequest::_alloc_shared_gc_old: + case ShenandoahAllocRequest::_alloc_shared_gc_promotion: case ShenandoahAllocRequest::_alloc_cds: // Counted implicitly by tlab/gclab allocs break; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp index 34713898fc6..44064dbd1a9 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp @@ -250,6 +250,8 @@ HeapWord* ShenandoahCardCluster::first_object_start(const size_t card_index, con HeapWord* right = MIN2(region->top(), end_range_of_interest); HeapWord* end_of_search_next = MIN2(right, tams); + // Since end_range_of_interest may not align on a card boundary, last_relevant_card_index is conservative. Not all of the + // memory within the last relevant card's span is < right. size_t last_relevant_card_index; if (end_range_of_interest == _end_of_heap) { last_relevant_card_index = _rs->card_index_for_addr(end_range_of_interest - 1); @@ -352,9 +354,8 @@ HeapWord* ShenandoahCardCluster::first_object_start(const size_t card_index, con return nullptr; } } while (!starts_object(following_card_index)); - assert(_rs->addr_for_card_index(following_card_index) + get_first_start(following_card_index), - "Result must precede right"); - return _rs->addr_for_card_index(following_card_index) + get_first_start(following_card_index); + HeapWord* result_candidate = _rs->addr_for_card_index(following_card_index) + get_first_start(following_card_index); + return (result_candidate >= right)? nullptr: result_candidate; } } } diff --git a/src/hotspot/share/gc/z/zBarrier.inline.hpp b/src/hotspot/share/gc/z/zBarrier.inline.hpp index b5923f01628..766a6eb8e4c 100644 --- a/src/hotspot/share/gc/z/zBarrier.inline.hpp +++ b/src/hotspot/share/gc/z/zBarrier.inline.hpp @@ -86,10 +86,6 @@ inline void ZBarrier::self_heal(ZBarrierFastPath fast_path, volatile zpointer* p assert(ZPointer::is_remapped(heal_ptr), "invariant"); for (;;) { - if (ptr == zpointer::null) { - assert(!ZVerifyOops || !ZHeap::heap()->is_in(uintptr_t(p)) || !ZHeap::heap()->is_old(p), "No raw null in old"); - } - assert_transition_monotonicity(ptr, heal_ptr); // Heal diff --git a/src/hotspot/share/gc/z/zBarrierSet.cpp b/src/hotspot/share/gc/z/zBarrierSet.cpp index 87f93043bdf..643eba1947e 100644 --- a/src/hotspot/share/gc/z/zBarrierSet.cpp +++ b/src/hotspot/share/gc/z/zBarrierSet.cpp @@ -223,27 +223,7 @@ void ZBarrierSet::on_slowpath_allocation_exit(JavaThread* thread, oop new_obj) { // breaks that promise. Take a few steps in the interpreter instead, which has // no such assumptions about where an object resides. deoptimize_allocation(thread); - return; } - - if (!ZGeneration::young()->is_phase_mark_complete()) { - return; - } - - if (!page->is_relocatable()) { - return; - } - - if (ZRelocate::compute_to_age(age) != ZPageAge::old) { - return; - } - - // If the object is young, we have to still be careful that it isn't racingly - // about to get promoted to the old generation. That causes issues when null - // pointers are supposed to be coloured, but the JIT is a bit sloppy and - // reinitializes memory with raw nulls. We detect this situation and detune - // rather than relying on the JIT to never be sloppy with redundant initialization. - deoptimize_allocation(thread); } void ZBarrierSet::print_on(outputStream* st) const { diff --git a/src/hotspot/share/gc/z/zGeneration.cpp b/src/hotspot/share/gc/z/zGeneration.cpp index d1680b6c336..2b632ef29a9 100644 --- a/src/hotspot/share/gc/z/zGeneration.cpp +++ b/src/hotspot/share/gc/z/zGeneration.cpp @@ -111,6 +111,16 @@ static const ZStatSampler ZSamplerJavaThreads("System", "Java Threads", ZStatUni ZGenerationYoung* ZGeneration::_young; ZGenerationOld* ZGeneration::_old; +class ZRendezvousHandshakeClosure : public HandshakeClosure { +public: + ZRendezvousHandshakeClosure() + : HandshakeClosure("ZRendezvous") {} + + void do_thread(Thread* thread) { + // Does nothing + } +}; + ZGeneration::ZGeneration(ZGenerationId id, ZPageTable* page_table, ZPageAllocator* page_allocator) : _id(id), _page_allocator(page_allocator), @@ -168,11 +178,19 @@ void ZGeneration::free_empty_pages(ZRelocationSetSelector* selector, int bulk) { } void ZGeneration::flip_age_pages(const ZRelocationSetSelector* selector) { - if (is_young()) { - _relocate.flip_age_pages(selector->not_selected_small()); - _relocate.flip_age_pages(selector->not_selected_medium()); - _relocate.flip_age_pages(selector->not_selected_large()); - } + _relocate.flip_age_pages(selector->not_selected_small()); + _relocate.flip_age_pages(selector->not_selected_medium()); + _relocate.flip_age_pages(selector->not_selected_large()); + + // Perform a handshake between flip promotion and running the promotion barrier. This ensures + // that ZBarrierSet::on_slowpath_allocation_exit() observing a young page that was then racingly + // flip promoted, will run any stores without barriers to completion before responding to the + // handshake at the subsequent safepoint poll. This ensures that the flip promotion barriers always + // run after compiled code missing barriers, but before relocate start. + ZRendezvousHandshakeClosure cl; + Handshake::execute(&cl); + + _relocate.barrier_flip_promoted_pages(_relocation_set.flip_promoted_pages()); } static double fragmentation_limit(ZGenerationId generation) { @@ -235,7 +253,9 @@ void ZGeneration::select_relocation_set(bool promote_all) { _relocation_set.install(&selector); // Flip age young pages that were not selected - flip_age_pages(&selector); + if (is_young()) { + flip_age_pages(&selector); + } // Setup forwarding table ZRelocationSetIterator rs_iter(&_relocation_set); @@ -1280,16 +1300,6 @@ bool ZGenerationOld::uses_clear_all_soft_reference_policy() const { return _reference_processor.uses_clear_all_soft_reference_policy(); } -class ZRendezvousHandshakeClosure : public HandshakeClosure { -public: - ZRendezvousHandshakeClosure() - : HandshakeClosure("ZRendezvous") {} - - void do_thread(Thread* thread) { - // Does nothing - } -}; - class ZRendezvousGCThreads: public VM_Operation { public: VMOp_Type type() const { return VMOp_ZRendezvousGCThreads; } diff --git a/src/hotspot/share/gc/z/zRelocate.cpp b/src/hotspot/share/gc/z/zRelocate.cpp index 69233da6f54..24c4bdeac16 100644 --- a/src/hotspot/share/gc/z/zRelocate.cpp +++ b/src/hotspot/share/gc/z/zRelocate.cpp @@ -1087,7 +1087,6 @@ private: ZRelocateSmallAllocator _small_allocator; ZRelocateMediumAllocator _medium_allocator; const size_t _total_forwardings; - volatile size_t _numa_local_forwardings; public: ZRelocateTask(ZRelocationSet* relocation_set, @@ -1104,8 +1103,7 @@ public: _medium_targets(medium_targets), _small_allocator(_generation), _medium_allocator(_generation, shared_medium_targets), - _total_forwardings(relocation_set->nforwardings()), - _numa_local_forwardings(0) { + _total_forwardings(relocation_set->nforwardings()) { for (uint32_t i = 0; i < ZNUMA::count(); i++) { ZRelocationSetParallelIterator* const iter = _iters->addr(i); @@ -1124,18 +1122,17 @@ public: // Signal that we're not using the queue anymore. Used mostly for asserts. _queue->deactivate(); - - if (ZNUMA::is_enabled()) { - log_debug(gc, reloc, numa)("Forwardings relocated NUMA-locally: %zu / %zu (%.0f%%)", - _numa_local_forwardings, _total_forwardings, percent_of(_numa_local_forwardings, _total_forwardings)); - } } virtual void work() { ZRelocateWork small(&_small_allocator, _small_targets->addr(), _generation); ZRelocateWork medium(&_medium_allocator, _medium_targets->addr(), _generation); + const uint32_t num_nodes = ZNUMA::count(); - uint32_t numa_local_forwardings_worker = 0; + const uint32_t start_node = ZNUMA::id(); + uint32_t current_node = start_node; + bool has_affinity = false; + bool has_affinity_current_node = false; const auto do_forwarding = [&](ZForwarding* forwarding) { ZPage* const page = forwarding->page(); @@ -1167,26 +1164,30 @@ public: const auto do_forwarding_one_from_iter = [&]() { ZForwarding* forwarding; - const uint32_t start_node = ZNUMA::id(); - uint32_t current_node = start_node; - for (uint32_t i = 0; i < num_nodes; i++) { + for (;;) { if (_iters->get(current_node).next_if(&forwarding, check_numa_local, current_node)) { - claim_and_do_forwarding(forwarding); - - if (current_node == start_node) { - // Track if this forwarding was relocated on the local NUMA node - numa_local_forwardings_worker++; + // Set thread affinity for NUMA-local processing (if needed) + if (UseNUMA && !has_affinity_current_node) { + os::numa_set_thread_affinity(Thread::current(), ZNUMA::numa_id_to_node(current_node)); + has_affinity = true; + has_affinity_current_node = true; } + // Perform the forwarding task + claim_and_do_forwarding(forwarding); return true; } - // Check next node. + // No work found on the current node, move to the next node current_node = (current_node + 1) % num_nodes; - } + has_affinity_current_node = false; - return false; + // If we've looped back to the starting node there's no more work to do + if (current_node == start_node) { + return false; + } + } }; for (;;) { @@ -1209,11 +1210,13 @@ public: } } - if (ZNUMA::is_enabled()) { - AtomicAccess::add(&_numa_local_forwardings, numa_local_forwardings_worker, memory_order_relaxed); - } - _queue->leave(); + + if (UseNUMA && has_affinity) { + // Restore the affinity of the thread so that it isn't bound to a specific + // node any more + os::numa_set_thread_affinity(Thread::current(), -1); + } } virtual void resize_workers(uint nworkers) { @@ -1322,7 +1325,7 @@ private: public: ZFlipAgePagesTask(const ZArray* pages) - : ZTask("ZPromotePagesTask"), + : ZTask("ZFlipAgePagesTask"), _iter(pages) {} virtual void work() { @@ -1337,16 +1340,6 @@ public: // Figure out if this is proper promotion const bool promotion = to_age == ZPageAge::old; - if (promotion) { - // Before promoting an object (and before relocate start), we must ensure that all - // contained zpointers are store good. The marking code ensures that for non-null - // pointers, but null pointers are ignored. This code ensures that even null pointers - // are made store good, for the promoted objects. - prev_page->object_iterate([&](oop obj) { - ZIterator::basic_oop_iterate_safe(obj, ZBarrier::promote_barrier_on_young_oop_field); - }); - } - // Logging prev_page->log_msg(promotion ? " (flip promoted)" : " (flip survived)"); @@ -1360,7 +1353,7 @@ public: if (promotion) { ZGeneration::young()->flip_promote(prev_page, new_page); - // Defer promoted page registration times the lock is taken + // Defer promoted page registration promoted_pages.push(prev_page); } @@ -1371,11 +1364,42 @@ public: } }; +class ZPromoteBarrierTask : public ZTask { +private: + ZArrayParallelIterator _iter; + +public: + ZPromoteBarrierTask(const ZArray* pages) + : ZTask("ZPromoteBarrierTask"), + _iter(pages) {} + + virtual void work() { + SuspendibleThreadSetJoiner sts_joiner; + + for (ZPage* page; _iter.next(&page);) { + // When promoting an object (and before relocate start), we must ensure that all + // contained zpointers are store good. The marking code ensures that for non-null + // pointers, but null pointers are ignored. This code ensures that even null pointers + // are made store good, for the promoted objects. + page->object_iterate([&](oop obj) { + ZIterator::basic_oop_iterate_safe(obj, ZBarrier::promote_barrier_on_young_oop_field); + }); + + SuspendibleThreadSet::yield(); + } + } +}; + void ZRelocate::flip_age_pages(const ZArray* pages) { ZFlipAgePagesTask flip_age_task(pages); workers()->run(&flip_age_task); } +void ZRelocate::barrier_flip_promoted_pages(const ZArray* pages) { + ZPromoteBarrierTask promote_barrier_task(pages); + workers()->run(&promote_barrier_task); +} + void ZRelocate::synchronize() { _queue.synchronize(); } diff --git a/src/hotspot/share/gc/z/zRelocate.hpp b/src/hotspot/share/gc/z/zRelocate.hpp index d0ddf7deecf..50111f24ee5 100644 --- a/src/hotspot/share/gc/z/zRelocate.hpp +++ b/src/hotspot/share/gc/z/zRelocate.hpp @@ -119,6 +119,7 @@ public: void relocate(ZRelocationSet* relocation_set); void flip_age_pages(const ZArray* pages); + void barrier_flip_promoted_pages(const ZArray* pages); void synchronize(); void desynchronize(); diff --git a/src/hotspot/share/gc/z/zVerify.cpp b/src/hotspot/share/gc/z/zVerify.cpp index 55f13be9b44..db3db14afa2 100644 --- a/src/hotspot/share/gc/z/zVerify.cpp +++ b/src/hotspot/share/gc/z/zVerify.cpp @@ -130,7 +130,10 @@ static void z_verify_root_oop_object(zaddress addr, void* p) { static void z_verify_old_oop(zpointer* p) { const zpointer o = *p; - assert(o != zpointer::null, "Old should not contain raw null"); + if (o == zpointer::null) { + guarantee(ZGeneration::young()->is_phase_mark_complete(), "Only possible when flip promoting"); + guarantee(ZHeap::heap()->page(p)->is_allocating(), "Raw nulls only possible in allocating pages"); + } if (!z_is_null_relaxed(o)) { if (ZPointer::is_mark_good(o)) { // Even though the pointer is mark good, we can't verify that it should diff --git a/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.cpp b/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.cpp index f7a725fce6d..534c9996cfe 100644 --- a/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.cpp +++ b/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.cpp @@ -217,7 +217,8 @@ static bool compute_top_frame(const JfrSampleRequest& request, frame& top_frame, const PcDesc* const pc_desc = get_pc_desc(sampled_nm, sampled_pc); if (is_valid(pc_desc)) { intptr_t* const synthetic_sp = sender_sp - sampled_nm->frame_size(); - top_frame = frame(synthetic_sp, synthetic_sp, sender_sp, pc_desc->real_pc(sampled_nm), sampled_nm); + intptr_t* const synthetic_fp = sender_sp AARCH64_ONLY( - frame::sender_sp_offset); + top_frame = frame(synthetic_sp, synthetic_sp, synthetic_fp, pc_desc->real_pc(sampled_nm), sampled_nm); in_continuation = is_in_continuation(top_frame, jt); return true; } diff --git a/src/hotspot/share/memory/memoryReserver.cpp b/src/hotspot/share/memory/memoryReserver.cpp index 11a0422f7b0..e8d1887f59f 100644 --- a/src/hotspot/share/memory/memoryReserver.cpp +++ b/src/hotspot/share/memory/memoryReserver.cpp @@ -437,7 +437,7 @@ ReservedSpace HeapReserver::Instance::try_reserve_range(char *highest_start, if (reserved.is_reserved()) { if (reserved.base() >= aligned_heap_base_min_address && - size <= (uintptr_t)(upper_bound - reserved.base())) { + size <= (size_t)(upper_bound - reserved.base())) { // Got a successful reservation. return reserved; } @@ -546,16 +546,16 @@ ReservedHeapSpace HeapReserver::Instance::reserve_compressed_oops_heap(const siz const size_t attach_point_alignment = lcm(alignment, os_attach_point_alignment); - char* aligned_heap_base_min_address = align_up((char*)HeapBaseMinAddress, alignment); - size_t noaccess_prefix = ((aligned_heap_base_min_address + size) > (char*)OopEncodingHeapMax) ? + uintptr_t aligned_heap_base_min_address = align_up(MAX2(HeapBaseMinAddress, alignment), alignment); + size_t noaccess_prefix = ((aligned_heap_base_min_address + size) > OopEncodingHeapMax) ? noaccess_prefix_size : 0; ReservedSpace reserved{}; // Attempt to alloc at user-given address. if (!FLAG_IS_DEFAULT(HeapBaseMinAddress)) { - reserved = try_reserve_memory(size + noaccess_prefix, alignment, page_size, aligned_heap_base_min_address); - if (reserved.base() != aligned_heap_base_min_address) { // Enforce this exact address. + reserved = try_reserve_memory(size + noaccess_prefix, alignment, page_size, (char*)aligned_heap_base_min_address); + if (reserved.base() != (char*)aligned_heap_base_min_address) { // Enforce this exact address. release(reserved); reserved = {}; } @@ -575,38 +575,41 @@ ReservedHeapSpace HeapReserver::Instance::reserve_compressed_oops_heap(const siz // Attempt to allocate so that we can run without base and scale (32-Bit unscaled compressed oops). // Give it several tries from top of range to bottom. - if (aligned_heap_base_min_address + size <= (char *)UnscaledOopHeapMax) { + if (aligned_heap_base_min_address + size <= UnscaledOopHeapMax) { // Calc address range within we try to attach (range of possible start addresses). - char* const highest_start = align_down((char *)UnscaledOopHeapMax - size, attach_point_alignment); - char* const lowest_start = align_up(aligned_heap_base_min_address, attach_point_alignment); - reserved = try_reserve_range(highest_start, lowest_start, attach_point_alignment, - aligned_heap_base_min_address, (char *)UnscaledOopHeapMax, size, alignment, page_size); + uintptr_t const highest_start = align_down(UnscaledOopHeapMax - size, attach_point_alignment); + uintptr_t const lowest_start = align_up(aligned_heap_base_min_address, attach_point_alignment); + assert(lowest_start <= highest_start, "lowest: " INTPTR_FORMAT " highest: " INTPTR_FORMAT , + lowest_start, highest_start); + reserved = try_reserve_range((char*)highest_start, (char*)lowest_start, attach_point_alignment, + (char*)aligned_heap_base_min_address, (char*)UnscaledOopHeapMax, size, alignment, page_size); } // zerobased: Attempt to allocate in the lower 32G. - char *zerobased_max = (char *)OopEncodingHeapMax; + const uintptr_t zerobased_max = OopEncodingHeapMax; // Give it several tries from top of range to bottom. if (aligned_heap_base_min_address + size <= zerobased_max && // Zerobased theoretical possible. ((!reserved.is_reserved()) || // No previous try succeeded. - (reserved.end() > zerobased_max))) { // Unscaled delivered an arbitrary address. + (reserved.end() > (char*)zerobased_max))) { // Unscaled delivered an arbitrary address. // Release previous reservation release(reserved); // Calc address range within we try to attach (range of possible start addresses). - char *const highest_start = align_down(zerobased_max - size, attach_point_alignment); + uintptr_t const highest_start = align_down(zerobased_max - size, attach_point_alignment); // Need to be careful about size being guaranteed to be less // than UnscaledOopHeapMax due to type constraints. - char *lowest_start = aligned_heap_base_min_address; - uint64_t unscaled_end = UnscaledOopHeapMax - size; - if (unscaled_end < UnscaledOopHeapMax) { // unscaled_end wrapped if size is large - lowest_start = MAX2(lowest_start, (char*)unscaled_end); + uintptr_t lowest_start = aligned_heap_base_min_address; + if (size < UnscaledOopHeapMax) { + lowest_start = MAX2(lowest_start, UnscaledOopHeapMax - size); } lowest_start = align_up(lowest_start, attach_point_alignment); - reserved = try_reserve_range(highest_start, lowest_start, attach_point_alignment, - aligned_heap_base_min_address, zerobased_max, size, alignment, page_size); + assert(lowest_start <= highest_start, "lowest: " INTPTR_FORMAT " highest: " INTPTR_FORMAT, + lowest_start, highest_start); + reserved = try_reserve_range((char*)highest_start, (char*)lowest_start, attach_point_alignment, + (char*)aligned_heap_base_min_address, (char*)zerobased_max, size, alignment, page_size); } // Now we go for heaps with base != 0. We need a noaccess prefix to efficiently @@ -616,17 +619,17 @@ ReservedHeapSpace HeapReserver::Instance::reserve_compressed_oops_heap(const siz // Try to attach at addresses that are aligned to OopEncodingHeapMax. Disjointbase mode. char** addresses = get_attach_addresses_for_disjoint_mode(); int i = 0; - while ((addresses[i] != nullptr) && // End of array not yet reached. - ((!reserved.is_reserved()) || // No previous try succeeded. - (reserved.end() > zerobased_max && // Not zerobased or unscaled address. - // Not disjoint address. + while ((addresses[i] != nullptr) && // End of array not yet reached. + ((!reserved.is_reserved()) || // No previous try succeeded. + (reserved.end() > (char*)zerobased_max && // Not zerobased or unscaled address. + // Not disjoint address. !CompressedOops::is_disjoint_heap_base_address((address)reserved.base())))) { // Release previous reservation release(reserved); char* const attach_point = addresses[i]; - assert(attach_point >= aligned_heap_base_min_address, "Flag support broken"); + assert((uintptr_t)attach_point >= aligned_heap_base_min_address, "Flag support broken"); reserved = try_reserve_memory(size + noaccess_prefix, alignment, page_size, attach_point); i++; } diff --git a/src/hotspot/share/memory/universe.cpp b/src/hotspot/share/memory/universe.cpp index d389fe81806..4d2897be5eb 100644 --- a/src/hotspot/share/memory/universe.cpp +++ b/src/hotspot/share/memory/universe.cpp @@ -182,7 +182,6 @@ int Universe::_base_vtable_size = 0; bool Universe::_bootstrapping = false; bool Universe::_module_initialized = false; bool Universe::_fully_initialized = false; -volatile bool Universe::_is_shutting_down = false; OopStorage* Universe::_vm_weak = nullptr; OopStorage* Universe::_vm_global = nullptr; @@ -1374,15 +1373,14 @@ static void log_cpu_time() { } void Universe::before_exit() { - { - // Acquire the Heap_lock to synchronize with VM_Heap_Sync_Operations, - // which may depend on the value of _is_shutting_down flag. - MutexLocker hl(Heap_lock); - log_cpu_time(); - AtomicAccess::release_store(&_is_shutting_down, true); - } + // Tell the GC that it is time to shutdown and to block requests for new GC pauses. + heap()->initiate_shutdown(); - heap()->before_exit(); + // Log CPU time statistics before stopping the GC threads. + log_cpu_time(); + + // Stop the GC threads. + heap()->stop(); // Print GC/heap related information. Log(gc, exit) log; diff --git a/src/hotspot/share/memory/universe.hpp b/src/hotspot/share/memory/universe.hpp index df2c1d66d3c..b2325c67ca0 100644 --- a/src/hotspot/share/memory/universe.hpp +++ b/src/hotspot/share/memory/universe.hpp @@ -128,9 +128,6 @@ class Universe: AllStatic { static bool _module_initialized; // true after call_initPhase2 called static bool _fully_initialized; // true after universe_init and initialize_vtables called - // Shutdown - static volatile bool _is_shutting_down; - // the array of preallocated errors with backtraces static objArrayOop preallocated_out_of_memory_errors(); @@ -328,8 +325,6 @@ class Universe: AllStatic { static bool is_module_initialized() { return _module_initialized; } static bool is_fully_initialized() { return _fully_initialized; } - static bool is_shutting_down() { return AtomicAccess::load_acquire(&_is_shutting_down); } - static bool on_page_boundary(void* addr); static bool should_fill_in_stack_trace(Handle throwable); static void check_alignment(uintx size, uintx alignment, const char* name); diff --git a/src/hotspot/share/oops/bsmAttribute.hpp b/src/hotspot/share/oops/bsmAttribute.hpp new file mode 100644 index 00000000000..a28d2757fb0 --- /dev/null +++ b/src/hotspot/share/oops/bsmAttribute.hpp @@ -0,0 +1,170 @@ +/* + * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_OOPS_BSMATTRIBUTE_HPP +#define SHARE_OOPS_BSMATTRIBUTE_HPP + +#include "oops/array.hpp" +#include "utilities/checkedCast.hpp" +#include "utilities/globalDefinitions.hpp" + +class ClassLoaderData; + +class BSMAttributeEntry { + friend class ConstantPool; + friend class BSMAttributeEntries; + + u2 _bootstrap_method_index; + u2 _argument_count; + + // The argument indexes are stored right after the object, in a contiguous array. + // [ bsmi_0 argc_0 arg_00 arg_01 ... arg_0N bsmi_1 argc_1 arg_10 ... arg_1N ... ] + // So in order to find the argument array, jump over ourselves. + const u2* argument_indexes() const { + return reinterpret_cast(this + 1); + } + u2* argument_indexes() { + return reinterpret_cast(this + 1); + } + // These are overlays on top of the BSMAttributeEntries data array, do not construct. + BSMAttributeEntry() = delete; + NONCOPYABLE(BSMAttributeEntry); + + void copy_args_into(BSMAttributeEntry* entry) const; + +public: + // Offsets for SA + enum { + _bsmi_offset = 0, + _argc_offset = 1, + _argv_offset = 2 + }; + + int bootstrap_method_index() const { + return _bootstrap_method_index; + } + int argument_count() const { + return _argument_count; + } + int argument(int n) const { + assert(checked_cast(n) < _argument_count, "oob"); + return argument_indexes()[n]; + } + + void set_argument(int index, u2 value) { + assert(index >= 0 && index < argument_count(), "invariant"); + argument_indexes()[index] = value; + } + + // How many u2s are required to store a BSM entry with argc arguments? + static int u2s_required (u2 argc) { + return 1 /* index */ + 1 /* argc */ + argc /* argv */; + } +}; + +// The BSMAttributeEntries stores the state of the BootstrapMethods attribute. +class BSMAttributeEntries { + friend class VMStructs; + friend class JVMCIVMStructs; + +public: + class InsertionIterator { + friend BSMAttributeEntries; + BSMAttributeEntries* _insert_into; + // Current unused offset into BSMAEs offset array. + int _cur_offset; + // Current unused offset into BSMAEs bsm-data array. + int _cur_array; + public: + InsertionIterator() : _insert_into(nullptr), _cur_offset(-1), _cur_array(-1) {} + InsertionIterator(BSMAttributeEntries* insert_into, int cur_offset, int cur_array) + : _insert_into(insert_into), + _cur_offset(cur_offset), + _cur_array(cur_array) {} + InsertionIterator(const InsertionIterator&) = default; + InsertionIterator& operator=(const InsertionIterator&) = default; + + int current_offset() const { return _cur_offset; } + // Add a new BSMAE, reserving the necessary memory for filling the argument vector. + // Returns null if there isn't enough space. + inline BSMAttributeEntry* reserve_new_entry(u2 bsmi, u2 argc); + }; + +private: + // Each bootstrap method has a variable-sized array associated with it. + // We want constant-time lookup of the Nth BSM. Therefore, we use an offset table, + // such that the Nth BSM is located at _bootstrap_methods[_offsets[N]]. + Array* _offsets; + Array* _bootstrap_methods; + + // Copy the first num_entries into iter. + void copy_into(InsertionIterator& iter, int num_entries) const; + +public: + BSMAttributeEntries() : _offsets(nullptr), _bootstrap_methods(nullptr) {} + BSMAttributeEntries(Array* offsets, Array* bootstrap_methods) + : _offsets(offsets), + _bootstrap_methods(bootstrap_methods) {} + + bool is_empty() const { + 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; } + + BSMAttributeEntry* entry(int bsms_attribute_index) { + return reinterpret_cast(_bootstrap_methods->adr_at(_offsets->at(bsms_attribute_index))); + } + const BSMAttributeEntry* entry(int bsms_attribute_index) const { + return reinterpret_cast(_bootstrap_methods->adr_at(_offsets->at(bsms_attribute_index))); + } + + int number_of_entries() const { + return _offsets == nullptr ? 0 : _offsets->length(); + } + + // The number of U2s the BSM data consists of. + int array_length() const { + return _bootstrap_methods == nullptr ? 0 : _bootstrap_methods->length(); + } + + void deallocate_contents(ClassLoaderData* loader_data); + + // Extend to have the space for both this BSMAEntries and other's. + // Does not copy in the other's BSMAEntrys, that must be done via the InsertionIterator. + // This starts an insertion iterator. Any call to start_extension must have a matching end_extension call. + InsertionIterator start_extension(const BSMAttributeEntries& other, ClassLoaderData* loader_data, TRAPS); + // Extend the BSMAEntries with an additional number_of_entries with a total data_size. + InsertionIterator start_extension(int number_of_entries, int data_size, ClassLoaderData* loader_data, TRAPS); + // Reallocates the underlying memory to fit the limits of the InsertionIterator precisely. + // This ends an insertion iteration. The memory is truncated to fit exactly the data used. + 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); +}; + +#endif // SHARE_OOPS_BSMATTRIBUTE_HPP diff --git a/src/hotspot/share/oops/bsmAttribute.inline.hpp b/src/hotspot/share/oops/bsmAttribute.inline.hpp new file mode 100644 index 00000000000..e678c280c26 --- /dev/null +++ b/src/hotspot/share/oops/bsmAttribute.inline.hpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_OOPS_BSMATTRIBUTE_INLINE_HPP +#define SHARE_OOPS_BSMATTRIBUTE_INLINE_HPP + +#include "oops/bsmAttribute.hpp" + +inline BSMAttributeEntry* BSMAttributeEntries::InsertionIterator::reserve_new_entry(u2 bsmi, u2 argc) { + assert(_insert_into->offsets() != nullptr, "must"); + assert(_insert_into->bootstrap_methods() != nullptr, "must"); + + if (_cur_offset + 1 > _insert_into->offsets()->length() || + _cur_array + BSMAttributeEntry::u2s_required(argc) > _insert_into->bootstrap_methods()->length()) { + return nullptr; + } + _insert_into->offsets()->at_put(_cur_offset, _cur_array); + BSMAttributeEntry* e = _insert_into->entry(_cur_offset); + e->_bootstrap_method_index = bsmi; + e->_argument_count = argc; + + _cur_array += 1 + 1 + argc; + _cur_offset += 1; + return e; +} + +inline void BSMAttributeEntry::copy_args_into(BSMAttributeEntry* entry) const { + assert(entry->argument_count() == this->argument_count(), "must be same"); + for (int i = 0; i < argument_count(); i++) { + entry->set_argument(i, this->argument(i)); + } +} + +#endif // SHARE_OOPS_BSMATTRIBUTE_INLINE_HPP diff --git a/src/hotspot/share/oops/constantPool.cpp b/src/hotspot/share/oops/constantPool.cpp index 95a43b07bd7..640b2f2460f 100644 --- a/src/hotspot/share/oops/constantPool.cpp +++ b/src/hotspot/share/oops/constantPool.cpp @@ -131,8 +131,7 @@ void ConstantPool::deallocate_contents(ClassLoaderData* loader_data) { MetadataFactory::free_array(loader_data, resolved_klasses()); set_resolved_klasses(nullptr); - MetadataFactory::free_array(loader_data, operands()); - set_operands(nullptr); + bsm_entries().deallocate_contents(loader_data); release_C_heap_structures(); @@ -152,7 +151,8 @@ void ConstantPool::metaspace_pointers_do(MetaspaceClosure* it) { it->push(&_tags, MetaspaceClosure::_writable); it->push(&_cache); it->push(&_pool_holder); - it->push(&_operands); + it->push(&bsm_entries().offsets()); + it->push(&bsm_entries().bootstrap_methods()); it->push(&_resolved_klasses, MetaspaceClosure::_writable); for (int i = 0; i < length(); i++) { @@ -761,7 +761,7 @@ Method* ConstantPool::method_at_if_loaded(const constantPoolHandle& cpool, if (cpool->cache() == nullptr) return nullptr; // nothing to load yet if (!(which >= 0 && which < cpool->resolved_method_entries_length())) { // FIXME: should be an assert - log_debug(class, resolve)("bad operand %d in:", which); cpool->print(); + log_debug(class, resolve)("bad BSM %d in:", which); cpool->print(); return nullptr; } return cpool->cache()->method_if_resolved(which); @@ -1562,8 +1562,8 @@ bool ConstantPool::compare_entry_to(int index1, const constantPoolHandle& cp2, int i1 = bootstrap_methods_attribute_index(index1); int i2 = cp2->bootstrap_methods_attribute_index(index2); bool match_entry = compare_entry_to(k1, cp2, k2); - bool match_operand = compare_operand_to(i1, cp2, i2); - return (match_entry && match_operand); + bool match_bsm = compare_bootstrap_entry_to(i1, cp2, i2); + return (match_entry && match_bsm); } break; case JVM_CONSTANT_InvokeDynamic: @@ -1573,8 +1573,8 @@ bool ConstantPool::compare_entry_to(int index1, const constantPoolHandle& cp2, int i1 = bootstrap_methods_attribute_index(index1); int i2 = cp2->bootstrap_methods_attribute_index(index2); bool match_entry = compare_entry_to(k1, cp2, k2); - bool match_operand = compare_operand_to(i1, cp2, i2); - return (match_entry && match_operand); + bool match_bsm = compare_bootstrap_entry_to(i1, cp2, i2); + return (match_entry && match_bsm); } break; case JVM_CONSTANT_String: @@ -1608,140 +1608,29 @@ bool ConstantPool::compare_entry_to(int index1, const constantPoolHandle& cp2, return false; } // end compare_entry_to() - -// Resize the operands array with delta_len and delta_size. +// Extend the BSMAttributeEntries with the length and size of the ext_cp BSMAttributeEntries. // Used in RedefineClasses for CP merge. -void ConstantPool::resize_operands(int delta_len, int delta_size, TRAPS) { - int old_len = operand_array_length(operands()); - int new_len = old_len + delta_len; - int min_len = (delta_len > 0) ? old_len : new_len; - - int old_size = operands()->length(); - int new_size = old_size + delta_size; - int min_size = (delta_size > 0) ? old_size : new_size; - - ClassLoaderData* loader_data = pool_holder()->class_loader_data(); - Array* new_ops = MetadataFactory::new_array(loader_data, new_size, CHECK); - - // Set index in the resized array for existing elements only - for (int idx = 0; idx < min_len; idx++) { - int offset = operand_offset_at(idx); // offset in original array - operand_offset_at_put(new_ops, idx, offset + 2*delta_len); // offset in resized array - } - // Copy the bootstrap specifiers only - Copy::conjoint_memory_atomic(operands()->adr_at(2*old_len), - new_ops->adr_at(2*new_len), - (min_size - 2*min_len) * sizeof(u2)); - // Explicitly deallocate old operands array. - // Note, it is not needed for 7u backport. - if ( operands() != nullptr) { // the safety check - MetadataFactory::free_array(loader_data, operands()); - } - set_operands(new_ops); -} // end resize_operands() +BSMAttributeEntries::InsertionIterator +ConstantPool::start_extension(const constantPoolHandle& ext_cp, TRAPS) { + BSMAttributeEntries::InsertionIterator iter = + bsm_entries().start_extension(ext_cp->bsm_entries(), pool_holder()->class_loader_data(), + CHECK_(BSMAttributeEntries::InsertionIterator())); + return iter; +} -// Extend the operands array with the length and size of the ext_cp operands. -// Used in RedefineClasses for CP merge. -void ConstantPool::extend_operands(const constantPoolHandle& ext_cp, TRAPS) { - int delta_len = operand_array_length(ext_cp->operands()); - if (delta_len == 0) { - return; // nothing to do - } - int delta_size = ext_cp->operands()->length(); - - assert(delta_len > 0 && delta_size > 0, "extended operands array must be bigger"); - - if (operand_array_length(operands()) == 0) { - ClassLoaderData* loader_data = pool_holder()->class_loader_data(); - Array* new_ops = MetadataFactory::new_array(loader_data, delta_size, CHECK); - // The first element index defines the offset of second part - operand_offset_at_put(new_ops, 0, 2*delta_len); // offset in new array - set_operands(new_ops); - } else { - resize_operands(delta_len, delta_size, CHECK); - } - -} // end extend_operands() +void ConstantPool::end_extension(BSMAttributeEntries::InsertionIterator iter, TRAPS) { + bsm_entries().end_extension(iter, pool_holder()->class_loader_data(), THREAD); +} -// Shrink the operands array to a smaller array with new_len length. -// Used in RedefineClasses for CP merge. -void ConstantPool::shrink_operands(int new_len, TRAPS) { - int old_len = operand_array_length(operands()); - if (new_len == old_len) { - return; // nothing to do - } - assert(new_len < old_len, "shrunken operands array must be smaller"); - - int free_base = operand_next_offset_at(new_len - 1); - int delta_len = new_len - old_len; - int delta_size = 2*delta_len + free_base - operands()->length(); - - resize_operands(delta_len, delta_size, CHECK); - -} // end shrink_operands() - - -void ConstantPool::copy_operands(const constantPoolHandle& from_cp, - const constantPoolHandle& to_cp, - TRAPS) { - - int from_oplen = operand_array_length(from_cp->operands()); - int old_oplen = operand_array_length(to_cp->operands()); - if (from_oplen != 0) { - ClassLoaderData* loader_data = to_cp->pool_holder()->class_loader_data(); - // append my operands to the target's operands array - if (old_oplen == 0) { - // Can't just reuse from_cp's operand list because of deallocation issues - int len = from_cp->operands()->length(); - Array* new_ops = MetadataFactory::new_array(loader_data, len, CHECK); - Copy::conjoint_memory_atomic( - from_cp->operands()->adr_at(0), new_ops->adr_at(0), len * sizeof(u2)); - to_cp->set_operands(new_ops); - } else { - int old_len = to_cp->operands()->length(); - int from_len = from_cp->operands()->length(); - int old_off = old_oplen * sizeof(u2); - int from_off = from_oplen * sizeof(u2); - // Use the metaspace for the destination constant pool - Array* new_operands = MetadataFactory::new_array(loader_data, old_len + from_len, CHECK); - int fillp = 0, len = 0; - // first part of dest - Copy::conjoint_memory_atomic(to_cp->operands()->adr_at(0), - new_operands->adr_at(fillp), - (len = old_off) * sizeof(u2)); - fillp += len; - // first part of src - Copy::conjoint_memory_atomic(from_cp->operands()->adr_at(0), - new_operands->adr_at(fillp), - (len = from_off) * sizeof(u2)); - fillp += len; - // second part of dest - Copy::conjoint_memory_atomic(to_cp->operands()->adr_at(old_off), - new_operands->adr_at(fillp), - (len = old_len - old_off) * sizeof(u2)); - fillp += len; - // second part of src - Copy::conjoint_memory_atomic(from_cp->operands()->adr_at(from_off), - new_operands->adr_at(fillp), - (len = from_len - from_off) * sizeof(u2)); - fillp += len; - assert(fillp == new_operands->length(), ""); - - // Adjust indexes in the first part of the copied operands array. - for (int j = 0; j < from_oplen; j++) { - int offset = operand_offset_at(new_operands, old_oplen + j); - assert(offset == operand_offset_at(from_cp->operands(), j), "correct copy"); - offset += old_len; // every new tuple is preceded by old_len extra u2's - operand_offset_at_put(new_operands, old_oplen + j, offset); - } - - // replace target operands array with combined array - to_cp->set_operands(new_operands); - } - } -} // end copy_operands() +void ConstantPool::copy_bsm_entries(const constantPoolHandle& from_cp, + const constantPoolHandle& to_cp, + TRAPS) { + to_cp->bsm_entries().append(from_cp->bsm_entries(), + to_cp->pool_holder()->class_loader_data(), + THREAD); +} // Copy this constant pool's entries at start_i to end_i (inclusive) @@ -1771,7 +1660,7 @@ void ConstantPool::copy_cp_to_impl(const constantPoolHandle& from_cp, int start_ break; } } - copy_operands(from_cp, to_cp, CHECK); + copy_bsm_entries(from_cp, to_cp, THREAD); } // end copy_cp_to_impl() @@ -1895,7 +1784,7 @@ void ConstantPool::copy_entry_to(const constantPoolHandle& from_cp, int from_i, { int k1 = from_cp->bootstrap_methods_attribute_index(from_i); int k2 = from_cp->bootstrap_name_and_type_ref_index_at(from_i); - k1 += operand_array_length(to_cp->operands()); // to_cp might already have operands + k1 += to_cp->bsm_entries().array_length(); // to_cp might already have a BSM attribute to_cp->dynamic_constant_at_put(to_i, k1, k2); } break; @@ -1903,7 +1792,7 @@ void ConstantPool::copy_entry_to(const constantPoolHandle& from_cp, int from_i, { int k1 = from_cp->bootstrap_methods_attribute_index(from_i); int k2 = from_cp->bootstrap_name_and_type_ref_index_at(from_i); - k1 += operand_array_length(to_cp->operands()); // to_cp might already have operands + k1 += to_cp->bsm_entries().array_length(); // to_cp might already have a BSM attribute to_cp->invoke_dynamic_at_put(to_i, k1, k2); } break; @@ -1939,9 +1828,9 @@ int ConstantPool::find_matching_entry(int pattern_i, // Compare this constant pool's bootstrap specifier at idx1 to the constant pool // cp2's bootstrap specifier at idx2. -bool ConstantPool::compare_operand_to(int idx1, const constantPoolHandle& cp2, int idx2) { - BSMAttributeEntry* e1 = bsm_attribute_entry(idx1); - BSMAttributeEntry* e2 = cp2->bsm_attribute_entry(idx2); +bool ConstantPool::compare_bootstrap_entry_to(int idx1, const constantPoolHandle& cp2, int idx2) { + const BSMAttributeEntry* const e1 = bsm_attribute_entry(idx1); + const BSMAttributeEntry* const e2 = cp2->bsm_attribute_entry(idx2); int k1 = e1->bootstrap_method_index(); int k2 = e2->bootstrap_method_index(); bool match = compare_entry_to(k1, cp2, k2); @@ -1949,34 +1838,37 @@ bool ConstantPool::compare_operand_to(int idx1, const constantPoolHandle& cp2, i if (!match) { return false; } - int argc = e1->argument_count(); - if (argc == e2->argument_count()) { - for (int j = 0; j < argc; j++) { - k1 = e1->argument_index(j); - k2 = e2->argument_index(j); - match = compare_entry_to(k1, cp2, k2); - if (!match) { - return false; - } - } - return true; // got through loop; all elements equal + + const int argc = e1->argument_count(); + if (argc != e2->argument_count()) { + return false; } - return false; -} // end compare_operand_to() + + for (int j = 0; j < argc; j++) { + k1 = e1->argument(j); + k2 = e2->argument(j); + match = compare_entry_to(k1, cp2, k2); + if (!match) { + return false; + } + } + + return true; // got through loop; all elements equal +} // end compare_bootstrap_entry_to() // Search constant pool search_cp for a bootstrap specifier that matches // this constant pool's bootstrap specifier data at pattern_i index. // Return the index of a matching bootstrap attribute record or (-1) if there is no match. -int ConstantPool::find_matching_operand(int pattern_i, - const constantPoolHandle& search_cp, int search_len) { - for (int i = 0; i < search_len; i++) { - bool found = compare_operand_to(pattern_i, search_cp, i); +int ConstantPool::find_matching_bsm_entry(int pattern_i, + const constantPoolHandle& search_cp, int offset_limit) { + for (int i = 0; i < offset_limit; i++) { + bool found = compare_bootstrap_entry_to(pattern_i, search_cp, i); if (found) { return i; } } return -1; // bootstrap specifier data not found; return unused index (-1) -} // end find_matching_operand() +} // end find_matching_bsm_entry() #ifndef PRODUCT @@ -2411,7 +2303,7 @@ void ConstantPool::print_value_on(outputStream* st) const { assert(is_constantPool(), "must be constantPool"); st->print("constant pool [%d]", length()); if (has_preresolution()) st->print("/preresolution"); - if (operands() != nullptr) st->print("/operands[%d]", operands()->length()); + if (!bsm_entries().is_empty()) st->print("/BSMs[%d]", bsm_entries().bootstrap_methods()->length()); print_address_on(st); if (pool_holder() != nullptr) { st->print(" for "); @@ -2446,3 +2338,87 @@ void ConstantPool::verify_on(outputStream* st) { guarantee(pool_holder()->is_klass(), "should be klass"); } } + +void BSMAttributeEntries::deallocate_contents(ClassLoaderData* loader_data) { + MetadataFactory::free_array(loader_data, this->_offsets); + MetadataFactory::free_array(loader_data, this->_bootstrap_methods); + this->_offsets = nullptr; + this->_bootstrap_methods = nullptr; +} + +void BSMAttributeEntries::copy_into(InsertionIterator& iter, int num_entries) const { + assert(num_entries + iter._cur_offset <= iter._insert_into->_offsets->length(), "must"); + for (int i = 0; i < num_entries; i++) { + const BSMAttributeEntry* e = entry(i); + BSMAttributeEntry* e_new = iter.reserve_new_entry(e->bootstrap_method_index(), e->argument_count()); + assert(e_new != nullptr, "must be"); + e->copy_args_into(e_new); + } +} + +BSMAttributeEntries::InsertionIterator +BSMAttributeEntries::start_extension(const BSMAttributeEntries& other, ClassLoaderData* loader_data, TRAPS) { + InsertionIterator iter = start_extension(other.number_of_entries(), other.array_length(), + loader_data, CHECK_(BSMAttributeEntries::InsertionIterator())); + return iter; +} + +BSMAttributeEntries::InsertionIterator +BSMAttributeEntries::start_extension(int number_of_entries, int array_length, + ClassLoaderData* loader_data, TRAPS) { + InsertionIterator extension_iterator(this, this->number_of_entries(), this->array_length()); + int new_number_of_entries = this->number_of_entries() + number_of_entries; + int new_array_length = this->array_length() + array_length; + int invalid_index = new_array_length; + + Array* new_offsets = + MetadataFactory::new_array(loader_data, new_number_of_entries, invalid_index, CHECK_(InsertionIterator())); + Array* new_array = MetadataFactory::new_array(loader_data, new_array_length, CHECK_(InsertionIterator())); + { // Copy over all the old BSMAEntry's and their respective offsets + BSMAttributeEntries carrier(new_offsets, new_array); + InsertionIterator copy_iter(&carrier, 0, 0); + copy_into(copy_iter, this->number_of_entries()); + } + // Replace content + deallocate_contents(loader_data); + _offsets = new_offsets; + _bootstrap_methods = new_array; + return extension_iterator; +} + + +void BSMAttributeEntries::append(const BSMAttributeEntries& other, ClassLoaderData* loader_data, TRAPS) { + if (other.number_of_entries() == 0) { + return; // Done! + } + InsertionIterator iter = start_extension(other, loader_data, CHECK); + other.copy_into(iter, other.number_of_entries()); + end_extension(iter, loader_data, THREAD); +} + +void BSMAttributeEntries::end_extension(InsertionIterator& iter, ClassLoaderData* loader_data, TRAPS) { + assert(iter._insert_into == this, "must be"); + assert(iter._cur_offset <= this->_offsets->length(), "must be"); + assert(iter._cur_array <= this->_bootstrap_methods->length(), "must be"); + + // Did we fill up all of the available space? If so, do nothing. + if (iter._cur_offset == this->_offsets->length() && + iter._cur_array == this->_bootstrap_methods->length()) { + return; + } + + // We used less, truncate by allocating new arrays + Array* new_offsets = + MetadataFactory::new_array(loader_data, iter._cur_offset, 0, CHECK); + Array* new_array = + MetadataFactory::new_array(loader_data, iter._cur_array, CHECK); + { // Copy over the constructed BSMAEntry's + BSMAttributeEntries carrier(new_offsets, new_array); + InsertionIterator copy_iter(&carrier, 0, 0); + copy_into(copy_iter, iter._cur_offset); + } + + deallocate_contents(loader_data); + _offsets = new_offsets; + _bootstrap_methods = new_array; +} diff --git a/src/hotspot/share/oops/constantPool.hpp b/src/hotspot/share/oops/constantPool.hpp index 9cbeb1245be..6c519945f4d 100644 --- a/src/hotspot/share/oops/constantPool.hpp +++ b/src/hotspot/share/oops/constantPool.hpp @@ -27,6 +27,7 @@ #include "memory/allocation.hpp" #include "oops/arrayOop.hpp" +#include "oops/bsmAttribute.inline.hpp" #include "oops/cpCache.hpp" #include "oops/objArrayOop.hpp" #include "oops/oopHandle.hpp" @@ -77,43 +78,6 @@ public: } }; -class BSMAttributeEntry { - friend class ConstantPool; - u2 _bootstrap_method_index; - u2 _argument_count; - - // The argument indexes are stored right after the object, in a contiguous array. - // [ bsmi_0 argc_0 arg_00 arg_01 ... arg_0N bsmi_1 argc_1 arg_10 ... arg_1N ... ] - // So in order to find the argument array, jump over ourselves. - const u2* argument_indexes() const { - return reinterpret_cast(this + 1); - } - u2* argument_indexes() { - return reinterpret_cast(this + 1); - } - // These are overlays on top of the operands array. Do not construct. - BSMAttributeEntry() = delete; - -public: - // Offsets for SA - enum { - _bsmi_offset = 0, - _argc_offset = 1, - _argv_offset = 2 - }; - - int bootstrap_method_index() const { - return _bootstrap_method_index; - } - int argument_count() const { - return _argument_count; - } - int argument_index(int n) const { - assert(checked_cast(n) < _argument_count, "oob"); - return argument_indexes()[n]; - } -}; - class ConstantPool : public Metadata { friend class VMStructs; friend class JVMCIVMStructs; @@ -126,7 +90,8 @@ class ConstantPool : public Metadata { Array* _tags; // the tag array describing the constant pool's contents ConstantPoolCache* _cache; // the cache holding interpreter runtime information InstanceKlass* _pool_holder; // the corresponding class - Array* _operands; // for variable-sized (InvokeDynamic) nodes, usually empty + + BSMAttributeEntries _bsm_entries; // Consider using an array of compressed klass pointers to // save space on 64-bit platforms. @@ -167,8 +132,6 @@ class ConstantPool : public Metadata { u1* tag_addr_at(int cp_index) const { return tags()->adr_at(cp_index); } - void set_operands(Array* operands) { _operands = operands; } - u2 flags() const { return _flags; } void set_flags(u2 f) { _flags = f; } @@ -208,7 +171,13 @@ class ConstantPool : public Metadata { virtual bool is_constantPool() const { return true; } Array* tags() const { return _tags; } - Array* operands() const { return _operands; } + + BSMAttributeEntries& bsm_entries() { + return _bsm_entries; + } + const BSMAttributeEntries& bsm_entries() const { + return _bsm_entries; + } bool has_preresolution() const { return (_flags & _has_preresolution) != 0; } void set_has_preresolution() { @@ -556,76 +525,21 @@ class ConstantPool : public Metadata { assert(tag_at(cp_index).has_bootstrap(), "Corrupted constant pool"); return extract_low_short_from_int(*int_at_addr(cp_index)); } - // The first part of the operands array consists of an index into the second part. - // Extract a 32-bit index value from the first part. - static int operand_offset_at(Array* operands, int bsms_attribute_index) { - int n = (bsms_attribute_index * 2); - assert(n >= 0 && n+2 <= operands->length(), "oob"); - // The first 32-bit index points to the beginning of the second part - // of the operands array. Make sure this index is in the first part. - DEBUG_ONLY(int second_part = build_int_from_shorts(operands->at(0), - operands->at(1))); - assert(second_part == 0 || n+2 <= second_part, "oob (2)"); - int offset = build_int_from_shorts(operands->at(n+0), - operands->at(n+1)); - // The offset itself must point into the second part of the array. - assert(offset == 0 || (offset >= second_part && offset <= operands->length()), "oob (3)"); - return offset; - } - static void operand_offset_at_put(Array* operands, int bsms_attribute_index, int offset) { - int n = bsms_attribute_index * 2; - assert(n >= 0 && n+2 <= operands->length(), "oob"); - operands->at_put(n+0, extract_low_short_from_int(offset)); - operands->at_put(n+1, extract_high_short_from_int(offset)); - } - static int operand_array_length(Array* operands) { - if (operands == nullptr || operands->length() == 0) return 0; - int second_part = operand_offset_at(operands, 0); - return (second_part / 2); - } - -#ifdef ASSERT - // operand tuples fit together exactly, end to end - static int operand_limit_at(Array* operands, int bsms_attribute_index) { - int nextidx = bsms_attribute_index + 1; - if (nextidx == operand_array_length(operands)) - return operands->length(); - else - return operand_offset_at(operands, nextidx); - } -#endif //ASSERT - - // These functions are used in RedefineClasses for CP merge - int operand_offset_at(int bsms_attribute_index) { - assert(0 <= bsms_attribute_index && - bsms_attribute_index < operand_array_length(operands()), - "Corrupted CP operands"); - return operand_offset_at(operands(), bsms_attribute_index); - } BSMAttributeEntry* bsm_attribute_entry(int bsms_attribute_index) { - int offset = operand_offset_at(bsms_attribute_index); - return reinterpret_cast(operands()->adr_at(offset)); + return _bsm_entries.entry(bsms_attribute_index); } - int operand_next_offset_at(int bsms_attribute_index) { - BSMAttributeEntry* bsme = bsm_attribute_entry(bsms_attribute_index); - u2* argv_start = bsme->argument_indexes(); - int offset = argv_start - operands()->data(); - return offset + bsme->argument_count(); - } - // Compare a bootstrap specifier data in the operands arrays - bool compare_operand_to(int bsms_attribute_index1, const constantPoolHandle& cp2, - int bsms_attribute_index2); - // Find a bootstrap specifier data in the operands array - int find_matching_operand(int bsms_attribute_index, const constantPoolHandle& search_cp, - int operands_cur_len); - // Resize the operands array with delta_len and delta_size - void resize_operands(int delta_len, int delta_size, TRAPS); - // Extend the operands array with the length and size of the ext_cp operands - void extend_operands(const constantPoolHandle& ext_cp, TRAPS); - // Shrink the operands array to a smaller array with new_len length - void shrink_operands(int new_len, TRAPS); + bool compare_bootstrap_entry_to(int bsms_attribute_index1, const constantPoolHandle& cp2, + int bsms_attribute_index2); + // Find a BSM entry in search_cp that matches the BSM at bsm_attribute_index. + // Return -1 if not found. + int find_matching_bsm_entry(int bsms_attribute_index, const constantPoolHandle& search_cp, + int offset_limit); + // Extend the BSM attribute storage to fit both the current data and the BSM data in ext_cp. + // Use the returned InsertionIterator to fill out the newly allocated space. + BSMAttributeEntries::InsertionIterator start_extension(const constantPoolHandle& ext_cp, TRAPS); + void end_extension(BSMAttributeEntries::InsertionIterator iter, TRAPS); u2 bootstrap_method_ref_index_at(int cp_index) { assert(tag_at(cp_index).has_bootstrap(), "Corrupted constant pool"); @@ -641,7 +555,7 @@ class ConstantPool : public Metadata { int bsmai = bootstrap_methods_attribute_index(cp_index); BSMAttributeEntry* bsme = bsm_attribute_entry(bsmai); assert((uint)j < (uint)bsme->argument_count(), "oob"); - return bsm_attribute_entry(bsmai)->argument_index(j); + return bsm_attribute_entry(bsmai)->argument(j); } // The following methods (name/signature/klass_ref_at, klass_ref_at_noresolve, @@ -848,7 +762,7 @@ private: } static void copy_cp_to_impl(const constantPoolHandle& from_cp, int start_cpi, int end_cpi, const constantPoolHandle& to_cp, int to_cpi, TRAPS); static void copy_entry_to(const constantPoolHandle& from_cp, int from_cpi, const constantPoolHandle& to_cp, int to_cpi); - static void copy_operands(const constantPoolHandle& from_cp, const constantPoolHandle& to_cp, TRAPS); + static void copy_bsm_entries(const constantPoolHandle& from_cp, const constantPoolHandle& to_cp, TRAPS); int find_matching_entry(int pattern_i, const constantPoolHandle& search_cp); int version() const { return _saved._version; } void set_version(int version) { _saved._version = version; } diff --git a/src/hotspot/share/opto/chaitin.cpp b/src/hotspot/share/opto/chaitin.cpp index 524dee6e06a..667270d96b4 100644 --- a/src/hotspot/share/opto/chaitin.cpp +++ b/src/hotspot/share/opto/chaitin.cpp @@ -1471,6 +1471,65 @@ static OptoReg::Name find_first_set(LRG& lrg, RegMask& mask) { return assigned; } +OptoReg::Name PhaseChaitin::select_bias_lrg_color(LRG& lrg) { + uint bias_lrg1_idx = _lrg_map.find(lrg._copy_bias); + uint bias_lrg2_idx = _lrg_map.find(lrg._copy_bias2); + + // If bias_lrg1 has a color + if (bias_lrg1_idx != 0 && !_ifg->_yanked->test(bias_lrg1_idx)) { + OptoReg::Name reg = lrgs(bias_lrg1_idx).reg(); + // and it is legal for lrg + if (is_legal_reg(lrg, reg)) { + return reg; + } + } + + // If bias_lrg2 has a color + if (bias_lrg2_idx != 0 && !_ifg->_yanked->test(bias_lrg2_idx)) { + OptoReg::Name reg = lrgs(bias_lrg2_idx).reg(); + // and it is legal for lrg + if (is_legal_reg(lrg, reg)) { + return reg; + } + } + + uint bias_lrg_idx = 0; + if (bias_lrg1_idx != 0 && bias_lrg2_idx != 0) { + // Since none of the bias live ranges are part of the IFG yet, constrain the + // definition mask with the bias live range with the least degrees of + // freedom. This will increase the chances of register sharing once the bias + // live range becomes part of the IFG. + lrgs(bias_lrg1_idx).compute_set_mask_size(); + lrgs(bias_lrg2_idx).compute_set_mask_size(); + bias_lrg_idx = lrgs(bias_lrg1_idx).degrees_of_freedom() > + lrgs(bias_lrg2_idx).degrees_of_freedom() + ? bias_lrg2_idx + : bias_lrg1_idx; + } else if (bias_lrg1_idx != 0) { + bias_lrg_idx = bias_lrg1_idx; + } else if (bias_lrg2_idx != 0) { + bias_lrg_idx = bias_lrg2_idx; + } + + // Register masks with offset excludes all mask bits before the offset. + // Such masks are mainly used for allocation from stack slots. Constrain the + // register mask of definition live range using bias mask only if + // both masks have zero offset. + if (bias_lrg_idx != 0 && !lrg.mask().is_offset() && + !lrgs(bias_lrg_idx).mask().is_offset()) { + // Choose a color which is legal for bias_lrg + ResourceMark rm(C->regmask_arena()); + RegMask tempmask(lrg.mask(), C->regmask_arena()); + tempmask.and_with(lrgs(bias_lrg_idx).mask()); + tempmask.clear_to_sets(lrg.num_regs()); + OptoReg::Name reg = find_first_set(lrg, tempmask); + if (OptoReg::is_valid(reg)) { + return reg; + } + } + return OptoReg::Bad; +} + // Choose a color using the biasing heuristic OptoReg::Name PhaseChaitin::bias_color(LRG& lrg) { @@ -1492,25 +1551,10 @@ OptoReg::Name PhaseChaitin::bias_color(LRG& lrg) { } } - uint copy_lrg = _lrg_map.find(lrg._copy_bias); - if (copy_lrg != 0) { - // If he has a color, - if(!_ifg->_yanked->test(copy_lrg)) { - OptoReg::Name reg = lrgs(copy_lrg).reg(); - // And it is legal for you, - if (is_legal_reg(lrg, reg)) { - return reg; - } - } else if (!lrg.mask().is_offset()) { - // Choose a color which is legal for him - ResourceMark rm(C->regmask_arena()); - RegMask tempmask(lrg.mask(), C->regmask_arena()); - tempmask.and_with(lrgs(copy_lrg).mask()); - tempmask.clear_to_sets(lrg.num_regs()); - OptoReg::Name reg = find_first_set(lrg, tempmask); - if (OptoReg::is_valid(reg)) - return reg; - } + // Try biasing the color with non-interfering bias live range[s]. + OptoReg::Name reg = select_bias_lrg_color(lrg); + if (OptoReg::is_valid(reg)) { + return reg; } // If no bias info exists, just go with the register selection ordering @@ -1524,7 +1568,7 @@ OptoReg::Name PhaseChaitin::bias_color(LRG& lrg) { // CNC - Fun hack. Alternate 1st and 2nd selection. Enables post-allocate // copy removal to remove many more copies, by preventing a just-assigned // register from being repeatedly assigned. - OptoReg::Name reg = lrg.mask().find_first_elem(); + reg = lrg.mask().find_first_elem(); if( (++_alternate & 1) && OptoReg::is_valid(reg) ) { // This 'Remove; find; Insert' idiom is an expensive way to find the // SECOND element in the mask. @@ -1640,6 +1684,27 @@ uint PhaseChaitin::Select( ) { } } } + + Node* def = lrg->_def; + if (lrg->is_singledef() && !lrg->_is_bound && def->is_Mach()) { + MachNode* mdef = def->as_Mach(); + if (Matcher::is_register_biasing_candidate(mdef, 1)) { + Node* in1 = mdef->in(mdef->operand_index(1)); + if (in1 != nullptr && lrg->_copy_bias == 0) { + lrg->_copy_bias = _lrg_map.find(in1); + } + } + + // For commutative operations, def allocation can also be + // biased towards LRG of second input's def. + if (Matcher::is_register_biasing_candidate(mdef, 2)) { + Node* in2 = mdef->in(mdef->operand_index(2)); + if (in2 != nullptr && lrg->_copy_bias2 == 0) { + lrg->_copy_bias2 = _lrg_map.find(in2); + } + } + } + //assert(is_infinite_stack == lrg->mask().is_infinite_stack(), "nbrs must not change InfiniteStackedness"); // Aligned pairs need aligned masks assert(!lrg->_is_vector || !lrg->_fat_proj, "sanity"); diff --git a/src/hotspot/share/opto/chaitin.hpp b/src/hotspot/share/opto/chaitin.hpp index b477c54fcae..ac072e94e2b 100644 --- a/src/hotspot/share/opto/chaitin.hpp +++ b/src/hotspot/share/opto/chaitin.hpp @@ -63,6 +63,7 @@ public: uint _risk_bias; // Index of LRG which we want to avoid color uint _copy_bias; // Index of LRG which we want to share color + uint _copy_bias2; // Index of second LRG which we want to share color uint _next; // Index of next LRG in linked list uint _prev; // Index of prev LRG in linked list @@ -703,6 +704,8 @@ private: OptoReg::Name choose_color(LRG& lrg); // Helper function which implements biasing heuristic OptoReg::Name bias_color(LRG& lrg); + // Helper function which implements color biasing + OptoReg::Name select_bias_lrg_color(LRG& lrg); // Split uncolorable live ranges // Return new number of live ranges diff --git a/src/hotspot/share/opto/idealGraphPrinter.cpp b/src/hotspot/share/opto/idealGraphPrinter.cpp index 6a738878a1b..5070a9f00e1 100644 --- a/src/hotspot/share/opto/idealGraphPrinter.cpp +++ b/src/hotspot/share/opto/idealGraphPrinter.cpp @@ -35,6 +35,97 @@ #ifndef PRODUCT +// Support for printing properties +class PrintProperties +{ +private: + IdealGraphPrinter* _printer; + +public: + PrintProperties(IdealGraphPrinter* printer) : _printer(printer) {} + void print_node_properties(Node* node); + void print_lrg_properties(const LRG& lrg, const char* buffer); + void print_property(int flag, const char* name); + void print_property(int flag, const char* name, const char* val); + void print_property(int flag, const char* name, int val); +}; + +void PrintProperties::print_node_properties(Node* node) { + const jushort flags = node->flags(); + print_property((flags & Node::Flag_is_Copy), "is_copy"); + print_property((flags & Node::Flag_rematerialize), "rematerialize"); + print_property((flags & Node::Flag_needs_anti_dependence_check), "needs_anti_dependence_check"); + print_property((flags & Node::Flag_is_macro), "is_macro"); + print_property((flags & Node::Flag_is_Con), "is_con"); + print_property((flags & Node::Flag_is_cisc_alternate), "is_cisc_alternate"); + print_property((flags & Node::Flag_is_dead_loop_safe), "is_dead_loop_safe"); + print_property((flags & Node::Flag_may_be_short_branch), "may_be_short_branch"); + print_property((flags & Node::Flag_has_call), "has_call"); + print_property((flags & Node::Flag_has_swapped_edges), "has_swapped_edges"); + Matcher* matcher = _printer->C->matcher(); + if (matcher != nullptr) { + print_property(matcher->is_shared(node),"is_shared"); + print_property(!(matcher->is_shared(node)), "is_shared", IdealGraphPrinter::FALSE_VALUE); + print_property(matcher->is_dontcare(node), "is_dontcare"); + print_property(!(matcher->is_dontcare(node)),"is_dontcare", IdealGraphPrinter::FALSE_VALUE); + Node* old = matcher->find_old_node(node); + if (old != nullptr) { + print_property(true, "old_node_idx", old->_idx); + } + } +} + +void PrintProperties::print_lrg_properties(const LRG &lrg, const char *buffer) { + print_property(true, "mask", buffer); + print_property(true, "mask_size", lrg.mask_size()); + if (lrg._degree_valid) { + print_property(true, "degree", lrg.degree()); + } + print_property(true, "num_regs", lrg.num_regs()); + print_property(true, "reg_pressure", lrg.reg_pressure()); + print_property(true, "cost", lrg._cost); + print_property(true, "area", lrg._area); + print_property(true, "score", lrg.score()); + print_property((lrg._risk_bias != 0), "risk_bias", lrg._risk_bias); + print_property((lrg._copy_bias != 0), "copy_bias", lrg._copy_bias); + print_property((lrg._copy_bias2 != 0), "copy_bias2", lrg._copy_bias2); + print_property(lrg.is_singledef(), "is_singledef"); + print_property(lrg.is_multidef(), "is_multidef"); + print_property(lrg._is_oop, "is_oop"); + print_property(lrg._is_float, "is_float"); + print_property(lrg._is_vector, "is_vector"); + print_property(lrg._is_predicate, "is_predicate"); + print_property(lrg._is_scalable, "is_scalable"); + print_property(lrg._was_spilled1, "was_spilled1"); + print_property(lrg._was_spilled2, "was_spilled2"); + print_property(lrg._direct_conflict, "direct_conflict"); + print_property(lrg._fat_proj, "fat_proj"); + print_property(lrg._was_lo, "_was_lo"); + print_property(lrg._has_copy, "has_copy"); + print_property(lrg._at_risk, "at_risk"); + print_property(lrg._must_spill, "must_spill"); + print_property(lrg._is_bound, "is_bound"); + print_property((lrg._msize_valid && lrg._degree_valid && lrg.lo_degree()), "trivial"); +} + +void PrintProperties::print_property(int flag, const char* name) { + if (flag != 0) { + _printer->print_prop(name, IdealGraphPrinter::TRUE_VALUE); + } +} + +void PrintProperties::print_property(int flag, const char* name, const char* val) { + if (flag != 0) { + _printer->print_prop(name, val); + } +} + +void PrintProperties::print_property(int flag, const char* name, int val) { + if (flag != 0) { + _printer->print_prop(name, val); + } +} + // Constants // Keep consistent with Java constants const char *IdealGraphPrinter::INDENT = " "; @@ -522,54 +613,8 @@ void IdealGraphPrinter::visit_node(Node* n, bool edges) { print_prop("jvms", buffer); } - const jushort flags = node->flags(); - if (flags & Node::Flag_is_Copy) { - print_prop("is_copy", "true"); - } - if (flags & Node::Flag_rematerialize) { - print_prop("rematerialize", "true"); - } - if (flags & Node::Flag_needs_anti_dependence_check) { - print_prop("needs_anti_dependence_check", "true"); - } - if (flags & Node::Flag_is_macro) { - print_prop("is_macro", "true"); - } - if (flags & Node::Flag_is_Con) { - print_prop("is_con", "true"); - } - if (flags & Node::Flag_is_cisc_alternate) { - print_prop("is_cisc_alternate", "true"); - } - if (flags & Node::Flag_is_dead_loop_safe) { - print_prop("is_dead_loop_safe", "true"); - } - if (flags & Node::Flag_may_be_short_branch) { - print_prop("may_be_short_branch", "true"); - } - if (flags & Node::Flag_has_call) { - print_prop("has_call", "true"); - } - if (flags & Node::Flag_has_swapped_edges) { - print_prop("has_swapped_edges", "true"); - } - - if (C->matcher() != nullptr) { - if (C->matcher()->is_shared(node)) { - print_prop("is_shared", "true"); - } else { - print_prop("is_shared", "false"); - } - if (C->matcher()->is_dontcare(node)) { - print_prop("is_dontcare", "true"); - } else { - print_prop("is_dontcare", "false"); - } - Node* old = C->matcher()->find_old_node(node); - if (old != nullptr) { - print_prop("old_node_idx", old->_idx); - } - } + PrintProperties print_node(this); + print_node.print_node_properties(node); if (node->is_Proj()) { print_prop("con", (int)node->as_Proj()->_con); @@ -1145,73 +1190,10 @@ void IdealGraphPrinter::print(const char* name, Node* node, GrowableArray { - private: + friend class PrintProperties; +private: static const char *INDENT; static const char *TOP_ELEMENT; static const char *GROUP_ELEMENT; diff --git a/src/hotspot/share/opto/loopTransform.cpp b/src/hotspot/share/opto/loopTransform.cpp index 31d1cbe0443..5c65103677b 100644 --- a/src/hotspot/share/opto/loopTransform.cpp +++ b/src/hotspot/share/opto/loopTransform.cpp @@ -1411,7 +1411,6 @@ void PhaseIdealLoop::insert_pre_post_loops(IdealLoopTree *loop, Node_List &old_n C->print_method(PHASE_BEFORE_PRE_MAIN_POST, 4, main_head); - Node *pre_header= main_head->in(LoopNode::EntryControl); Node *init = main_head->init_trip(); Node *incr = main_end ->incr(); Node *limit = main_end ->limit(); diff --git a/src/hotspot/share/opto/loopnode.cpp b/src/hotspot/share/opto/loopnode.cpp index dfff7ef96a5..03cc5cbcff6 100644 --- a/src/hotspot/share/opto/loopnode.cpp +++ b/src/hotspot/share/opto/loopnode.cpp @@ -1162,13 +1162,16 @@ bool PhaseIdealLoop::create_loop_nest(IdealLoopTree* loop, Node_List &old_new) { class CloneShortLoopPredicateVisitor : public PredicateVisitor { ClonePredicateToTargetLoop _clone_predicate_to_loop; PhaseIdealLoop* const _phase; + Node* const _new_init; public: CloneShortLoopPredicateVisitor(LoopNode* target_loop_head, + Node* new_init, const NodeInSingleLoopBody &node_in_loop_body, PhaseIdealLoop* phase) : _clone_predicate_to_loop(target_loop_head, node_in_loop_body, phase), - _phase(phase) { + _phase(phase), + _new_init(new_init) { } NONCOPYABLE(CloneShortLoopPredicateVisitor); @@ -1180,11 +1183,32 @@ public: } void visit(const TemplateAssertionPredicate& template_assertion_predicate) override { - _clone_predicate_to_loop.clone_template_assertion_predicate(template_assertion_predicate); + _clone_predicate_to_loop.clone_template_assertion_predicate_and_replace_init(template_assertion_predicate, _new_init); template_assertion_predicate.kill(_phase->igvn()); } }; +// For an int counted loop, try_make_short_running_loop() transforms the loop from: +// for (int = start; i < stop; i+= stride) { ... } +// to +// for (int = 0; i < stop - start; i+= stride) { ... } +// Template Assertion Predicates added so far were with an init value of start. They need to be updated with the new +// init value of 0 (otherwise when a template assertion predicate is turned into an initialized assertion predicate, it +// performs an incorrect check): +// zero +// init | +// | ===> OpaqueLoopInit init +// OpaqueLoopInit \ / +// AddI +// +Node* PhaseIdealLoop::new_assertion_predicate_opaque_init(Node* entry_control, Node* init, Node* int_zero) { + OpaqueLoopInitNode* new_opaque_init = new OpaqueLoopInitNode(C, int_zero); + register_new_node(new_opaque_init, entry_control); + Node* new_init = new AddINode(new_opaque_init, init); + register_new_node(new_init, entry_control); + return new_init; +} + // If the loop is either statically known to run for a small enough number of iterations or if profile data indicates // that, we don't want an outer loop because the overhead of having an outer loop whose backedge is never taken, has a // measurable cost. Furthermore, creating the loop nest usually causes one iteration of the loop to be peeled so @@ -1236,6 +1260,7 @@ bool PhaseIdealLoop::try_make_short_running_loop(IdealLoopTree* loop, jint strid } register_new_node(new_limit, entry_control); + Node* int_zero = intcon(0); PhiNode* phi = head->phi()->as_Phi(); if (profile_short_running_loop) { // Add a Short Running Long Loop Predicate. It's the first predicate in the predicate chain before entering a loop @@ -1261,9 +1286,11 @@ bool PhaseIdealLoop::try_make_short_running_loop(IdealLoopTree* loop, jint strid if (!short_running_long_loop_predicate_block->has_parse_predicate()) { // already trapped return false; } + Node* new_init = new_assertion_predicate_opaque_init(entry_control, init, int_zero); + PredicateIterator predicate_iterator(entry_control); NodeInSingleLoopBody node_in_short_loop_body(this, loop); - CloneShortLoopPredicateVisitor clone_short_loop_predicates_visitor(head, node_in_short_loop_body, this); + CloneShortLoopPredicateVisitor clone_short_loop_predicates_visitor(head, new_init, node_in_short_loop_body, this); predicate_iterator.for_each(clone_short_loop_predicates_visitor); entry_control = head->skip_strip_mined()->in(LoopNode::EntryControl); @@ -1311,6 +1338,10 @@ bool PhaseIdealLoop::try_make_short_running_loop(IdealLoopTree* loop, jint strid register_new_node(new_limit, predicates.entry()); } else { assert(bt == T_INT && known_short_running_loop, "only CountedLoop statically known to be short running"); + PredicateIterator predicate_iterator(entry_control); + Node* new_init = new_assertion_predicate_opaque_init(entry_control, init, int_zero); + UpdateInitForTemplateAssertionPredicates update_init_for_template_assertion_predicates(new_init, this); + predicate_iterator.for_each(update_init_for_template_assertion_predicates); } IfNode* exit_test = head->loopexit(); @@ -1320,7 +1351,6 @@ bool PhaseIdealLoop::try_make_short_running_loop(IdealLoopTree* loop, jint strid register_new_node(new_limit, entry_control); } - Node* int_zero = intcon(0); if (stride_con < 0) { new_limit = new SubINode(int_zero, new_limit); register_new_node(new_limit, entry_control); diff --git a/src/hotspot/share/opto/loopnode.hpp b/src/hotspot/share/opto/loopnode.hpp index 1e34331f213..3b97d76773f 100644 --- a/src/hotspot/share/opto/loopnode.hpp +++ b/src/hotspot/share/opto/loopnode.hpp @@ -1969,6 +1969,8 @@ public: Node* ensure_node_and_inputs_are_above_pre_end(CountedLoopEndNode* pre_end, Node* node); + Node* new_assertion_predicate_opaque_init(Node* entry_control, Node* init, Node* int_zero); + bool try_make_short_running_loop(IdealLoopTree* loop, jint stride_con, const Node_List& range_checks, const uint iters_limit); ConINode* intcon(jint i); diff --git a/src/hotspot/share/opto/loopopts.cpp b/src/hotspot/share/opto/loopopts.cpp index 3ef6a085b1c..ee3f138b8af 100644 --- a/src/hotspot/share/opto/loopopts.cpp +++ b/src/hotspot/share/opto/loopopts.cpp @@ -4180,6 +4180,33 @@ bool PhaseIdealLoop::partial_peel( IdealLoopTree *loop, Node_List &old_new ) { return true; } +#ifdef ASSERT + +// Moves Template Assertion Predicates to a target loop by cloning and killing the old ones. The target loop is the +// original, not-cloned loop. This is currently only used with StressLoopBackedge which is a develop flag only and +// false with product builds. We can therefore guard it with an ifdef. More details can be found at the use-site. +class MoveAssertionPredicatesVisitor : public PredicateVisitor { + ClonePredicateToTargetLoop _clone_predicate_to_loop; + PhaseIdealLoop* const _phase; + +public: + MoveAssertionPredicatesVisitor(LoopNode* target_loop_head, + const NodeInSingleLoopBody &node_in_loop_body, + PhaseIdealLoop* phase) + : _clone_predicate_to_loop(target_loop_head, node_in_loop_body, phase), + _phase(phase) { + } + NONCOPYABLE(MoveAssertionPredicatesVisitor); + + using PredicateVisitor::visit; + + void visit(const TemplateAssertionPredicate& template_assertion_predicate) override { + _clone_predicate_to_loop.clone_template_assertion_predicate(template_assertion_predicate); + template_assertion_predicate.kill(_phase->igvn()); + } +}; +#endif // ASSERT + // Transform: // // loop<-----------------+ @@ -4248,6 +4275,7 @@ bool PhaseIdealLoop::duplicate_loop_backedge(IdealLoopTree *loop, Node_List &old IfNode* exit_test = nullptr; uint inner; float f; +#ifdef ASSERT if (StressDuplicateBackedge) { if (head->is_strip_mined()) { return false; @@ -4266,7 +4294,9 @@ bool PhaseIdealLoop::duplicate_loop_backedge(IdealLoopTree *loop, Node_List &old } inner = 1; - } else { + } else +#endif //ASSERT + { // Is the shape of the loop that of a counted loop... Node* back_control = loop_exit_control(head, loop); if (back_control == nullptr) { @@ -4457,6 +4487,19 @@ bool PhaseIdealLoop::duplicate_loop_backedge(IdealLoopTree *loop, Node_List &old } } +#ifdef ASSERT + if (StressDuplicateBackedge && head->is_CountedLoop()) { + // The Template Assertion Predicates from the old counted loop are now at the new outer loop - clone them to + // the inner counted loop and kill the old ones. We only need to do this with debug builds because + // StressDuplicateBackedge is a devlop flag and false by default. Without StressDuplicateBackedge 'head' will be a + // non-counted loop, and thus we have no Template Assertion Predicates above the old loop to move down. + PredicateIterator predicate_iterator(outer_head->in(LoopNode::EntryControl)); + NodeInSingleLoopBody node_in_body(this, loop); + MoveAssertionPredicatesVisitor move_assertion_predicates_visitor(head, node_in_body, this); + predicate_iterator.for_each(move_assertion_predicates_visitor); + } +#endif // ASSERT + C->set_major_progress(); C->print_method(PHASE_AFTER_DUPLICATE_LOOP_BACKEDGE, 4, outer_head); diff --git a/src/hotspot/share/opto/machnode.cpp b/src/hotspot/share/opto/machnode.cpp index e58befd8032..ec861865ff5 100644 --- a/src/hotspot/share/opto/machnode.cpp +++ b/src/hotspot/share/opto/machnode.cpp @@ -460,6 +460,13 @@ int MachNode::operand_index(Node* def) const { return -1; } +int MachNode::operand_num_edges(uint oper_index) const { + if (num_opnds() > oper_index) { + return _opnds[oper_index]->num_edges(); + } + return 0; +} + //------------------------------peephole--------------------------------------- // Apply peephole rule(s) to this instruction int MachNode::peephole(Block *block, int block_index, PhaseCFG* cfg_, PhaseRegAlloc *ra_) { diff --git a/src/hotspot/share/opto/machnode.hpp b/src/hotspot/share/opto/machnode.hpp index 093f466678c..b60313b7f75 100644 --- a/src/hotspot/share/opto/machnode.hpp +++ b/src/hotspot/share/opto/machnode.hpp @@ -266,6 +266,7 @@ public: int operand_index(uint operand) const; int operand_index(const MachOper *oper) const; int operand_index(Node* m) const; + int operand_num_edges(uint operand) const; // Register class input is expected in virtual const RegMask &in_RegMask(uint) const; diff --git a/src/hotspot/share/opto/matcher.hpp b/src/hotspot/share/opto/matcher.hpp index 01f11b1fdc9..ca13d0166a1 100644 --- a/src/hotspot/share/opto/matcher.hpp +++ b/src/hotspot/share/opto/matcher.hpp @@ -512,6 +512,8 @@ public: DEBUG_ONLY( bool verify_after_postselect_cleanup(); ) public: + static bool is_register_biasing_candidate(const MachNode* mdef, int oper_index); + // This routine is run whenever a graph fails to match. // If it returns, the compiler should bailout to interpreter without error. // In non-product mode, SoftMatchFailure is false to detect non-canonical diff --git a/src/hotspot/share/opto/node.hpp b/src/hotspot/share/opto/node.hpp index 6067bcbac8d..2e19d1d247b 100644 --- a/src/hotspot/share/opto/node.hpp +++ b/src/hotspot/share/opto/node.hpp @@ -828,26 +828,26 @@ public: #undef DEFINE_CLASS_ID // Flags are sorted by usage frequency. - enum NodeFlags { - Flag_is_Copy = 1 << 0, // should be first bit to avoid shift - Flag_rematerialize = 1 << 1, - Flag_needs_anti_dependence_check = 1 << 2, - Flag_is_macro = 1 << 3, - Flag_is_Con = 1 << 4, - Flag_is_cisc_alternate = 1 << 5, - Flag_is_dead_loop_safe = 1 << 6, - Flag_may_be_short_branch = 1 << 7, - Flag_avoid_back_to_back_before = 1 << 8, - Flag_avoid_back_to_back_after = 1 << 9, - Flag_has_call = 1 << 10, - Flag_has_swapped_edges = 1 << 11, - Flag_is_scheduled = 1 << 12, - Flag_is_expensive = 1 << 13, - Flag_is_predicated_vector = 1 << 14, - Flag_for_post_loop_opts_igvn = 1 << 15, - Flag_for_merge_stores_igvn = 1 << 16, - Flag_is_removed_by_peephole = 1 << 17, - Flag_is_predicated_using_blend = 1 << 18, + enum NodeFlags : uint64_t { + Flag_is_Copy = 1ULL << 0, // should be first bit to avoid shift + Flag_rematerialize = 1ULL << 1, + Flag_needs_anti_dependence_check = 1ULL << 2, + Flag_is_macro = 1ULL << 3, + Flag_is_Con = 1ULL << 4, + Flag_is_cisc_alternate = 1ULL << 5, + Flag_is_dead_loop_safe = 1ULL << 6, + Flag_may_be_short_branch = 1ULL << 7, + Flag_avoid_back_to_back_before = 1ULL << 8, + Flag_avoid_back_to_back_after = 1ULL << 9, + Flag_has_call = 1ULL << 10, + Flag_has_swapped_edges = 1ULL << 11, + Flag_is_scheduled = 1ULL << 12, + Flag_is_expensive = 1ULL << 13, + Flag_is_predicated_vector = 1ULL << 14, + Flag_for_post_loop_opts_igvn = 1ULL << 15, + Flag_for_merge_stores_igvn = 1ULL << 16, + Flag_is_removed_by_peephole = 1ULL << 17, + Flag_is_predicated_using_blend = 1ULL << 18, _last_flag = Flag_is_predicated_using_blend }; @@ -2176,7 +2176,10 @@ class BFSActions : public StackObj { virtual bool is_target_node(Node* node) const = 0; // Defines an action that should be taken when we visit a target node in the BFS traversal. - virtual void target_node_action(Node* target_node) = 0; + // To give more freedom, we pass the direct child node to the target node such that + // child->in(i) == target node. This allows to also directly replace the target node instead + // of only updating its inputs. + virtual void target_node_action(Node* child, uint i) = 0; }; // Class to perform a BFS traversal on the data nodes from a given start node. The provided BFSActions guide which @@ -2198,7 +2201,7 @@ class DataNodeBFS : public StackObj { Node* input = next->in(j); if (_bfs_actions.is_target_node(input)) { assert(_bfs_actions.should_visit(input), "must also pass node filter"); - _bfs_actions.target_node_action(input); + _bfs_actions.target_node_action(next, j); } else if (_bfs_actions.should_visit(input)) { _nodes_to_visit.push(input); } diff --git a/src/hotspot/share/opto/output.cpp b/src/hotspot/share/opto/output.cpp index 84c01c68e38..136fc8ac864 100644 --- a/src/hotspot/share/opto/output.cpp +++ b/src/hotspot/share/opto/output.cpp @@ -1347,20 +1347,18 @@ CodeBuffer* PhaseOutput::init_buffer() { // nmethod and CodeBuffer count stubs & constants as part of method's code. // class HandlerImpl is platform-specific and defined in the *.ad files. - int exception_handler_req = HandlerImpl::size_exception_handler() + MAX_stubs_size; // add marginal slop for handler int deopt_handler_req = HandlerImpl::size_deopt_handler() + MAX_stubs_size; // add marginal slop for handler stub_req += MAX_stubs_size; // ensure per-stub margin code_req += MAX_inst_size; // ensure per-instruction margin if (StressCodeBuffers) - code_req = const_req = stub_req = exception_handler_req = deopt_handler_req = 0x10; // force expansion + code_req = const_req = stub_req = deopt_handler_req = 0x10; // force expansion int total_req = const_req + code_req + pad_req + stub_req + - exception_handler_req + deopt_handler_req; // deopt handler CodeBuffer* cb = code_buffer(); @@ -1789,8 +1787,6 @@ void PhaseOutput::fill_buffer(C2_MacroAssembler* masm, uint* blk_starts) { // Only java methods have exception handlers and deopt handlers // class HandlerImpl is platform-specific and defined in the *.ad files. if (C->method()) { - // Emit the exception handler code. - _code_offsets.set_value(CodeOffsets::Exceptions, HandlerImpl::emit_exception_handler(masm)); if (C->failing()) { return; // CodeBuffer::expand failed } diff --git a/src/hotspot/share/opto/phaseX.cpp b/src/hotspot/share/opto/phaseX.cpp index 1fe911aa7ac..4a0933b89f2 100644 --- a/src/hotspot/share/opto/phaseX.cpp +++ b/src/hotspot/share/opto/phaseX.cpp @@ -1132,7 +1132,7 @@ void PhaseIterGVN::verify_empty_worklist(Node* node) { // (1) Integer "widen" changes, but the range is the same. // (2) LoadNode performs deep traversals. Load is not notified for changes far away. // (3) CmpPNode performs deep traversals if it compares oopptr. CmpP is not notified for changes far away. -bool PhaseIterGVN::verify_Value_for(Node* n) { +bool PhaseIterGVN::verify_Value_for(Node* n, bool strict) { // If we assert inside type(n), because the type is still a null, then maybe // the node never went through gvn.transform, which would be a bug. const Type* told = type(n); @@ -1152,7 +1152,7 @@ bool PhaseIterGVN::verify_Value_for(Node* n) { } // Exception (2) // LoadNode performs deep traversals. Load is not notified for changes far away. - if (n->is_Load() && !told->singleton()) { + if (!strict && n->is_Load() && !told->singleton()) { // MemNode::can_see_stored_value looks up through many memory nodes, // which means we would need to notify modifications from far up in // the inputs all the way down to the LoadNode. We don't do that. @@ -1160,7 +1160,7 @@ bool PhaseIterGVN::verify_Value_for(Node* n) { } // Exception (3) // CmpPNode performs deep traversals if it compares oopptr. CmpP is not notified for changes far away. - if (n->Opcode() == Op_CmpP && type(n->in(1))->isa_oopptr() && type(n->in(2))->isa_oopptr()) { + if (!strict && n->Opcode() == Op_CmpP && type(n->in(1))->isa_oopptr() && type(n->in(2))->isa_oopptr()) { // SubNode::Value // CmpPNode::sub // MemNode::detect_ptr_independence @@ -2799,6 +2799,7 @@ void PhaseCCP::analyze() { // Compile is over. The local arena gets de-allocated at the end of its scope. ResourceArea local_arena(mtCompiler); Unique_Node_List worklist(&local_arena); + Unique_Node_List worklist_revisit(&local_arena); DEBUG_ONLY(Unique_Node_List worklist_verify(&local_arena);) // Push root onto worklist @@ -2807,45 +2808,86 @@ void PhaseCCP::analyze() { assert(_root_and_safepoints.size() == 0, "must be empty (unused)"); _root_and_safepoints.push(C->root()); - // Pull from worklist; compute new value; push changes out. - // This loop is the meat of CCP. + // This is the meat of CCP: pull from worklist; compute new value; push changes out. + + // Do the first round. Since all initial types are TOP, this will visit all alive nodes. while (worklist.size() != 0) { Node* n = fetch_next_node(worklist); DEBUG_ONLY(worklist_verify.push(n);) + if (needs_revisit(n)) { + worklist_revisit.push(n); + } if (n->is_SafePoint()) { // Make sure safepoints are processed by PhaseCCP::transform even if they are // not reachable from the bottom. Otherwise, infinite loops would be removed. _root_and_safepoints.push(n); } - const Type* new_type = n->Value(this); - if (new_type != type(n)) { - DEBUG_ONLY(verify_type(n, new_type, type(n));) - dump_type_and_node(n, new_type); - set_type(n, new_type); - push_child_nodes_to_worklist(worklist, n); - } - 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_nodes.push(n); - } + analyze_step(worklist, n); } + + // More rounds to catch updates far in the graph. + // Revisit nodes that might be able to refine their types at the end of the round. + // If so, process these nodes. If there is remaining work, start another round. + do { + while (worklist.size() != 0) { + Node* n = fetch_next_node(worklist); + analyze_step(worklist, n); + } + for (uint t = 0; t < worklist_revisit.size(); t++) { + Node* n = worklist_revisit.at(t); + analyze_step(worklist, n); + } + } while (worklist.size() != 0); + DEBUG_ONLY(verify_analyze(worklist_verify);) } +void PhaseCCP::analyze_step(Unique_Node_List& worklist, Node* n) { + const Type* new_type = n->Value(this); + if (new_type != type(n)) { + DEBUG_ONLY(verify_type(n, new_type, type(n));) + dump_type_and_node(n, new_type); + set_type(n, new_type); + push_child_nodes_to_worklist(worklist, n); + } + 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_nodes.push(n); + } +} + +// Some nodes can refine their types due to type change somewhere deep +// in the graph. We will need to revisit them before claiming convergence. +// Add nodes here if particular *Node::Value is doing deep graph traversals +// not handled by PhaseCCP::push_more_uses(). +bool PhaseCCP::needs_revisit(Node* n) const { + // LoadNode performs deep traversals. Load is not notified for changes far away. + if (n->is_Load()) { + return true; + } + // CmpPNode performs deep traversals if it compares oopptr. CmpP is not notified for changes far away. + if (n->Opcode() == Op_CmpP && type(n->in(1))->isa_oopptr() && type(n->in(2))->isa_oopptr()) { + return true; + } + return false; +} + #ifdef ASSERT // For every node n on verify list, check if type(n) == n->Value() -// We have a list of exceptions, see comments in verify_Value_for. +// Note for CCP the non-convergence can lead to unsound analysis and mis-compilation. +// Therefore, we are verifying Value convergence strictly. void PhaseCCP::verify_analyze(Unique_Node_List& worklist_verify) { bool failure = false; while (worklist_verify.size()) { Node* n = worklist_verify.pop(); - failure |= verify_Value_for(n); + failure |= verify_Value_for(n, /* strict = */ true); } // If we get this assert, check why the reported nodes were not processed again in CCP. // We should either make sure that these nodes are properly added back to the CCP worklist - // in PhaseCCP::push_child_nodes_to_worklist() to update their type or add an exception - // in the verification code above if that is not possible for some reason (like Load nodes). + // in PhaseCCP::push_child_nodes_to_worklist() to update their type in the same round, + // or that they are added in PhaseCCP::needs_revisit() so that analysis revisits + // them at the end of the round. assert(!failure, "PhaseCCP not at fixpoint: analysis result may be unsound."); } #endif diff --git a/src/hotspot/share/opto/phaseX.hpp b/src/hotspot/share/opto/phaseX.hpp index 083e77bf6d9..473231e6af5 100644 --- a/src/hotspot/share/opto/phaseX.hpp +++ b/src/hotspot/share/opto/phaseX.hpp @@ -490,7 +490,7 @@ public: void optimize(); #ifdef ASSERT void verify_optimize(); - bool verify_Value_for(Node* n); + bool verify_Value_for(Node* n, bool strict = false); bool verify_Ideal_for(Node* n, bool can_reshape); bool verify_Identity_for(Node* n); void verify_empty_worklist(Node* n); @@ -659,6 +659,8 @@ class PhaseCCP : public PhaseIterGVN { // Worklist algorithm identifies constants void analyze(); + void analyze_step(Unique_Node_List& worklist, Node* n); + bool needs_revisit(Node* n) const; #ifdef ASSERT void verify_type(Node* n, const Type* tnew, const Type* told); // For every node n on verify list, check if type(n) == n->Value() diff --git a/src/hotspot/share/opto/predicates.cpp b/src/hotspot/share/opto/predicates.cpp index 208bd6583c5..2489ff563a9 100644 --- a/src/hotspot/share/opto/predicates.cpp +++ b/src/hotspot/share/opto/predicates.cpp @@ -198,12 +198,21 @@ TemplateAssertionPredicate TemplateAssertionPredicate::clone_and_replace_opaque_ Node* new_opaque_input, CountedLoopNode* new_loop_node, PhaseIdealLoop* phase) const { - DEBUG_ONLY(verify();) OpaqueLoopInitNode* new_opaque_init = new OpaqueLoopInitNode(phase->C, new_opaque_input); phase->register_new_node(new_opaque_init, new_control); + return clone_and_replace_init(new_control, new_opaque_init, new_loop_node, phase); +} + +// Clone this Template Assertion Predicate and replace the old OpaqueLoopInit node with 'new_init'. +// Note: 'new_init' could also have the 'OpaqueLoopInit` as parent node further up. +TemplateAssertionPredicate TemplateAssertionPredicate::clone_and_replace_init(Node* new_control, + Node* new_init, + CountedLoopNode* new_loop_node, + PhaseIdealLoop* phase) const { + DEBUG_ONLY(verify();) TemplateAssertionExpression template_assertion_expression(opaque_node(), phase); OpaqueTemplateAssertionPredicateNode* new_opaque_node = - template_assertion_expression.clone_and_replace_init(new_control, new_opaque_init, new_loop_node); + template_assertion_expression.clone_and_replace_init(new_control, new_init, new_loop_node); AssertionPredicateIfCreator assertion_predicate_if_creator(phase); IfTrueNode* success_proj = assertion_predicate_if_creator.create_for_template(new_control, _if_node->Opcode(), new_opaque_node, @@ -238,8 +247,40 @@ class ReplaceOpaqueStrideInput : public BFSActions { return node->is_OpaqueLoopStride(); } - void target_node_action(Node* target_node) override { - _igvn.replace_input_of(target_node, 1, _new_opaque_stride_input); + void target_node_action(Node* child, uint i) override { + assert(child->in(i)->is_OpaqueLoopStride(), "must be OpaqueLoopStride"); + _igvn.replace_input_of(child->in(i), 1, _new_opaque_stride_input); + } +}; + +// This class is used to replace the OpaqueLoopInitNode with a new node while leaving the other nodes +// unchanged. +class ReplaceOpaqueInitNode : public BFSActions { + Node* _new_opaque_init_node; + PhaseIterGVN& _igvn; + + public: + ReplaceOpaqueInitNode(Node* new_opaque_init_node, PhaseIterGVN& igvn) + : _new_opaque_init_node(new_opaque_init_node), + _igvn(igvn) {} + NONCOPYABLE(ReplaceOpaqueInitNode); + + void replace_for(OpaqueTemplateAssertionPredicateNode* opaque_node) { + DataNodeBFS bfs(*this); + bfs.run(opaque_node); + } + + bool should_visit(Node* node) const override { + return TemplateAssertionExpressionNode::is_maybe_in_expression(node); + } + + bool is_target_node(Node* node) const override { + return node->is_OpaqueLoopInit(); + } + + void target_node_action(Node* child, uint i) override { + assert(child->in(i)->is_OpaqueLoopInit(), "must be old OpaqueLoopInit"); + _igvn.replace_input_of(child, i, _new_opaque_init_node); } }; @@ -250,6 +291,13 @@ void TemplateAssertionPredicate::replace_opaque_stride_input(Node* new_stride, P replace_opaque_stride_input.replace_for(opaque_node()); } +// Replace the OpaqueLoopInitNode with 'new_init' and leave the other nodes unchanged. +void TemplateAssertionPredicate::replace_opaque_init_node(Node* new_init, PhaseIterGVN& igvn) const { + DEBUG_ONLY(verify();) + ReplaceOpaqueInitNode replace_opaque_init_node(new_init, igvn); + replace_opaque_init_node.replace_for(opaque_node()); +} + // Create a new Initialized Assertion Predicate from this template at the template success projection. InitializedAssertionPredicate TemplateAssertionPredicate::initialize(PhaseIdealLoop* phase) const { DEBUG_ONLY(verify();) @@ -308,7 +356,8 @@ class OpaqueLoopNodesVerifier : public BFSActions { return node->is_Opaque1(); } - void target_node_action(Node* target_node) override { + void target_node_action(Node* child, uint i) override { + Node* target_node = child->in(i); if (target_node->is_OpaqueLoopInit()) { assert(!_found_init, "should only find one OpaqueLoopInitNode"); _found_init = true; @@ -1094,6 +1143,18 @@ void ClonePredicateToTargetLoop::clone_template_assertion_predicate( _target_loop_predicate_chain.insert_predicate(cloned_template_assertion_predicate); } +// Clones the provided Template Assertion Predicate to the head of the current predicate chain at the target loop and +// replaces the current OpaqueLoopInit with 'new_init'. +// Note: 'new_init' could also have the 'OpaqueLoopInit` as parent node further up. +void ClonePredicateToTargetLoop::clone_template_assertion_predicate_and_replace_init( + const TemplateAssertionPredicate& template_assertion_predicate, Node* new_init) { + TemplateAssertionPredicate cloned_template_assertion_predicate = + template_assertion_predicate.clone_and_replace_init(_old_target_loop_entry, new_init, _target_loop_head->as_CountedLoop(), _phase); + template_assertion_predicate.rewire_loop_data_dependencies(cloned_template_assertion_predicate.tail(), + _node_in_loop_body, _phase); + _target_loop_predicate_chain.insert_predicate(cloned_template_assertion_predicate); +} + CloneUnswitchedLoopPredicatesVisitor::CloneUnswitchedLoopPredicatesVisitor( LoopNode* true_path_loop_head, LoopNode* false_path_loop_head, const NodeInOriginalLoopBody& node_in_true_path_loop_body, const NodeInClonedLoopBody& node_in_false_path_loop_body, @@ -1182,6 +1243,10 @@ void UpdateStrideForAssertionPredicates::connect_initialized_assertion_predicate } } +void UpdateInitForTemplateAssertionPredicates::visit(const TemplateAssertionPredicate& template_assertion_predicate) { + template_assertion_predicate.replace_opaque_init_node(_new_init, _phase->igvn()); +} + // Do the following to find and eliminate useless Parse and Template Assertion Predicates: // 1. Mark all Parse and Template Assertion Predicates "maybe useful". // 2. Walk through the loop tree and iterate over all Predicates above each loop head. All found Parse and Template diff --git a/src/hotspot/share/opto/predicates.hpp b/src/hotspot/share/opto/predicates.hpp index 32b1c1cd3c4..cd0832cc062 100644 --- a/src/hotspot/share/opto/predicates.hpp +++ b/src/hotspot/share/opto/predicates.hpp @@ -438,7 +438,10 @@ class TemplateAssertionPredicate : public Predicate { TemplateAssertionPredicate clone(Node* new_control, CountedLoopNode* new_loop_node, PhaseIdealLoop* phase) const; TemplateAssertionPredicate clone_and_replace_opaque_input(Node* new_control, Node* new_opaque_input, CountedLoopNode* new_loop_node, PhaseIdealLoop* phase) const; + TemplateAssertionPredicate clone_and_replace_init(Node* new_control, Node* new_input, + CountedLoopNode* new_loop_node, PhaseIdealLoop* phase) const; void replace_opaque_stride_input(Node* new_stride, PhaseIterGVN& igvn) const; + void replace_opaque_init_node(Node* new_init, PhaseIterGVN& igvn) const; InitializedAssertionPredicate initialize(PhaseIdealLoop* phase) const; void rewire_loop_data_dependencies(IfTrueNode* target_predicate, const NodeInLoopBody& data_in_loop_body, const PhaseIdealLoop* phase) const; @@ -1228,6 +1231,7 @@ public: } void clone_template_assertion_predicate(const TemplateAssertionPredicate& template_assertion_predicate); + void clone_template_assertion_predicate_and_replace_init(const TemplateAssertionPredicate& template_assertion_predicate, Node* new_init); }; // Visitor to clone Parse and Template Assertion Predicates from a loop to its unswitched true and false path loop. @@ -1300,6 +1304,22 @@ class UpdateStrideForAssertionPredicates : public PredicateVisitor { void visit(const InitializedAssertionPredicate& initialized_assertion_predicate) override; }; +// This visitor replaces the OpaqueLoopInitNode for an Assertion Predicate with the expression passed as input. +class UpdateInitForTemplateAssertionPredicates : public PredicateVisitor { + Node* const _new_init; + PhaseIdealLoop* const _phase; + +public: + UpdateInitForTemplateAssertionPredicates(Node* const new_init, PhaseIdealLoop* phase) + : _new_init(new_init), + _phase(phase) {} + NONCOPYABLE(UpdateInitForTemplateAssertionPredicates); + + using PredicateVisitor::visit; + + void visit(const TemplateAssertionPredicate& template_assertion_predicate) override; +}; + // Eliminate all useless Parse and Template Assertion Predicates. They become useless when they can no longer be found // from a loop head. We mark these useless to clean them up later during IGVN. A Predicate that is marked useless will // no longer be visited by a PredicateVisitor. diff --git a/src/hotspot/share/opto/type.cpp b/src/hotspot/share/opto/type.cpp index 96fee925e5d..ecb8c2c1cd8 100644 --- a/src/hotspot/share/opto/type.cpp +++ b/src/hotspot/share/opto/type.cpp @@ -45,6 +45,8 @@ #include "opto/type.hpp" #include "runtime/stubRoutines.hpp" #include "utilities/checkedCast.hpp" +#include "utilities/debug.hpp" +#include "utilities/ostream.hpp" #include "utilities/powerOfTwo.hpp" #include "utilities/stringUtils.hpp" @@ -2979,15 +2981,22 @@ const char *const TypePtr::ptr_msg[TypePtr::lastPTR] = { #ifndef PRODUCT void TypePtr::dump2( Dict &d, uint depth, outputStream *st ) const { - if( _ptr == Null ) st->print("null"); - else st->print("%s *", ptr_msg[_ptr]); - if( _offset == OffsetTop ) st->print("+top"); - else if( _offset == OffsetBot ) st->print("+bot"); - else if( _offset ) st->print("+%d", _offset); + st->print("ptr:%s", ptr_msg[_ptr]); + dump_offset(st); dump_inline_depth(st); dump_speculative(st); } +void TypePtr::dump_offset(outputStream* st) const { + if (_offset == OffsetBot) { + st->print("+bot"); + } else if (_offset == OffsetTop) { + st->print("+top"); + } else { + st->print("+%d", _offset); + } +} + /** *dump the speculative part of the type */ @@ -3159,11 +3168,12 @@ uint TypeRawPtr::hash(void) const { //------------------------------dump2------------------------------------------ #ifndef PRODUCT -void TypeRawPtr::dump2( Dict &d, uint depth, outputStream *st ) const { - if( _ptr == Constant ) - st->print(INTPTR_FORMAT, p2i(_bits)); - else +void TypeRawPtr::dump2(Dict& d, uint depth, outputStream* st) const { + if (_ptr == Constant) { + st->print("rawptr:Constant:" INTPTR_FORMAT, p2i(_bits)); + } else { st->print("rawptr:%s", ptr_msg[_ptr]); + } } #endif @@ -3798,24 +3808,29 @@ uint TypeOopPtr::hash(void) const { //------------------------------dump2------------------------------------------ #ifndef PRODUCT -void TypeOopPtr::dump2( Dict &d, uint depth, outputStream *st ) const { +void TypeOopPtr::dump2(Dict& d, uint depth, outputStream* st) const { st->print("oopptr:%s", ptr_msg[_ptr]); - if( _klass_is_exact ) st->print(":exact"); - if( const_oop() ) st->print(INTPTR_FORMAT, p2i(const_oop())); - switch( _offset ) { - case OffsetTop: st->print("+top"); break; - case OffsetBot: st->print("+any"); break; - case 0: break; - default: st->print("+%d",_offset); break; + if (_klass_is_exact) { + st->print(":exact"); } - if (_instance_id == InstanceTop) - st->print(",iid=top"); - else if (_instance_id != InstanceBot) - st->print(",iid=%d",_instance_id); - + if (const_oop() != nullptr) { + st->print(":" INTPTR_FORMAT, p2i(const_oop())); + } + dump_offset(st); + dump_instance_id(st); dump_inline_depth(st); dump_speculative(st); } + +void TypeOopPtr::dump_instance_id(outputStream* st) const { + if (_instance_id == InstanceTop) { + st->print(",iid=top"); + } else if (_instance_id == InstanceBot) { + st->print(",iid=bot"); + } else { + st->print(",iid=%d", _instance_id); + } +} #endif //------------------------------singleton-------------------------------------- @@ -4453,50 +4468,30 @@ bool TypeInstPtr::maybe_java_subtype_of_helper(const TypeOopPtr* other, bool thi #ifndef PRODUCT void TypeInstPtr::dump2(Dict &d, uint depth, outputStream* st) const { // Print the name of the klass. + st->print("instptr:"); klass()->print_name_on(st); _interfaces->dump(st); - switch( _ptr ) { - case Constant: - if (WizardMode || Verbose) { - ResourceMark rm; - stringStream ss; + if (_ptr == Constant && (WizardMode || Verbose)) { + ResourceMark rm; + stringStream ss; - st->print(" "); - const_oop()->print_oop(&ss); - // 'const_oop->print_oop()' may emit newlines('\n') into ss. - // suppress newlines from it so -XX:+Verbose -XX:+PrintIdeal dumps one-liner for each node. - char* buf = ss.as_string(/* c_heap= */false); - StringUtils::replace_no_expand(buf, "\n", ""); - st->print_raw(buf); - } - case BotPTR: - if (!WizardMode && !Verbose) { - if( _klass_is_exact ) st->print(":exact"); - break; - } - case TopPTR: - case AnyNull: - case NotNull: - st->print(":%s", ptr_msg[_ptr]); - if( _klass_is_exact ) st->print(":exact"); - break; - default: - break; + st->print(" "); + const_oop()->print_oop(&ss); + // 'const_oop->print_oop()' may emit newlines('\n') into ss. + // suppress newlines from it so -XX:+Verbose -XX:+PrintIdeal dumps one-liner for each node. + char* buf = ss.as_string(/* c_heap= */false); + StringUtils::replace_no_expand(buf, "\n", ""); + st->print_raw(buf); } - if( _offset ) { // Dump offset, if any - if( _offset == OffsetBot ) st->print("+any"); - else if( _offset == OffsetTop ) st->print("+unknown"); - else st->print("+%d", _offset); + st->print(":%s", ptr_msg[_ptr]); + if (_klass_is_exact) { + st->print(":exact"); } - st->print(" *"); - if (_instance_id == InstanceTop) - st->print(",iid=top"); - else if (_instance_id != InstanceBot) - st->print(",iid=%d",_instance_id); - + dump_offset(st); + dump_instance_id(st); dump_inline_depth(st); dump_speculative(st); } @@ -5089,26 +5084,17 @@ const Type *TypeAryPtr::xdual() const { //------------------------------dump2------------------------------------------ #ifndef PRODUCT void TypeAryPtr::dump2( Dict &d, uint depth, outputStream *st ) const { - _ary->dump2(d,depth,st); + st->print("aryptr:"); + _ary->dump2(d, depth, st); _interfaces->dump(st); - switch( _ptr ) { - case Constant: + if (_ptr == Constant) { const_oop()->print(st); - break; - case BotPTR: - if (!WizardMode && !Verbose) { - if( _klass_is_exact ) st->print(":exact"); - break; - } - case TopPTR: - case AnyNull: - case NotNull: - st->print(":%s", ptr_msg[_ptr]); - if( _klass_is_exact ) st->print(":exact"); - break; - default: - break; + } + + st->print(":%s", ptr_msg[_ptr]); + if (_klass_is_exact) { + st->print(":exact"); } if( _offset != 0 ) { @@ -5126,12 +5112,8 @@ void TypeAryPtr::dump2( Dict &d, uint depth, outputStream *st ) const { } } } - st->print(" *"); - if (_instance_id == InstanceTop) - st->print(",iid=top"); - else if (_instance_id != InstanceBot) - st->print(",iid=%d",_instance_id); + dump_instance_id(st); dump_inline_depth(st); dump_speculative(st); } @@ -5490,13 +5472,10 @@ const Type *TypeMetadataPtr::xdual() const { #ifndef PRODUCT void TypeMetadataPtr::dump2( Dict &d, uint depth, outputStream *st ) const { st->print("metadataptr:%s", ptr_msg[_ptr]); - if( metadata() ) st->print(INTPTR_FORMAT, p2i(metadata())); - switch( _offset ) { - case OffsetTop: st->print("+top"); break; - case OffsetBot: st->print("+any"); break; - case 0: break; - default: st->print("+%d",_offset); break; + if (metadata() != nullptr) { + st->print(":" INTPTR_FORMAT, p2i(metadata())); } + dump_offset(st); } #endif @@ -5644,44 +5623,6 @@ intptr_t TypeKlassPtr::get_con() const { return (intptr_t)k->constant_encoding(); } -//------------------------------dump2------------------------------------------ -// Dump Klass Type -#ifndef PRODUCT -void TypeKlassPtr::dump2(Dict & d, uint depth, outputStream *st) const { - switch(_ptr) { - case Constant: - st->print("precise "); - case NotNull: - { - const char *name = klass()->name()->as_utf8(); - if (name) { - st->print("%s: " INTPTR_FORMAT, name, p2i(klass())); - } else { - ShouldNotReachHere(); - } - _interfaces->dump(st); - } - case BotPTR: - if (!WizardMode && !Verbose && _ptr != Constant) break; - case TopPTR: - case AnyNull: - st->print(":%s", ptr_msg[_ptr]); - if (_ptr == Constant) st->print(":exact"); - break; - default: - break; - } - - if (_offset) { // Dump offset, if any - if (_offset == OffsetBot) { st->print("+any"); } - else if (_offset == OffsetTop) { st->print("+unknown"); } - else { st->print("+%d", _offset); } - } - - st->print(" *"); -} -#endif - //============================================================================= // Convenience common pre-built types. @@ -6036,6 +5977,15 @@ const TypeKlassPtr* TypeInstKlassPtr::try_improve() const { return this; } +#ifndef PRODUCT +void TypeInstKlassPtr::dump2(Dict& d, uint depth, outputStream* st) const { + st->print("instklassptr:"); + klass()->print_name_on(st); + _interfaces->dump(st); + st->print(":%s", ptr_msg[_ptr]); + dump_offset(st); +} +#endif // PRODUCT const TypeAryKlassPtr *TypeAryKlassPtr::make(PTR ptr, const Type* elem, ciKlass* k, int offset) { return (TypeAryKlassPtr*)(new TypeAryKlassPtr(ptr, elem, k, offset))->hashcons(); @@ -6507,34 +6457,11 @@ ciKlass* TypeAryKlassPtr::klass() const { // Dump Klass Type #ifndef PRODUCT void TypeAryKlassPtr::dump2( Dict & d, uint depth, outputStream *st ) const { - switch( _ptr ) { - case Constant: - st->print("precise "); - case NotNull: - { - st->print("["); - _elem->dump2(d, depth, st); - _interfaces->dump(st); - st->print(": "); - } - case BotPTR: - if( !WizardMode && !Verbose && _ptr != Constant ) break; - case TopPTR: - case AnyNull: - st->print(":%s", ptr_msg[_ptr]); - if( _ptr == Constant ) st->print(":exact"); - break; - default: - break; - } - - if( _offset ) { // Dump offset, if any - if( _offset == OffsetBot ) { st->print("+any"); } - else if( _offset == OffsetTop ) { st->print("+unknown"); } - else { st->print("+%d", _offset); } - } - - st->print(" *"); + st->print("aryklassptr:["); + _elem->dump2(d, depth, st); + _interfaces->dump(st); + st->print(":%s", ptr_msg[_ptr]); + dump_offset(st); } #endif diff --git a/src/hotspot/share/opto/type.hpp b/src/hotspot/share/opto/type.hpp index c61c2a64278..4666cfbcf2d 100644 --- a/src/hotspot/share/opto/type.hpp +++ b/src/hotspot/share/opto/type.hpp @@ -1176,15 +1176,15 @@ protected: int hash_speculative() const; const TypePtr* add_offset_speculative(intptr_t offset) const; const TypePtr* with_offset_speculative(intptr_t offset) const; -#ifndef PRODUCT - void dump_speculative(outputStream *st) const; -#endif // utility methods to work on the inline depth of the type int dual_inline_depth() const; int meet_inline_depth(int depth) const; + #ifndef PRODUCT - void dump_inline_depth(outputStream *st) const; + void dump_speculative(outputStream* st) const; + void dump_inline_depth(outputStream* st) const; + void dump_offset(outputStream* st) const; #endif // TypeInstPtr (TypeAryPtr resp.) and TypeInstKlassPtr (TypeAryKlassPtr resp.) implement very similar meet logic. @@ -1364,6 +1364,10 @@ protected: virtual ciKlass* exact_klass_helper() const { return nullptr; } virtual ciKlass* klass() const { return _klass; } +#ifndef PRODUCT + void dump_instance_id(outputStream* st) const; +#endif // PRODUCT + public: bool is_java_subtype_of(const TypeOopPtr* other) const { @@ -1832,9 +1836,6 @@ public: virtual const TypeKlassPtr* try_improve() const { return this; } -#ifndef PRODUCT - virtual void dump2( Dict &d, uint depth, outputStream *st ) const; // Specialized per-Type dumping -#endif private: virtual bool is_meet_subtype_of(const TypePtr* other) const { return is_meet_subtype_of_helper(other->is_klassptr(), klass_is_exact(), other->is_klassptr()->klass_is_exact()); @@ -1914,6 +1915,11 @@ public: // Convenience common pre-built types. static const TypeInstKlassPtr* OBJECT; // Not-null object klass or below static const TypeInstKlassPtr* OBJECT_OR_NULL; // Maybe-null version of same + +#ifndef PRODUCT + virtual void dump2(Dict& d, uint depth, outputStream* st) const; +#endif // PRODUCT + private: virtual bool is_meet_subtype_of_helper(const TypeKlassPtr* other, bool this_xk, bool other_xk) const; }; diff --git a/src/hotspot/share/opto/vectorization.cpp b/src/hotspot/share/opto/vectorization.cpp index 98f3d79c9f5..15b2df663b6 100644 --- a/src/hotspot/share/opto/vectorization.cpp +++ b/src/hotspot/share/opto/vectorization.cpp @@ -1022,27 +1022,39 @@ bool VPointer::can_make_speculative_aliasing_check_with(const VPointer& other) c // or at the multiversion_if. That is before the pre-loop. From the construction of // VPointer, we already know that all its variables (except iv) are pre-loop invariant. // - // For the computation of main_init, we also need the pre_limit, and so we need - // to check that this value is pre-loop invariant. In the case of non-equal iv_scales, - // we also need the main_limit in the aliasing check, and so this value must then - // also be pre-loop invariant. + // In VPointer::make_speculative_aliasing_check_with we compute main_init in all + // cases. For this, we require pre_init and pre_limit. These values must be available + // for the speculative check, i.e. their control must dominate the speculative check. + // Further, "if vp1.iv_scale() != vp2.iv_scale()" we additionally need to have + // main_limit available for the speculative check. + // Note: no matter if the speculative check is inserted as a predicate or at the + // multiversion if, the speculative check happens before (dominates) the + // pre-loop. + Node* pre_init = _vloop.pre_loop_end()->init_trip(); Opaque1Node* pre_limit_opaq = _vloop.pre_loop_end()->limit()->as_Opaque1(); Node* pre_limit = pre_limit_opaq->in(1); Node* main_limit = _vloop.cl()->limit(); - - if (!_vloop.is_pre_loop_invariant(pre_limit)) { + if (!_vloop.is_available_for_speculative_check(pre_init)) { #ifdef ASSERT if (_vloop.is_trace_speculative_aliasing_analysis()) { - tty->print_cr("VPointer::can_make_speculative_aliasing_check_with: pre_limit is not pre-loop independent!"); + tty->print_cr("VPointer::can_make_speculative_aliasing_check_with: pre_limit is not available at speculative check!"); + } +#endif + return false; + } + if (!_vloop.is_available_for_speculative_check(pre_limit)) { +#ifdef ASSERT + if (_vloop.is_trace_speculative_aliasing_analysis()) { + tty->print_cr("VPointer::can_make_speculative_aliasing_check_with: pre_limit is not available at speculative check!"); } #endif return false; } - if (vp1.iv_scale() != vp2.iv_scale() && !_vloop.is_pre_loop_invariant(main_limit)) { + if (vp1.iv_scale() != vp2.iv_scale() && !_vloop.is_available_for_speculative_check(main_limit)) { #ifdef ASSERT if (_vloop.is_trace_speculative_aliasing_analysis()) { - tty->print_cr("VPointer::can_make_speculative_aliasing_check_with: main_limit is not pre-loop independent!"); + tty->print_cr("VPointer::can_make_speculative_aliasing_check_with: main_limit is not available at speculative check!"); } #endif return false; @@ -1119,6 +1131,8 @@ BoolNode* VPointer::make_speculative_aliasing_check_with(const VPointer& other, Node* pre_limit = pre_limit_opaq->in(1); assert(_vloop.is_pre_loop_invariant(pre_init), "needed for aliasing check before pre-loop"); assert(_vloop.is_pre_loop_invariant(pre_limit), "needed for aliasing check before pre-loop"); + assert(_vloop.is_available_for_speculative_check(pre_init), "ctrl must be early enough to avoid cycles"); + assert(_vloop.is_available_for_speculative_check(pre_limit), "ctrl must be early enough to avoid cycles"); Node* pre_initL = new ConvI2LNode(pre_init); Node* pre_limitL = new ConvI2LNode(pre_limit); @@ -1180,6 +1194,7 @@ BoolNode* VPointer::make_speculative_aliasing_check_with(const VPointer& other, jint main_iv_stride = _vloop.iv_stride(); Node* main_limit = _vloop.cl()->limit(); assert(_vloop.is_pre_loop_invariant(main_limit), "needed for aliasing check before pre-loop"); + assert(_vloop.is_available_for_speculative_check(main_limit), "ctrl must be early enough to avoid cycles"); Node* main_limitL = new ConvI2LNode(main_limit); phase->register_new_node_with_ctrl_of(main_limitL, pre_init); diff --git a/src/hotspot/share/opto/vectorization.hpp b/src/hotspot/share/opto/vectorization.hpp index f7099b5b7c0..aacd406f798 100644 --- a/src/hotspot/share/opto/vectorization.hpp +++ b/src/hotspot/share/opto/vectorization.hpp @@ -236,6 +236,8 @@ public: // Some nodes must be pre-loop invariant, so that they can be used for conditions // before or inside the pre-loop. For example, alignment of main-loop vector // memops must be achieved in the pre-loop, via the exit check in the pre-loop. + // Note: this condition is NOT strong enough for speculative checks, those happen + // before the pre-loop. See is_available_for_speculative_check bool is_pre_loop_invariant(Node* n) const { // Must be in the main-loop, otherwise we can't access the pre-loop. // This fails during SuperWord::unrolling_analysis, but that is ok. @@ -257,6 +259,28 @@ public: return is_before_pre_loop(early); } + // Nodes that are to be used in speculative checks must be available early enough. + // Note: the speculative check happens before the pre-loop, either at the auto + // vectorization predicate or the multiversion if. This is before the + // pre-loop, and thus the condition here is stronger then the one from + // is_pre_loop_invariant. + bool is_available_for_speculative_check(Node* n) const { + assert(are_speculative_checks_possible(), "meaningless without speculative check"); + ParsePredicateSuccessProj* parse_predicate_proj = auto_vectorization_parse_predicate_proj(); + // Find the control of the predicate: + ProjNode* proj = (parse_predicate_proj != nullptr) ? parse_predicate_proj : multiversioning_fast_proj(); + Node* check_ctrl = proj->in(0)->as_If()->in(0); + + // Often, the control of n already dominates that of the predicate. + Node* n_ctrl = phase()->get_ctrl(n); + if (phase()->is_dominator(n_ctrl, check_ctrl)) { return true; } + + // But in some cases, the ctrl of n is after that of the predicate, + // but the early ctrl is before the predicate. + Node* n_early = phase()->compute_early_ctrl(n, n_ctrl); + return phase()->is_dominator(n_early, check_ctrl); + } + // Check if the loop passes some basic preconditions for vectorization. // Return indicates if analysis succeeded. bool check_preconditions(); diff --git a/src/hotspot/share/prims/jvmtiClassFileReconstituter.cpp b/src/hotspot/share/prims/jvmtiClassFileReconstituter.cpp index a441d405f8d..5077a1743b9 100644 --- a/src/hotspot/share/prims/jvmtiClassFileReconstituter.cpp +++ b/src/hotspot/share/prims/jvmtiClassFileReconstituter.cpp @@ -25,6 +25,7 @@ #include "classfile/symbolTable.hpp" #include "interpreter/bytecodeStream.hpp" #include "memory/universe.hpp" +#include "oops/bsmAttribute.inline.hpp" #include "oops/constantPool.inline.hpp" #include "oops/fieldStreams.inline.hpp" #include "oops/instanceKlass.inline.hpp" @@ -389,20 +390,13 @@ void JvmtiClassFileReconstituter::write_annotations_attribute(const char* attr_n // } bootstrap_methods[num_bootstrap_methods]; // } void JvmtiClassFileReconstituter::write_bootstrapmethod_attribute() { - Array* operands = cpool()->operands(); write_attribute_name_index("BootstrapMethods"); - int num_bootstrap_methods = ConstantPool::operand_array_length(operands); - - // calculate length of attribute - u4 length = sizeof(u2); // num_bootstrap_methods - for (int n = 0; n < num_bootstrap_methods; n++) { - u2 num_bootstrap_arguments = cpool()->bsm_attribute_entry(n)->argument_count(); - length += sizeof(u2); // bootstrap_method_ref - length += sizeof(u2); // num_bootstrap_arguments - length += (u4)sizeof(u2) * num_bootstrap_arguments; // bootstrap_arguments[num_bootstrap_arguments] - } + u4 length = sizeof(u2) + // Size of num_bootstrap_methods + // The rest of the data for the attribute is exactly the u2s in the data array. + sizeof(u2) * cpool()->bsm_entries().array_length(); write_u4(length); + int num_bootstrap_methods = cpool()->bsm_entries().number_of_entries(); // write attribute write_u2(checked_cast(num_bootstrap_methods)); for (int n = 0; n < num_bootstrap_methods; n++) { @@ -411,7 +405,7 @@ void JvmtiClassFileReconstituter::write_bootstrapmethod_attribute() { write_u2(bsme->bootstrap_method_index()); write_u2(num_bootstrap_arguments); for (int arg = 0; arg < num_bootstrap_arguments; arg++) { - u2 bootstrap_argument = bsme->argument_index(arg); + u2 bootstrap_argument = bsme->argument(arg); write_u2(bootstrap_argument); } } @@ -798,7 +792,7 @@ void JvmtiClassFileReconstituter::write_class_attributes() { if (type_anno != nullptr) { ++attr_count; // has RuntimeVisibleTypeAnnotations attribute } - if (cpool()->operands() != nullptr) { + if (!cpool()->bsm_entries().is_empty()) { ++attr_count; } if (ik()->nest_host_index() != 0) { @@ -843,7 +837,7 @@ void JvmtiClassFileReconstituter::write_class_attributes() { if (ik()->record_components() != nullptr) { write_record_attribute(); } - if (cpool()->operands() != nullptr) { + if (!cpool()->bsm_entries().is_empty()) { write_bootstrapmethod_attribute(); } if (inner_classes_length > 0) { diff --git a/src/hotspot/share/prims/jvmtiRedefineClasses.cpp b/src/hotspot/share/prims/jvmtiRedefineClasses.cpp index ef8875d582e..13b239b4df0 100644 --- a/src/hotspot/share/prims/jvmtiRedefineClasses.cpp +++ b/src/hotspot/share/prims/jvmtiRedefineClasses.cpp @@ -45,7 +45,8 @@ #include "memory/resourceArea.hpp" #include "memory/universe.hpp" #include "oops/annotations.hpp" -#include "oops/constantPool.hpp" +#include "oops/bsmAttribute.inline.hpp" +#include "oops/constantPool.inline.hpp" #include "oops/fieldStreams.inline.hpp" #include "oops/klass.inline.hpp" #include "oops/klassVtable.hpp" @@ -573,9 +574,9 @@ void VM_RedefineClasses::append_entry(const constantPoolHandle& scratch_cp, case JVM_CONSTANT_Dynamic: // fall through case JVM_CONSTANT_InvokeDynamic: { - // Index of the bootstrap specifier in the operands array + // Index of the bootstrap specifier in the BSM array int old_bs_i = scratch_cp->bootstrap_methods_attribute_index(scratch_i); - int new_bs_i = find_or_append_operand(scratch_cp, old_bs_i, merge_cp_p, + int new_bs_i = find_or_append_bsm_entry(scratch_cp, old_bs_i, merge_cp_p, merge_cp_length_p); // The bootstrap method NameAndType_info index int old_ref_i = scratch_cp->bootstrap_name_and_type_ref_index_at(scratch_i); @@ -591,10 +592,11 @@ void VM_RedefineClasses::append_entry(const constantPoolHandle& scratch_cp, ("Dynamic entry@%d name_and_type_index change: %d to %d", *merge_cp_length_p, old_ref_i, new_ref_i); } - if (scratch_cp->tag_at(scratch_i).is_dynamic_constant()) + if (scratch_cp->tag_at(scratch_i).is_dynamic_constant()) { (*merge_cp_p)->dynamic_constant_at_put(*merge_cp_length_p, new_bs_i, new_ref_i); - else + } else { (*merge_cp_p)->invoke_dynamic_at_put(*merge_cp_length_p, new_bs_i, new_ref_i); + } if (scratch_i != *merge_cp_length_p) { // The new entry in *merge_cp_p is at a different index than // the new entry in scratch_cp so we need to map the index values. @@ -660,10 +662,10 @@ u2 VM_RedefineClasses::find_or_append_indirect_entry(const constantPoolHandle& s } // end find_or_append_indirect_entry() -// Append a bootstrap specifier into the merge_cp operands that is semantically equal -// to the scratch_cp operands bootstrap specifier passed by the old_bs_i index. +// Append a bootstrap specifier into the merge_cp BSM entries that is semantically equal +// to the scratch_cp BSM entries' bootstrap specifier passed by the old_bs_i index. // Recursively append new merge_cp entries referenced by the new bootstrap specifier. -void VM_RedefineClasses::append_operand(const constantPoolHandle& scratch_cp, const int old_bs_i, +int VM_RedefineClasses::append_bsm_entry(const constantPoolHandle& scratch_cp, const int old_bs_i, constantPoolHandle *merge_cp_p, int *merge_cp_length_p) { BSMAttributeEntry* old_bsme = scratch_cp->bsm_attribute_entry(old_bs_i); @@ -672,90 +674,82 @@ void VM_RedefineClasses::append_operand(const constantPoolHandle& scratch_cp, co merge_cp_length_p); if (new_ref_i != old_ref_i) { log_trace(redefine, class, constantpool) - ("operands entry@%d bootstrap method ref_index change: %d to %d", _operands_cur_length, old_ref_i, new_ref_i); + ("BSM attribute entry@%d bootstrap method ref_index change: %d to %d", _bsmae_iter.current_offset() - 1, old_ref_i, new_ref_i); } - Array* merge_ops = (*merge_cp_p)->operands(); - int new_bs_i = _operands_cur_length; - // We have _operands_cur_length == 0 when the merge_cp operands is empty yet. - // However, the operand_offset_at(0) was set in the extend_operands() call. - int new_base = (new_bs_i == 0) ? (*merge_cp_p)->operand_offset_at(0) - : (*merge_cp_p)->operand_next_offset_at(new_bs_i - 1); - u2 argc = old_bsme->argument_count(); - - ConstantPool::operand_offset_at_put(merge_ops, _operands_cur_length, new_base); - merge_ops->at_put(new_base++, new_ref_i); - merge_ops->at_put(new_base++, argc); - - for (int i = 0; i < argc; i++) { - u2 old_arg_ref_i = old_bsme->argument_index(i); + const int new_bs_i = _bsmae_iter.current_offset(); + BSMAttributeEntry* new_bsme = + _bsmae_iter.reserve_new_entry(new_ref_i, old_bsme->argument_count()); + assert(new_bsme != nullptr, "must be"); + for (int i = 0; i < new_bsme->argument_count(); i++) { + u2 old_arg_ref_i = old_bsme->argument(i); u2 new_arg_ref_i = find_or_append_indirect_entry(scratch_cp, old_arg_ref_i, merge_cp_p, merge_cp_length_p); - merge_ops->at_put(new_base++, new_arg_ref_i); + new_bsme->set_argument(i, new_arg_ref_i); + if (new_arg_ref_i != old_arg_ref_i) { log_trace(redefine, class, constantpool) - ("operands entry@%d bootstrap method argument ref_index change: %d to %d", - _operands_cur_length, old_arg_ref_i, new_arg_ref_i); + ("BSM attribute entry@%d bootstrap method argument ref_index change: %d to %d", + _bsmae_iter.current_offset() - 1, old_arg_ref_i, new_arg_ref_i); } } - if (old_bs_i != _operands_cur_length) { - // The bootstrap specifier in *merge_cp_p is at a different index than - // that in scratch_cp so we need to map the index values. - map_operand_index(old_bs_i, new_bs_i); - } - _operands_cur_length++; -} // end append_operand() + // This is only for the logging + map_bsm_index(old_bs_i, new_bs_i); + return new_bs_i; +} // end append_bsm_entry() -int VM_RedefineClasses::find_or_append_operand(const constantPoolHandle& scratch_cp, +int VM_RedefineClasses::find_or_append_bsm_entry(const constantPoolHandle& scratch_cp, int old_bs_i, constantPoolHandle *merge_cp_p, int *merge_cp_length_p) { + const int max_offset_in_merge = _bsmae_iter.current_offset(); int new_bs_i = old_bs_i; // bootstrap specifier index - bool match = (old_bs_i < _operands_cur_length) && - scratch_cp->compare_operand_to(old_bs_i, *merge_cp_p, old_bs_i); + // Has the old_bs_i index been used already? Check if it's the same so we know + // whether or not a remapping is required. + bool match = (old_bs_i < max_offset_in_merge) && + scratch_cp->compare_bootstrap_entry_to(old_bs_i, *merge_cp_p, old_bs_i); if (!match) { // forward reference in *merge_cp_p or not a direct match - int found_i = scratch_cp->find_matching_operand(old_bs_i, *merge_cp_p, - _operands_cur_length); + int found_i = scratch_cp->find_matching_bsm_entry(old_bs_i, *merge_cp_p, + max_offset_in_merge); if (found_i != -1) { - guarantee(found_i != old_bs_i, "compare_operand_to() and find_matching_operand() disagree"); - // found a matching operand somewhere else in *merge_cp_p so just need a mapping + guarantee(found_i != old_bs_i, "compare_bootstrap_entry_to() and find_matching_bsm_entry() disagree"); + // found a matching BSM entry somewhere else in *merge_cp_p so just need a mapping new_bs_i = found_i; - map_operand_index(old_bs_i, found_i); + map_bsm_index(old_bs_i, found_i); } else { // no match found so we have to append this bootstrap specifier to *merge_cp_p - append_operand(scratch_cp, old_bs_i, merge_cp_p, merge_cp_length_p); - new_bs_i = _operands_cur_length - 1; + new_bs_i = append_bsm_entry(scratch_cp, old_bs_i, merge_cp_p, merge_cp_length_p); } } return new_bs_i; -} // end find_or_append_operand() +} // end find_or_append_bsm_entry() -void VM_RedefineClasses::finalize_operands_merge(const constantPoolHandle& merge_cp, TRAPS) { - if (merge_cp->operands() == nullptr) { +void VM_RedefineClasses::finalize_bsm_entries_merge(const constantPoolHandle& merge_cp, TRAPS) { + if (merge_cp->bsm_entries().number_of_entries() == 0) { return; } - // Shrink the merge_cp operands - merge_cp->shrink_operands(_operands_cur_length, CHECK); + // Finished extending the BSMAEs + merge_cp->end_extension(_bsmae_iter, CHECK); if (log_is_enabled(Trace, redefine, class, constantpool)) { // don't want to loop unless we are tracing int count = 0; - for (int i = 1; i < _operands_index_map_p->length(); i++) { - int value = _operands_index_map_p->at(i); + for (int i = 1; i < _bsm_index_map_p->length(); i++) { + int value = _bsm_index_map_p->at(i); if (value != -1) { - log_trace(redefine, class, constantpool)("operands_index_map[%d]: old=%d new=%d", count, i, value); + log_trace(redefine, class, constantpool)("bsm_index_map[%d]: old=%d new=%d", count, i, value); count++; } } } // Clean-up - _operands_index_map_p = nullptr; - _operands_cur_length = 0; - _operands_index_map_count = 0; -} // end finalize_operands_merge() + _bsm_index_map_p = nullptr; + _bsm_index_map_count = 0; + _bsmae_iter = BSMAttributeEntries::InsertionIterator(); +} // end finalize_bsmentries_merge() // Symbol* comparator for qsort // The caller must have an active ResourceMark. @@ -1272,26 +1266,26 @@ u2 VM_RedefineClasses::find_new_index(int old_index) { // Find new bootstrap specifier index value for old bootstrap specifier index // value by searching the index map. Returns unused index (-1) if there is // no mapped value for the old bootstrap specifier index. -int VM_RedefineClasses::find_new_operand_index(int old_index) { - if (_operands_index_map_count == 0) { +int VM_RedefineClasses::find_new_bsm_index(int old_index) { + if (_bsm_index_map_count == 0) { // map is empty so nothing can be found return -1; } - if (old_index == -1 || old_index >= _operands_index_map_p->length()) { + if (old_index == -1 || old_index >= _bsm_index_map_p->length()) { // The old_index is out of range so it is not mapped. // This should not happen in regular constant pool merging use. return -1; } - int value = _operands_index_map_p->at(old_index); + int value = _bsm_index_map_p->at(old_index); if (value == -1) { // the old_index is not mapped return -1; } return value; -} // end find_new_operand_index() +} // end find_new_bsm_index() // The bug 6214132 caused the verification to fail. @@ -1560,22 +1554,15 @@ void VM_RedefineClasses::map_index(const constantPoolHandle& scratch_cp, // Map old_index to new_index as needed. -void VM_RedefineClasses::map_operand_index(int old_index, int new_index) { - if (find_new_operand_index(old_index) != -1) { - // old_index is already mapped - return; - } - +void VM_RedefineClasses::map_bsm_index(int old_index, int new_index) { if (old_index == new_index) { // no mapping is needed return; } - - _operands_index_map_p->at_put(old_index, new_index); - _operands_index_map_count++; - + _bsm_index_map_p->at_put(old_index, new_index); + _bsm_index_map_count++; log_trace(redefine, class, constantpool)("mapped bootstrap specifier at index %d to %d", old_index, new_index); -} // end map_index() +} // end map_bsm_index() // Merge old_cp and scratch_cp and return the results of the merge via @@ -1639,8 +1626,8 @@ bool VM_RedefineClasses::merge_constant_pools(const constantPoolHandle& old_cp, } } // end for each old_cp entry - ConstantPool::copy_operands(old_cp, merge_cp_p, CHECK_false); - merge_cp_p->extend_operands(scratch_cp, CHECK_false); + ConstantPool::copy_bsm_entries(old_cp, merge_cp_p, CHECK_false); + _bsmae_iter = merge_cp_p->start_extension(scratch_cp, CHECK_false); // We don't need to sanity check that *merge_cp_length_p is within // *merge_cp_p bounds since we have the minimum on-entry check above. @@ -1737,7 +1724,7 @@ bool VM_RedefineClasses::merge_constant_pools(const constantPoolHandle& old_cp, ("after pass 1b: merge_cp_len=%d, scratch_i=%d, index_map_len=%d", merge_cp_length_p, scratch_i, _index_map_count); } - finalize_operands_merge(merge_cp_p, CHECK_false); + finalize_bsm_entries_merge(merge_cp_p, CHECK_false); return true; } // end merge_constant_pools() @@ -1807,12 +1794,11 @@ jvmtiError VM_RedefineClasses::merge_cp_and_rewrite( _index_map_count = 0; _index_map_p = new intArray(scratch_cp->length(), scratch_cp->length(), -1); - _operands_cur_length = ConstantPool::operand_array_length(old_cp->operands()); - _operands_index_map_count = 0; - int operands_index_map_len = ConstantPool::operand_array_length(scratch_cp->operands()); - _operands_index_map_p = new intArray(operands_index_map_len, operands_index_map_len, -1); + _bsm_index_map_count = 0; + int bsm_data_len = scratch_cp->bsm_entries().array_length(); + _bsm_index_map_p = new intArray(bsm_data_len, bsm_data_len, -1); - // reference to the cp holder is needed for copy_operands() + // reference to the cp holder is needed for reallocating the BSM attribute merge_cp->set_pool_holder(scratch_class); bool result = merge_constant_pools(old_cp, scratch_cp, merge_cp, merge_cp_length, THREAD); @@ -3500,7 +3486,7 @@ void VM_RedefineClasses::set_new_constant_pool( smaller_cp->set_version(version); // attach klass to new constant pool - // reference to the cp holder is needed for copy_operands() + // reference to the cp holder is needed for reallocating the BSM attribute smaller_cp->set_pool_holder(scratch_class); smaller_cp->copy_fields(scratch_cp()); diff --git a/src/hotspot/share/prims/jvmtiRedefineClasses.hpp b/src/hotspot/share/prims/jvmtiRedefineClasses.hpp index d2eda1f3eed..3f1b555b175 100644 --- a/src/hotspot/share/prims/jvmtiRedefineClasses.hpp +++ b/src/hotspot/share/prims/jvmtiRedefineClasses.hpp @@ -363,11 +363,16 @@ class VM_RedefineClasses: public VM_Operation { int _index_map_count; intArray * _index_map_p; - // _operands_index_map_count is just an optimization for knowing if - // _operands_index_map_p contains any entries. - int _operands_cur_length; - int _operands_index_map_count; - intArray * _operands_index_map_p; + // _bsm_index_map_count is just an optimization for knowing if + // _bsm_index_map_p contains any entries. + int _bsm_index_map_count; + intArray * _bsm_index_map_p; + + // After merge_constant_pools "Pass 0", the BSMAttribute entries of merge_cp_p will have been expanded to fit + // scratch_cp's BSMAttribute entries as well. + // However, the newly acquired space will not have been filled in yet. + // To append to this new space, the iterator is used. + BSMAttributeEntries::InsertionIterator _bsmae_iter; // ptr to _class_count scratch_classes InstanceKlass** _scratch_classes; @@ -429,17 +434,18 @@ class VM_RedefineClasses: public VM_Operation { // Support for constant pool merging (these routines are in alpha order): void append_entry(const constantPoolHandle& scratch_cp, int scratch_i, constantPoolHandle *merge_cp_p, int *merge_cp_length_p); - void append_operand(const constantPoolHandle& scratch_cp, int scratch_bootstrap_spec_index, + // Returns the index of the appended BSM + int append_bsm_entry(const constantPoolHandle& scratch_cp, int scratch_bootstrap_spec_index, constantPoolHandle *merge_cp_p, int *merge_cp_length_p); - void finalize_operands_merge(const constantPoolHandle& merge_cp, TRAPS); + void finalize_bsm_entries_merge(const constantPoolHandle& merge_cp, TRAPS); u2 find_or_append_indirect_entry(const constantPoolHandle& scratch_cp, int scratch_i, constantPoolHandle *merge_cp_p, int *merge_cp_length_p); - int find_or_append_operand(const constantPoolHandle& scratch_cp, int scratch_bootstrap_spec_index, + int find_or_append_bsm_entry(const constantPoolHandle& scratch_cp, int scratch_bootstrap_spec_index, constantPoolHandle *merge_cp_p, int *merge_cp_length_p); u2 find_new_index(int old_index); - int find_new_operand_index(int old_bootstrap_spec_index); + int find_new_bsm_index(int old_bootstrap_spec_index); void map_index(const constantPoolHandle& scratch_cp, int old_index, int new_index); - void map_operand_index(int old_bootstrap_spec_index, int new_bootstrap_spec_index); + void map_bsm_index(int old_bootstrap_spec_index, int new_bootstrap_spec_index); bool merge_constant_pools(const constantPoolHandle& old_cp, const constantPoolHandle& scratch_cp, constantPoolHandle& merge_cp_p, int& merge_cp_length_p, TRAPS); diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index 55ee7641a5f..4a983095593 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -1478,10 +1478,10 @@ void Arguments::set_conservative_max_heap_alignment() { // the alignments imposed by several sources: any requirements from the heap // itself and the maximum page size we may run the VM with. size_t heap_alignment = GCConfig::arguments()->conservative_max_heap_alignment(); - _conservative_max_heap_alignment = MAX4(heap_alignment, + _conservative_max_heap_alignment = MAX3(heap_alignment, os::vm_allocation_granularity(), - os::max_page_size(), - GCArguments::compute_heap_alignment()); + os::max_page_size()); + assert(is_power_of_2(_conservative_max_heap_alignment), "Expected to be a power-of-2"); } jint Arguments::set_ergonomics_flags() { @@ -1589,8 +1589,8 @@ void Arguments::set_heap_size() { } if (UseCompressedOops) { - size_t heap_end = HeapBaseMinAddress + MaxHeapSize; - size_t max_coop_heap = max_heap_for_compressed_oops(); + uintptr_t heap_end = HeapBaseMinAddress + MaxHeapSize; + uintptr_t max_coop_heap = max_heap_for_compressed_oops(); // Limit the heap size to the maximum possible when using compressed oops if (heap_end < max_coop_heap) { @@ -1607,7 +1607,7 @@ void Arguments::set_heap_size() { aot_log_info(aot)("UseCompressedOops disabled due to " "max heap %zu > compressed oop heap %zu. " "Please check the setting of MaxRAMPercentage %5.2f.", - reasonable_max, max_coop_heap, MaxRAMPercentage); + reasonable_max, (size_t)max_coop_heap, MaxRAMPercentage); FLAG_SET_ERGO(UseCompressedOops, false); } else { reasonable_max = max_coop_heap; diff --git a/src/hotspot/share/runtime/atomic.hpp b/src/hotspot/share/runtime/atomic.hpp index 5b4d7d8659f..b8960fd796b 100644 --- a/src/hotspot/share/runtime/atomic.hpp +++ b/src/hotspot/share/runtime/atomic.hpp @@ -75,6 +75,7 @@ // v.release_store(x) -> void // v.release_store_fence(x) -> void // v.compare_exchange(x, y [, o]) -> T +// v.exchange(x [, o]) -> T // // (2) All atomic types are default constructible. // @@ -92,7 +93,6 @@ // (3) Atomic pointers and atomic integers additionally provide // // member functions: -// v.exchange(x [, o]) -> T // v.add_then_fetch(i [, o]) -> T // v.sub_then_fetch(i [, o]) -> T // v.fetch_then_add(i [, o]) -> T @@ -102,10 +102,7 @@ // type of i must be signed, or both must be unsigned. Atomic pointers perform // element arithmetic. // -// (4) An atomic translated type additionally provides the exchange -// function if its associated atomic decayed type provides that function. -// -// (5) Atomic integers additionally provide +// (4) Atomic integers additionally provide // // member functions: // v.and_then_fetch(x [, o]) -> T @@ -115,7 +112,7 @@ // v.fetch_then_or(x [, o]) -> T // v.fetch_then_xor(x [, o]) -> T // -// (6) Atomic pointers additionally provide +// (5) Atomic pointers additionally provide // // nested types: // ElementType -> std::remove_pointer_t @@ -127,9 +124,6 @@ // stand out a little more when used in surrounding non-atomic code. Without // the "AtomicAccess::" qualifier, some of those names are easily overlooked. // -// Atomic bytes don't provide exchange(). This is because that operation -// hasn't been implemented for 1 byte values. That could be changed if needed. -// // Atomic for 2 byte integers is not supported. This is because atomic // operations of that size have not been implemented. There haven't been // required use-cases. Many platforms don't provide hardware support. @@ -184,15 +178,8 @@ private: // Helper base classes, providing various parts of the APIs. template class CommonCore; - template class SupportsExchange; template class SupportsArithmetic; - // Support conditional exchange() for atomic translated types. - template class HasExchange; - template class DecayedHasExchange; - template::value> - class TranslatedExchange; - public: template()> class Atomic; @@ -275,15 +262,7 @@ public: atomic_memory_order order = memory_order_conservative) { return AtomicAccess::cmpxchg(value_ptr(), compare_value, new_value, order); } -}; -template -class AtomicImpl::SupportsExchange : public CommonCore { -protected: - explicit SupportsExchange(T value) : CommonCore(value) {} - ~SupportsExchange() = default; - -public: T exchange(T new_value, atomic_memory_order order = memory_order_conservative) { return AtomicAccess::xchg(this->value_ptr(), new_value, order); @@ -291,7 +270,7 @@ public: }; template -class AtomicImpl::SupportsArithmetic : public SupportsExchange { +class AtomicImpl::SupportsArithmetic : public CommonCore { // Guarding the AtomicAccess calls with constexpr checking of Offset produces // better compile-time error messages. template @@ -311,7 +290,7 @@ class AtomicImpl::SupportsArithmetic : public SupportsExchange { } protected: - explicit SupportsArithmetic(T value) : SupportsExchange(value) {} + explicit SupportsArithmetic(T value) : CommonCore(value) {} ~SupportsArithmetic() = default; public: @@ -424,54 +403,8 @@ public: // Atomic translated type -// Test whether Atomic has exchange(). template -class AtomicImpl::HasExchange { - template static void* test(decltype(&Check::exchange)); - template static int test(...); - using test_type = decltype(test>(nullptr)); -public: - static constexpr bool value = std::is_pointer_v; -}; - -// Test whether the atomic decayed type associated with T has exchange(). -template -class AtomicImpl::DecayedHasExchange { - using Translator = PrimitiveConversions::Translate; - using Decayed = typename Translator::Decayed; - - // "Unit test" HasExchange<>. - static_assert(HasExchange::value); - static_assert(HasExchange::value); - static_assert(!HasExchange::value); - -public: - static constexpr bool value = HasExchange::value; -}; - -// Base class for atomic translated type if atomic decayed type doesn't have -// exchange(). -template -class AtomicImpl::TranslatedExchange {}; - -// Base class for atomic translated type if atomic decayed type does have -// exchange(). -template -class AtomicImpl::TranslatedExchange { -public: - T exchange(T new_value, - atomic_memory_order order = memory_order_conservative) { - return static_cast(this)->exchange_impl(new_value, order); - } -}; - -template -class AtomicImpl::Atomic - : public TranslatedExchange, T> -{ - // Give TranslatedExchange<> access to exchange_impl() if needed. - friend class TranslatedExchange, T>; - +class AtomicImpl::Atomic { using Translator = PrimitiveConversions::Translate; using Decayed = typename Translator::Decayed; @@ -533,12 +466,7 @@ public: order)); } -private: - // Implementation of exchange() if needed. - // Exclude when not needed, to prevent reference to non-existent function - // of atomic decayed type if someone explicitly instantiates Atomic. - template::value)> - T exchange_impl(T new_value, atomic_memory_order order) { + T exchange(T new_value, atomic_memory_order order = memory_order_conservative) { return recover(_value.exchange(decay(new_value), order)); } }; diff --git a/src/hotspot/share/runtime/atomicAccess.hpp b/src/hotspot/share/runtime/atomicAccess.hpp index 72b7f92cf04..fb06f084366 100644 --- a/src/hotspot/share/runtime/atomicAccess.hpp +++ b/src/hotspot/share/runtime/atomicAccess.hpp @@ -419,8 +419,8 @@ private: struct XchgImpl; // Platform-specific implementation of xchg. Support for sizes - // of 4, and sizeof(intptr_t) are required. The class is a function - // object that must be default constructable, with these requirements: + // of 1, 4, and 8 are required. The class is a function object + // that must be default constructable, with these requirements: // // - dest is of type T*. // - exchange_value is of type T. diff --git a/src/hotspot/share/runtime/cpuTimeCounters.cpp b/src/hotspot/share/runtime/cpuTimeCounters.cpp index e5364550b6c..e174407089c 100644 --- a/src/hotspot/share/runtime/cpuTimeCounters.cpp +++ b/src/hotspot/share/runtime/cpuTimeCounters.cpp @@ -118,8 +118,5 @@ ThreadTotalCPUTimeClosure::~ThreadTotalCPUTimeClosure() { } void ThreadTotalCPUTimeClosure::do_thread(Thread* thread) { - // The default code path (fast_thread_cpu_time()) asserts that - // pthread_getcpuclockid() and clock_gettime() must return 0. Thus caller - // must ensure the thread exists and has not terminated. _total += os::thread_cpu_time(thread); } diff --git a/src/hotspot/share/runtime/deoptimization.cpp b/src/hotspot/share/runtime/deoptimization.cpp index 0aa7b392b17..e2029a26d37 100644 --- a/src/hotspot/share/runtime/deoptimization.cpp +++ b/src/hotspot/share/runtime/deoptimization.cpp @@ -498,6 +498,9 @@ Deoptimization::UnrollBlock* Deoptimization::fetch_unroll_info_helper(JavaThread RegisterMap::WalkContinuation::skip); // Now get the deoptee with a valid map frame deoptee = stub_frame.sender(&map); + if (exec_mode == Unpack_deopt) { + assert(deoptee.is_deoptimized_frame(), "frame is not marked for deoptimization"); + } // Set the deoptee nmethod assert(current->deopt_compiled_method() == nullptr, "Pending deopt!"); nmethod* nm = deoptee.cb()->as_nmethod_or_null(); diff --git a/src/hotspot/share/runtime/frame.cpp b/src/hotspot/share/runtime/frame.cpp index b5cd4acc75d..8f969600ba8 100644 --- a/src/hotspot/share/runtime/frame.cpp +++ b/src/hotspot/share/runtime/frame.cpp @@ -206,7 +206,7 @@ address frame::raw_pc() const { if (is_deoptimized_frame()) { nmethod* nm = cb()->as_nmethod_or_null(); assert(nm != nullptr, "only nmethod is expected here"); - return nm->deopt_handler_begin() - pc_return_offset; + return nm->deopt_handler_entry() - pc_return_offset; } else { return (pc() - pc_return_offset); } @@ -355,7 +355,7 @@ void frame::deoptimize(JavaThread* thread) { // If the call site is a MethodHandle call site use the MH deopt handler. nmethod* nm = _cb->as_nmethod(); - address deopt = nm->deopt_handler_begin(); + address deopt = nm->deopt_handler_entry(); NativePostCallNop* inst = nativePostCallNop_at(pc()); diff --git a/src/hotspot/share/runtime/os.hpp b/src/hotspot/share/runtime/os.hpp index e008f29eecc..b65bf643cbf 100644 --- a/src/hotspot/share/runtime/os.hpp +++ b/src/hotspot/share/runtime/os.hpp @@ -534,6 +534,7 @@ class os: AllStatic { static void realign_memory(char *addr, size_t bytes, size_t alignment_hint); // NUMA-specific interface + static void numa_set_thread_affinity(Thread* thread, int node); static void numa_make_local(char *addr, size_t bytes, int lgrp_hint); static void numa_make_global(char *addr, size_t bytes); static size_t numa_get_groups_num(); diff --git a/src/hotspot/share/runtime/sharedRuntime.cpp b/src/hotspot/share/runtime/sharedRuntime.cpp index 79c7c0b32b4..e277e1fb569 100644 --- a/src/hotspot/share/runtime/sharedRuntime.cpp +++ b/src/hotspot/share/runtime/sharedRuntime.cpp @@ -87,6 +87,9 @@ #ifdef COMPILER1 #include "c1/c1_Runtime1.hpp" #endif +#ifdef COMPILER2 +#include "opto/runtime.hpp" +#endif #if INCLUDE_JFR #include "jfr/jfr.inline.hpp" #endif @@ -601,6 +604,11 @@ address SharedRuntime::raw_exception_handler_for_return_address(JavaThread* curr // The deferred StackWatermarkSet::after_unwind check will be performed in // * OptoRuntime::handle_exception_C_helper for C2 code // * exception_handler_for_pc_helper via Runtime1::handle_exception_from_callee_id for C1 code +#ifdef COMPILER2 + if (nm->compiler_type() == compiler_c2) { + return OptoRuntime::exception_blob()->entry_point(); + } +#endif // COMPILER2 return nm->exception_begin(); } } diff --git a/src/hotspot/share/runtime/vmStructs.cpp b/src/hotspot/share/runtime/vmStructs.cpp index a75e67e9b56..25a99c2d758 100644 --- a/src/hotspot/share/runtime/vmStructs.cpp +++ b/src/hotspot/share/runtime/vmStructs.cpp @@ -54,6 +54,7 @@ #include "oops/array.hpp" #include "oops/arrayKlass.hpp" #include "oops/arrayOop.hpp" +#include "oops/bsmAttribute.hpp" #include "oops/constantPool.hpp" #include "oops/constMethod.hpp" #include "oops/cpCache.hpp" @@ -166,10 +167,12 @@ nonstatic_field(ArrayKlass, _dimension, int) \ volatile_nonstatic_field(ArrayKlass, _higher_dimension, ObjArrayKlass*) \ volatile_nonstatic_field(ArrayKlass, _lower_dimension, ArrayKlass*) \ + nonstatic_field(BSMAttributeEntries, _offsets, Array*) \ + nonstatic_field(BSMAttributeEntries, _bootstrap_methods, Array*) \ + nonstatic_field(ConstantPool, _bsm_entries, BSMAttributeEntries) \ nonstatic_field(ConstantPool, _tags, Array*) \ nonstatic_field(ConstantPool, _cache, ConstantPoolCache*) \ nonstatic_field(ConstantPool, _pool_holder, InstanceKlass*) \ - nonstatic_field(ConstantPool, _operands, Array*) \ nonstatic_field(ConstantPool, _resolved_klasses, Array*) \ nonstatic_field(ConstantPool, _length, int) \ nonstatic_field(ConstantPool, _minor_version, u2) \ @@ -534,7 +537,7 @@ nonstatic_field(nmethod, _osr_link, nmethod*) \ nonstatic_field(nmethod, _state, volatile signed char) \ nonstatic_field(nmethod, _exception_offset, int) \ - nonstatic_field(nmethod, _deopt_handler_offset, int) \ + nonstatic_field(nmethod, _deopt_handler_entry_offset, int) \ nonstatic_field(nmethod, _orig_pc_offset, int) \ nonstatic_field(nmethod, _stub_offset, int) \ nonstatic_field(nmethod, _immutable_data_ref_count_offset, int) \ @@ -733,6 +736,7 @@ unchecked_nonstatic_field(Array, _data, sizeof(int)) \ unchecked_nonstatic_field(Array, _data, sizeof(u1)) \ unchecked_nonstatic_field(Array, _data, sizeof(u2)) \ + unchecked_nonstatic_field(Array, _data, sizeof(u4)) \ unchecked_nonstatic_field(Array, _data, sizeof(Method*)) \ unchecked_nonstatic_field(Array, _data, sizeof(Klass*)) \ unchecked_nonstatic_field(Array, _data, sizeof(ResolvedFieldEntry)) \ @@ -964,6 +968,7 @@ declare_toplevel_type(volatile Metadata*) \ \ declare_toplevel_type(DataLayout) \ + declare_toplevel_type(BSMAttributeEntries) \ \ /********/ \ /* Oops */ \ diff --git a/src/hotspot/share/services/cpuTimeUsage.cpp b/src/hotspot/share/services/cpuTimeUsage.cpp index 0c7ecfdb655..27b5e90fbaf 100644 --- a/src/hotspot/share/services/cpuTimeUsage.cpp +++ b/src/hotspot/share/services/cpuTimeUsage.cpp @@ -36,7 +36,6 @@ volatile bool CPUTimeUsage::Error::_has_error = false; static inline jlong thread_cpu_time_or_zero(Thread* thread) { - assert(!Universe::is_shutting_down(), "Should not query during shutdown"); jlong cpu_time = os::thread_cpu_time(thread); if (cpu_time == -1) { CPUTimeUsage::Error::mark_error(); diff --git a/src/hotspot/share/services/diagnosticCommand.cpp b/src/hotspot/share/services/diagnosticCommand.cpp index 5bef650891d..91b23904676 100644 --- a/src/hotspot/share/services/diagnosticCommand.cpp +++ b/src/hotspot/share/services/diagnosticCommand.cpp @@ -22,6 +22,7 @@ * */ +#include "cds/aotMetaspace.hpp" #include "cds/cds_globals.hpp" #include "cds/cdsConfig.hpp" #include "classfile/classLoaderDataGraph.hpp" @@ -165,6 +166,7 @@ void DCmd::register_dcmds(){ #if INCLUDE_CDS DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); #endif // INCLUDE_CDS DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); @@ -986,6 +988,28 @@ void ClassesDCmd::execute(DCmdSource source, TRAPS) { VMThread::execute(&vmop); } +#if INCLUDE_CDS +void AOTEndRecordingDCmd::execute(DCmdSource source, TRAPS) { + if (!CDSConfig::is_dumping_preimage_static_archive()) { + output()->print_cr("AOT.end_recording is unsupported when VM flags -XX:AOTMode=record or -XX:AOTCacheOutput= are missing."); + return; + } + + if (AOTMetaspace::preimage_static_archive_dumped()) { + output()->print_cr("Recording has already ended."); + return; + } + + AOTMetaspace::dump_static_archive(THREAD); + if (!AOTMetaspace::preimage_static_archive_dumped()) { + output()->print_cr("Error: Failed to end recording."); + return; + } + + output()->print_cr("Recording ended successfully."); +} +#endif // INCLUDE_CDS + #if INCLUDE_CDS #define DEFAULT_CDS_ARCHIVE_FILENAME "java_pid%p_.jsa" diff --git a/src/hotspot/share/services/diagnosticCommand.hpp b/src/hotspot/share/services/diagnosticCommand.hpp index 2364b0ce4cd..c41e7bf2e2e 100644 --- a/src/hotspot/share/services/diagnosticCommand.hpp +++ b/src/hotspot/share/services/diagnosticCommand.hpp @@ -325,6 +325,21 @@ public: virtual void execute(DCmdSource source, TRAPS); }; +#if INCLUDE_CDS +class AOTEndRecordingDCmd : public DCmd { +public: + AOTEndRecordingDCmd(outputStream* output, bool heap) : DCmd(output, heap) { } + static const char* name() { return "AOT.end_recording"; } + static const char* description() { + return "End AOT recording."; + } + static const char* impact() { + return "Medium: Pause time depends on number of loaded classes"; + } + virtual void execute(DCmdSource source, TRAPS); +}; +#endif // INCLUDE_CDS + #if INCLUDE_CDS class DumpSharedArchiveDCmd: public DCmdWithParser { protected: diff --git a/src/hotspot/share/utilities/vmError.cpp b/src/hotspot/share/utilities/vmError.cpp index e0cbb60c744..a290602e0be 100644 --- a/src/hotspot/share/utilities/vmError.cpp +++ b/src/hotspot/share/utilities/vmError.cpp @@ -664,6 +664,7 @@ void VMError::report(outputStream* st, bool _verbose) { BEGIN if (MemTracker::enabled() && NmtVirtualMemory_lock != nullptr && + _thread != nullptr && NmtVirtualMemory_lock->owned_by_self()) { // Manually unlock to avoid reentrancy due to mallocs in detailed mode. NmtVirtualMemory_lock->unlock(); @@ -1305,7 +1306,7 @@ void VMError::report(outputStream* st, bool _verbose) { os::print_signal_handlers(st, buf, sizeof(buf)); st->cr(); - STEP_IF("Native Memory Tracking", _verbose) + STEP_IF("Native Memory Tracking", _verbose && _thread != nullptr) MemTracker::error_report(st); st->cr(); diff --git a/src/hotspot/share/utilities/waitBarrier_generic.cpp b/src/hotspot/share/utilities/waitBarrier_generic.cpp index a6436d93ffc..b268b10c757 100644 --- a/src/hotspot/share/utilities/waitBarrier_generic.cpp +++ b/src/hotspot/share/utilities/waitBarrier_generic.cpp @@ -23,7 +23,6 @@ * */ -#include "runtime/atomicAccess.hpp" #include "runtime/orderAccess.hpp" #include "runtime/os.hpp" #include "utilities/spinYield.hpp" @@ -79,10 +78,10 @@ void GenericWaitBarrier::arm(int barrier_tag) { assert(barrier_tag != 0, "Pre arm: Should be arming with armed value"); - assert(AtomicAccess::load(&_barrier_tag) == 0, + assert(_barrier_tag.load_relaxed() == 0, "Pre arm: Should not be already armed. Tag: %d", - AtomicAccess::load(&_barrier_tag)); - AtomicAccess::release_store(&_barrier_tag, barrier_tag); + _barrier_tag.load_relaxed()); + _barrier_tag.release_store(barrier_tag); Cell &cell = tag_to_cell(barrier_tag); cell.arm(barrier_tag); @@ -92,9 +91,9 @@ void GenericWaitBarrier::arm(int barrier_tag) { } void GenericWaitBarrier::disarm() { - int barrier_tag = AtomicAccess::load_acquire(&_barrier_tag); + int barrier_tag = _barrier_tag.load_acquire(); assert(barrier_tag != 0, "Pre disarm: Should be armed. Tag: %d", barrier_tag); - AtomicAccess::release_store(&_barrier_tag, 0); + _barrier_tag.release_store(0); Cell &cell = tag_to_cell(barrier_tag); cell.disarm(barrier_tag); @@ -121,7 +120,7 @@ void GenericWaitBarrier::Cell::arm(int32_t requested_tag) { SpinYield sp; while (true) { - state = AtomicAccess::load_acquire(&_state); + state = _state.load_acquire(); assert(decode_tag(state) == 0, "Pre arm: Should not be armed. " "Tag: " INT32_FORMAT "; Waiters: " INT32_FORMAT, @@ -134,7 +133,7 @@ void GenericWaitBarrier::Cell::arm(int32_t requested_tag) { // Try to swing cell to armed. This should always succeed after the check above. int64_t new_state = encode(requested_tag, 0); - int64_t prev_state = AtomicAccess::cmpxchg(&_state, state, new_state); + int64_t prev_state = _state.compare_exchange(state, new_state); if (prev_state != state) { fatal("Cannot arm the wait barrier. " "Tag: " INT32_FORMAT "; Waiters: " INT32_FORMAT, @@ -145,14 +144,14 @@ void GenericWaitBarrier::Cell::arm(int32_t requested_tag) { int GenericWaitBarrier::Cell::signal_if_needed(int max) { int signals = 0; while (true) { - int cur = AtomicAccess::load_acquire(&_outstanding_wakeups); + int cur = _outstanding_wakeups.load_acquire(); if (cur == 0) { // All done, no more waiters. return 0; } assert(cur > 0, "Sanity"); - int prev = AtomicAccess::cmpxchg(&_outstanding_wakeups, cur, cur - 1); + int prev = _outstanding_wakeups.compare_exchange(cur, cur - 1); if (prev != cur) { // Contention, return to caller for early return or backoff. return prev; @@ -172,7 +171,7 @@ void GenericWaitBarrier::Cell::disarm(int32_t expected_tag) { int32_t waiters; while (true) { - int64_t state = AtomicAccess::load_acquire(&_state); + int64_t state = _state.load_acquire(); int32_t tag = decode_tag(state); waiters = decode_waiters(state); @@ -182,7 +181,7 @@ void GenericWaitBarrier::Cell::disarm(int32_t expected_tag) { tag, waiters); int64_t new_state = encode(0, waiters); - if (AtomicAccess::cmpxchg(&_state, state, new_state) == state) { + if (_state.compare_exchange(state, new_state) == state) { // Successfully disarmed. break; } @@ -191,19 +190,19 @@ void GenericWaitBarrier::Cell::disarm(int32_t expected_tag) { // Wake up waiters, if we have at least one. // Allow other threads to assist with wakeups, if possible. if (waiters > 0) { - AtomicAccess::release_store(&_outstanding_wakeups, waiters); + _outstanding_wakeups.release_store(waiters); SpinYield sp; while (signal_if_needed(INT_MAX) > 0) { sp.wait(); } } - assert(AtomicAccess::load(&_outstanding_wakeups) == 0, "Post disarm: Should not have outstanding wakeups"); + assert(_outstanding_wakeups.load_relaxed() == 0, "Post disarm: Should not have outstanding wakeups"); } void GenericWaitBarrier::Cell::wait(int32_t expected_tag) { // Try to register ourselves as pending waiter. while (true) { - int64_t state = AtomicAccess::load_acquire(&_state); + int64_t state = _state.load_acquire(); int32_t tag = decode_tag(state); if (tag != expected_tag) { // Cell tag had changed while waiting here. This means either the cell had @@ -219,7 +218,7 @@ void GenericWaitBarrier::Cell::wait(int32_t expected_tag) { tag, waiters); int64_t new_state = encode(tag, waiters + 1); - if (AtomicAccess::cmpxchg(&_state, state, new_state) == state) { + if (_state.compare_exchange(state, new_state) == state) { // Success! Proceed to wait. break; } @@ -238,7 +237,7 @@ void GenericWaitBarrier::Cell::wait(int32_t expected_tag) { // Register ourselves as completed waiter before leaving. while (true) { - int64_t state = AtomicAccess::load_acquire(&_state); + int64_t state = _state.load_acquire(); int32_t tag = decode_tag(state); int32_t waiters = decode_waiters(state); @@ -248,7 +247,7 @@ void GenericWaitBarrier::Cell::wait(int32_t expected_tag) { tag, waiters); int64_t new_state = encode(tag, waiters - 1); - if (AtomicAccess::cmpxchg(&_state, state, new_state) == state) { + if (_state.compare_exchange(state, new_state) == state) { // Success! break; } diff --git a/src/hotspot/share/utilities/waitBarrier_generic.hpp b/src/hotspot/share/utilities/waitBarrier_generic.hpp index 8ed9ef3ac6e..0cbba1041db 100644 --- a/src/hotspot/share/utilities/waitBarrier_generic.hpp +++ b/src/hotspot/share/utilities/waitBarrier_generic.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,6 +27,7 @@ #include "memory/allocation.hpp" #include "memory/padded.hpp" +#include "runtime/atomic.hpp" #include "runtime/semaphore.hpp" #include "utilities/globalDefinitions.hpp" @@ -43,10 +44,10 @@ private: Semaphore _sem; // Cell state, tracks the arming + waiters status - volatile int64_t _state; + Atomic _state; // Wakeups to deliver for current waiters - volatile int _outstanding_wakeups; + Atomic _outstanding_wakeups; int signal_if_needed(int max); @@ -83,7 +84,7 @@ private: // Trailing padding to protect the last cell. DEFINE_PAD_MINUS_SIZE(0, DEFAULT_PADDING_SIZE, 0); - volatile int _barrier_tag; + Atomic _barrier_tag; // Trailing padding to insulate the rest of the barrier from adjacent // data structures. The leading padding is not needed, as cell padding diff --git a/src/java.base/share/classes/com/sun/crypto/provider/DHKEM.java b/src/java.base/share/classes/com/sun/crypto/provider/DHKEM.java index b27320ed24b..c7372a4c2c8 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/DHKEM.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/DHKEM.java @@ -26,26 +26,51 @@ package com.sun.crypto.provider; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.Serial; import java.math.BigInteger; -import java.security.*; -import java.security.interfaces.ECKey; +import java.security.AsymmetricKey; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.ProviderException; +import java.security.PublicKey; +import java.security.SecureRandom; import java.security.interfaces.ECPublicKey; -import java.security.interfaces.XECKey; import java.security.interfaces.XECPublicKey; -import java.security.spec.*; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPrivateKeySpec; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.security.spec.NamedParameterSpec; +import java.security.spec.XECPrivateKeySpec; +import java.security.spec.XECPublicKeySpec; import java.util.Arrays; import java.util.Objects; -import javax.crypto.*; -import javax.crypto.spec.SecretKeySpec; +import javax.crypto.DecapsulateException; +import javax.crypto.KDF; +import javax.crypto.KEM; +import javax.crypto.KEMSpi; +import javax.crypto.KeyAgreement; +import javax.crypto.SecretKey; import javax.crypto.spec.HKDFParameterSpec; +import javax.crypto.spec.SecretKeySpec; import sun.security.jca.JCAUtil; -import sun.security.util.*; - -import jdk.internal.access.SharedSecrets; +import sun.security.util.ArrayUtil; +import sun.security.util.CurveDB; +import sun.security.util.ECUtil; +import sun.security.util.InternalPrivateKey; +import sun.security.util.NamedCurve; +import sun.security.util.SliceableSecretKey; // Implementing DHKEM defined inside https://www.rfc-editor.org/rfc/rfc9180.html, -// without the AuthEncap and AuthDecap functions public class DHKEM implements KEMSpi { private static final byte[] KEM = new byte[] @@ -65,80 +90,86 @@ public class DHKEM implements KEMSpi { private static final byte[] EMPTY = new byte[0]; private record Handler(Params params, SecureRandom secureRandom, - PrivateKey skR, PublicKey pkR) + PrivateKey skS, PublicKey pkS, // sender keys + PrivateKey skR, PublicKey pkR) // receiver keys implements EncapsulatorSpi, DecapsulatorSpi { @Override public KEM.Encapsulated engineEncapsulate(int from, int to, String algorithm) { - Objects.checkFromToIndex(from, to, params.Nsecret); + Objects.checkFromToIndex(from, to, params.nsecret); Objects.requireNonNull(algorithm, "null algorithm"); KeyPair kpE = params.generateKeyPair(secureRandom); PrivateKey skE = kpE.getPrivate(); PublicKey pkE = kpE.getPublic(); - byte[] pkEm = params.SerializePublicKey(pkE); - byte[] pkRm = params.SerializePublicKey(pkR); - byte[] kem_context = concat(pkEm, pkRm); - byte[] key = null; + byte[] pkEm = params.serializePublicKey(pkE); + byte[] pkRm = params.serializePublicKey(pkR); try { - byte[] dh = params.DH(skE, pkR); - key = params.ExtractAndExpand(dh, kem_context); - return new KEM.Encapsulated( - new SecretKeySpec(key, from, to - from, algorithm), - pkEm, null); + SecretKey key; + if (skS == null) { + byte[] kem_context = concat(pkEm, pkRm); + key = params.deriveKey(algorithm, from, to, kem_context, + params.dh(skE, pkR)); + } else { + byte[] pkSm = params.serializePublicKey(pkS); + byte[] kem_context = concat(pkEm, pkRm, pkSm); + key = params.deriveKey(algorithm, from, to, kem_context, + params.dh(skE, pkR), params.dh(skS, pkR)); + } + return new KEM.Encapsulated(key, pkEm, null); + } catch (UnsupportedOperationException e) { + throw e; } catch (Exception e) { throw new ProviderException("internal error", e); - } finally { - // `key` has been cloned into the `SecretKeySpec` within the - // returned `KEM.Encapsulated`, so it can now be cleared. - if (key != null) { - Arrays.fill(key, (byte)0); - } } } @Override public SecretKey engineDecapsulate(byte[] encapsulation, int from, int to, String algorithm) throws DecapsulateException { - Objects.checkFromToIndex(from, to, params.Nsecret); + Objects.checkFromToIndex(from, to, params.nsecret); Objects.requireNonNull(algorithm, "null algorithm"); Objects.requireNonNull(encapsulation, "null encapsulation"); - if (encapsulation.length != params.Npk) { + if (encapsulation.length != params.npk) { throw new DecapsulateException("incorrect encapsulation size"); } - byte[] key = null; try { - PublicKey pkE = params.DeserializePublicKey(encapsulation); - byte[] dh = params.DH(skR, pkE); - byte[] pkRm = params.SerializePublicKey(pkR); - byte[] kem_context = concat(encapsulation, pkRm); - key = params.ExtractAndExpand(dh, kem_context); - return new SecretKeySpec(key, from, to - from, algorithm); + PublicKey pkE = params.deserializePublicKey(encapsulation); + byte[] pkRm = params.serializePublicKey(pkR); + if (pkS == null) { + byte[] kem_context = concat(encapsulation, pkRm); + return params.deriveKey(algorithm, from, to, kem_context, + params.dh(skR, pkE)); + } else { + byte[] pkSm = params.serializePublicKey(pkS); + byte[] kem_context = concat(encapsulation, pkRm, pkSm); + return params.deriveKey(algorithm, from, to, kem_context, + params.dh(skR, pkE), params.dh(skR, pkS)); + } + } catch (UnsupportedOperationException e) { + throw e; } catch (IOException | InvalidKeyException e) { throw new DecapsulateException("Cannot decapsulate", e); } catch (Exception e) { throw new ProviderException("internal error", e); - } finally { - if (key != null) { - Arrays.fill(key, (byte)0); - } } } @Override public int engineSecretSize() { - return params.Nsecret; + return params.nsecret; } @Override public int engineEncapsulationSize() { - return params.Npk; + return params.npk; } } // Not really a random. For KAT test only. It generates key pair from ikm. public static class RFC9180DeriveKeyPairSR extends SecureRandom { - static final long serialVersionUID = 0L; + @Serial + private static final long serialVersionUID = 0L; private final byte[] ikm; @@ -147,7 +178,7 @@ public class DHKEM implements KEMSpi { this.ikm = ikm; } - public KeyPair derive(Params params) { + private KeyPair derive(Params params) { try { return params.deriveKeyPair(ikm); } catch (Exception e) { @@ -183,9 +214,9 @@ public class DHKEM implements KEMSpi { ; private final int kem_id; - private final int Nsecret; - private final int Nsk; - private final int Npk; + private final int nsecret; + private final int nsk; + private final int npk; private final String kaAlgorithm; private final String keyAlgorithm; private final AlgorithmParameterSpec spec; @@ -193,18 +224,18 @@ public class DHKEM implements KEMSpi { private final byte[] suiteId; - Params(int kem_id, int Nsecret, int Nsk, int Npk, + Params(int kem_id, int nsecret, int nsk, int npk, String kaAlgorithm, String keyAlgorithm, AlgorithmParameterSpec spec, String hkdfAlgorithm) { this.kem_id = kem_id; this.spec = spec; - this.Nsecret = Nsecret; - this.Nsk = Nsk; - this.Npk = Npk; + this.nsecret = nsecret; + this.nsk = nsk; + this.npk = npk; this.kaAlgorithm = kaAlgorithm; this.keyAlgorithm = keyAlgorithm; this.hkdfAlgorithm = hkdfAlgorithm; - suiteId = concat(KEM, I2OSP(kem_id, 2)); + suiteId = concat(KEM, i2OSP(kem_id, 2)); } private boolean isEC() { @@ -224,18 +255,18 @@ public class DHKEM implements KEMSpi { } } - private byte[] SerializePublicKey(PublicKey k) { + private byte[] serializePublicKey(PublicKey k) { if (isEC()) { ECPoint w = ((ECPublicKey) k).getW(); return ECUtil.encodePoint(w, ((NamedCurve) spec).getCurve()); } else { byte[] uArray = ((XECPublicKey) k).getU().toByteArray(); ArrayUtil.reverse(uArray); - return Arrays.copyOf(uArray, Npk); + return Arrays.copyOf(uArray, npk); } } - private PublicKey DeserializePublicKey(byte[] data) + private PublicKey deserializePublicKey(byte[] data) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { KeySpec keySpec; if (isEC()) { @@ -251,29 +282,59 @@ public class DHKEM implements KEMSpi { return KeyFactory.getInstance(keyAlgorithm).generatePublic(keySpec); } - private byte[] DH(PrivateKey skE, PublicKey pkR) + private SecretKey dh(PrivateKey skE, PublicKey pkR) throws NoSuchAlgorithmException, InvalidKeyException { KeyAgreement ka = KeyAgreement.getInstance(kaAlgorithm); ka.init(skE); ka.doPhase(pkR, true); - return ka.generateSecret(); + return ka.generateSecret("Generic"); } - private byte[] ExtractAndExpand(byte[] dh, byte[] kem_context) - throws NoSuchAlgorithmException, InvalidKeyException { - KDF hkdf = KDF.getInstance(hkdfAlgorithm); - SecretKey eae_prk = LabeledExtract(hkdf, suiteId, EAE_PRK, dh); - try { - return LabeledExpand(hkdf, suiteId, eae_prk, SHARED_SECRET, - kem_context, Nsecret); - } finally { - if (eae_prk instanceof SecretKeySpec s) { - SharedSecrets.getJavaxCryptoSpecAccess() - .clearSecretKeySpec(s); + // The final shared secret derivation of either the encapsulator + // or the decapsulator. The key slicing is implemented inside. + // Throws UOE if a slice of the key cannot be found. + private SecretKey deriveKey(String alg, int from, int to, + byte[] kem_context, SecretKey... dhs) + throws NoSuchAlgorithmException { + if (from == 0 && to == nsecret) { + return extractAndExpand(kem_context, alg, dhs); + } else { + // First get shared secrets in "Generic" and then get a slice + // of it in the requested algorithm. + var fullKey = extractAndExpand(kem_context, "Generic", dhs); + if ("RAW".equalsIgnoreCase(fullKey.getFormat())) { + byte[] km = fullKey.getEncoded(); + if (km == null) { + // Should not happen if format is "RAW" + throw new UnsupportedOperationException("Key extract failed"); + } else { + try { + return new SecretKeySpec(km, from, to - from, alg); + } finally { + Arrays.fill(km, (byte)0); + } + } + } else if (fullKey instanceof SliceableSecretKey ssk) { + return ssk.slice(alg, from, to); + } else { + throw new UnsupportedOperationException("Cannot extract key"); } } } + private SecretKey extractAndExpand(byte[] kem_context, String alg, SecretKey... dhs) + throws NoSuchAlgorithmException { + var kdf = KDF.getInstance(hkdfAlgorithm); + var builder = labeledExtract(suiteId, EAE_PRK); + for (var dh : dhs) builder.addIKM(dh); + try { + return kdf.deriveKey(alg, + labeledExpand(builder, suiteId, SHARED_SECRET, kem_context, nsecret)); + } catch (InvalidAlgorithmParameterException e) { + throw new ProviderException(e); + } + } + private PublicKey getPublicKey(PrivateKey sk) throws InvalidKeyException { if (!(sk instanceof InternalPrivateKey)) { @@ -298,45 +359,37 @@ public class DHKEM implements KEMSpi { // For KAT tests only. See RFC9180DeriveKeyPairSR. public KeyPair deriveKeyPair(byte[] ikm) throws Exception { - KDF hkdf = KDF.getInstance(hkdfAlgorithm); - SecretKey dkp_prk = LabeledExtract(hkdf, suiteId, DKP_PRK, ikm); - try { - if (isEC()) { - NamedCurve curve = (NamedCurve) spec; - BigInteger sk = BigInteger.ZERO; - int counter = 0; - while (sk.signum() == 0 || - sk.compareTo(curve.getOrder()) >= 0) { - if (counter > 255) { - throw new RuntimeException(); - } - byte[] bytes = LabeledExpand(hkdf, suiteId, dkp_prk, - CANDIDATE, I2OSP(counter, 1), Nsk); - // bitmask is defined to be 0xFF for P-256 and P-384, - // and 0x01 for P-521 - if (this == Params.P521) { - bytes[0] = (byte) (bytes[0] & 0x01); - } - sk = new BigInteger(1, (bytes)); - counter = counter + 1; + var kdf = KDF.getInstance(hkdfAlgorithm); + var builder = labeledExtract(suiteId, DKP_PRK).addIKM(ikm); + if (isEC()) { + NamedCurve curve = (NamedCurve) spec; + BigInteger sk = BigInteger.ZERO; + int counter = 0; + while (sk.signum() == 0 || sk.compareTo(curve.getOrder()) >= 0) { + if (counter > 255) { + // So unlucky and should not happen + throw new ProviderException("DeriveKeyPairError"); } - PrivateKey k = DeserializePrivateKey(sk.toByteArray()); - return new KeyPair(getPublicKey(k), k); - } else { - byte[] sk = LabeledExpand(hkdf, suiteId, dkp_prk, SK, EMPTY, - Nsk); - PrivateKey k = DeserializePrivateKey(sk); - return new KeyPair(getPublicKey(k), k); - } - } finally { - if (dkp_prk instanceof SecretKeySpec s) { - SharedSecrets.getJavaxCryptoSpecAccess() - .clearSecretKeySpec(s); + byte[] bytes = kdf.deriveData(labeledExpand(builder, + suiteId, CANDIDATE, i2OSP(counter, 1), nsk)); + // bitmask is defined to be 0xFF for P-256 and P-384, and 0x01 for P-521 + if (this == Params.P521) { + bytes[0] = (byte) (bytes[0] & 0x01); + } + sk = new BigInteger(1, (bytes)); + counter = counter + 1; } + PrivateKey k = deserializePrivateKey(sk.toByteArray()); + return new KeyPair(getPublicKey(k), k); + } else { + byte[] sk = kdf.deriveData(labeledExpand(builder, + suiteId, SK, EMPTY, nsk)); + PrivateKey k = deserializePrivateKey(sk); + return new KeyPair(getPublicKey(k), k); } } - private PrivateKey DeserializePrivateKey(byte[] data) throws Exception { + private PrivateKey deserializePrivateKey(byte[] data) throws Exception { KeySpec keySpec = isEC() ? new ECPrivateKeySpec(new BigInteger(1, (data)), (NamedCurve) spec) : new XECPrivateKeySpec(spec, data); @@ -359,7 +412,22 @@ public class DHKEM implements KEMSpi { throw new InvalidAlgorithmParameterException("no spec needed"); } Params params = paramsFromKey(pk); - return new Handler(params, getSecureRandom(secureRandom), null, pk); + return new Handler(params, getSecureRandom(secureRandom), null, null, null, pk); + } + + // AuthEncap is not public KEM API + public EncapsulatorSpi engineNewAuthEncapsulator(PublicKey pkR, PrivateKey skS, + AlgorithmParameterSpec spec, SecureRandom secureRandom) + throws InvalidAlgorithmParameterException, InvalidKeyException { + if (pkR == null || skS == null) { + throw new InvalidKeyException("input key is null"); + } + if (spec != null) { + throw new InvalidAlgorithmParameterException("no spec needed"); + } + Params params = paramsFromKey(pkR); + return new Handler(params, getSecureRandom(secureRandom), + skS, params.getPublicKey(skS), null, pkR); } @Override @@ -372,20 +440,34 @@ public class DHKEM implements KEMSpi { throw new InvalidAlgorithmParameterException("no spec needed"); } Params params = paramsFromKey(sk); - return new Handler(params, null, sk, params.getPublicKey(sk)); + return new Handler(params, null, null, null, sk, params.getPublicKey(sk)); } - private Params paramsFromKey(Key k) throws InvalidKeyException { - if (k instanceof ECKey eckey) { - if (ECUtil.equals(eckey.getParams(), CurveDB.P_256)) { + // AuthDecap is not public KEM API + public DecapsulatorSpi engineNewAuthDecapsulator( + PrivateKey skR, PublicKey pkS, AlgorithmParameterSpec spec) + throws InvalidAlgorithmParameterException, InvalidKeyException { + if (skR == null || pkS == null) { + throw new InvalidKeyException("input key is null"); + } + if (spec != null) { + throw new InvalidAlgorithmParameterException("no spec needed"); + } + Params params = paramsFromKey(skR); + return new Handler(params, null, null, pkS, skR, params.getPublicKey(skR)); + } + + private Params paramsFromKey(AsymmetricKey k) throws InvalidKeyException { + var p = k.getParams(); + if (p instanceof ECParameterSpec ecp) { + if (ECUtil.equals(ecp, CurveDB.P_256)) { return Params.P256; - } else if (ECUtil.equals(eckey.getParams(), CurveDB.P_384)) { + } else if (ECUtil.equals(ecp, CurveDB.P_384)) { return Params.P384; - } else if (ECUtil.equals(eckey.getParams(), CurveDB.P_521)) { + } else if (ECUtil.equals(ecp, CurveDB.P_521)) { return Params.P521; } - } else if (k instanceof XECKey xkey - && xkey.getParams() instanceof NamedParameterSpec ns) { + } else if (p instanceof NamedParameterSpec ns) { if (ns.getName().equalsIgnoreCase("X25519")) { return Params.X25519; } else if (ns.getName().equalsIgnoreCase("X448")) { @@ -401,8 +483,11 @@ public class DHKEM implements KEMSpi { return o.toByteArray(); } - private static byte[] I2OSP(int n, int w) { - assert n < 256; + // I2OSP(n, w) as defined in RFC 9180 Section 3. + // In DHKEM and HPKE, number is always <65536 + // and converted to at most 2 bytes. + public static byte[] i2OSP(int n, int w) { + assert n < 65536; assert w == 1 || w == 2; if (w == 1) { return new byte[] { (byte) n }; @@ -411,32 +496,32 @@ public class DHKEM implements KEMSpi { } } - private static SecretKey LabeledExtract(KDF hkdf, byte[] suite_id, - byte[] label, byte[] ikm) throws InvalidKeyException { - SecretKeySpec s = new SecretKeySpec(concat(HPKE_V1, suite_id, label, - ikm), "IKM"); - try { - HKDFParameterSpec spec = - HKDFParameterSpec.ofExtract().addIKM(s).extractOnly(); - return hkdf.deriveKey("Generic", spec); - } catch (InvalidAlgorithmParameterException | - NoSuchAlgorithmException e) { - throw new InvalidKeyException(e.getMessage(), e); - } finally { - SharedSecrets.getJavaxCryptoSpecAccess().clearSecretKeySpec(s); - } + // Create a LabeledExtract builder with labels. + // You can add more IKM and salt into the result. + public static HKDFParameterSpec.Builder labeledExtract( + byte[] suiteId, byte[] label) { + return HKDFParameterSpec.ofExtract() + .addIKM(HPKE_V1).addIKM(suiteId).addIKM(label); } - private static byte[] LabeledExpand(KDF hkdf, byte[] suite_id, - SecretKey prk, byte[] label, byte[] info, int L) - throws InvalidKeyException { - byte[] labeled_info = concat(I2OSP(L, 2), HPKE_V1, suite_id, label, - info); - try { - return hkdf.deriveData(HKDFParameterSpec.expandOnly( - prk, labeled_info, L)); - } catch (InvalidAlgorithmParameterException iape) { - throw new InvalidKeyException(iape.getMessage(), iape); - } + // Create a labeled info from info and labels + private static byte[] labeledInfo( + byte[] suiteId, byte[] label, byte[] info, int length) { + return concat(i2OSP(length, 2), HPKE_V1, suiteId, label, info); + } + + // LabeledExpand from a builder + public static HKDFParameterSpec labeledExpand( + HKDFParameterSpec.Builder builder, + byte[] suiteId, byte[] label, byte[] info, int length) { + return builder.thenExpand( + labeledInfo(suiteId, label, info, length), length); + } + + // LabeledExpand from a prk + public static HKDFParameterSpec labeledExpand( + SecretKey prk, byte[] suiteId, byte[] label, byte[] info, int length) { + return HKDFParameterSpec.expandOnly( + prk, labeledInfo(suiteId, label, info, length), length); } } diff --git a/src/java.base/share/classes/com/sun/crypto/provider/HPKE.java b/src/java.base/share/classes/com/sun/crypto/provider/HPKE.java new file mode 100644 index 00000000000..eee5f59cc75 --- /dev/null +++ b/src/java.base/share/classes/com/sun/crypto/provider/HPKE.java @@ -0,0 +1,588 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.sun.crypto.provider; + +import sun.security.util.CurveDB; +import sun.security.util.ECUtil; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.CipherSpi; +import javax.crypto.DecapsulateException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.KDF; +import javax.crypto.KEM; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.ShortBufferException; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.HPKEParameterSpec; +import javax.crypto.spec.IvParameterSpec; +import java.io.ByteArrayOutputStream; +import java.nio.ByteBuffer; +import java.security.AlgorithmParameters; +import java.security.AsymmetricKey; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.ProviderException; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.NamedParameterSpec; +import java.util.Arrays; + +public class HPKE extends CipherSpi { + + private static final byte[] HPKE = new byte[] + {'H', 'P', 'K', 'E'}; + private static final byte[] SEC = new byte[] + {'s', 'e', 'c'}; + private static final byte[] PSK_ID_HASH = new byte[] + {'p', 's', 'k', '_', 'i', 'd', '_', 'h', 'a', 's', 'h'}; + private static final byte[] INFO_HASH = new byte[] + {'i', 'n', 'f', 'o', '_', 'h', 'a', 's', 'h'}; + private static final byte[] SECRET = new byte[] + {'s', 'e', 'c', 'r', 'e', 't'}; + private static final byte[] EXP = new byte[] + {'e', 'x', 'p'}; + private static final byte[] KEY = new byte[] + {'k', 'e', 'y'}; + private static final byte[] BASE_NONCE = new byte[] + {'b', 'a', 's', 'e', '_', 'n', 'o', 'n', 'c', 'e'}; + + private static final int BEGIN = 1; + private static final int EXPORT_ONLY = 2; // init done with aead_id == 65535 + private static final int ENCRYPT_AND_EXPORT = 3; // int done with AEAD + private static final int AFTER_FINAL = 4; // after doFinal, need reinit internal cipher + + private int state = BEGIN; + private Impl impl; + + @Override + protected void engineSetMode(String mode) throws NoSuchAlgorithmException { + throw new NoSuchAlgorithmException(mode); + } + + @Override + protected void engineSetPadding(String padding) throws NoSuchPaddingException { + throw new NoSuchPaddingException(padding); + } + + @Override + protected int engineGetBlockSize() { + if (state == ENCRYPT_AND_EXPORT || state == AFTER_FINAL) { + return impl.aead.cipher.getBlockSize(); + } else { + return 0; + } + } + + @Override + protected int engineGetOutputSize(int inputLen) { + if (state == ENCRYPT_AND_EXPORT || state == AFTER_FINAL) { + return impl.aead.cipher.getOutputSize(inputLen); + } else { + return 0; + } + } + + @Override + protected byte[] engineGetIV() { + return (state == BEGIN || impl.kemEncaps == null) + ? null : impl.kemEncaps.clone(); + } + + @Override + protected AlgorithmParameters engineGetParameters() { + return null; + } + + @Override + protected void engineInit(int opmode, Key key, SecureRandom random) + throws InvalidKeyException { + throw new InvalidKeyException("HPKEParameterSpec must be provided"); + } + + @Override + protected void engineInit(int opmode, Key key, + AlgorithmParameterSpec params, SecureRandom random) + throws InvalidKeyException, InvalidAlgorithmParameterException { + impl = new Impl(opmode); + if (!(key instanceof AsymmetricKey ak)) { + throw new InvalidKeyException("Not an asymmetric key"); + } + if (params == null) { + throw new InvalidAlgorithmParameterException( + "HPKEParameterSpec must be provided"); + } else if (params instanceof HPKEParameterSpec hps) { + impl.init(ak, hps, random); + } else { + throw new InvalidAlgorithmParameterException( + "Unsupported params type: " + params.getClass()); + } + if (impl.hasEncrypt()) { + impl.aead.start(impl.opmode, impl.context.k, impl.context.computeNonce()); + state = ENCRYPT_AND_EXPORT; + } else { + state = EXPORT_ONLY; + } + } + + @Override + protected void engineInit(int opmode, Key key, + AlgorithmParameters params, SecureRandom random) + throws InvalidKeyException, InvalidAlgorithmParameterException { + throw new InvalidKeyException("HPKEParameterSpec must be provided"); + } + + // state is ENCRYPT_AND_EXPORT after this call succeeds + private void maybeReinitInternalCipher() { + if (state == BEGIN) { + throw new IllegalStateException("Illegal state: " + state); + } + if (state == EXPORT_ONLY) { + throw new UnsupportedOperationException(); + } + if (state == AFTER_FINAL) { + impl.aead.start(impl.opmode, impl.context.k, impl.context.computeNonce()); + state = ENCRYPT_AND_EXPORT; + } + } + + @Override + protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) { + maybeReinitInternalCipher(); + return impl.aead.cipher.update(input, inputOffset, inputLen); + } + + @Override + protected int engineUpdate(byte[] input, int inputOffset, int inputLen, + byte[] output, int outputOffset) throws ShortBufferException { + maybeReinitInternalCipher(); + return impl.aead.cipher.update( + input, inputOffset, inputLen, output, outputOffset); + } + + @Override + protected void engineUpdateAAD(byte[] src, int offset, int len) { + maybeReinitInternalCipher(); + impl.aead.cipher.updateAAD(src, offset, len); + } + + @Override + protected void engineUpdateAAD(ByteBuffer src) { + maybeReinitInternalCipher(); + impl.aead.cipher.updateAAD(src); + } + + @Override + protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen) + throws IllegalBlockSizeException, BadPaddingException { + maybeReinitInternalCipher(); + impl.context.IncrementSeq(); + state = AFTER_FINAL; + if (input == null) { // a bug in doFinal(null, ?, ?) + return impl.aead.cipher.doFinal(); + } else { + return impl.aead.cipher.doFinal(input, inputOffset, inputLen); + } + } + + @Override + protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, + byte[] output, int outputOffset) throws ShortBufferException, + IllegalBlockSizeException, BadPaddingException { + maybeReinitInternalCipher(); + impl.context.IncrementSeq(); + state = AFTER_FINAL; + return impl.aead.cipher.doFinal( + input, inputOffset, inputLen, output, outputOffset); + } + + //@Override + protected SecretKey engineExportKey(String algorithm, byte[] context, int length) { + if (state == BEGIN) { + throw new IllegalStateException("State: " + state); + } else { + return impl.context.exportKey(algorithm, context, length); + } + } + + //@Override + protected byte[] engineExportData(byte[] context, int length) { + if (state == BEGIN) { + throw new IllegalStateException("State: " + state); + } else { + return impl.context.exportData(context, length); + } + } + + private static class AEAD { + final Cipher cipher; + final int nk, nn, nt; + final int id; + public AEAD(int id) throws InvalidAlgorithmParameterException { + this.id = id; + try { + switch (id) { + case HPKEParameterSpec.AEAD_AES_128_GCM -> { + cipher = Cipher.getInstance("AES/GCM/NoPadding"); + nk = 16; + } + case HPKEParameterSpec.AEAD_AES_256_GCM -> { + cipher = Cipher.getInstance("AES/GCM/NoPadding"); + nk = 32; + } + case HPKEParameterSpec.AEAD_CHACHA20_POLY1305 -> { + cipher = Cipher.getInstance("ChaCha20-Poly1305"); + nk = 32; + } + case HPKEParameterSpec.EXPORT_ONLY -> { + cipher = null; + nk = -1; + } + default -> throw new InvalidAlgorithmParameterException( + "Unknown aead_id: " + id); + } + } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { + throw new ProviderException("Internal error", e); + } + nn = 12; nt = 16; + } + + void start(int opmode, SecretKey key, byte[] nonce) { + try { + if (id == HPKEParameterSpec.AEAD_CHACHA20_POLY1305) { + cipher.init(opmode, key, new IvParameterSpec(nonce)); + } else { + cipher.init(opmode, key, new GCMParameterSpec(nt * 8, nonce)); + } + } catch (InvalidAlgorithmParameterException | InvalidKeyException e) { + throw new ProviderException("Internal error", e); + } + } + } + + private static class Impl { + + final int opmode; + + HPKEParameterSpec params; + Context context; + AEAD aead; + + byte[] suite_id; + String kdfAlg; + int kdfNh; + + // only used on sender side + byte[] kemEncaps; + + class Context { + final SecretKey k; // null if only export + final byte[] base_nonce; + final SecretKey exporter_secret; + + byte[] seq = new byte[aead.nn]; + + public Context(SecretKey sk, byte[] base_nonce, + SecretKey exporter_secret) { + this.k = sk; + this.base_nonce = base_nonce; + this.exporter_secret = exporter_secret; + } + + SecretKey exportKey(String algorithm, byte[] exporter_context, int length) { + if (exporter_context == null) { + throw new IllegalArgumentException("Null exporter_context"); + } + try { + var kdf = KDF.getInstance(kdfAlg); + return kdf.deriveKey(algorithm, DHKEM.labeledExpand( + exporter_secret, suite_id, SEC, exporter_context, length)); + } catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException e) { + // algorithm not accepted by HKDF, length too big or too small + throw new IllegalArgumentException("Invalid input", e); + } + } + + byte[] exportData(byte[] exporter_context, int length) { + if (exporter_context == null) { + throw new IllegalArgumentException("Null exporter_context"); + } + try { + var kdf = KDF.getInstance(kdfAlg); + return kdf.deriveData(DHKEM.labeledExpand( + exporter_secret, suite_id, SEC, exporter_context, length)); + } catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException e) { + // algorithm not accepted by HKDF, length too big or too small + throw new IllegalArgumentException("Invalid input", e); + } + } + + private byte[] computeNonce() { + var result = new byte[aead.nn]; + for (var i = 0; i < result.length; i++) { + result[i] = (byte)(seq[i] ^ base_nonce[i]); + } + return result; + } + + private void IncrementSeq() { + for (var i = seq.length - 1; i >= 0; i--) { + if ((seq[i] & 0xff) == 0xff) { + seq[i] = 0; + } else { + seq[i]++; + return; + } + } + // seq >= (1 << (8*aead.Nn)) - 1 when this method is called + throw new ProviderException("MessageLimitReachedError"); + } + } + + public Impl(int opmode) { + this.opmode = opmode; + } + + public boolean hasEncrypt() { + return params.aead_id() != 65535; + } + + // Section 7.2.1 of RFC 9180 has restrictions on size of psk, psk_id, + // info, and exporter_context (~2^61 for HMAC-SHA256 and ~2^125 for + // HMAC-SHA384 and HMAC-SHA512). This method does not pose any + // restrictions. + public void init(AsymmetricKey key, HPKEParameterSpec p, SecureRandom rand) + throws InvalidKeyException, InvalidAlgorithmParameterException { + if (opmode != Cipher.ENCRYPT_MODE && opmode != Cipher.DECRYPT_MODE) { + throw new UnsupportedOperationException( + "Can only be used for encryption and decryption"); + } + setParams(p); + SecretKey shared_secret; + if (opmode == Cipher.ENCRYPT_MODE) { + if (!(key instanceof PublicKey pk)) { + throw new InvalidKeyException( + "Cannot encrypt with private key"); + } + if (p.encapsulation() != null) { + throw new InvalidAlgorithmParameterException( + "Must not provide key encapsulation message on sender side"); + } + checkMatch(false, pk, params.kem_id()); + KEM.Encapsulated enc; + switch (p.authKey()) { + case null -> { + var e = kem().newEncapsulator(pk, rand); + enc = e.encapsulate(); + } + case PrivateKey skS -> { + checkMatch(true, skS, params.kem_id()); + // AuthEncap not public KEM API but it's internally supported + var e = new DHKEM().engineNewAuthEncapsulator(pk, skS, null, rand); + enc = e.engineEncapsulate(0, e.engineSecretSize(), "Generic"); + } + default -> throw new InvalidAlgorithmParameterException( + "Cannot auth with public key"); + } + kemEncaps = enc.encapsulation(); + shared_secret = enc.key(); + } else { + if (!(key instanceof PrivateKey sk)) { + throw new InvalidKeyException("Cannot decrypt with public key"); + } + checkMatch(false, sk, params.kem_id()); + try { + var encap = p.encapsulation(); + if (encap == null) { + throw new InvalidAlgorithmParameterException( + "Must provide key encapsulation message on recipient side"); + } + switch (p.authKey()) { + case null -> { + var d = kem().newDecapsulator(sk); + shared_secret = d.decapsulate(encap); + } + case PublicKey pkS -> { + checkMatch(true, pkS, params.kem_id()); + // AuthDecap not public KEM API but it's internally supported + var d = new DHKEM().engineNewAuthDecapsulator(sk, pkS, null); + shared_secret = d.engineDecapsulate( + encap, 0, d.engineSecretSize(), "Generic"); + } + default -> throw new InvalidAlgorithmParameterException( + "Cannot auth with private key"); + } + } catch (DecapsulateException e) { + throw new InvalidAlgorithmParameterException(e); + } + } + + var usePSK = usePSK(params.psk()); + int mode = params.authKey() == null ? (usePSK ? 1 : 0) : (usePSK ? 3 : 2); + context = keySchedule(mode, shared_secret, + params.info(), + params.psk(), + params.psk_id()); + } + + private static void checkMatch(boolean inSpec, AsymmetricKey k, int kem_id) + throws InvalidKeyException, InvalidAlgorithmParameterException { + var p = k.getParams(); + switch (p) { + case ECParameterSpec ecp -> { + if ((!ECUtil.equals(ecp, CurveDB.P_256) + || kem_id != HPKEParameterSpec.KEM_DHKEM_P_256_HKDF_SHA256) + && (!ECUtil.equals(ecp, CurveDB.P_384) + || kem_id != HPKEParameterSpec.KEM_DHKEM_P_384_HKDF_SHA384) + && (!ECUtil.equals(ecp, CurveDB.P_521) + || kem_id != HPKEParameterSpec.KEM_DHKEM_P_521_HKDF_SHA512)) { + var name = ECUtil.getCurveName(ecp); + throw new InvalidAlgorithmParameterException( + name + " does not match " + kem_id); + } + } + case NamedParameterSpec ns -> { + var name = ns.getName(); + if ((!name.equalsIgnoreCase("x25519") + || kem_id != HPKEParameterSpec.KEM_DHKEM_X25519_HKDF_SHA256) + && (!name.equalsIgnoreCase("x448") + || kem_id != HPKEParameterSpec.KEM_DHKEM_X448_HKDF_SHA512)) { + throw new InvalidAlgorithmParameterException( + name + " does not match " + kem_id); + } + } + case null, default -> { + var msg = k.getClass() + " does not match " + kem_id; + if (inSpec) { + throw new InvalidAlgorithmParameterException(msg); + } else { + throw new InvalidKeyException(msg); + } + } + } + } + + private KEM kem() { + try { + return KEM.getInstance("DHKEM"); + } catch (NoSuchAlgorithmException e) { + throw new ProviderException("Internal error", e); + } + } + + private void setParams(HPKEParameterSpec p) + throws InvalidAlgorithmParameterException { + params = p; + suite_id = concat( + HPKE, + DHKEM.i2OSP(params.kem_id(), 2), + DHKEM.i2OSP(params.kdf_id(), 2), + DHKEM.i2OSP(params.aead_id(), 2)); + switch (params.kdf_id()) { + case HPKEParameterSpec.KDF_HKDF_SHA256 -> { + kdfAlg = "HKDF-SHA256"; + kdfNh = 32; + } + case HPKEParameterSpec.KDF_HKDF_SHA384 -> { + kdfAlg = "HKDF-SHA384"; + kdfNh = 48; + } + case HPKEParameterSpec.KDF_HKDF_SHA512 -> { + kdfAlg = "HKDF-SHA512"; + kdfNh = 64; + } + default -> throw new InvalidAlgorithmParameterException( + "Unsupported kdf_id: " + params.kdf_id()); + } + aead = new AEAD(params.aead_id()); + } + + private Context keySchedule(int mode, + SecretKey shared_secret, + byte[] info, + SecretKey psk, + byte[] psk_id) { + try { + var psk_id_hash_x = DHKEM.labeledExtract(suite_id, PSK_ID_HASH) + .addIKM(psk_id).extractOnly(); + var info_hash_x = DHKEM.labeledExtract(suite_id, INFO_HASH) + .addIKM(info).extractOnly(); + + // deriveData must and can be called because all info to + // thw builder are just byte arrays. Any KDF impl can handle this. + var kdf = KDF.getInstance(kdfAlg); + var key_schedule_context = concat(new byte[]{(byte) mode}, + kdf.deriveData(psk_id_hash_x), + kdf.deriveData(info_hash_x)); + + var secret_x_builder = DHKEM.labeledExtract(suite_id, SECRET); + if (psk != null) { + secret_x_builder.addIKM(psk); + } + secret_x_builder.addSalt(shared_secret); + var secret_x = kdf.deriveKey("Generic", secret_x_builder.extractOnly()); + + // A new KDF object must be created because secret_x_builder + // might contain provider-specific keys which the previous + // KDF (provider already chosen) cannot handle. + kdf = KDF.getInstance(kdfAlg); + var exporter_secret = kdf.deriveKey("Generic", DHKEM.labeledExpand( + secret_x, suite_id, EXP, key_schedule_context, kdfNh)); + + if (hasEncrypt()) { + // ChaCha20-Poly1305 does not care about algorithm name + var key = kdf.deriveKey("AES", DHKEM.labeledExpand(secret_x, + suite_id, KEY, key_schedule_context, aead.nk)); + // deriveData must be called because we need to increment nonce + var base_nonce = kdf.deriveData(DHKEM.labeledExpand(secret_x, + suite_id, BASE_NONCE, key_schedule_context, aead.nn)); + return new Context(key, base_nonce, exporter_secret); + } else { + return new Context(null, null, exporter_secret); + } + } catch (InvalidAlgorithmParameterException + | NoSuchAlgorithmException | UnsupportedOperationException e) { + throw new ProviderException("Internal error", e); + } + } + } + + private static boolean usePSK(SecretKey psk) { + return psk != null; + } + + private static byte[] concat(byte[]... inputs) { + var o = new ByteArrayOutputStream(); + Arrays.stream(inputs).forEach(o::writeBytes); + return o.toByteArray(); + } +} diff --git a/src/java.base/share/classes/com/sun/crypto/provider/JceKeyStore.java b/src/java.base/share/classes/com/sun/crypto/provider/JceKeyStore.java index ec8e0f3757d..ad98653b9c2 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/JceKeyStore.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/JceKeyStore.java @@ -661,6 +661,10 @@ public final class JceKeyStore extends KeyStoreSpi { dos.close(); } } + + if (debug != null) { + emitWeakKeyStoreWarning(); + } } } @@ -862,6 +866,10 @@ public final class JceKeyStore extends KeyStoreSpi { secretKeyCount); } + if (debug != null) { + emitWeakKeyStoreWarning(); + } + /* * If a password has been provided, we check the keyed digest * at the end. If this check fails, the store has been tampered @@ -978,4 +986,12 @@ public final class JceKeyStore extends KeyStoreSpi { return Status.UNDECIDED; } } + + private void emitWeakKeyStoreWarning() { + debug.println("WARNING: JCEKS uses outdated cryptographic " + + "algorithms and will be removed in a future " + + "release. Migrate to PKCS12 using:"); + debug.println("keytool -importkeystore -srckeystore " + + "-destkeystore -deststoretype pkcs12"); + } } diff --git a/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java b/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java index 22d5f17c6e0..4b38bd55809 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java @@ -371,6 +371,8 @@ public final class SunJCE extends Provider { ps("Cipher", "PBEWithHmacSHA512/256AndAES_256", "com.sun.crypto.provider.PBES2Core$HmacSHA512_256AndAES_256"); + ps("Cipher", "HPKE", "com.sun.crypto.provider.HPKE"); + /* * Key(pair) Generator engines */ diff --git a/src/java.base/share/classes/java/lang/Character.java b/src/java.base/share/classes/java/lang/Character.java index d866202909c..b71849eaee7 100644 --- a/src/java.base/share/classes/java/lang/Character.java +++ b/src/java.base/share/classes/java/lang/Character.java @@ -743,7 +743,8 @@ class Character implements java.io.Serializable, Comparable, Constabl */ public static final class UnicodeBlock extends Subset { /** - * NUM_ENTITIES should match the total number of UnicodeBlocks. + * NUM_ENTITIES should match the total number of UnicodeBlock identifier + * names plus their aliases. * It should be adjusted whenever the Unicode Character Database * is upgraded. */ diff --git a/src/java.base/share/classes/java/lang/Class.java b/src/java.base/share/classes/java/lang/Class.java index cfd2fc82235..eab1993a2b4 100644 --- a/src/java.base/share/classes/java/lang/Class.java +++ b/src/java.base/share/classes/java/lang/Class.java @@ -43,6 +43,7 @@ import java.lang.reflect.Executable; import java.lang.reflect.Field; import java.lang.reflect.GenericArrayType; import java.lang.reflect.GenericDeclaration; +import java.lang.reflect.GenericSignatureFormatError; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Member; import java.lang.reflect.Method; @@ -159,6 +160,10 @@ import sun.reflect.annotation.*; * other members are the classes and interfaces whose declarations are * enclosed within the top-level class declaration. * + *

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

Hidden Classes

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

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

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

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

Note that any annotations returned by this method are * declaration annotations. * - * @throws NullPointerException {@inheritDoc} * @since 1.8 */ @Override @@ -3831,6 +3831,7 @@ public final class Class implements java.io.Serializable, * @since 11 */ public boolean isNestmateOf(Class c) { + Objects.requireNonNull(c); if (this == c) { return true; } diff --git a/src/java.base/share/classes/java/lang/LazyConstant.java b/src/java.base/share/classes/java/lang/LazyConstant.java index 34f3d754a10..703d67b8abf 100644 --- a/src/java.base/share/classes/java/lang/LazyConstant.java +++ b/src/java.base/share/classes/java/lang/LazyConstant.java @@ -39,12 +39,16 @@ import java.util.function.Supplier; *

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

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

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

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

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

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

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

* This method never triggers initialization of this lazy constant. diff --git a/src/java.base/share/classes/java/lang/ThreadBuilders.java b/src/java.base/share/classes/java/lang/ThreadBuilders.java index 62c29651d11..033c237877c 100644 --- a/src/java.base/share/classes/java/lang/ThreadBuilders.java +++ b/src/java.base/share/classes/java/lang/ThreadBuilders.java @@ -309,7 +309,7 @@ class ThreadBuilders { String nextThreadName() { if (hasCounter) { - return name + (long) COUNT.getAndAdd(this, 1); + return name + (long) COUNT.getAndAdd(this, 1L); } else { return name; } diff --git a/src/java.base/share/classes/java/lang/classfile/attribute/package-info.java b/src/java.base/share/classes/java/lang/classfile/attribute/package-info.java index 1ddd0511d85..43bf1984a00 100644 --- a/src/java.base/share/classes/java/lang/classfile/attribute/package-info.java +++ b/src/java.base/share/classes/java/lang/classfile/attribute/package-info.java @@ -32,9 +32,8 @@ * including {@link Attribute}, {@link AttributedElement}, {@link AttributeMapper}, and {@link CustomAttribute}, which * do not reside in this package. *

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

Reading Attributes

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

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

Reading the constant pool entries

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

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

Reading of instructions

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

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

Symbolic information

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

Consistency checks, syntax checks and verification

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

Consistency checks, syntax checks and verification

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

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

* No consistency checks are performed while building or transforming classfiles * (except for null and representable arguments checks). All builders and diff --git a/src/java.base/share/classes/java/lang/invoke/MethodType.java b/src/java.base/share/classes/java/lang/invoke/MethodType.java index 3d15ce68710..afbe63709fd 100644 --- a/src/java.base/share/classes/java/lang/invoke/MethodType.java +++ b/src/java.base/share/classes/java/lang/invoke/MethodType.java @@ -1194,10 +1194,6 @@ class MethodType static MethodType fromDescriptor(String descriptor, ClassLoader loader) throws IllegalArgumentException, TypeNotPresentException { - if (!descriptor.startsWith("(") || // also generates NPE if needed - descriptor.indexOf(')') < 0 || - descriptor.indexOf('.') >= 0) - throw newIllegalArgumentException("not a method descriptor: "+descriptor); List> types = BytecodeDescriptor.parseMethod(descriptor, loader); Class rtype = types.remove(types.size() - 1); Class[] ptypes = listToArray(types); diff --git a/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java b/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java index 1637d26b571..54000b916e2 100644 --- a/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java +++ b/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java @@ -97,6 +97,8 @@ public class AccessibleObject implements AnnotatedElement { * objects in the array * @throws SecurityException if an element in the array is a constructor for {@code * java.lang.Class} + * @throws NullPointerException if {@code array} or any of its elements is + * {@code null} */ @CallerSensitive public static void setAccessible(AccessibleObject[] array, boolean flag) { diff --git a/src/java.base/share/classes/java/lang/reflect/Array.java b/src/java.base/share/classes/java/lang/reflect/Array.java index 2fbad52c374..4e676c050d7 100644 --- a/src/java.base/share/classes/java/lang/reflect/Array.java +++ b/src/java.base/share/classes/java/lang/reflect/Array.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -98,8 +98,8 @@ class Array { * @param dimensions an array of {@code int} representing the dimensions of * the new array * @return the new array - * @throws NullPointerException if the specified - * {@code componentType} argument is null + * @throws NullPointerException if any of the specified + * {@code componentType} or {@code dimensions} arguments is null * @throws IllegalArgumentException if the specified {@code dimensions} * argument is a zero-dimensional array, if componentType is {@link * Void#TYPE}, or if the number of dimensions of the requested array @@ -117,8 +117,8 @@ class Array { * * @param array the array * @return the length of the array - * @throws IllegalArgumentException if the object argument is not - * an array + * @throws NullPointerException if {@code array} is {@code null} + * @throws IllegalArgumentException if {@code array} is not an array */ @IntrinsicCandidate public static native int getLength(Object array) diff --git a/src/java.base/share/classes/java/lang/reflect/ClassFileFormatVersion.java b/src/java.base/share/classes/java/lang/reflect/ClassFileFormatVersion.java index 4a63dd157f8..16fe000091c 100644 --- a/src/java.base/share/classes/java/lang/reflect/ClassFileFormatVersion.java +++ b/src/java.base/share/classes/java/lang/reflect/ClassFileFormatVersion.java @@ -433,6 +433,7 @@ public enum ClassFileFormatVersion { * ClassFileFormatVersion.valueOf(Runtime.Version.parse("17"))} * * @param rv runtime version to map to a class file format version + * @throws NullPointerException if {@code rv} is {@code null} * @throws IllegalArgumentException if the feature of version * argument is greater than the feature of the platform version. */ diff --git a/src/java.base/share/classes/java/lang/reflect/InaccessibleObjectException.java b/src/java.base/share/classes/java/lang/reflect/InaccessibleObjectException.java index 7bc9ea33b2b..29d6eb13602 100644 --- a/src/java.base/share/classes/java/lang/reflect/InaccessibleObjectException.java +++ b/src/java.base/share/classes/java/lang/reflect/InaccessibleObjectException.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -47,7 +47,7 @@ public class InaccessibleObjectException extends RuntimeException { * message. * * @param msg - * The detail message + * The detail message, may be {@code null} */ public InaccessibleObjectException(String msg) { super(msg); diff --git a/src/java.base/share/classes/java/lang/reflect/InvocationTargetException.java b/src/java.base/share/classes/java/lang/reflect/InvocationTargetException.java index a6ef4fa289c..efdca9bcbd1 100644 --- a/src/java.base/share/classes/java/lang/reflect/InvocationTargetException.java +++ b/src/java.base/share/classes/java/lang/reflect/InvocationTargetException.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -62,7 +62,7 @@ public class InvocationTargetException extends ReflectiveOperationException { /** * Constructs a InvocationTargetException with a target exception. * - * @param target the target exception + * @param target the target exception, may be {@code null} */ public InvocationTargetException(Throwable target) { super((Throwable)null); // Disallow initCause @@ -73,8 +73,8 @@ public class InvocationTargetException extends ReflectiveOperationException { * Constructs a InvocationTargetException with a target exception * and a detail message. * - * @param target the target exception - * @param s the detail message + * @param target the target exception, may be {@code null} + * @param s the detail message, may be {@code null} */ public InvocationTargetException(Throwable target, String s) { super(s, null); // Disallow initCause diff --git a/src/java.base/share/classes/java/lang/reflect/MalformedParametersException.java b/src/java.base/share/classes/java/lang/reflect/MalformedParametersException.java index ae2da8c9279..fa754be5474 100644 --- a/src/java.base/share/classes/java/lang/reflect/MalformedParametersException.java +++ b/src/java.base/share/classes/java/lang/reflect/MalformedParametersException.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -65,7 +65,7 @@ public class MalformedParametersException extends RuntimeException { /** * Create a {@code MalformedParametersException}. * - * @param reason The reason for the exception. + * @param reason The reason for the exception, may be {@code null} */ public MalformedParametersException(String reason) { super(reason); diff --git a/src/java.base/share/classes/java/lang/reflect/Proxy.java b/src/java.base/share/classes/java/lang/reflect/Proxy.java index dc512e86590..b811deb863d 100644 --- a/src/java.base/share/classes/java/lang/reflect/Proxy.java +++ b/src/java.base/share/classes/java/lang/reflect/Proxy.java @@ -897,7 +897,8 @@ public class Proxy implements java.io.Serializable { * of interfaces but in a different order will result in two distinct * proxy classes. * - * @param loader the class loader to define the proxy class + * @param loader the class loader to define the proxy class, may be + * {@code null} to represent the bootstrap class loader * @param interfaces the list of interfaces for the proxy class * to implement * @param h the invocation handler to dispatch method invocations to diff --git a/src/java.base/share/classes/java/lang/reflect/UndeclaredThrowableException.java b/src/java.base/share/classes/java/lang/reflect/UndeclaredThrowableException.java index 0b086330de2..25d8affa474 100644 --- a/src/java.base/share/classes/java/lang/reflect/UndeclaredThrowableException.java +++ b/src/java.base/share/classes/java/lang/reflect/UndeclaredThrowableException.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -61,7 +61,7 @@ public class UndeclaredThrowableException extends RuntimeException { * specified {@code Throwable}. * * @param undeclaredThrowable the undeclared checked exception - * that was thrown + * that was thrown, may be {@code null} */ public UndeclaredThrowableException(Throwable undeclaredThrowable) { super(null, undeclaredThrowable); // Disallow initCause @@ -72,8 +72,8 @@ public class UndeclaredThrowableException extends RuntimeException { * specified {@code Throwable} and a detail message. * * @param undeclaredThrowable the undeclared checked exception - * that was thrown - * @param s the detail message + * that was thrown, may be {@code null} + * @param s the detail message, may be {@code null} */ public UndeclaredThrowableException(Throwable undeclaredThrowable, String s) diff --git a/src/java.base/share/classes/java/net/HostPortrange.java b/src/java.base/share/classes/java/net/HostPortrange.java index 289846841c9..f7a5b911633 100644 --- a/src/java.base/share/classes/java/net/HostPortrange.java +++ b/src/java.base/share/classes/java/net/HostPortrange.java @@ -60,6 +60,11 @@ class HostPortrange { } HostPortrange(String scheme, String host) { + // Defensive validation first + if (host == null || host.isEmpty()) { + throw new IllegalArgumentException("Invalid URL authority"); + } + // Parse the host name. A name has up to three components, the // hostname, a port number, or two numbers representing a port // range. "www.example.com:8080-9090" is a valid host name. diff --git a/src/java.base/share/classes/java/net/URLPermission.java b/src/java.base/share/classes/java/net/URLPermission.java index bf87cad8077..ac51d6c60a1 100644 --- a/src/java.base/share/classes/java/net/URLPermission.java +++ b/src/java.base/share/classes/java/net/URLPermission.java @@ -527,6 +527,9 @@ public final class URLPermission extends Permission { HostPortrange p; Authority(String scheme, String authority) { + if (authority == null || authority.isEmpty()) { + throw new IllegalArgumentException("Invalid URL: authority is empty"); + } int at = authority.indexOf('@'); if (at == -1) { p = new HostPortrange(scheme, authority); diff --git a/src/java.base/share/classes/java/time/MonthDay.java b/src/java.base/share/classes/java/time/MonthDay.java index a25c9beec95..26ca69bbe0d 100644 --- a/src/java.base/share/classes/java/time/MonthDay.java +++ b/src/java.base/share/classes/java/time/MonthDay.java @@ -88,6 +88,8 @@ import java.time.temporal.UnsupportedTemporalTypeException; import java.time.temporal.ValueRange; import java.util.Objects; +import jdk.internal.util.DecimalDigits; + /** * A month-day in the ISO-8601 calendar system, such as {@code --12-03}. *

@@ -764,10 +766,12 @@ public final class MonthDay */ @Override public String toString() { - return new StringBuilder(10).append("--") - .append(month < 10 ? "0" : "").append(month) - .append(day < 10 ? "-0" : "-").append(day) - .toString(); + StringBuilder buf = new StringBuilder(10); + buf.append("--"); + DecimalDigits.appendPair(buf, month); + buf.append('-'); + DecimalDigits.appendPair(buf, day); + return buf.toString(); } //----------------------------------------------------------------------- diff --git a/src/java.base/share/classes/java/time/YearMonth.java b/src/java.base/share/classes/java/time/YearMonth.java index b3d1aff7bc2..665c8c85544 100644 --- a/src/java.base/share/classes/java/time/YearMonth.java +++ b/src/java.base/share/classes/java/time/YearMonth.java @@ -101,6 +101,8 @@ import java.time.temporal.UnsupportedTemporalTypeException; import java.time.temporal.ValueRange; import java.util.Objects; +import jdk.internal.util.DecimalDigits; + /** * A year-month in the ISO-8601 calendar system, such as {@code 2007-12}. *

@@ -1213,18 +1215,17 @@ public final class YearMonth public String toString() { int absYear = Math.abs(year); StringBuilder buf = new StringBuilder(9); - if (absYear < 1000) { + if (absYear < 10000) { if (year < 0) { - buf.append(year - 10000).deleteCharAt(1); - } else { - buf.append(year + 10000).deleteCharAt(0); + buf.append('-'); } + DecimalDigits.appendQuad(buf, absYear); } else { buf.append(year); } - return buf.append(month < 10 ? "-0" : "-") - .append(month) - .toString(); + buf.append('-'); + DecimalDigits.appendPair(buf, month); + return buf.toString(); } //----------------------------------------------------------------------- diff --git a/src/java.base/share/classes/java/time/ZoneOffset.java b/src/java.base/share/classes/java/time/ZoneOffset.java index 4199d17735c..2a45e7cbf82 100644 --- a/src/java.base/share/classes/java/time/ZoneOffset.java +++ b/src/java.base/share/classes/java/time/ZoneOffset.java @@ -88,6 +88,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicReferenceArray; +import jdk.internal.util.DecimalDigits; import jdk.internal.vm.annotation.Stable; /** @@ -465,12 +466,14 @@ public final class ZoneOffset StringBuilder buf = new StringBuilder(); int absHours = absTotalSeconds / SECONDS_PER_HOUR; int absMinutes = (absTotalSeconds / SECONDS_PER_MINUTE) % MINUTES_PER_HOUR; - buf.append(totalSeconds < 0 ? "-" : "+") - .append(absHours < 10 ? "0" : "").append(absHours) - .append(absMinutes < 10 ? ":0" : ":").append(absMinutes); + buf.append(totalSeconds < 0 ? '-' : '+'); + DecimalDigits.appendPair(buf, absHours); + buf.append(':'); + DecimalDigits.appendPair(buf, absMinutes); int absSeconds = absTotalSeconds % SECONDS_PER_MINUTE; if (absSeconds != 0) { - buf.append(absSeconds < 10 ? ":0" : ":").append(absSeconds); + buf.append(':'); + DecimalDigits.appendPair(buf, absSeconds); } return buf.toString(); } diff --git a/src/java.base/share/classes/java/time/chrono/ChronoLocalDateImpl.java b/src/java.base/share/classes/java/time/chrono/ChronoLocalDateImpl.java index ca226b70d24..67f08c5cb4f 100644 --- a/src/java.base/share/classes/java/time/chrono/ChronoLocalDateImpl.java +++ b/src/java.base/share/classes/java/time/chrono/ChronoLocalDateImpl.java @@ -74,6 +74,8 @@ import java.time.temporal.UnsupportedTemporalTypeException; import java.time.temporal.ValueRange; import java.util.Objects; +import jdk.internal.util.DecimalDigits; + /** * A date expressed in terms of a standard year-month-day calendar system. *

@@ -426,18 +428,22 @@ abstract class ChronoLocalDateImpl @Override public String toString() { - // getLong() reduces chances of exceptions in toString() - long yoe = getLong(YEAR_OF_ERA); - long moy = getLong(MONTH_OF_YEAR); - long dom = getLong(DAY_OF_MONTH); + // Using get() instead of getLong() for performance reasons, + // as the values of YEAR_OF_ERA, MONTH_OF_YEAR, and DAY_OF_MONTH + // are guaranteed to be within the int range for all chronologies. + int yoe = get(YEAR_OF_ERA); + int moy = get(MONTH_OF_YEAR); + int dom = get(DAY_OF_MONTH); StringBuilder buf = new StringBuilder(30); buf.append(getChronology().toString()) .append(" ") .append(getEra()) .append(" ") .append(yoe) - .append(moy < 10 ? "-0" : "-").append(moy) - .append(dom < 10 ? "-0" : "-").append(dom); + .append('-'); + DecimalDigits.appendPair(buf, moy); + buf.append('-'); + DecimalDigits.appendPair(buf, dom); return buf.toString(); } diff --git a/src/java.base/share/classes/java/util/List.java b/src/java.base/share/classes/java/util/List.java index 6ab80b83ef8..43408de292a 100644 --- a/src/java.base/share/classes/java/util/List.java +++ b/src/java.base/share/classes/java/util/List.java @@ -1207,7 +1207,7 @@ public interface List extends SequencedCollection { * The provided computing function is guaranteed to be successfully * invoked at most once per list index, even in a multi-threaded environment. * Competing threads accessing an element already under computation will block until - * an element is computed or the computing function completes abnormally + * an element is computed or the computing function completes abnormally. *

* If invoking the provided computing function throws an exception, it is rethrown * to the initial caller and no value for the element is recorded. @@ -1235,7 +1235,7 @@ public interface List extends SequencedCollection { * one or more lazy elements. *

* The returned lazy list strongly references its computing - * function used to compute elements at least so long as there are uninitialized + * function used to compute elements at least as long as there are uninitialized * elements. *

* The returned List is not {@linkplain Serializable}. diff --git a/src/java.base/share/classes/java/util/Map.java b/src/java.base/share/classes/java/util/Map.java index abee819069f..177f0522b1b 100644 --- a/src/java.base/share/classes/java/util/Map.java +++ b/src/java.base/share/classes/java/util/Map.java @@ -1788,7 +1788,7 @@ public interface Map { * one or more lazy elements. *

* The returned lazy map strongly references its underlying - * computing function used to compute values at least so long as there are + * computing function used to compute values at least as long as there are * uncomputed values. *

* The returned Map is not {@linkplain Serializable}. diff --git a/src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java b/src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java index 4cb7048a798..9295f0deb59 100644 --- a/src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java +++ b/src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java @@ -1372,12 +1372,20 @@ public class ConcurrentHashMap extends AbstractMap Node[] t; int f = (t = table) == null ? 0 : t.length; Traverser it = new Traverser(t, f, 0, f); - for (Node p; (p = it.advance()) != null; ) { - V val = p.val; - Object v = m.get(p.key); - if (v == null || (v != val && !v.equals(val))) - return false; + + try { + for (Node p; (p = it.advance()) != null; ) { + V val = p.val; + Object v = m.get(p.key); + if (v == null || (v != val && !v.equals(val))) + return false; + } + } catch (ClassCastException | NullPointerException _) { + // m.get(p.key) is contractually allowed to throw CCE or NPE + // but CHM doesn't allow null keys, so NPE shouldn't occur in practice + return false; } + for (Map.Entry e : m.entrySet()) { Object mk, mv, v; if ((mk = e.getKey()) == null || diff --git a/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java b/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java index d1f8283489b..1ded0211932 100644 --- a/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java +++ b/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java @@ -360,31 +360,36 @@ final class DelayScheduler extends Thread { u.heapIndex = -1; } } - if (t != null) { // sift down - for (int cs; (cs = (k << 2) + 1) < n; ) { - ScheduledForkJoinTask leastChild = null, c; + if (t != null) { + while (k > 0) { // sift up if replaced with smaller value + ScheduledForkJoinTask parent; int pk; + if ((parent = h[pk = (k - 1) >>> 2]) == null || + parent.when <= d) + break; + parent.heapIndex = k; + h[k] = parent; + k = pk; + } + for (int cs; (cs = (k << 2) + 1) < n; ) { // sift down + ScheduledForkJoinTask leastChild = null; int leastIndex = 0; - long leastValue = Long.MAX_VALUE; - for (int ck = cs, j = 4;;) { // at most 4 children - if ((c = h[ck]) == null) - break; - long cd = c.when; - if (c.status < 0 && alsoReplace < 0) { - alsoReplace = ck; // at most once per pass - c.heapIndex = -1; - } - else if (leastChild == null || cd < leastValue) { + long leastValue = d; // at most 4 children + for (int ck, j = 0; j < 4 && (ck = j + cs) < n; ++j) { + ScheduledForkJoinTask c; long cd; + if ((c = h[ck]) != null && (cd = c.when) < leastValue) { leastValue = cd; leastIndex = ck; leastChild = c; } - if (--j == 0 || ++ck >= n) - break; } - if (leastChild == null || d <= leastValue) + if (leastChild == null) // already ordered break; - leastChild.heapIndex = k; - h[k] = leastChild; + if ((h[k] = leastChild).status >= 0 || alsoReplace >= 0) + leastChild.heapIndex = k; + else { + leastChild.heapIndex = -1; + alsoReplace = k; + } k = leastIndex; } t.heapIndex = k; @@ -393,6 +398,7 @@ final class DelayScheduler extends Thread { k = alsoReplace; } } + assert checkHeap(h, n); return n; } @@ -451,6 +457,33 @@ final class DelayScheduler extends Thread { } } + /** + * Invariant checks + */ + private static boolean checkHeap(ScheduledForkJoinTask[] h, int n) { + for (int i = 0; i < h.length; ++i) { + ScheduledForkJoinTask t = h[i]; + if (t == null) { // unused slots all null + if (i < n) + return false; + } + else { + long v = t.when; + int x = t.heapIndex; + if (x != i && x >= 0) // valid index unless removing + return false; + if (i > 0 && h[(i - 1) >>> 2].when > v) // ordered wrt parent + return false; + int cs = (i << 2) + 1; // ordered wrt children + for (int ck, j = 0; j < 4 && (ck = cs + j) < n; ++j) { + if (h[ck].when < v) + return false; + } + } + } + return true; + } + /** * Task class for DelayScheduler operations */ diff --git a/src/java.base/share/classes/java/util/concurrent/LinkedTransferQueue.java b/src/java.base/share/classes/java/util/concurrent/LinkedTransferQueue.java index 118d648c7a2..787d8d5fecd 100644 --- a/src/java.base/share/classes/java/util/concurrent/LinkedTransferQueue.java +++ b/src/java.base/share/classes/java/util/concurrent/LinkedTransferQueue.java @@ -588,13 +588,15 @@ public class LinkedTransferQueue extends AbstractQueue do { m = p.item; q = p.next; - if (p.isData != haveData && haveData != (m != null) && - p.cmpExItem(m, e) == m) { - Thread w = p.waiter; // matched complementary node - if (p != h && h == cmpExHead(h, (q == null) ? p : q)) - h.next = h; // advance head; self-link old - LockSupport.unpark(w); - return m; + if (p.isData != haveData && haveData != (m != null)) { + if (p.cmpExItem(m, e) == m) { + Thread w = p.waiter; // matched complementary node + if (p != h && h == cmpExHead(h, (q == null) ? p : q)) + h.next = h; // advance head; self-link old + LockSupport.unpark(w); + return m; + } + continue restart; } else if (q == null) { if (ns == 0L) // try to append unless immediate break restart; diff --git a/src/java.base/share/classes/java/util/concurrent/StructuredTaskScope.java b/src/java.base/share/classes/java/util/concurrent/StructuredTaskScope.java index 941d6bd55cb..01bf1158bf6 100644 --- a/src/java.base/share/classes/java/util/concurrent/StructuredTaskScope.java +++ b/src/java.base/share/classes/java/util/concurrent/StructuredTaskScope.java @@ -914,8 +914,8 @@ public sealed interface StructuredTaskScope * forking subtasks will create an unnamed virtual thread for each subtask. * *

If a {@linkplain Configuration#withTimeout(Duration) timeout} is set then it - * starts when the scope is opened. If the timeout expires before the scope has - * {@linkplain #join() joined} then the scope is {@linkplain ##Cancellation cancelled} + * starts when the scope is opened. If the timeout expires before or while waiting in + * {@link #join()} then the scope is {@linkplain ##Cancellation cancelled} * and the {@code Joiner}'s {@link Joiner#onTimeout() onTimeout()} method is invoked * to optionally throw {@link TimeoutException TimeoutException}. * diff --git a/src/java.base/share/classes/java/util/concurrent/ThreadLocalRandom.java b/src/java.base/share/classes/java/util/concurrent/ThreadLocalRandom.java index 3713d616a3a..deed7f018c3 100644 --- a/src/java.base/share/classes/java/util/concurrent/ThreadLocalRandom.java +++ b/src/java.base/share/classes/java/util/concurrent/ThreadLocalRandom.java @@ -128,11 +128,6 @@ public final class ThreadLocalRandom extends Random { * Implementations of non-core methods are mostly the same as in * SplittableRandom, that were in part derived from a previous * version of this class. - * - * This implementation of ThreadLocalRandom overrides the - * definition of the nextGaussian() method in the class Random, - * and instead uses the ziggurat-based algorithm that is the - * default for the RandomGenerator interface. */ private static int mix32(long z) { @@ -499,6 +494,23 @@ public final class ThreadLocalRandom extends Random { return super.nextLong(origin, bound); } + /** + * Returns a {@code double} value pseudorandomly chosen from a Gaussian + * (normal) distribution whose mean is 0 and whose standard deviation is 1. + * + * @return a {@code double} value pseudorandomly chosen from a + * Gaussian distribution + * + * @implNote This implementation invokes the default implementation of + * {@link java.util.random.RandomGenerator#nextGaussian()}, + * and so it uses McFarland's fast modified ziggurat algorithm + * rather than the polar method described in the superclass. + */ + @Override + public double nextGaussian() { + return RandomSupport.computeNextGaussian(this); + } + /** * {@inheritDoc} */ @@ -510,7 +522,6 @@ public final class ThreadLocalRandom extends Random { /** * {@inheritDoc} * @throws IllegalArgumentException {@inheritDoc} - * @implNote {@inheritDoc} * * @since 17 */ @@ -522,7 +533,6 @@ public final class ThreadLocalRandom extends Random { /** * {@inheritDoc} * @throws IllegalArgumentException {@inheritDoc} - * @implNote {@inheritDoc} * * @since 17 */ @@ -542,7 +552,6 @@ public final class ThreadLocalRandom extends Random { /** * {@inheritDoc} * @throws IllegalArgumentException {@inheritDoc} - * @implNote {@inheritDoc} */ @Override public double nextDouble(double bound) { @@ -552,7 +561,6 @@ public final class ThreadLocalRandom extends Random { /** * {@inheritDoc} * @throws IllegalArgumentException {@inheritDoc} - * @implNote {@inheritDoc} */ @Override public double nextDouble(double origin, double bound) { diff --git a/src/java.base/share/classes/java/util/random/RandomGenerator.java b/src/java.base/share/classes/java/util/random/RandomGenerator.java index e019e073a2d..2195774e591 100644 --- a/src/java.base/share/classes/java/util/random/RandomGenerator.java +++ b/src/java.base/share/classes/java/util/random/RandomGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -917,7 +917,6 @@ public interface RandomGenerator { * a discrete distribution also plays a role. */ default double nextGaussian() { - // See Knuth, TAOCP, Vol. 2, 3rd edition, Section 3.4.1 Algorithm C. return RandomSupport.computeNextGaussian(this); } diff --git a/src/java.base/share/classes/java/util/stream/SortedOps.java b/src/java.base/share/classes/java/util/stream/SortedOps.java index 91892eba44f..1286735ebd9 100644 --- a/src/java.base/share/classes/java/util/stream/SortedOps.java +++ b/src/java.base/share/classes/java/util/stream/SortedOps.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -108,13 +108,10 @@ final class SortedOps { * {@code Comparable}. */ OfRef(AbstractPipeline upstream) { - super(upstream, StreamShape.REFERENCE, - StreamOpFlag.IS_ORDERED | StreamOpFlag.IS_SORTED); - this.isNaturalSort = true; // Will throw CCE when we try to sort if T is not Comparable @SuppressWarnings("unchecked") Comparator comp = (Comparator) Comparator.naturalOrder(); - this.comparator = comp; + this(upstream, comp); } /** @@ -123,10 +120,13 @@ final class SortedOps { * @param comparator The comparator to be used to evaluate ordering. */ OfRef(AbstractPipeline upstream, Comparator comparator) { + Objects.requireNonNull(comparator); + boolean isNaturalSort = Comparator.naturalOrder().equals(comparator); + this.comparator = comparator; + this.isNaturalSort = isNaturalSort; super(upstream, StreamShape.REFERENCE, - StreamOpFlag.IS_ORDERED | StreamOpFlag.NOT_SORTED); - this.isNaturalSort = false; - this.comparator = Objects.requireNonNull(comparator); + StreamOpFlag.IS_ORDERED | + (isNaturalSort ? StreamOpFlag.IS_SORTED : StreamOpFlag.NOT_SORTED)); } @Override diff --git a/src/java.base/share/classes/java/util/stream/StreamOpFlag.java b/src/java.base/share/classes/java/util/stream/StreamOpFlag.java index df98125cbc0..0e7b2f6607b 100644 --- a/src/java.base/share/classes/java/util/stream/StreamOpFlag.java +++ b/src/java.base/share/classes/java/util/stream/StreamOpFlag.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,6 +24,7 @@ */ package java.util.stream; +import java.util.Comparator; import java.util.EnumMap; import java.util.Map; import java.util.Spliterator; @@ -738,9 +739,9 @@ enum StreamOpFlag { * * @implSpec * If the spliterator is naturally {@code SORTED} (the associated - * {@code Comparator} is {@code null}) then the characteristic is converted - * to the {@link #SORTED} flag, otherwise the characteristic is not - * converted. + * {@code Comparator} is {@code null} or {@code Comparator.naturalOrder()}) then + * the characteristic is converted to the {@link #SORTED} flag, otherwise + * the characteristic is not converted. * * @param spliterator the spliterator from which to obtain characteristic * bit set. @@ -748,14 +749,15 @@ enum StreamOpFlag { */ static int fromCharacteristics(Spliterator spliterator) { int characteristics = spliterator.characteristics(); - if ((characteristics & Spliterator.SORTED) != 0 && spliterator.getComparator() != null) { - // Do not propagate the SORTED characteristic if it does not correspond - // to a natural sort order - return characteristics & SPLITERATOR_CHARACTERISTICS_MASK & ~Spliterator.SORTED; - } - else { - return characteristics & SPLITERATOR_CHARACTERISTICS_MASK; + if ((characteristics & Spliterator.SORTED) != 0) { + Comparator comparator = spliterator.getComparator(); + if (comparator != null && !Comparator.naturalOrder().equals(comparator)) { + // Do not propagate the SORTED characteristic if it does not correspond + // to a natural sort order + return characteristics & SPLITERATOR_CHARACTERISTICS_MASK & ~Spliterator.SORTED; + } } + return characteristics & SPLITERATOR_CHARACTERISTICS_MASK; } /** diff --git a/src/java.base/share/classes/javax/crypto/spec/HPKEParameterSpec.java b/src/java.base/share/classes/javax/crypto/spec/HPKEParameterSpec.java new file mode 100644 index 00000000000..6776ddcdb75 --- /dev/null +++ b/src/java.base/share/classes/javax/crypto/spec/HPKEParameterSpec.java @@ -0,0 +1,443 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package javax.crypto.spec; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import java.nio.charset.StandardCharsets; +import java.security.AsymmetricKey; +import java.security.Key; +import java.security.spec.AlgorithmParameterSpec; +import java.util.Arrays; +import java.util.HexFormat; +import java.util.Objects; + +/** + * This immutable class specifies the set of parameters used with a {@code Cipher} for the + * Hybrid Public Key Encryption + * (HPKE) algorithm. HPKE is a public key encryption scheme for encrypting + * arbitrary-sized plaintexts with a recipient's public key. It combines a key + * encapsulation mechanism (KEM), a key derivation function (KDF), and an + * authenticated encryption with additional data (AEAD) cipher. + *

+ * The + * standard algorithm name for the cipher is "HPKE". Unlike most other + * ciphers, HPKE is not expressed as a transformation string of the form + * "algorithm/mode/padding". Therefore, the argument to {@code Cipher.getInstance} + * must be the single algorithm name "HPKE". + *

+ * In HPKE, the sender's {@code Cipher} is always initialized with the + * recipient's public key in {@linkplain Cipher#ENCRYPT_MODE encrypt mode}, + * while the recipient's {@code Cipher} object is initialized with its own + * private key in {@linkplain Cipher#DECRYPT_MODE decrypt mode}. + *

+ * An {@code HPKEParameterSpec} object must be provided at HPKE + * {@linkplain Cipher#init(int, Key, AlgorithmParameterSpec) cipher initialization}. + *

+ * The {@link #of(int, int, int)} static method returns an {@code HPKEParameterSpec} + * object with the specified KEM, KDF, and AEAD algorithm identifiers. + * The terms "KEM algorithm identifiers", "KDF algorithm identifiers", and + * "AEAD algorithm identifiers" refer to their respective numeric values + * (specifically, {@code kem_id}, {@code kdf_id}, and {@code aead_id}) as + * defined in Section 7 + * of RFC 9180 and maintained on the + * IANA HPKE page. + *

+ * Once an {@code HPKEParameterSpec} object is created, additional methods + * are available to generate new {@code HPKEParameterSpec} objects with + * different features: + *

    + *
  • + * Application-supplied information can be provided using the + * {@link #withInfo(byte[])} method by both sides. + *
  • + * To authenticate using a pre-shared key ({@code mode_psk}), the + * pre-shared key and its identifier must be provided using the + * {@link #withPsk(SecretKey, byte[])} method by both sides. + *
  • + * To authenticate using an asymmetric key ({@code mode_auth}), + * the asymmetric keys must be provided using the {@link #withAuthKey(AsymmetricKey)} + * method. Precisely, the sender must call this method with its own private key + * and the recipient must call it with the sender's public key. + *
  • + * To authenticate using both a PSK and an asymmetric key + * ({@code mode_auth_psk}), both {@link #withAuthKey(AsymmetricKey)} and + * {@link #withPsk(SecretKey, byte[])} methods must be called as described above. + *
  • + * In HPKE, a shared secret is negotiated during the KEM step and a key + * encapsulation message must be transmitted from the sender to the recipient + * so that the recipient can recover the shared secret. On the sender side, + * after the cipher is initialized, the key encapsulation message can be + * retrieved using the {@link Cipher#getIV()} method. On the recipient side, + * this message must be supplied as part of an {@code HPKEParameterSpec} + * object obtained from the {@link #withEncapsulation(byte[])} method. + *
+ * For successful interoperability, both sides need to have identical algorithm + * identifiers, and supply identical + * {@code info}, {@code psk}, and {@code psk_id} or matching authentication + * keys if provided. For details about HPKE modes, refer to + * Section 5 + * of RFC 9180. + *

+ * If an HPKE cipher is {@linkplain Cipher#init(int, Key) initialized without + * parameters}, an {@code InvalidKeyException} is thrown. + *

+ * At HPKE cipher initialization, if no HPKE implementation supports the + * provided key type, an {@code InvalidKeyException} is thrown. If the provided + * {@code HPKEParameterSpec} is not accepted by any HPKE implementation, + * an {@code InvalidAlgorithmParameterException} is thrown. For example: + *

    + *
  • An algorithm identifier is unsupported or does not match the provided key type. + *
  • A key encapsulation message is provided on the sender side. + *
  • A key encapsulation message is not provided on the recipient side. + *
  • An attempt to use {@code withAuthKey(key)} is made with an incompatible key. + *
  • An attempt to use {@code withAuthKey(key)} is made but {@code mode_auth} + * or {@code mode_auth_psk} is not supported by the KEM algorithm used. + *
+ * After initialization, both the sender and recipient can process multiple + * messages in sequence with repeated {@code doFinal} calls, optionally preceded + * by one or more {@code updateAAD} and {@code update}. Each {@code doFinal} + * performs a complete HPKE encryption or decryption operation using a distinct + * IV derived from an internal sequence counter, as specified in + * Section 5.2 + * of RFC 9180. On the recipient side, each {@code doFinal} call must correspond + * to exactly one complete ciphertext, and the number and order of calls must + * match those on the sender side. This differs from the direct use of an AEAD + * cipher, where the caller must provide a fresh IV and reinitialize the cipher + * for each message. By managing IVs internally, HPKE allows a single + * initialization to support multiple messages while still ensuring IV + * uniqueness and preserving AEAD security guarantees. + *

+ * This example shows a sender and a recipient using HPKE to securely exchange + * messages with an X25519 key pair. + * {@snippet lang=java class="PackageSnippets" region="hpke-spec-example"} + * + * @implNote This class defines constants for some of the standard algorithm + * identifiers such as {@link #KEM_DHKEM_P_256_HKDF_SHA256}, + * {@link #KDF_HKDF_SHA256}, and {@link #AEAD_AES_128_GCM}. An HPKE {@code Cipher} + * implementation may support all, some, or none of the algorithm identifiers + * defined here. An implementation may also support additional identifiers not + * listed here, including private or experimental values. + * + * @spec https://www.rfc-editor.org/info/rfc9180 + * RFC 9180: Hybrid Public Key Encryption + * @spec security/standard-names.html + * Java Security Standard Algorithm Names + * @since 26 + */ +public final class HPKEParameterSpec implements AlgorithmParameterSpec { + + /** + * KEM algorithm identifier for DHKEM(P-256, HKDF-SHA256) as defined in RFC 9180. + */ + public static final int KEM_DHKEM_P_256_HKDF_SHA256 = 0x10; + + /** + * KEM algorithm identifier for DHKEM(P-384, HKDF-SHA384) as defined in RFC 9180. + */ + public static final int KEM_DHKEM_P_384_HKDF_SHA384 = 0x11; + + /** + * KEM algorithm identifier for DHKEM(P-521, HKDF-SHA512) as defined in RFC 9180. + */ + public static final int KEM_DHKEM_P_521_HKDF_SHA512 = 0x12; + + /** + * KEM algorithm identifier for DHKEM(X25519, HKDF-SHA256) as defined in RFC 9180. + */ + public static final int KEM_DHKEM_X25519_HKDF_SHA256 = 0x20; + + /** + * KEM algorithm identifier for DHKEM(X448, HKDF-SHA512) as defined in RFC 9180. + */ + public static final int KEM_DHKEM_X448_HKDF_SHA512 = 0x21; + + /** + * KDF algorithm identifier for HKDF-SHA256 as defined in RFC 9180. + */ + public static final int KDF_HKDF_SHA256 = 0x1; + + /** + * KDF algorithm identifier for HKDF-SHA384 as defined in RFC 9180. + */ + public static final int KDF_HKDF_SHA384 = 0x2; + + /** + * KDF algorithm identifier for HKDF-SHA512 as defined in RFC 9180. + */ + public static final int KDF_HKDF_SHA512 = 0x3; + + /** + * AEAD algorithm identifier for AES-128-GCM as defined in RFC 9180. + */ + public static final int AEAD_AES_128_GCM = 0x1; + + /** + * AEAD algorithm identifier for AES-256-GCM as defined in RFC 9180. + */ + public static final int AEAD_AES_256_GCM = 0x2; + + /** + * AEAD algorithm identifier for ChaCha20Poly1305 as defined in RFC 9180. + */ + public static final int AEAD_CHACHA20_POLY1305 = 0x3; + + /** + * AEAD algorithm identifier for Export-only as defined in RFC 9180. + */ + public static final int EXPORT_ONLY = 0xffff; + + private final int kem_id; + private final int kdf_id; + private final int aead_id; + private final byte[] info; // never null, can be empty + private final SecretKey psk; // null if not used + private final byte[] psk_id; // never null, can be empty + private final AsymmetricKey kS; // null if not used + private final byte[] encapsulation; // null if none + + // Note: this constructor does not clone array arguments. + private HPKEParameterSpec(int kem_id, int kdf_id, int aead_id, byte[] info, + SecretKey psk, byte[] psk_id, AsymmetricKey kS, byte[] encapsulation) { + this.kem_id = kem_id; + this.kdf_id = kdf_id; + this.aead_id = aead_id; + this.info = info; + this.psk = psk; + this.psk_id = psk_id; + this.kS = kS; + this.encapsulation = encapsulation; + } + + /** + * A factory method to create a new {@code HPKEParameterSpec} object with + * specified KEM, KDF, and AEAD algorithm identifiers in {@code mode_base} + * mode with an empty {@code info}. + * + * @param kem_id algorithm identifier for KEM, must be between 0 and 65535 (inclusive) + * @param kdf_id algorithm identifier for KDF, must be between 0 and 65535 (inclusive) + * @param aead_id algorithm identifier for AEAD, must be between 0 and 65535 (inclusive) + * @return a new {@code HPKEParameterSpec} object + * @throws IllegalArgumentException if any input value + * is out of range (must be between 0 and 65535, inclusive). + */ + public static HPKEParameterSpec of(int kem_id, int kdf_id, int aead_id) { + if (kem_id < 0 || kem_id > 65535) { + throw new IllegalArgumentException("Invalid kem_id: " + kem_id); + } + if (kdf_id < 0 || kdf_id > 65535) { + throw new IllegalArgumentException("Invalid kdf_id: " + kdf_id); + } + if (aead_id < 0 || aead_id > 65535) { + throw new IllegalArgumentException("Invalid aead_id: " + aead_id); + } + return new HPKEParameterSpec(kem_id, kdf_id, aead_id, + new byte[0], null, new byte[0], null, null); + } + + /** + * Creates a new {@code HPKEParameterSpec} object with the specified + * {@code info} value. + *

+ * For interoperability, RFC 9180 Section 7.2.1 recommends limiting + * this value to a maximum of 64 bytes. + * + * @param info application-supplied information. + * The contents of the array are copied to protect + * against subsequent modification. + * @return a new {@code HPKEParameterSpec} object + * @throws NullPointerException if {@code info} is {@code null} + * @throws IllegalArgumentException if {@code info} is empty. + */ + public HPKEParameterSpec withInfo(byte[] info) { + Objects.requireNonNull(info); + if (info.length == 0) { + throw new IllegalArgumentException("info is empty"); + } + return new HPKEParameterSpec(kem_id, kdf_id, aead_id, + info.clone(), psk, psk_id, kS, encapsulation); + } + + /** + * Creates a new {@code HPKEParameterSpec} object with the specified + * {@code psk} and {@code psk_id} values. + *

+ * RFC 9180 Section 5.1.2 requires the PSK MUST have at least 32 bytes + * of entropy. For interoperability, RFC 9180 Section 7.2.1 recommends + * limiting the key size and identifier length to a maximum of 64 bytes. + * + * @param psk pre-shared key + * @param psk_id identifier for PSK. The contents of the array are copied + * to protect against subsequent modification. + * @return a new {@code HPKEParameterSpec} object + * @throws NullPointerException if {@code psk} or {@code psk_id} is {@code null} + * @throws IllegalArgumentException if {@code psk} is shorter than 32 bytes + * or {@code psk_id} is empty + */ + public HPKEParameterSpec withPsk(SecretKey psk, byte[] psk_id) { + Objects.requireNonNull(psk); + Objects.requireNonNull(psk_id); + if (psk_id.length == 0) { + throw new IllegalArgumentException("psk_id is empty"); + } + if ("RAW".equalsIgnoreCase(psk.getFormat())) { + // We can only check when psk is extractable. We can only + // check the length and not the real entropy size + var keyBytes = psk.getEncoded(); + assert keyBytes != null; + Arrays.fill(keyBytes, (byte)0); + if (keyBytes.length < 32) { + throw new IllegalArgumentException("psk is too short"); + } + } + return new HPKEParameterSpec(kem_id, kdf_id, aead_id, + info, psk, psk_id.clone(), kS, encapsulation); + } + + /** + * Creates a new {@code HPKEParameterSpec} object with the specified + * key encapsulation message value that will be used by the recipient. + * + * @param encapsulation the key encapsulation message. + * The contents of the array are copied to protect against + * subsequent modification. + * + * @return a new {@code HPKEParameterSpec} object + * @throws NullPointerException if {@code encapsulation} is {@code null} + */ + public HPKEParameterSpec withEncapsulation(byte[] encapsulation) { + return new HPKEParameterSpec(kem_id, kdf_id, aead_id, + info, psk, psk_id, kS, + Objects.requireNonNull(encapsulation).clone()); + } + + /** + * Creates a new {@code HPKEParameterSpec} object with the specified + * authentication key value. + *

+ * Note: this method does not check whether the KEM algorithm supports + * {@code mode_auth} or {@code mode_auth_psk}. If the resulting object is + * used to initialize an HPKE cipher with an unsupported mode, an + * {@code InvalidAlgorithmParameterException} will be thrown at that time. + * + * @param kS the authentication key + * @return a new {@code HPKEParameterSpec} object + * @throws NullPointerException if {@code kS} is {@code null} + */ + public HPKEParameterSpec withAuthKey(AsymmetricKey kS) { + return new HPKEParameterSpec(kem_id, kdf_id, aead_id, + info, psk, psk_id, + Objects.requireNonNull(kS), + encapsulation); + } + + /** + * {@return the algorithm identifier for KEM } + */ + public int kem_id() { + return kem_id; + } + + /** + * {@return the algorithm identifier for KDF } + */ + public int kdf_id() { + return kdf_id; + } + + /** + * {@return the algorithm identifier for AEAD } + */ + public int aead_id() { + return aead_id; + } + + /** + * {@return a copy of the application-supplied information, empty if none} + */ + public byte[] info() { + return info.clone(); + } + + /** + * {@return pre-shared key, {@code null} if none} + */ + public SecretKey psk() { + return psk; + } + + /** + * {@return a copy of the identifier for PSK, empty if none} + */ + public byte[] psk_id() { + return psk_id.clone(); + } + + /** + * {@return the key for authentication, {@code null} if none} + */ + public AsymmetricKey authKey() { + return kS; + } + + /** + * {@return a copy of the key encapsulation message, {@code null} if none} + */ + public byte[] encapsulation() { + return encapsulation == null ? null : encapsulation.clone(); + } + + @Override + public String toString() { + return "HPKEParameterSpec{" + + "kem_id=" + kem_id + + ", kdf_id=" + kdf_id + + ", aead_id=" + aead_id + + ", info=" + bytesToString(info) + + ", " + (psk == null + ? (kS == null ? "mode_base" : "mode_auth") + : (kS == null ? "mode_psk" : "mode_auth_psk")) + "}"; + } + + // Returns a human-readable representation of a byte array. + private static String bytesToString(byte[] input) { + if (input.length == 0) { + return "(empty)"; + } else { + for (byte b : input) { + if (b < 0x20 || b > 0x7E || b == '"') { + // Non-ASCII or control characters are hard to read, and + // `"` requires character escaping. If any of these are + // present, return only the HEX representation. + return HexFormat.of().formatHex(input); + } + } + // Otherwise, all characters are printable and safe. + // Return both HEX and ASCII representations. + return HexFormat.of().formatHex(input) + + " (\"" + new String(input, StandardCharsets.US_ASCII) + "\")"; + } + } +} diff --git a/src/java.base/share/classes/javax/crypto/spec/snippet-files/PackageSnippets.java b/src/java.base/share/classes/javax/crypto/spec/snippet-files/PackageSnippets.java new file mode 100644 index 00000000000..e4074c1c4a9 --- /dev/null +++ b/src/java.base/share/classes/javax/crypto/spec/snippet-files/PackageSnippets.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. 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. + */ +import javax.crypto.Cipher; +import javax.crypto.spec.HPKEParameterSpec; +import java.nio.charset.StandardCharsets; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.util.Arrays; +import java.util.HexFormat; + +class PackageSnippets { + public static void main(String[] args) throws Exception { + + // @start region="hpke-spec-example" + // Recipient key pair generation + KeyPairGenerator g = KeyPairGenerator.getInstance("X25519"); + KeyPair kp = g.generateKeyPair(); + + // The HPKE sender cipher is initialized with the recipient's public + // key and an HPKEParameterSpec using specified algorithm identifiers + // and application-supplied info. + Cipher senderCipher = Cipher.getInstance("HPKE"); + HPKEParameterSpec ps = HPKEParameterSpec.of( + HPKEParameterSpec.KEM_DHKEM_X25519_HKDF_SHA256, + HPKEParameterSpec.KDF_HKDF_SHA256, + HPKEParameterSpec.AEAD_AES_128_GCM) + .withInfo(HexFormat.of().parseHex("010203040506")); + senderCipher.init(Cipher.ENCRYPT_MODE, kp.getPublic(), ps); + + // Retrieve the key encapsulation message (from the KEM step) from + // the sender. + byte[] kemEncap = senderCipher.getIV(); + + // The HPKE recipient cipher is initialized with its own private key, + // an HPKEParameterSpec using the same algorithm identifiers as used by + // the sender, and the key encapsulation message from the sender. + Cipher recipientCipher = Cipher.getInstance("HPKE"); + HPKEParameterSpec pr = HPKEParameterSpec.of( + HPKEParameterSpec.KEM_DHKEM_X25519_HKDF_SHA256, + HPKEParameterSpec.KDF_HKDF_SHA256, + HPKEParameterSpec.AEAD_AES_128_GCM) + .withInfo(HexFormat.of().parseHex("010203040506")) + .withEncapsulation(kemEncap); + recipientCipher.init(Cipher.DECRYPT_MODE, kp.getPrivate(), pr); + + // Encryption and decryption + byte[] msg = "Hello World".getBytes(StandardCharsets.UTF_8); + byte[] ct = senderCipher.doFinal(msg); + byte[] pt = recipientCipher.doFinal(ct); + + assert Arrays.equals(msg, pt); + // @end + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractPoolEntry.java b/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractPoolEntry.java index 962a2057585..ec747d06c21 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractPoolEntry.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractPoolEntry.java @@ -35,6 +35,7 @@ import jdk.internal.access.SharedSecrets; import jdk.internal.constant.ClassOrInterfaceDescImpl; import jdk.internal.constant.PrimitiveClassDescImpl; import jdk.internal.util.ArraysSupport; +import jdk.internal.util.ModifiedUtf; import jdk.internal.vm.annotation.Stable; import static java.util.Objects.requireNonNull; @@ -141,7 +142,7 @@ public abstract sealed class AbstractPoolEntry { @Stable TypeDescriptor typeSym; Utf8EntryImpl(ConstantPool cpm, int index, - byte[] rawBytes, int offset, int rawLen) { + byte[] rawBytes, int offset, int rawLen) { super(cpm, index, 0); this.rawBytes = rawBytes; this.offset = offset; @@ -154,6 +155,10 @@ public abstract sealed class AbstractPoolEntry { } Utf8EntryImpl(ConstantPool cpm, int index, String s, int contentHash) { + // Prevent creation of unwritable entries + if (!ModifiedUtf.isValidLengthInConstantPool(s)) { + throw new IllegalArgumentException("utf8 length out of range of u2: " + ModifiedUtf.utfLen(s)); + } super(cpm, index, 0); this.rawBytes = null; this.offset = 0; diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/BufWriterImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/BufWriterImpl.java index b30592a4ebd..6daef5cab9a 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/BufWriterImpl.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/BufWriterImpl.java @@ -277,10 +277,9 @@ public final class BufWriterImpl implements BufWriter { int strlen = str.length(); int countNonZeroAscii = JLA.countNonZeroAscii(str); long utflenLong = utfLen(str, countNonZeroAscii); - if (!ExactConversionsSupport.isLongToCharExact(utflenLong)) { - throw new IllegalArgumentException("utf8 length out of range of u2: " + utflenLong); - } - int utflen = (int)utflenLong; + // Utf8Entry should always be writable + assert ExactConversionsSupport.isLongToCharExact(utflenLong) : utflenLong; + int utflen = (int) utflenLong; reserveSpace(utflen + 3); int offset = this.offset; diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/ClassHierarchyImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/ClassHierarchyImpl.java index 5be14f42baa..e4e67afb17f 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/ClassHierarchyImpl.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/ClassHierarchyImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,12 +25,11 @@ */ package jdk.internal.classfile.impl; -import java.io.BufferedInputStream; -import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; import java.lang.classfile.ClassHierarchyResolver; +import java.lang.classfile.constantpool.ClassEntry; import java.lang.constant.ClassDesc; import java.util.Collection; import java.util.HashMap; @@ -38,7 +37,6 @@ import java.util.Map; import java.util.function.Function; import static java.lang.classfile.ClassFile.ACC_INTERFACE; -import static java.lang.classfile.constantpool.PoolEntry.*; import static java.lang.constant.ConstantDescs.CD_Object; import static java.util.Objects.requireNonNull; import static jdk.internal.constant.ConstantUtils.referenceClassDesc; @@ -164,31 +162,12 @@ public final class ClassHierarchyImpl { public ClassHierarchyResolver.ClassHierarchyInfo getClassInfo(ClassDesc classDesc) { var ci = streamProvider.apply(classDesc); if (ci == null) return null; - try (var in = new DataInputStream(new BufferedInputStream(ci))) { - in.skipBytes(8); - int cpLength = in.readUnsignedShort(); - String[] cpStrings = new String[cpLength]; - int[] cpClasses = new int[cpLength]; - for (int i = 1; i < cpLength; i++) { - int tag; - switch (tag = in.readUnsignedByte()) { - case TAG_UTF8 -> cpStrings[i] = in.readUTF(); - case TAG_CLASS -> cpClasses[i] = in.readUnsignedShort(); - case TAG_STRING, TAG_METHOD_TYPE, TAG_MODULE, TAG_PACKAGE -> in.skipBytes(2); - case TAG_METHOD_HANDLE -> in.skipBytes(3); - case TAG_INTEGER, TAG_FLOAT, TAG_FIELDREF, TAG_METHODREF, TAG_INTERFACE_METHODREF, - TAG_NAME_AND_TYPE, TAG_DYNAMIC, TAG_INVOKE_DYNAMIC -> in.skipBytes(4); - case TAG_LONG, TAG_DOUBLE -> { - in.skipBytes(8); - i++; - } - default -> throw new IllegalStateException("Bad tag (" + tag + ") at index (" + i + ")"); - } - } - boolean isInterface = (in.readUnsignedShort() & ACC_INTERFACE) != 0; - in.skipBytes(2); - int superIndex = in.readUnsignedShort(); - var superClass = superIndex > 0 ? ClassDesc.ofInternalName(cpStrings[cpClasses[superIndex]]) : null; + try (ci) { + var reader = new ClassReaderImpl(ci.readAllBytes(), ClassFileImpl.DEFAULT_CONTEXT); + boolean isInterface = (reader.flags() & ACC_INTERFACE) != 0; + ClassDesc superClass = reader.superclassEntry() + .map(ClassEntry::asSymbol) + .orElse(null); return new ClassHierarchyInfoImpl(superClass, isInterface); } catch (IOException ioe) { throw new UncheckedIOException(ioe); diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/TransformImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/TransformImpl.java index 23387fcb098..4cb0517ec76 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/TransformImpl.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/TransformImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -120,9 +120,9 @@ public final class TransformImpl { @Override public ClassTransform andThen(ClassTransform next) { - if (next instanceof ClassMethodTransform cmt) - return new ClassMethodTransform(transform.andThen(cmt.transform), - mm -> filter.test(mm) && cmt.filter.test(mm)); + // Optimized for shared _ -> true filter in ClassTransform.transformingMethods(MethodTransform) + if (next instanceof ClassMethodTransform(var nextTransform, var nextFilter) && filter == nextFilter) + return new ClassMethodTransform(transform.andThen(nextTransform), filter); else return UnresolvedClassTransform.super.andThen(next); } @@ -143,9 +143,9 @@ public final class TransformImpl { @Override public ClassTransform andThen(ClassTransform next) { - if (next instanceof ClassFieldTransform cft) - return new ClassFieldTransform(transform.andThen(cft.transform), - mm -> filter.test(mm) && cft.filter.test(mm)); + // Optimized for shared _ -> true filter in ClassTransform.transformingFields(FieldTransform) + if (next instanceof ClassFieldTransform(var nextTransform, var nextFilter) && filter == nextFilter) + return new ClassFieldTransform(transform.andThen(nextTransform), filter); else return UnresolvedClassTransform.super.andThen(next); } @@ -208,8 +208,8 @@ public final class TransformImpl { @Override public MethodTransform andThen(MethodTransform next) { - return (next instanceof TransformImpl.MethodCodeTransform mct) - ? new TransformImpl.MethodCodeTransform(xform.andThen(mct.xform)) + return (next instanceof MethodCodeTransform(var nextXform)) + ? new TransformImpl.MethodCodeTransform(xform.andThen(nextXform)) : UnresolvedMethodTransform.super.andThen(next); } diff --git a/src/java.base/share/classes/jdk/internal/foreign/SegmentFactories.java b/src/java.base/share/classes/jdk/internal/foreign/SegmentFactories.java index 3edcac2e44c..728ee235547 100644 --- a/src/java.base/share/classes/jdk/internal/foreign/SegmentFactories.java +++ b/src/java.base/share/classes/jdk/internal/foreign/SegmentFactories.java @@ -212,7 +212,9 @@ public class SegmentFactories { allocationBase = allocateMemoryWrapper(allocationSize); result = Utils.alignUp(allocationBase, byteAlignment); } else { - allocationSize = alignedSize; + // always allocate at least 'byteAlignment' bytes, so that malloc is guaranteed to + // return a pointer aligned to that alignment, for cases where byteAlignment > alignedSize + allocationSize = Math.max(alignedSize, byteAlignment); if (shouldReserve) { AbstractMemorySegmentImpl.NIO_ACCESS.reserveMemory(allocationSize, byteSize); } diff --git a/src/java.base/share/classes/jdk/internal/net/quic/QuicTLSContext.java b/src/java.base/share/classes/jdk/internal/net/quic/QuicTLSContext.java index 5cf0c999fb9..15b3c450d03 100644 --- a/src/java.base/share/classes/jdk/internal/net/quic/QuicTLSContext.java +++ b/src/java.base/share/classes/jdk/internal/net/quic/QuicTLSContext.java @@ -74,7 +74,7 @@ public final class QuicTLSContext { /** * {@return {@code true} if protocols of the given {@code parameters} support QUIC TLS, {@code false} otherwise} */ - public static boolean isQuicCompatible(SSLParameters parameters) { + private static boolean isQuicCompatible(SSLParameters parameters) { String[] protocols = parameters.getProtocols(); return protocols != null && Arrays.asList(protocols).contains("TLSv1.3"); } diff --git a/src/java.base/share/classes/jdk/internal/util/DateTimeHelper.java b/src/java.base/share/classes/jdk/internal/util/DateTimeHelper.java index bbb0b6738d1..4d9d560fded 100644 --- a/src/java.base/share/classes/jdk/internal/util/DateTimeHelper.java +++ b/src/java.base/share/classes/jdk/internal/util/DateTimeHelper.java @@ -49,24 +49,23 @@ public final class DateTimeHelper { * Requires extra capacity of 10 to avoid StringBuilder reallocation. */ public static void formatTo(StringBuilder buf, LocalDate date) { - int year = date.getYear(), - month = date.getMonthValue(), - day = date.getDayOfMonth(); - int absYear = Math.abs(year); - if (absYear < 1000) { + int year = date.getYear(), + absYear = Math.abs(year); + if (absYear < 10000) { if (year < 0) { buf.append('-'); } - buf.repeat('0', absYear < 10 ? 3 : absYear < 100 ? 2 : 1); - buf.append(absYear); + DecimalDigits.appendQuad(buf, absYear); } else { if (year > 9999) { buf.append('+'); } buf.append(year); } - buf.append(month < 10 ? "-0" : "-").append(month) - .append(day < 10 ? "-0" : "-").append(day); + buf.append('-'); + DecimalDigits.appendPair(buf, date.getMonthValue()); + buf.append('-'); + DecimalDigits.appendPair(buf, date.getDayOfMonth()); } /** @@ -74,14 +73,14 @@ public final class DateTimeHelper { * Requires extra capacity of 18 to avoid StringBuilder reallocation. */ public static void formatTo(StringBuilder buf, LocalTime time) { - int hour = time.getHour(), - minute = time.getMinute(), - second = time.getSecond(), + DecimalDigits.appendPair(buf, time.getHour()); + buf.append(':'); + DecimalDigits.appendPair(buf, time.getMinute()); + int second = time.getSecond(), nano = time.getNano(); - buf.append(hour < 10 ? "0" : "").append(hour) - .append(minute < 10 ? ":0" : ":").append(minute); if ((second | nano) > 0) { - buf.append(second < 10 ? ":0" : ":").append(second); + buf.append(':'); + DecimalDigits.appendPair(buf, second); if (nano > 0) { buf.append('.'); int zeros = 9 - DecimalDigits.stringSize(nano); diff --git a/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java b/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java index 6c0c745651e..b55b6ce63b0 100644 --- a/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java +++ b/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java @@ -25,6 +25,8 @@ package jdk.internal.util; +import jdk.internal.access.JavaLangAccess; +import jdk.internal.access.SharedSecrets; import jdk.internal.misc.Unsafe; import jdk.internal.vm.annotation.Stable; @@ -36,6 +38,7 @@ import static jdk.internal.misc.Unsafe.ARRAY_BYTE_BASE_OFFSET; * @since 21 */ public final class DecimalDigits { + private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess(); private static final Unsafe UNSAFE = Unsafe.getUnsafe(); /** @@ -443,4 +446,56 @@ public final class DecimalDigits { assert charPos >= 0 && charPos < (buf.length >> 1); UNSAFE.putCharUnaligned(buf, ARRAY_BYTE_BASE_OFFSET + ((long) charPos << 1), (char) c); } + + /** + * Appends the two-digit string representation of the {@code int} + * argument to the given {@code StringBuilder}. + *

+ * The integer {@code v} is formatted as two decimal digits. + * Values from 0 to 9 are formatted with a leading zero (e.g., 5 becomes "05"), + * and values from 10 to 99 are formatted as regular two-digit numbers. + * If the value is outside the range 0-99, the behavior is unspecified. + * + * @param buf the {@code StringBuilder} to append to. + * @param v the {@code int} value (should be between 0 and 99 inclusive). + */ + public static void appendPair(StringBuilder buf, int v) { + // The & 0x7f operation keeps the index within the safe range [0, 127] for the DIGITS array, + // which allows the JIT compiler to eliminate array bounds checks for performance. + int packed = DIGITS[v & 0x7f]; + // The temporary String and byte[] objects created here are typically eliminated + // by the JVM's escape analysis and scalar replacement optimizations during + // runtime compilation, avoiding actual heap allocations in optimized code. + buf.append( + JLA.uncheckedNewStringWithLatin1Bytes( + new byte[] {(byte) packed, (byte) (packed >> 8)})); + } + + /** + * Appends the four-digit string representation of the {@code int} + * argument to the given {@code StringBuilder}. + *

+ * The integer {@code v} is formatted as four decimal digits. + * Values from 0 to 9 are formatted with leading zeros (e.g., 5 becomes "0005"), + * values from 10 to 99 add two leading zeros (e.g., 25 becomes "0025"), + * values from 100 to 999 add one leading zero (e.g., 123 becomes "0123"), + * and values from 1000 to 9999 have no leading zeros. + * If the value is outside the range 0-9999, the behavior is unspecified. + * + * @param buf the {@code StringBuilder} to append to. + * @param v the {@code int} value (should be between 0 and 9999 inclusive). + */ + public static void appendQuad(StringBuilder buf, int v) { + // The & 0x7f operation keeps the index within the safe range [0, 127] for the DIGITS array, + // which allows the JIT compiler to eliminate array bounds checks for performance. + int packedHigh = DIGITS[(v / 100) & 0x7f]; + int packedLow = DIGITS[(v % 100) & 0x7f]; + // The temporary String and byte[] objects created here are typically eliminated + // by the JVM's escape analysis and scalar replacement optimizations during + // runtime compilation, avoiding actual heap allocations in optimized code. + buf.append( + JLA.uncheckedNewStringWithLatin1Bytes( + new byte[] {(byte) packedHigh, (byte) (packedHigh >> 8), + (byte) packedLow, (byte) (packedLow >> 8)})); + } } diff --git a/src/java.base/share/classes/jdk/internal/util/ModifiedUtf.java b/src/java.base/share/classes/jdk/internal/util/ModifiedUtf.java index 46885e12adf..4221ec22de3 100644 --- a/src/java.base/share/classes/jdk/internal/util/ModifiedUtf.java +++ b/src/java.base/share/classes/jdk/internal/util/ModifiedUtf.java @@ -28,19 +28,17 @@ package jdk.internal.util; import jdk.internal.vm.annotation.ForceInline; -/** - * Helper to JDK UTF putChar and Calculate length - * - * @since 24 - */ -public abstract class ModifiedUtf { - // Maximum number of bytes allowed for a Modified UTF-8 encoded string - // in a ClassFile constant pool entry (CONSTANT_Utf8_info). +/// Utilities for string encoding and decoding with the +/// [Modified UTF-8][java.io.DataInput##modified-utf-8] format. +public final class ModifiedUtf { + /// Maximum number of bytes allowed for a Modified UTF-8 encoded string + /// in a [java.lang.classfile.constantpool.Utf8Entry] or a hotspot `Symbol`. public static final int CONSTANT_POOL_UTF8_MAX_BYTES = 65535; private ModifiedUtf() { } + /// Writes a char to the pre-sized modified UTF buffer. @ForceInline public static int putChar(byte[] buf, int offset, char c) { if (c != 0 && c < 0x80) { @@ -58,11 +56,23 @@ public abstract class ModifiedUtf { return offset; } - /** - * Calculate the utf length of a string - * @param str input string - * @param countNonZeroAscii the number of non-zero ascii characters in the prefix calculated by JLA.countNonZeroAscii(str) - */ + /// Calculate the encoded length of an input String. + /// For many workloads that have fast paths for ASCII-only prefixes, + /// [#utfLen(String, int)] skips scanning that prefix. + /// + /// @param str input string + public static long utfLen(String str) { + return utfLen(str, 0); + } + + /// Calculate the encoded length of trailing parts of an input String, + /// after [jdk.internal.access.JavaLangAccess#countNonZeroAscii(String)] + /// calculates the number of contiguous single-byte characters in the + /// beginning of the string. + /// + /// @param str input string + /// @param countNonZeroAscii the number of non-zero ascii characters in the + /// prefix calculated by JLA.countNonZeroAscii(str) @ForceInline public static long utfLen(String str, int countNonZeroAscii) { long utflen = str.length(); @@ -74,11 +84,11 @@ public abstract class ModifiedUtf { return utflen; } - /** - * Checks whether the Modified UTF-8 encoded length of the given string - * fits within the ClassFile constant pool limit (u2 length = 65535 bytes). - * @param str the string to check - */ + /// Checks whether an input String can be encoded in a + /// [java.lang.classfile.constantpool.Utf8Entry], or represented as a + /// hotspot `Symbol` (which has the same length limit). + /// + /// @param str input string @ForceInline public static boolean isValidLengthInConstantPool(String str) { // Quick approximation: each char can be at most 3 bytes in Modified UTF-8. @@ -91,7 +101,7 @@ public abstract class ModifiedUtf { return false; } // Check exact Modified UTF-8 length. - long utfLen = utfLen(str, 0); + long utfLen = utfLen(str); return utfLen <= CONSTANT_POOL_UTF8_MAX_BYTES; } } diff --git a/src/java.base/share/classes/sun/invoke/util/BytecodeDescriptor.java b/src/java.base/share/classes/sun/invoke/util/BytecodeDescriptor.java index bd5ea6d7635..b3671d19333 100644 --- a/src/java.base/share/classes/sun/invoke/util/BytecodeDescriptor.java +++ b/src/java.base/share/classes/sun/invoke/util/BytecodeDescriptor.java @@ -63,33 +63,24 @@ public class BytecodeDescriptor { /// @throws TypeNotPresentException if a reference type cannot be found by /// the loader (before the descriptor is found invalid) public static List> parseMethod(String descriptor, ClassLoader loader) { - return parseMethod(descriptor, 0, descriptor.length(), loader); - } - - /** - * @param loader the class loader in which to look up the types (null means - * bootstrap class loader) - */ - static List> parseMethod(String bytecodeSignature, - int start, int end, ClassLoader loader) { - String str = bytecodeSignature; - int[] i = {start}; + int end = descriptor.length(); // implicit null check + int[] i = {0}; var ptypes = new ArrayList>(); - if (i[0] < end && str.charAt(i[0]) == '(') { + if (i[0] < end && descriptor.charAt(i[0]) == '(') { ++i[0]; // skip '(' - while (i[0] < end && str.charAt(i[0]) != ')') { - Class pt = parseSig(str, i, end, loader); + while (i[0] < end && descriptor.charAt(i[0]) != ')') { + Class pt = parseSig(descriptor, i, end, loader); if (pt == null || pt == void.class) - parseError(str, "bad argument type"); + parseError(descriptor, "bad argument type"); ptypes.add(pt); } ++i[0]; // skip ')' } else { - parseError(str, "not a method type"); + parseError(descriptor, "not a method type"); } - Class rtype = parseSig(str, i, end, loader); + Class rtype = parseSig(descriptor, i, end, loader); if (rtype == null || i[0] != end) - parseError(str, "bad return type"); + parseError(descriptor, "bad return type"); ptypes.add(rtype); return ptypes; } @@ -115,8 +106,22 @@ public class BytecodeDescriptor { if (i[0] == end) return null; char c = str.charAt(i[0]++); if (c == 'L') { - int begc = i[0], endc = str.indexOf(';', begc); - if (endc < 0) return null; + int begc = i[0]; + int identifierStart = begc; + int endc; + while (true) { + int next = nextNonIdentifier(str, identifierStart, end); + if (identifierStart == next || next >= end) return null; // Empty name segment, or the end + char ch = str.charAt(next); + if (ch == ';') { + endc = next; + break; + } else if (ch == '/') { + identifierStart = next + 1; // Next name segment + } else { + return null; // Bad char [ or . + } + } i[0] = endc+1; String name = str.substring(begc, endc).replace('/', '.'); try { @@ -148,6 +153,23 @@ public class BytecodeDescriptor { } } + private static final int CHECK_OFFSET = 32; + private static final long NON_IDENTIFIER_MASK = (1L << ('.' - CHECK_OFFSET)) + | (1L << ('/' - CHECK_OFFSET)) + | (1L << (';' - CHECK_OFFSET)) + | (1L << ('[' - CHECK_OFFSET)); + + private static int nextNonIdentifier(String str, int index, int end) { + while (index < end) { + int check = str.charAt(index) - CHECK_OFFSET; + if ((check & -Long.SIZE) == 0 && (NON_IDENTIFIER_MASK & (1L << check)) != 0) { + break; + } + index++; + } + return index; + } + public static String unparse(Class type) { if (type == Object.class) { return "Ljava/lang/Object;"; diff --git a/src/java.base/share/classes/sun/security/provider/JavaKeyStore.java b/src/java.base/share/classes/sun/security/provider/JavaKeyStore.java index 3e771c015f4..73ca0c6bf16 100644 --- a/src/java.base/share/classes/sun/security/provider/JavaKeyStore.java +++ b/src/java.base/share/classes/sun/security/provider/JavaKeyStore.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -627,6 +627,10 @@ public abstract sealed class JavaKeyStore extends KeyStoreSpi { dos.write(digest); dos.flush(); + + if (debug != null) { + emitWeakKeyStoreWarning(); + } } } @@ -790,6 +794,10 @@ public abstract sealed class JavaKeyStore extends KeyStoreSpi { privateKeyCount + ". trusted key count: " + trustedKeyCount); } + if (debug != null) { + emitWeakKeyStoreWarning(); + } + /* * If a password has been provided, we check the keyed digest * at the end. If this check fails, the store has been tampered @@ -838,4 +846,16 @@ public abstract sealed class JavaKeyStore extends KeyStoreSpi { } return passwdBytes; } + + private void emitWeakKeyStoreWarning() { + String type = this.getClass().getSimpleName(). + toUpperCase(Locale.ROOT); + if (type.equals("JKS")){ + debug.println("WARNING: JKS uses outdated cryptographic " + + "algorithms and will be removed in a future " + + "release. Migrate to PKCS12 using:"); + debug.println("keytool -importkeystore -srckeystore " + + "-destkeystore -deststoretype pkcs12"); + } + } } diff --git a/src/java.base/share/classes/sun/security/tools/keytool/resources/keytool.properties b/src/java.base/share/classes/sun/security/tools/keytool/resources/keytool.properties index fb03573a7d5..751e3af9c9c 100644 --- a/src/java.base/share/classes/sun/security/tools/keytool/resources/keytool.properties +++ b/src/java.base/share/classes/sun/security/tools/keytool/resources/keytool.properties @@ -321,7 +321,8 @@ Unable.to.parse.denyAfter.string.in.exception.message=Unable to parse denyAfter whose.sigalg.weak=%1$s uses the %2$s signature algorithm which is considered a security risk. whose.key.disabled=%1$s uses a %2$s which is considered a security risk and is disabled. whose.key.weak=%1$s uses a %2$s which is considered a security risk. It will be disabled in a future update. -jks.storetype.warning=The %1$s keystore uses a proprietary format. It is recommended to migrate to PKCS12 which is an industry standard format using "keytool -importkeystore -srckeystore %2$s -destkeystore %2$s -deststoretype pkcs12". +jks.storetype.warning=%1$s uses outdated cryptographic algorithms and will be removed in a future release. Migrate to PKCS12 using:\n\ +keytool -importkeystore -srckeystore %2$s -destkeystore %2$s -deststoretype pkcs12 migrate.keystore.warning=Migrated "%1$s" to %4$s. The %2$s keystore is backed up as "%3$s". backup.keystore.warning=The original keystore "%1$s" is backed up as "%3$s"... importing.keystore.status=Importing keystore %1$s to %2$s... diff --git a/src/java.base/share/classes/sun/security/util/SliceableSecretKey.java b/src/java.base/share/classes/sun/security/util/SliceableSecretKey.java new file mode 100644 index 00000000000..4dc3fe0a3e8 --- /dev/null +++ b/src/java.base/share/classes/sun/security/util/SliceableSecretKey.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package sun.security.util; + +import javax.crypto.SecretKey; + +/** + * An interface for SecretKeys that support using its slice as a new + * SecretKey. + *

+ * This is mainly used by PKCS #11 implementations that support the + * EXTRACT_KEY_FROM_KEY mechanism even if the key itself is sensitive + * and non-extractable. + */ +public interface SliceableSecretKey { + + /** + * Returns a slice as a new SecretKey. + * + * @param alg the new algorithm name + * @param from the byte offset of the new key in the full key + * @param to the to offset (exclusive) of the new key in the full key + * @return the new key + * @throws ArrayIndexOutOfBoundsException for improper from + * and to values + * @throws UnsupportedOperationException if slicing is not supported + */ + SecretKey slice(String alg, int from, int to); +} diff --git a/src/java.base/unix/native/libjava/java_props_md.c b/src/java.base/unix/native/libjava/java_props_md.c index 000eb4b953e..1cbc3665ebc 100644 --- a/src/java.base/unix/native/libjava/java_props_md.c +++ b/src/java.base/unix/native/libjava/java_props_md.c @@ -388,8 +388,12 @@ GetJavaProperties(JNIEnv *env) /* supported instruction sets */ { char list[258]; - sysinfo(SI_ISALIST, list, sizeof(list)); - sprops.cpu_isalist = strdup(list); + int ret = sysinfo(SI_ISALIST, list, sizeof(list)); + if (ret == 0) { + sprops.cpu_isalist = strdup(list); + } else { + sprops.cpu_isalist = NULL; + } } #else sprops.cpu_isalist = NULL; @@ -438,7 +442,7 @@ GetJavaProperties(JNIEnv *env) /* Determine the language, country, variant, and encoding from the host, * and store these in the user.language, user.country, user.variant and - * file.encoding system properties. */ + * native.encoding system properties. */ setlocale(LC_ALL, ""); if (ParseLocale(env, LC_CTYPE, &(sprops.format_language), diff --git a/src/java.base/windows/native/libjava/java_props_md.c b/src/java.base/windows/native/libjava/java_props_md.c index 75587a09d0b..e152dbe9bef 100644 --- a/src/java.base/windows/native/libjava/java_props_md.c +++ b/src/java.base/windows/native/libjava/java_props_md.c @@ -569,7 +569,7 @@ GetJavaProperties(JNIEnv* env) /* * user.language * user.script, user.country, user.variant (if user's environment specifies them) - * file.encoding + * native.encoding */ { /* diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/bmp/BMPImageReaderSpi.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/bmp/BMPImageReaderSpi.java index a3098bd9684..63cc1464460 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/bmp/BMPImageReaderSpi.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/bmp/BMPImageReaderSpi.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -61,6 +61,7 @@ public class BMPImageReaderSpi extends ImageReaderSpi { null, null); } + @Override public void onRegistration(ServiceRegistry registry, Class category) { if (registered) { @@ -69,10 +70,12 @@ public class BMPImageReaderSpi extends ImageReaderSpi { registered = true; } + @Override public String getDescription(Locale locale) { return "Standard BMP Image Reader"; } + @Override public boolean canDecodeInput(Object source) throws IOException { if (!(source instanceof ImageInputStream)) { return false; @@ -87,6 +90,7 @@ public class BMPImageReaderSpi extends ImageReaderSpi { return full && (b[0] == 0x42) && (b[1] == 0x4d); } + @Override public ImageReader createReaderInstance(Object extension) throws IIOException { return new BMPImageReader(this); diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/bmp/BMPImageWriterSpi.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/bmp/BMPImageWriterSpi.java index ae6a7f65a3b..736a1c2194e 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/bmp/BMPImageWriterSpi.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/bmp/BMPImageWriterSpi.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -66,10 +66,12 @@ public class BMPImageWriterSpi extends ImageWriterSpi { null, null); } + @Override public String getDescription(Locale locale) { return "Standard BMP Image Writer"; } + @Override public void onRegistration(ServiceRegistry registry, Class category) { if (registered) { @@ -79,6 +81,7 @@ public class BMPImageWriterSpi extends ImageWriterSpi { registered = true; } + @Override public boolean canEncodeImage(ImageTypeSpecifier type) { int dataType= type.getSampleModel().getDataType(); if (dataType < DataBuffer.TYPE_BYTE || dataType > DataBuffer.TYPE_INT) @@ -99,6 +102,7 @@ public class BMPImageWriterSpi extends ImageWriterSpi { return true; } + @Override public ImageWriter createWriterInstance(Object extension) throws IIOException { return new BMPImageWriter(this); diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/bmp/BMPMetadata.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/bmp/BMPMetadata.java index 5e5a4a52d35..48d3bb12f1c 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/bmp/BMPMetadata.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/bmp/BMPMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -94,10 +94,12 @@ public class BMPMetadata extends IIOMetadata implements BMPConstants { null, null); } + @Override public boolean isReadOnly() { return true; } + @Override public Node getAsTree(String formatName) { if (formatName.equals(nativeMetadataFormatName)) { return getNativeTree(); @@ -177,6 +179,7 @@ public class BMPMetadata extends IIOMetadata implements BMPConstants { } // Standard tree node methods + @Override protected IIOMetadataNode getStandardChromaNode() { if ((palette != null) && (paletteSize > 0)) { @@ -202,6 +205,7 @@ public class BMPMetadata extends IIOMetadata implements BMPConstants { return null; } + @Override protected IIOMetadataNode getStandardCompressionNode() { IIOMetadataNode node = new IIOMetadataNode("Compression"); @@ -212,6 +216,7 @@ public class BMPMetadata extends IIOMetadata implements BMPConstants { return node; } + @Override protected IIOMetadataNode getStandardDataNode() { IIOMetadataNode node = new IIOMetadataNode("Data"); @@ -230,6 +235,7 @@ public class BMPMetadata extends IIOMetadata implements BMPConstants { return node; } + @Override protected IIOMetadataNode getStandardDimensionNode() { if (yPixelsPerMeter > 0.0F && xPixelsPerMeter > 0.0F) { IIOMetadataNode node = new IIOMetadataNode("Dimension"); @@ -251,14 +257,17 @@ public class BMPMetadata extends IIOMetadata implements BMPConstants { return null; } + @Override public void setFromTree(String formatName, Node root) { throw new IllegalStateException(I18N.getString("BMPMetadata1")); } + @Override public void mergeTree(String formatName, Node root) { throw new IllegalStateException(I18N.getString("BMPMetadata1")); } + @Override public void reset() { throw new IllegalStateException(I18N.getString("BMPMetadata1")); } diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/bmp/BMPMetadataFormat.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/bmp/BMPMetadataFormat.java index b9ea0ee0fe3..277169a34e0 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/bmp/BMPMetadataFormat.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/bmp/BMPMetadataFormat.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2004, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -194,6 +194,7 @@ public class BMPMetadataFormat extends IIOMetadataFormatImpl { DATATYPE_STRING, true, null); } + @Override public boolean canNodeAppear(String elementName, ImageTypeSpecifier imageType) { return true; diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/bmp/BMPMetadataFormatResources.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/bmp/BMPMetadataFormatResources.java index 1f2f444acd7..4d6c4fe49ec 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/bmp/BMPMetadataFormatResources.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/bmp/BMPMetadataFormatResources.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2005, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -33,6 +33,7 @@ public class BMPMetadataFormatResources extends ListResourceBundle { public BMPMetadataFormatResources() {} + @Override protected Object[][] getContents() { return new Object[][] { diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/common/BogusColorSpace.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/common/BogusColorSpace.java index 5a064f4f4c5..107ee313b70 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/common/BogusColorSpace.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/common/BogusColorSpace.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2014, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -79,6 +79,7 @@ public class BogusColorSpace extends ColorSpace { // specified behavior of the methods vis-a-vis exceptions. // + @Override public float[] toRGB(float[] colorvalue) { if(colorvalue.length < getNumComponents()) { throw new ArrayIndexOutOfBoundsException @@ -93,6 +94,7 @@ public class BogusColorSpace extends ColorSpace { return rgbvalue; } + @Override public float[] fromRGB(float[] rgbvalue) { if(rgbvalue.length < 3) { throw new ArrayIndexOutOfBoundsException @@ -107,6 +109,7 @@ public class BogusColorSpace extends ColorSpace { return colorvalue; } + @Override public float[] toCIEXYZ(float[] colorvalue) { if(colorvalue.length < getNumComponents()) { throw new ArrayIndexOutOfBoundsException @@ -121,6 +124,7 @@ public class BogusColorSpace extends ColorSpace { return xyzvalue; } + @Override public float[] fromCIEXYZ(float[] xyzvalue) { if(xyzvalue.length < 3) { throw new ArrayIndexOutOfBoundsException diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/common/InputStreamAdapter.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/common/InputStreamAdapter.java index 1b63a54d8ff..b11ffeb6287 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/common/InputStreamAdapter.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/common/InputStreamAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -39,10 +39,12 @@ public class InputStreamAdapter extends InputStream { this.stream = stream; } + @Override public int read() throws IOException { return stream.read(); } + @Override public int read(byte[] b, int off, int len) throws IOException { return stream.read(b, off, len); } diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/common/SimpleCMYKColorSpace.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/common/SimpleCMYKColorSpace.java index a5c283e6c9c..1662d09984d 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/common/SimpleCMYKColorSpace.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/common/SimpleCMYKColorSpace.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -58,14 +58,17 @@ public final class SimpleCMYKColorSpace extends ColorSpace { csRGB = ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB); } + @Override public boolean equals(Object o) { return o instanceof SimpleCMYKColorSpace; } + @Override public int hashCode() { return System.identityHashCode(theInstance); } + @Override public float[] toRGB(float[] colorvalue) { float C = colorvalue[0]; float M = colorvalue[1]; @@ -97,6 +100,7 @@ public final class SimpleCMYKColorSpace extends ColorSpace { return rgbvalue; } + @Override public float[] fromRGB(float[] rgbvalue) { // Convert from sRGB to linear RGB. for (int i = 0; i < 3; i++) { @@ -128,10 +132,12 @@ public final class SimpleCMYKColorSpace extends ColorSpace { return new float[] {C, M, Y, K}; } + @Override public float[] toCIEXYZ(float[] colorvalue) { return csRGB.toCIEXYZ(toRGB(colorvalue)); } + @Override public float[] fromCIEXYZ(float[] xyzvalue) { return fromRGB(csRGB.fromCIEXYZ(xyzvalue)); } diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/common/SimpleRenderedImage.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/common/SimpleRenderedImage.java index 1d8c850b4a9..f598aeb06a1 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/common/SimpleRenderedImage.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/common/SimpleRenderedImage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -74,6 +74,7 @@ public abstract class SimpleRenderedImage implements RenderedImage { protected Hashtable properties = new Hashtable(); /** Returns the X coordinate of the leftmost column of the image. */ + @Override public int getMinX() { return minX; } @@ -89,6 +90,7 @@ public abstract class SimpleRenderedImage implements RenderedImage { } /** Returns the X coordinate of the uppermost row of the image. */ + @Override public int getMinY() { return minY; } @@ -104,11 +106,13 @@ public abstract class SimpleRenderedImage implements RenderedImage { } /** Returns the width of the image. */ + @Override public int getWidth() { return width; } /** Returns the height of the image. */ + @Override public int getHeight() { return height; } @@ -119,11 +123,13 @@ public abstract class SimpleRenderedImage implements RenderedImage { } /** Returns the width of a tile. */ + @Override public int getTileWidth() { return tileWidth; } /** Returns the height of a tile. */ + @Override public int getTileHeight() { return tileHeight; } @@ -131,6 +137,7 @@ public abstract class SimpleRenderedImage implements RenderedImage { /** * Returns the X coordinate of the upper-left pixel of tile (0, 0). */ + @Override public int getTileGridXOffset() { return tileGridXOffset; } @@ -138,6 +145,7 @@ public abstract class SimpleRenderedImage implements RenderedImage { /** * Returns the Y coordinate of the upper-left pixel of tile (0, 0). */ + @Override public int getTileGridYOffset() { return tileGridYOffset; } @@ -147,6 +155,7 @@ public abstract class SimpleRenderedImage implements RenderedImage { * getMinTileX() is implemented in terms of getMinX() * and so does not need to be implemented by subclasses. */ + @Override public int getMinTileX() { return XToTileX(getMinX()); } @@ -166,6 +175,7 @@ public abstract class SimpleRenderedImage implements RenderedImage { * of getMinTileX() and getMaxTileX() and so does not need to be * implemented by subclasses. */ + @Override public int getNumXTiles() { return getMaxTileX() - getMinTileX() + 1; } @@ -175,6 +185,7 @@ public abstract class SimpleRenderedImage implements RenderedImage { * is implemented in terms of getMinY() and so does not need to be * implemented by subclasses. */ + @Override public int getMinTileY() { return YToTileY(getMinY()); } @@ -194,16 +205,19 @@ public abstract class SimpleRenderedImage implements RenderedImage { * of getMinTileY() and getMaxTileY() and so does not need to be * implemented by subclasses. */ + @Override public int getNumYTiles() { return getMaxTileY() - getMinTileY() + 1; } /** Returns the SampleModel of the image. */ + @Override public SampleModel getSampleModel() { return sampleModel; } /** Returns the ColorModel of the image. */ + @Override public ColorModel getColorModel() { return colorModel; } @@ -218,6 +232,7 @@ public abstract class SimpleRenderedImage implements RenderedImage { * Object, or the value * java.awt.Image.UndefinedProperty. */ + @Override public Object getProperty(String name) { name = name.toLowerCase(); Object value = properties.get(name); @@ -232,6 +247,7 @@ public abstract class SimpleRenderedImage implements RenderedImage { * @return an array of Strings representing valid * property names. */ + @Override public String[] getPropertyNames() { String[] names = null; @@ -379,6 +395,7 @@ public abstract class SimpleRenderedImage implements RenderedImage { return ty*tileHeight + tileGridYOffset; } + @Override public Vector getSources() { return null; } @@ -399,6 +416,7 @@ public abstract class SimpleRenderedImage implements RenderedImage { * * @return a Raster containing a copy of this image's data. */ + @Override public Raster getData() { Rectangle rect = new Rectangle(getMinX(), getMinY(), getWidth(), getHeight()); @@ -422,6 +440,7 @@ public abstract class SimpleRenderedImage implements RenderedImage { * * @param bounds the region of the RenderedImage to be returned. */ + @Override public Raster getData(Rectangle bounds) { // Get the image bounds. Rectangle imageBounds = getBounds(); @@ -511,6 +530,7 @@ public abstract class SimpleRenderedImage implements RenderedImage { * @return a reference to the supplied WritableRaster, or to a * new WritableRaster if the supplied one was null. */ + @Override public WritableRaster copyData(WritableRaster dest) { // Get the image bounds. Rectangle imageBounds = getBounds(); diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/common/SingleTileRenderedImage.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/common/SingleTileRenderedImage.java index 0cbdefa9127..315270e5916 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/common/SingleTileRenderedImage.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/common/SingleTileRenderedImage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -58,6 +58,7 @@ public class SingleTileRenderedImage extends SimpleRenderedImage { /** * Returns the image's Raster as tile (0, 0). */ + @Override public Raster getTile(int tileX, int tileY) { if (tileX != 0 || tileY != 0) { throw new IllegalArgumentException("tileX != 0 || tileY != 0"); diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/common/StandardMetadataFormat.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/common/StandardMetadataFormat.java index d880c507cba..05fa72f24ae 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/common/StandardMetadataFormat.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/common/StandardMetadataFormat.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -492,6 +492,7 @@ public class StandardMetadataFormat extends IIOMetadataFormatImpl { null); } + @Override public boolean canNodeAppear(String elementName, ImageTypeSpecifier imageType) { return true; diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/common/StandardMetadataFormatResources.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/common/StandardMetadataFormatResources.java index 4a94450d003..20e81dd76fa 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/common/StandardMetadataFormatResources.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/common/StandardMetadataFormatResources.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,6 +31,7 @@ public class StandardMetadataFormatResources extends ListResourceBundle { public StandardMetadataFormatResources() {} + @Override protected Object[][] getContents() { return new Object[][] { diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/common/SubImageInputStream.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/common/SubImageInputStream.java index 5d6bd401dc6..487f678c1eb 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/common/SubImageInputStream.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/common/SubImageInputStream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -43,6 +43,7 @@ public final class SubImageInputStream extends ImageInputStreamImpl { this.startingLength = this.length = length; } + @Override public int read() throws IOException { if (length == 0) { // Local EOF return -1; @@ -52,6 +53,7 @@ public final class SubImageInputStream extends ImageInputStreamImpl { } } + @Override public int read(byte[] b, int off, int len) throws IOException { if (length == 0) { // Local EOF return -1; @@ -63,10 +65,12 @@ public final class SubImageInputStream extends ImageInputStreamImpl { return bytes; } + @Override public long length() { return startingLength; } + @Override public void seek(long pos) throws IOException { stream.seek(pos - startingPos); streamPos = pos; diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFImageMetadata.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFImageMetadata.java index 061f3b1eaf0..bf2768af4e1 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFImageMetadata.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFImageMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -115,10 +115,12 @@ public class GIFImageMetadata extends GIFMetadata { null, null); } + @Override public boolean isReadOnly() { return true; } + @Override public Node getAsTree(String formatName) { if (formatName.equals(nativeMetadataFormatName)) { return getNativeTree(); @@ -252,6 +254,7 @@ public class GIFImageMetadata extends GIFMetadata { return root; } + @Override public IIOMetadataNode getStandardChromaNode() { IIOMetadataNode chroma_node = new IIOMetadataNode("Chroma"); IIOMetadataNode node = null; // scratch node @@ -294,6 +297,7 @@ public class GIFImageMetadata extends GIFMetadata { return chroma_node; } + @Override public IIOMetadataNode getStandardCompressionNode() { IIOMetadataNode compression_node = new IIOMetadataNode("Compression"); IIOMetadataNode node = null; // scratch node @@ -315,6 +319,7 @@ public class GIFImageMetadata extends GIFMetadata { return compression_node; } + @Override public IIOMetadataNode getStandardDataNode() { IIOMetadataNode data_node = new IIOMetadataNode("Data"); IIOMetadataNode node = null; // scratch node @@ -332,6 +337,7 @@ public class GIFImageMetadata extends GIFMetadata { return data_node; } + @Override public IIOMetadataNode getStandardDimensionNode() { IIOMetadataNode dimension_node = new IIOMetadataNode("Dimension"); IIOMetadataNode node = null; // scratch node @@ -365,6 +371,7 @@ public class GIFImageMetadata extends GIFMetadata { // Document not in image + @Override public IIOMetadataNode getStandardTextNode() { if (comments == null) { return null; @@ -391,6 +398,7 @@ public class GIFImageMetadata extends GIFMetadata { return text_node; } + @Override public IIOMetadataNode getStandardTransparencyNode() { if (!transparentColorFlag) { return null; @@ -414,22 +422,26 @@ public class GIFImageMetadata extends GIFMetadata { return transparency_node; } + @Override public void setFromTree(String formatName, Node root) throws IIOInvalidTreeException { throw new IllegalStateException("Metadata is read-only!"); } + @Override protected void mergeNativeTree(Node root) throws IIOInvalidTreeException { throw new IllegalStateException("Metadata is read-only!"); } + @Override protected void mergeStandardTree(Node root) throws IIOInvalidTreeException { throw new IllegalStateException("Metadata is read-only!"); } + @Override public void reset() { throw new IllegalStateException("Metadata is read-only!"); } diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFImageMetadataFormat.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFImageMetadataFormat.java index fa55d72bbda..5a6a85a2b57 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFImageMetadataFormat.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFImageMetadataFormat.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -157,6 +157,7 @@ public class GIFImageMetadataFormat extends IIOMetadataFormatImpl { DATATYPE_STRING, true, null); } + @Override public boolean canNodeAppear(String elementName, ImageTypeSpecifier imageType) { return true; diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFImageMetadataFormatResources.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFImageMetadataFormatResources.java index aa9697a8ee9..f9a29cdebba 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFImageMetadataFormatResources.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFImageMetadataFormatResources.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2005, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,6 +31,7 @@ public class GIFImageMetadataFormatResources extends ListResourceBundle { public GIFImageMetadataFormatResources() {} + @Override protected Object[][] getContents() { return new Object[][] { diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFImageReaderSpi.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFImageReaderSpi.java index 96a5ba436aa..9484484abf8 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFImageReaderSpi.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFImageReaderSpi.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -71,10 +71,12 @@ public class GIFImageReaderSpi extends ImageReaderSpi { ); } + @Override public String getDescription(Locale locale) { return "Standard GIF image reader"; } + @Override public boolean canDecodeInput(Object input) throws IOException { if (!(input instanceof ImageInputStream)) { return false; @@ -91,6 +93,7 @@ public class GIFImageReaderSpi extends ImageReaderSpi { (b[4] == '7' || b[4] == '9') && b[5] == 'a'; } + @Override public ImageReader createReaderInstance(Object extension) { return new GIFImageReader(this); } diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFImageWriterSpi.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFImageWriterSpi.java index 0f1b8aec102..6da6332eb50 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFImageWriterSpi.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFImageWriterSpi.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -73,6 +73,7 @@ public class GIFImageWriterSpi extends ImageWriterSpi { ); } + @Override public boolean canEncodeImage(ImageTypeSpecifier type) { if (type == null) { throw new IllegalArgumentException("type == null!"); @@ -94,10 +95,12 @@ public class GIFImageWriterSpi extends ImageWriterSpi { } } + @Override public String getDescription(Locale locale) { return "Standard GIF image writer"; } + @Override public ImageWriter createWriterInstance(Object extension) { return new GIFImageWriter(this); } diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFMetadata.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFMetadata.java index 5fb4dc007b7..dca232352d9 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFMetadata.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -242,6 +242,7 @@ abstract class GIFMetadata extends IIOMetadata { extraMetadataFormatClassNames); } + @Override public void mergeTree(String formatName, Node root) throws IIOInvalidTreeException { if (formatName.equals(nativeMetadataFormatName)) { diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFStreamMetadata.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFStreamMetadata.java index 31c1305b58a..b34e96907ee 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFStreamMetadata.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFStreamMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -77,10 +77,12 @@ public class GIFStreamMetadata extends GIFMetadata { } + @Override public boolean isReadOnly() { return true; } + @Override public Node getAsTree(String formatName) { if (formatName.equals(nativeMetadataFormatName)) { return getNativeTree(); @@ -149,6 +151,7 @@ public class GIFStreamMetadata extends GIFMetadata { return root; } + @Override public IIOMetadataNode getStandardChromaNode() { IIOMetadataNode chroma_node = new IIOMetadataNode("Chroma"); IIOMetadataNode node = null; // scratch node @@ -190,6 +193,7 @@ public class GIFStreamMetadata extends GIFMetadata { return chroma_node; } + @Override public IIOMetadataNode getStandardCompressionNode() { IIOMetadataNode compression_node = new IIOMetadataNode("Compression"); IIOMetadataNode node = null; // scratch node @@ -208,6 +212,7 @@ public class GIFStreamMetadata extends GIFMetadata { return compression_node; } + @Override public IIOMetadataNode getStandardDataNode() { IIOMetadataNode data_node = new IIOMetadataNode("Data"); IIOMetadataNode node = null; // scratch node @@ -230,6 +235,7 @@ public class GIFStreamMetadata extends GIFMetadata { return data_node; } + @Override public IIOMetadataNode getStandardDimensionNode() { IIOMetadataNode dimension_node = new IIOMetadataNode("Dimension"); IIOMetadataNode node = null; // scratch node @@ -270,6 +276,7 @@ public class GIFStreamMetadata extends GIFMetadata { return dimension_node; } + @Override public IIOMetadataNode getStandardDocumentNode() { IIOMetadataNode document_node = new IIOMetadataNode("Document"); IIOMetadataNode node = null; // scratch node @@ -285,32 +292,38 @@ public class GIFStreamMetadata extends GIFMetadata { return document_node; } + @Override public IIOMetadataNode getStandardTextNode() { // Not in stream return null; } + @Override public IIOMetadataNode getStandardTransparencyNode() { // Not in stream return null; } + @Override public void setFromTree(String formatName, Node root) throws IIOInvalidTreeException { throw new IllegalStateException("Metadata is read-only!"); } + @Override protected void mergeNativeTree(Node root) throws IIOInvalidTreeException { throw new IllegalStateException("Metadata is read-only!"); } + @Override protected void mergeStandardTree(Node root) throws IIOInvalidTreeException { throw new IllegalStateException("Metadata is read-only!"); } + @Override public void reset() { throw new IllegalStateException("Metadata is read-only!"); } diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFStreamMetadataFormat.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFStreamMetadataFormat.java index b598674ccb3..fcdb8b0d13e 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFStreamMetadataFormat.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFStreamMetadataFormat.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2004, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -92,6 +92,7 @@ public class GIFStreamMetadataFormat extends IIOMetadataFormatImpl { "0", "255", true, true); } + @Override public boolean canNodeAppear(String elementName, ImageTypeSpecifier imageType) { return true; diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFStreamMetadataFormatResources.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFStreamMetadataFormatResources.java index 0404b2c88da..65e9f89c557 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFStreamMetadataFormatResources.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFStreamMetadataFormatResources.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2005, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,6 +31,7 @@ public class GIFStreamMetadataFormatResources extends ListResourceBundle { public GIFStreamMetadataFormatResources() {} + @Override protected Object[][] getContents() { return new Object[][] { diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFWritableImageMetadata.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFWritableImageMetadata.java index a3e8a769490..cec4f354ee0 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFWritableImageMetadata.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFWritableImageMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -49,10 +49,12 @@ class GIFWritableImageMetadata extends GIFImageMetadata { null, null); } + @Override public boolean isReadOnly() { return false; } + @Override public void reset() { // Fields from Image Descriptor imageLeftPosition = 0; @@ -96,6 +98,7 @@ class GIFWritableImageMetadata extends GIFImageMetadata { return data.getBytes(ISO_8859_1); } + @Override protected void mergeNativeTree(Node root) throws IIOInvalidTreeException { Node node = root; if (!node.getNodeName().equals(nativeMetadataFormatName)) { @@ -292,6 +295,7 @@ class GIFWritableImageMetadata extends GIFImageMetadata { } } + @Override protected void mergeStandardTree(Node root) throws IIOInvalidTreeException { Node node = root; @@ -389,6 +393,7 @@ class GIFWritableImageMetadata extends GIFImageMetadata { } } + @Override public void setFromTree(String formatName, Node root) throws IIOInvalidTreeException { diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFWritableStreamMetadata.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFWritableStreamMetadata.java index 8a9a6d865a8..d8863cb4a2d 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFWritableStreamMetadata.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFWritableStreamMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -55,10 +55,12 @@ class GIFWritableStreamMetadata extends GIFStreamMetadata { reset(); } + @Override public boolean isReadOnly() { return false; } + @Override public void mergeTree(String formatName, Node root) throws IIOInvalidTreeException { if (formatName.equals(nativeMetadataFormatName)) { @@ -77,6 +79,7 @@ class GIFWritableStreamMetadata extends GIFStreamMetadata { } } + @Override public void reset() { version = null; @@ -90,6 +93,7 @@ class GIFWritableStreamMetadata extends GIFStreamMetadata { globalColorTable = null; } + @Override protected void mergeNativeTree(Node root) throws IIOInvalidTreeException { Node node = root; if (!node.getNodeName().equals(nativeMetadataFormatName)) { @@ -164,6 +168,7 @@ class GIFWritableStreamMetadata extends GIFStreamMetadata { } } + @Override protected void mergeStandardTree(Node root) throws IIOInvalidTreeException { Node node = root; @@ -258,6 +263,7 @@ class GIFWritableStreamMetadata extends GIFStreamMetadata { } } + @Override public void setFromTree(String formatName, Node root) throws IIOInvalidTreeException { diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/AdobeMarkerSegment.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/AdobeMarkerSegment.java index 551f4dc4d72..9b1fbd27fc4 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/AdobeMarkerSegment.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/AdobeMarkerSegment.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -73,6 +73,7 @@ class AdobeMarkerSegment extends MarkerSegment { updateFromNativeNode(node, true); } + @Override IIOMetadataNode getNativeNode() { IIOMetadataNode node = new IIOMetadataNode("app14Adobe"); node.setAttribute("version", Integer.toString(version)); @@ -108,6 +109,7 @@ class AdobeMarkerSegment extends MarkerSegment { * Writes the data for this segment to the stream in * valid JPEG format. */ + @Override void write(ImageOutputStream ios) throws IOException { length = 14; writeTag(ios); @@ -124,6 +126,7 @@ class AdobeMarkerSegment extends MarkerSegment { (new AdobeMarkerSegment(transform)).write(ios); } + @Override void print () { printTag("Adobe APP14"); System.out.print("Version: "); diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/COMMarkerSegment.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/COMMarkerSegment.java index f28e7b35658..f45bded991a 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/COMMarkerSegment.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/COMMarkerSegment.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -104,6 +104,7 @@ class COMMarkerSegment extends MarkerSegment { * as a user object and a string encoded using ISO-8895-1, as an * attribute. */ + @Override IIOMetadataNode getNativeNode() { IIOMetadataNode node = new IIOMetadataNode("com"); node.setAttribute("comment", getComment()); @@ -117,12 +118,14 @@ class COMMarkerSegment extends MarkerSegment { * Writes the data for this segment to the stream in * valid JPEG format, directly from the data array. */ + @Override void write(ImageOutputStream ios) throws IOException { length = 2 + data.length; writeTag(ios); ios.write(data); } + @Override void print() { printTag("COM"); System.out.println("<" + getComment() + ">"); diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/DHTMarkerSegment.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/DHTMarkerSegment.java index c14402522db..634a266354d 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/DHTMarkerSegment.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/DHTMarkerSegment.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -90,6 +90,7 @@ class DHTMarkerSegment extends MarkerSegment { } } + @Override protected Object clone() { DHTMarkerSegment newGuy = (DHTMarkerSegment) super.clone(); newGuy.tables = new ArrayList<>(tables.size()); @@ -99,6 +100,7 @@ class DHTMarkerSegment extends MarkerSegment { return newGuy; } + @Override IIOMetadataNode getNativeNode() { IIOMetadataNode node = new IIOMetadataNode("dht"); for (int i= 0; i(tables.size()); @@ -92,6 +93,7 @@ class DQTMarkerSegment extends MarkerSegment { return newGuy; } + @Override IIOMetadataNode getNativeNode() { IIOMetadataNode node = new IIOMetadataNode("dqt"); for (int i= 0; i MAX_THUMB_WIDTH) @@ -949,6 +960,7 @@ class JFIFMarkerSegment extends MarkerSegment { writeThumbnailData(ios, data, writer); } + @Override void print() { System.out.print(name + " width: "); System.out.println(thumbWidth); @@ -978,10 +990,12 @@ class JFIFMarkerSegment extends MarkerSegment { super(thumb); } + @Override int getLength() { return (thumbWidth*thumbHeight*3); } + @Override BufferedImage getThumbnail(ImageInputStream iis, JPEGImageReader reader) throws IOException { @@ -1014,6 +1028,7 @@ class JFIFMarkerSegment extends MarkerSegment { null); } + @Override void write(ImageOutputStream ios, JPEGImageWriter writer) throws IOException { super.write(ios, writer); // width and height @@ -1050,10 +1065,12 @@ class JFIFMarkerSegment extends MarkerSegment { } } + @Override int getLength() { return (thumbWidth*thumbHeight + PALETTE_SIZE); } + @Override BufferedImage getThumbnail(ImageInputStream iis, JPEGImageReader reader) throws IOException { @@ -1091,6 +1108,7 @@ class JFIFMarkerSegment extends MarkerSegment { null); } + @Override void write(ImageOutputStream ios, JPEGImageWriter writer) throws IOException { super.write(ios, writer); // width and height @@ -1221,6 +1239,7 @@ class JFIFMarkerSegment extends MarkerSegment { } } + @Override int getWidth() { int retval = 0; SOFMarkerSegment sof = @@ -1232,6 +1251,7 @@ class JFIFMarkerSegment extends MarkerSegment { return retval; } + @Override int getHeight() { int retval = 0; SOFMarkerSegment sof = @@ -1249,21 +1269,31 @@ class JFIFMarkerSegment extends MarkerSegment { ThumbnailReadListener (JPEGImageReader reader) { this.reader = reader; } + @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) { reader.thumbnailProgress(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) {} } + @Override BufferedImage getThumbnail(ImageInputStream iis, JPEGImageReader reader) throws IOException { @@ -1279,6 +1309,7 @@ class JFIFMarkerSegment extends MarkerSegment { return ret; } + @Override protected Object clone() { JFIFThumbJPEG newGuy = (JFIFThumbJPEG) super.clone(); if (thumbMetadata != null) { @@ -1287,6 +1318,7 @@ class JFIFMarkerSegment extends MarkerSegment { return newGuy; } + @Override IIOMetadataNode getNativeNode() { IIOMetadataNode node = new IIOMetadataNode("JFIFthumbJPEG"); if (thumbMetadata != null) { @@ -1295,6 +1327,7 @@ class JFIFMarkerSegment extends MarkerSegment { return node; } + @Override int getLength() { if (data == null) { return 0; @@ -1303,6 +1336,7 @@ class JFIFMarkerSegment extends MarkerSegment { } } + @Override void write(ImageOutputStream ios, JPEGImageWriter writer) throws IOException { int progInterval = data.length / 20; // approx. every 5% @@ -1322,6 +1356,7 @@ class JFIFMarkerSegment extends MarkerSegment { } } + @Override void print () { System.out.println("JFIF thumbnail stored as JPEG"); } @@ -1445,6 +1480,7 @@ class JFIFMarkerSegment extends MarkerSegment { } } + @Override protected Object clone () { ICCMarkerSegment newGuy = (ICCMarkerSegment) super.clone(); if (profile != null) { @@ -1541,6 +1577,7 @@ class JFIFMarkerSegment extends MarkerSegment { return retval; } + @Override IIOMetadataNode getNativeNode() { IIOMetadataNode node = new IIOMetadataNode("app2ICC"); if (profile != null) { @@ -1553,10 +1590,12 @@ class JFIFMarkerSegment extends MarkerSegment { * No-op. Profiles are never written from metadata. * They are written from the ColorSpace of the image. */ + @Override void write(ImageOutputStream ios) throws IOException { // No-op } + @Override void print () { printTag("ICC Profile APP2"); } diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageMetadataFormat.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageMetadataFormat.java index 4c858a47ab1..20b94fb339b 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageMetadataFormat.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageMetadataFormat.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -337,6 +337,7 @@ public class JPEGImageMetadataFormat extends JPEGMetadataFormat { tabids); } + @Override public boolean canNodeAppear(String elementName, ImageTypeSpecifier imageType) { // All images can have these diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageMetadataFormatResources.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageMetadataFormatResources.java index e242a355a17..3cbc0054c85 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageMetadataFormatResources.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageMetadataFormatResources.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2005, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -122,6 +122,7 @@ public class JPEGImageMetadataFormatResources public JPEGImageMetadataFormatResources() {} + @Override protected Object[][] getContents() { // return a copy of the combined commonContents and imageContents; // in theory we want a deep clone of the combined arrays, diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageReaderResources.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageReaderResources.java index e4b09197aa7..be6fc32a761 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageReaderResources.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageReaderResources.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2005, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,6 +31,7 @@ public class JPEGImageReaderResources extends ListResourceBundle { public JPEGImageReaderResources() {} + @Override protected Object[][] getContents() { return new Object[][] { diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageReaderSpi.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageReaderSpi.java index 450dd89b6f4..ab942ea8de2 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageReaderSpi.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageReaderSpi.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2004, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -59,10 +59,12 @@ public class JPEGImageReaderSpi extends ImageReaderSpi { ); } + @Override public String getDescription(Locale locale) { return "Standard JPEG Image Reader"; } + @Override public boolean canDecodeInput(Object source) throws IOException { if (!(source instanceof ImageInputStream)) { return false; @@ -80,6 +82,7 @@ public class JPEGImageReaderSpi extends ImageReaderSpi { return false; } + @Override public ImageReader createReaderInstance(Object extension) throws IIOException { return new JPEGImageReader(this); diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageWriterResources.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageWriterResources.java index 3db20b3d39d..3b2a149f6b4 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageWriterResources.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageWriterResources.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2005, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,6 +31,7 @@ public class JPEGImageWriterResources extends ListResourceBundle { public JPEGImageWriterResources() {} + @Override protected Object[][] getContents() { return new Object[][] { diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageWriterSpi.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageWriterSpi.java index bd6bcc8d784..d6f5abbbccb 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageWriterSpi.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageWriterSpi.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2004, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -63,14 +63,17 @@ public class JPEGImageWriterSpi extends ImageWriterSpi { ); } + @Override public String getDescription(Locale locale) { return "Standard JPEG Image Writer"; } + @Override public boolean isFormatLossless() { return false; } + @Override public boolean canEncodeImage(ImageTypeSpecifier type) { SampleModel sampleModel = type.getSampleModel(); ColorModel cm = type.getColorModel(); @@ -95,6 +98,7 @@ public class JPEGImageWriterSpi extends ImageWriterSpi { return true; } + @Override public ImageWriter createWriterInstance(Object extension) throws IIOException { return new JPEGImageWriter(this); diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGMetadata.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGMetadata.java index c7ad982b35a..3081fd43404 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGMetadata.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGMetadata.java @@ -725,6 +725,7 @@ public class JPEGMetadata extends IIOMetadata implements Cloneable { // Implement Cloneable, but restrict access + @Override protected Object clone() { JPEGMetadata newGuy = null; try { @@ -755,6 +756,7 @@ public class JPEGMetadata extends IIOMetadata implements Cloneable { // Tree methods + @Override public Node getAsTree(String formatName) { if (formatName == null) { throw new IllegalArgumentException("null formatName!"); @@ -810,6 +812,7 @@ public class JPEGMetadata extends IIOMetadata implements Cloneable { // Standard tree node methods + @Override protected IIOMetadataNode getStandardChromaNode() { hasAlpha = false; // Unless we find otherwise @@ -950,6 +953,7 @@ public class JPEGMetadata extends IIOMetadata implements Cloneable { return chroma; } + @Override protected IIOMetadataNode getStandardCompressionNode() { IIOMetadataNode compression = new IIOMetadataNode("Compression"); @@ -980,6 +984,7 @@ public class JPEGMetadata extends IIOMetadata implements Cloneable { return compression; } + @Override protected IIOMetadataNode getStandardDimensionNode() { // If we have a JFIF marker segment, we know a little // otherwise all we know is the orientation, which is always normal @@ -1055,6 +1060,7 @@ public class JPEGMetadata extends IIOMetadata implements Cloneable { return doc; } + @Override protected IIOMetadataNode getStandardTextNode() { IIOMetadataNode text = null; // Add a text entry for each COM Marker Segment @@ -1073,6 +1079,7 @@ public class JPEGMetadata extends IIOMetadata implements Cloneable { return text; } + @Override protected IIOMetadataNode getStandardTransparencyNode() { IIOMetadataNode trans = null; if (hasAlpha == true) { @@ -1086,10 +1093,12 @@ public class JPEGMetadata extends IIOMetadata implements Cloneable { // Editing + @Override public boolean isReadOnly() { return false; } + @Override public void mergeTree(String formatName, Node root) throws IIOInvalidTreeException { if (formatName == null) { @@ -2160,6 +2169,7 @@ public class JPEGMetadata extends IIOMetadata implements Cloneable { } + @Override public void setFromTree(String formatName, Node root) throws IIOInvalidTreeException { if (formatName == null) { @@ -2404,6 +2414,7 @@ public class JPEGMetadata extends IIOMetadata implements Cloneable { //// End of writer support + @Override public void reset() { if (resetSequence != null) { // Otherwise no need to reset markerSequence = resetSequence; diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGMetadataFormat.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGMetadataFormat.java index 475cc36359e..53b30f333f0 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGMetadataFormat.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGMetadataFormat.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -123,6 +123,7 @@ abstract class JPEGMetadataFormat extends IIOMetadataFormatImpl { addObjectValue("unknown", byte[].class, 1, MAX_JPEG_DATA_SIZE); } + @Override public boolean canNodeAppear(String elementName, ImageTypeSpecifier imageType) { // Just check if it appears in the format diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGStreamMetadataFormatResources.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGStreamMetadataFormatResources.java index ea6d2b70138..5e29101e83b 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGStreamMetadataFormatResources.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGStreamMetadataFormatResources.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2005, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -32,6 +32,7 @@ public class JPEGStreamMetadataFormatResources public JPEGStreamMetadataFormatResources() {} + @Override protected Object[][] getContents() { // return a copy of commonContents; in theory we want a deep clone // of commonContents, but since it only contains (immutable) Strings, diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/MarkerSegment.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/MarkerSegment.java index f4ba27b0fcd..74bf598c4d5 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/MarkerSegment.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/MarkerSegment.java @@ -110,6 +110,7 @@ class MarkerSegment implements Cloneable { /** * Deep copy of data array. */ + @Override protected Object clone() { MarkerSegment newGuy = null; try { diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/SOFMarkerSegment.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/SOFMarkerSegment.java index e5b7e861924..fa7acba3325 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/SOFMarkerSegment.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/SOFMarkerSegment.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -95,6 +95,7 @@ class SOFMarkerSegment extends MarkerSegment { updateFromNativeNode(node, true); } + @Override protected Object clone() { SOFMarkerSegment newGuy = (SOFMarkerSegment) super.clone(); if (componentSpecs != null) { @@ -107,6 +108,7 @@ class SOFMarkerSegment extends MarkerSegment { return newGuy; } + @Override IIOMetadataNode getNativeNode() { IIOMetadataNode node = new IIOMetadataNode("sof"); node.setAttribute("process", Integer.toString(tag-JPEG.SOF0)); @@ -154,10 +156,12 @@ class SOFMarkerSegment extends MarkerSegment { * Writes the data for this segment to the stream in * valid JPEG format. */ + @Override void write(ImageOutputStream ios) throws IOException { // We don't write SOF segments; the IJG library does. } + @Override void print () { printTag("SOF"); System.out.print("Sample precision: "); @@ -231,6 +235,7 @@ class SOFMarkerSegment extends MarkerSegment { 0, 3, true); } + @Override protected Object clone() { try { return super.clone(); diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/SOSMarkerSegment.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/SOSMarkerSegment.java index f40acdd0375..a34fc43486a 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/SOSMarkerSegment.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/SOSMarkerSegment.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -89,6 +89,7 @@ class SOSMarkerSegment extends MarkerSegment { updateFromNativeNode(node, true); } + @Override protected Object clone () { SOSMarkerSegment newGuy = (SOSMarkerSegment) super.clone(); if (componentSpecs != null) { @@ -101,6 +102,7 @@ class SOSMarkerSegment extends MarkerSegment { return newGuy; } + @Override IIOMetadataNode getNativeNode() { IIOMetadataNode node = new IIOMetadataNode("sos"); node.setAttribute("numScanComponents", @@ -152,10 +154,12 @@ class SOSMarkerSegment extends MarkerSegment { * Writes the data for this segment to the stream in * valid JPEG format. */ + @Override void write(ImageOutputStream ios) throws IOException { // We don't write SOS segments; the IJG library does. } + @Override void print () { printTag("SOS"); System.out.print("Start spectral selection: "); @@ -208,6 +212,7 @@ class SOSMarkerSegment extends MarkerSegment { 0, 3, true); } + @Override protected Object clone() { try { return super.clone(); diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/png/PNGImageReaderSpi.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/png/PNGImageReaderSpi.java index 60105e30f8a..bf0576ecb74 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/png/PNGImageReaderSpi.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/png/PNGImageReaderSpi.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -70,10 +70,12 @@ public class PNGImageReaderSpi extends ImageReaderSpi { ); } + @Override public String getDescription(Locale locale) { return "Standard PNG image reader"; } + @Override public boolean canDecodeInput(Object input) throws IOException { if (!(input instanceof ImageInputStream)) { return false; @@ -96,6 +98,7 @@ public class PNGImageReaderSpi extends ImageReaderSpi { b[7] == (byte)10); } + @Override public ImageReader createReaderInstance(Object extension) { return new PNGImageReader(this); } diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/png/PNGImageWriterSpi.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/png/PNGImageWriterSpi.java index 44080a05a16..3ce07aa4ba5 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/png/PNGImageWriterSpi.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/png/PNGImageWriterSpi.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -74,6 +74,7 @@ public class PNGImageWriterSpi extends ImageWriterSpi { ); } + @Override public boolean canEncodeImage(ImageTypeSpecifier type) { SampleModel sampleModel = type.getSampleModel(); ColorModel colorModel = type.getColorModel(); @@ -116,10 +117,12 @@ public class PNGImageWriterSpi extends ImageWriterSpi { return true; } + @Override public String getDescription(Locale locale) { return "Standard PNG image writer"; } + @Override public ImageWriter createWriterInstance(Object extension) { return new PNGImageWriter(this); } diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/png/PNGMetadata.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/png/PNGMetadata.java index 03d00f7ae8e..730294c9f01 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/png/PNGMetadata.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/png/PNGMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -463,6 +463,7 @@ public class PNGMetadata extends IIOMetadata implements Cloneable { IHDR_present = true; } + @Override public boolean isReadOnly() { return false; } @@ -480,6 +481,7 @@ public class PNGMetadata extends IIOMetadata implements Cloneable { } // Deep clone + @Override public Object clone() { PNGMetadata metadata; try { @@ -495,6 +497,7 @@ public class PNGMetadata extends IIOMetadata implements Cloneable { return metadata; } + @Override public Node getAsTree(String formatName) { if (formatName.equals(nativeMetadataFormatName)) { return getNativeTree(); @@ -847,6 +850,7 @@ public class PNGMetadata extends IIOMetadata implements Cloneable { return numChannels; } + @Override public IIOMetadataNode getStandardChromaNode() { IIOMetadataNode chroma_node = new IIOMetadataNode("Chroma"); IIOMetadataNode node = null; // scratch node @@ -919,6 +923,7 @@ public class PNGMetadata extends IIOMetadata implements Cloneable { return chroma_node; } + @Override public IIOMetadataNode getStandardCompressionNode() { IIOMetadataNode compression_node = new IIOMetadataNode("Compression"); IIOMetadataNode node = null; // scratch node @@ -952,6 +957,7 @@ public class PNGMetadata extends IIOMetadata implements Cloneable { return sb.toString(); } + @Override public IIOMetadataNode getStandardDataNode() { IIOMetadataNode data_node = new IIOMetadataNode("Data"); IIOMetadataNode node = null; // scratch node @@ -998,6 +1004,7 @@ public class PNGMetadata extends IIOMetadata implements Cloneable { return data_node; } + @Override public IIOMetadataNode getStandardDimensionNode() { IIOMetadataNode dimension_node = new IIOMetadataNode("Dimension"); IIOMetadataNode node = null; // scratch node @@ -1027,6 +1034,7 @@ public class PNGMetadata extends IIOMetadata implements Cloneable { return dimension_node; } + @Override public IIOMetadataNode getStandardDocumentNode() { IIOMetadataNode document_node = null; @@ -1067,6 +1075,7 @@ public class PNGMetadata extends IIOMetadata implements Cloneable { return document_node; } + @Override public IIOMetadataNode getStandardTextNode() { int numEntries = tEXt_keyword.size() + iTXt_keyword.size() + zTXt_keyword.size(); @@ -1114,6 +1123,7 @@ public class PNGMetadata extends IIOMetadata implements Cloneable { return text_node; } + @Override public IIOMetadataNode getStandardTransparencyNode() { IIOMetadataNode transparency_node = new IIOMetadataNode("Transparency"); @@ -1285,6 +1295,7 @@ public class PNGMetadata extends IIOMetadata implements Cloneable { return getAttribute(node, name, null, true); } + @Override public void mergeTree(String formatName, Node root) throws IIOInvalidTreeException { if (formatName.equals(nativeMetadataFormatName)) { @@ -2267,6 +2278,7 @@ public class PNGMetadata extends IIOMetadata implements Cloneable { } // Reset all instance variables to their initial state + @Override public void reset() { IHDR_present = false; PLTE_present = false; diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/png/PNGMetadataFormat.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/png/PNGMetadataFormat.java index b7d96dc3f2b..1518d097903 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/png/PNGMetadataFormat.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/png/PNGMetadataFormat.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2004, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -486,6 +486,7 @@ public class PNGMetadataFormat extends IIOMetadataFormatImpl { addObjectValue("UnknownChunk", byte.class, 0, Integer.MAX_VALUE); } + @Override public boolean canNodeAppear(String elementName, ImageTypeSpecifier imageType) { return true; diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/png/PNGMetadataFormatResources.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/png/PNGMetadataFormatResources.java index d99946bd890..818a2364e23 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/png/PNGMetadataFormatResources.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/png/PNGMetadataFormatResources.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2005, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,6 +31,7 @@ public class PNGMetadataFormatResources extends ListResourceBundle { public PNGMetadataFormatResources() {} + @Override protected Object[][] getContents() { return new Object[][] { diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFBaseJPEGCompressor.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFBaseJPEGCompressor.java index d5adb4e8d35..31806941e35 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFBaseJPEGCompressor.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFBaseJPEGCompressor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -335,6 +335,7 @@ public abstract class TIFFBaseJPEGCompressor extends TIFFCompressor { return JPEGImageMetadata; } + @Override public final int encode(byte[] b, int off, int width, int height, int[] bitsPerSample, diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFCIELabColorConverter.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFCIELabColorConverter.java index bc20c57da69..dbddc94516e 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFCIELabColorConverter.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFCIELabColorConverter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -56,6 +56,7 @@ public class TIFFCIELabColorConverter extends TIFFColorConverter { } } + @Override public void fromRGB(float r, float g, float b, float[] result) { float X = 0.412453f*r + 0.357580f*g + 0.180423f*b; float Y = 0.212671f*r + 0.715160f*g + 0.072169f*b; @@ -100,6 +101,7 @@ public class TIFFCIELabColorConverter extends TIFFColorConverter { result[2] = clamp2(bStar); } + @Override public void toRGB(float x0, float x1, float x2, float[] rgb) { float LStar = x0*100.0f/255.0f; float aStar = (x1 > 128.0f) ? (x1 - 256.0f) : x1; diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFDeflateDecompressor.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFDeflateDecompressor.java index 1ce7d56c1c7..5ade4bacfb4 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFDeflateDecompressor.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFDeflateDecompressor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -48,6 +48,7 @@ public class TIFFDeflateDecompressor extends TIFFDecompressor { this.predictor = predictor; } + @Override public synchronized void decodeRaw(byte[] b, int dstOffset, int bitsPerPixel, diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFDeflater.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFDeflater.java index 0fde32a229f..3a5a10245df 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFDeflater.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFDeflater.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -58,6 +58,7 @@ public class TIFFDeflater extends TIFFCompressor { this.deflater = new Deflater(deflateLevel); } + @Override public int encode(byte[] b, int off, int width, int height, int[] bitsPerSample, diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFExifJPEGCompressor.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFExifJPEGCompressor.java index 3541ce110db..992cba6cbe5 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFExifJPEGCompressor.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFExifJPEGCompressor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -39,6 +39,7 @@ public class TIFFExifJPEGCompressor extends TIFFBaseJPEGCompressor { param); } + @Override public void setMetadata(IIOMetadata metadata) { // Set the metadata. super.setMetadata(metadata); diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFFaxCompressor.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFFaxCompressor.java index 5c126acab53..2af59c682c4 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFFaxCompressor.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFFaxCompressor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -242,6 +242,7 @@ abstract class TIFFFaxCompressor extends TIFFCompressor { * * @see #getMetadata() */ + @Override public void setMetadata(IIOMetadata metadata) { super.setMetadata(metadata); diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFFaxDecompressor.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFFaxDecompressor.java index a1947dc6b72..64839c033da 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFFaxDecompressor.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFFaxDecompressor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -591,6 +591,7 @@ class TIFFFaxDecompressor extends TIFFDecompressor { * Invokes the superclass method and then sets instance variables on * the basis of the metadata set on this decompressor. */ + @Override public void beginDecoding() { super.beginDecoding(); @@ -627,6 +628,7 @@ class TIFFFaxDecompressor extends TIFFDecompressor { } } + @Override public void decodeRaw(byte[] b, int dstOffset, int pixelBitStride, // will always be 1 int scanlineStride) throws IOException { diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFFieldNode.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFFieldNode.java index 6f23fbad4f2..e8878d93315 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFFieldNode.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFFieldNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -162,6 +162,7 @@ public class TIFFFieldNode extends IIOMetadataNode { // Need to override this method to avoid a stack overflow exception // which will occur if super.appendChild is called from initialize(). + @Override public Node appendChild(Node newChild) { if (newChild == null) { throw new NullPointerException("newChild == null!"); @@ -172,53 +173,63 @@ public class TIFFFieldNode extends IIOMetadataNode { // Override all methods which refer to child nodes. + @Override public boolean hasChildNodes() { initialize(); return super.hasChildNodes(); } + @Override public int getLength() { initialize(); return super.getLength(); } + @Override public Node getFirstChild() { initialize(); return super.getFirstChild(); } + @Override public Node getLastChild() { initialize(); return super.getLastChild(); } + @Override public Node getPreviousSibling() { initialize(); return super.getPreviousSibling(); } + @Override public Node getNextSibling() { initialize(); return super.getNextSibling(); } + @Override public Node insertBefore(Node newChild, Node refChild) { initialize(); return super.insertBefore(newChild, refChild); } + @Override public Node replaceChild(Node newChild, Node oldChild) { initialize(); return super.replaceChild(newChild, oldChild); } + @Override public Node removeChild(Node oldChild) { initialize(); return super.removeChild(oldChild); } + @Override public Node cloneNode(boolean deep) { initialize(); return super.cloneNode(deep); diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageMetadata.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageMetadata.java index 82ae068d7e4..a3cf39505e6 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageMetadata.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -93,6 +93,7 @@ public class TIFFImageMetadata extends IIOMetadata { rootIFD.addTIFFField(field); } + @Override public boolean isReadOnly() { return false; } @@ -149,6 +150,7 @@ public class TIFFImageMetadata extends IIOMetadata { return IFDRoot; } + @Override public Node getAsTree(String formatName) { if (formatName.equals(nativeMetadataFormatName)) { return getNativeTree(); @@ -181,6 +183,7 @@ public class TIFFImageMetadata extends IIOMetadata { "Lab", // ICCLab }; + @Override public IIOMetadataNode getStandardChromaNode() { IIOMetadataNode chroma_node = new IIOMetadataNode("Chroma"); IIOMetadataNode node = null; // scratch node @@ -278,6 +281,7 @@ public class TIFFImageMetadata extends IIOMetadata { return chroma_node; } + @Override public IIOMetadataNode getStandardCompressionNode() { IIOMetadataNode compression_node = new IIOMetadataNode("Compression"); IIOMetadataNode node = null; // scratch node @@ -336,6 +340,7 @@ public class TIFFImageMetadata extends IIOMetadata { return sb.toString(); } + @Override public IIOMetadataNode getStandardDataNode() { IIOMetadataNode data_node = new IIOMetadataNode("Data"); IIOMetadataNode node = null; // scratch node @@ -476,6 +481,7 @@ public class TIFFImageMetadata extends IIOMetadata { "Rotate90", }; + @Override public IIOMetadataNode getStandardDimensionNode() { IIOMetadataNode dimension_node = new IIOMetadataNode("Dimension"); IIOMetadataNode node = null; // scratch node @@ -604,6 +610,7 @@ public class TIFFImageMetadata extends IIOMetadata { return dimension_node; } + @Override public IIOMetadataNode getStandardDocumentNode() { IIOMetadataNode document_node = new IIOMetadataNode("Document"); IIOMetadataNode node = null; // scratch node @@ -669,6 +676,7 @@ public class TIFFImageMetadata extends IIOMetadata { return document_node; } + @Override public IIOMetadataNode getStandardTextNode() { IIOMetadataNode text_node = null; IIOMetadataNode node = null; // scratch node @@ -705,6 +713,7 @@ public class TIFFImageMetadata extends IIOMetadata { return text_node; } + @Override public IIOMetadataNode getStandardTransparencyNode() { IIOMetadataNode transparency_node = new IIOMetadataNode("Transparency"); @@ -1579,6 +1588,7 @@ public class TIFFImageMetadata extends IIOMetadata { } } + @Override public void mergeTree(String formatName, Node root) throws IIOInvalidTreeException{ if (formatName.equals(nativeMetadataFormatName)) { @@ -1597,6 +1607,7 @@ public class TIFFImageMetadata extends IIOMetadata { } } + @Override public void reset() { rootIFD = new TIFFIFD(tagSets); } diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageMetadataFormat.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageMetadataFormat.java index 59c425ec517..edfa3a5be9c 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageMetadataFormat.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageMetadataFormat.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -34,6 +34,7 @@ public class TIFFImageMetadataFormat extends TIFFMetadataFormat { static { } + @Override public boolean canNodeAppear(String elementName, ImageTypeSpecifier imageType) { return false; diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageMetadataFormatResources.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageMetadataFormatResources.java index 070836e8854..59e68d0dfe2 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageMetadataFormatResources.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageMetadataFormatResources.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -96,6 +96,7 @@ public class TIFFImageMetadataFormatResources extends ListResourceBundle { public TIFFImageMetadataFormatResources() { } + @Override public Object[][] getContents() { return contents.clone(); } diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageReaderSpi.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageReaderSpi.java index 2332e4be2d1..ef21e0b2042 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageReaderSpi.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageReaderSpi.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -56,10 +56,12 @@ public class TIFFImageReaderSpi extends ImageReaderSpi { ); } + @Override public String getDescription(Locale locale) { return "Standard TIFF image reader"; } + @Override public boolean canDecodeInput(Object input) throws IOException { if (!(input instanceof ImageInputStream)) { return false; @@ -78,10 +80,12 @@ public class TIFFImageReaderSpi extends ImageReaderSpi { b[2] == (byte)0x00 && b[3] == (byte)0x2a)); } + @Override public ImageReader createReaderInstance(Object extension) { return new TIFFImageReader(this); } + @Override public void onRegistration(ServiceRegistry registry, Class category) { if (registered) { diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageWriterSpi.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageWriterSpi.java index 0b93cbedc0b..a742db6f6bb 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageWriterSpi.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageWriterSpi.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -55,18 +55,22 @@ public class TIFFImageWriterSpi extends ImageWriterSpi { ); } + @Override public boolean canEncodeImage(ImageTypeSpecifier type) { return true; } + @Override public String getDescription(Locale locale) { return "Standard TIFF image writer"; } + @Override public ImageWriter createWriterInstance(Object extension) { return new TIFFImageWriter(this); } + @Override public void onRegistration(ServiceRegistry registry, Class category) { if (registered) { diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFJPEGCompressor.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFJPEGCompressor.java index f524f34e32e..e73ccb2b282 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFJPEGCompressor.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFJPEGCompressor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -54,6 +54,7 @@ public class TIFFJPEGCompressor extends TIFFBaseJPEGCompressor { private static class JPEGSPIFilter implements ServiceRegistry.Filter { JPEGSPIFilter() {} + @Override public boolean filter(Object provider) { ImageReaderSpi readerSPI = (ImageReaderSpi)provider; @@ -112,6 +113,7 @@ public class TIFFJPEGCompressor extends TIFFBaseJPEGCompressor { * * @see #getMetadata() */ + @Override public void setMetadata(IIOMetadata metadata) { super.setMetadata(metadata); diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFJPEGDecompressor.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFJPEGDecompressor.java index 3c2ce905958..7353ccea56f 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFJPEGDecompressor.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFJPEGDecompressor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -54,6 +54,7 @@ public class TIFFJPEGDecompressor extends TIFFDecompressor { public TIFFJPEGDecompressor() {} + @Override public void beginDecoding() { // Initialize the JPEG reader if needed. if(this.JPEGReader == null) { @@ -90,6 +91,7 @@ public class TIFFJPEGDecompressor extends TIFFDecompressor { } } + @Override public void decodeRaw(byte[] b, int dstOffset, int bitsPerPixel, diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFLSBCompressor.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFLSBCompressor.java index b7bceb89ae4..5052ffedbff 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFLSBCompressor.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFLSBCompressor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -36,6 +36,7 @@ public class TIFFLSBCompressor extends TIFFCompressor { super("", BaselineTIFFTagSet.COMPRESSION_NONE, true); } + @Override public int encode(byte[] b, int off, int width, int height, int[] bitsPerSample, diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFLSBDecompressor.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFLSBDecompressor.java index d871b6a3e86..6724b779334 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFLSBDecompressor.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFLSBDecompressor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -35,6 +35,7 @@ public class TIFFLSBDecompressor extends TIFFDecompressor { public TIFFLSBDecompressor() {} + @Override public void decodeRaw(byte[] b, int dstOffset, int bitsPerPixel, diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFLZWCompressor.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFLZWCompressor.java index 4b2b945acb7..6780d65053f 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFLZWCompressor.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFLZWCompressor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -41,10 +41,12 @@ public class TIFFLZWCompressor extends TIFFCompressor { this.predictor = predictorValue; } + @Override public void setStream(ImageOutputStream stream) { super.setStream(stream); } + @Override public int encode(byte[] b, int off, int width, int height, int[] bitsPerSample, diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFLZWDecompressor.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFLZWDecompressor.java index fc682589ce2..dcf5b2a7f71 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFLZWDecompressor.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFLZWDecompressor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -76,6 +76,7 @@ class TIFFLZWDecompressor extends TIFFDecompressor { flipBits = fillOrder == BaselineTIFFTagSet.FILL_ORDER_RIGHT_TO_LEFT; } + @Override public void decodeRaw(byte[] b, int dstOffset, int bitsPerPixel, diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFMetadataFormat.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFMetadataFormat.java index 1091cab9bb2..92d15bfdcb4 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFMetadataFormat.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFMetadataFormat.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -39,6 +39,7 @@ public abstract class TIFFMetadataFormat implements IIOMetadataFormat { protected String resourceBaseName; protected String rootName; + @Override public String getRootName() { return rootName; } @@ -85,16 +86,19 @@ public abstract class TIFFMetadataFormat implements IIOMetadataFormat { return info; } + @Override public int getElementMinChildren(String elementName) { TIFFElementInfo info = getElementInfo(elementName); return info.minChildren; } + @Override public int getElementMaxChildren(String elementName) { TIFFElementInfo info = getElementInfo(elementName); return info.maxChildren; } + @Override public String getElementDescription(String elementName, Locale locale) { if (!elementInfoMap.containsKey(elementName)) { throw new IllegalArgumentException("No such element: " + @@ -103,64 +107,77 @@ public abstract class TIFFMetadataFormat implements IIOMetadataFormat { return getResource(elementName, locale); } + @Override public int getChildPolicy(String elementName) { TIFFElementInfo info = getElementInfo(elementName); return info.childPolicy; } + @Override public String[] getChildNames(String elementName) { TIFFElementInfo info = getElementInfo(elementName); return info.childNames; } + @Override public String[] getAttributeNames(String elementName) { TIFFElementInfo info = getElementInfo(elementName); return info.attributeNames; } + @Override public int getAttributeValueType(String elementName, String attrName) { TIFFAttrInfo info = getAttrInfo(elementName, attrName); return info.valueType; } + @Override public int getAttributeDataType(String elementName, String attrName) { TIFFAttrInfo info = getAttrInfo(elementName, attrName); return info.dataType; } + @Override public boolean isAttributeRequired(String elementName, String attrName) { TIFFAttrInfo info = getAttrInfo(elementName, attrName); return info.isRequired; } + @Override public String getAttributeDefaultValue(String elementName, String attrName) { return null; } + @Override public String[] getAttributeEnumerations(String elementName, String attrName) { throw new IllegalArgumentException("The attribute is not an enumeration."); } + @Override public String getAttributeMinValue(String elementName, String attrName) { throw new IllegalArgumentException("The attribute is not a range."); } + @Override public String getAttributeMaxValue(String elementName, String attrName) { throw new IllegalArgumentException("The attribute is not a range."); } + @Override public int getAttributeListMinLength(String elementName, String attrName) { TIFFAttrInfo info = getAttrInfo(elementName, attrName); return info.listMinLength; } + @Override public int getAttributeListMaxLength(String elementName, String attrName) { TIFFAttrInfo info = getAttrInfo(elementName, attrName); return info.listMaxLength; } + @Override public String getAttributeDescription(String elementName, String attrName, Locale locale) { String key = elementName + "/" + attrName; @@ -170,11 +187,13 @@ public abstract class TIFFMetadataFormat implements IIOMetadataFormat { return getResource(key, locale); } + @Override public int getObjectValueType(String elementName) { TIFFElementInfo info = getElementInfo(elementName); return info.objectValueType; } + @Override public Class getObjectClass(String elementName) { TIFFElementInfo info = getElementInfo(elementName); if (info.objectValueType == VALUE_NONE) { @@ -184,6 +203,7 @@ public abstract class TIFFMetadataFormat implements IIOMetadataFormat { return info.objectClass; } + @Override public Object getObjectDefaultValue(String elementName) { TIFFElementInfo info = getElementInfo(elementName); if (info.objectValueType == VALUE_NONE) { @@ -193,6 +213,7 @@ public abstract class TIFFMetadataFormat implements IIOMetadataFormat { return info.objectDefaultValue; } + @Override public Object[] getObjectEnumerations(String elementName) { TIFFElementInfo info = getElementInfo(elementName); if (info.objectValueType == VALUE_NONE) { @@ -202,6 +223,7 @@ public abstract class TIFFMetadataFormat implements IIOMetadataFormat { return info.objectEnumerations; } + @Override public Comparable getObjectMinValue(String elementName) { TIFFElementInfo info = getElementInfo(elementName); if (info.objectValueType == VALUE_NONE) { @@ -211,6 +233,7 @@ public abstract class TIFFMetadataFormat implements IIOMetadataFormat { return info.objectMinValue; } + @Override public Comparable getObjectMaxValue(String elementName) { TIFFElementInfo info = getElementInfo(elementName); if (info.objectValueType == VALUE_NONE) { @@ -220,6 +243,7 @@ public abstract class TIFFMetadataFormat implements IIOMetadataFormat { return info.objectMaxValue; } + @Override public int getObjectArrayMinLength(String elementName) { TIFFElementInfo info = getElementInfo(elementName); if (info.objectValueType == VALUE_NONE) { @@ -229,6 +253,7 @@ public abstract class TIFFMetadataFormat implements IIOMetadataFormat { return info.objectArrayMinLength; } + @Override public int getObjectArrayMaxLength(String elementName) { TIFFElementInfo info = getElementInfo(elementName); if (info.objectValueType == VALUE_NONE) { diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFNullCompressor.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFNullCompressor.java index fc366f791f7..8f6428035d9 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFNullCompressor.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFNullCompressor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -33,6 +33,7 @@ public class TIFFNullCompressor extends TIFFCompressor { super("", BaselineTIFFTagSet.COMPRESSION_NONE, true); } + @Override public int encode(byte[] b, int off, int width, int height, int[] bitsPerSample, diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFNullDecompressor.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFNullDecompressor.java index 9b5a746eec1..b73580ef823 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFNullDecompressor.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFNullDecompressor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -56,6 +56,7 @@ public class TIFFNullDecompressor extends TIFFDecompressor { // change beginDecoding() and decode() to use the active region values // when random access is easy and the entire region values otherwise. // + @Override public void beginDecoding() { // Determine number of bits per pixel. int bitsPerPixel = 0; @@ -89,6 +90,7 @@ public class TIFFNullDecompressor extends TIFFDecompressor { super.beginDecoding(); } + @Override public void decode() throws IOException { super.decode(); @@ -105,6 +107,7 @@ public class TIFFNullDecompressor extends TIFFDecompressor { } } + @Override public void decodeRaw(byte[] b, int dstOffset, int bitsPerPixel, diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFOldJPEGDecompressor.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFOldJPEGDecompressor.java index 2935ca5caf8..77594ea8086 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFOldJPEGDecompressor.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFOldJPEGDecompressor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -545,6 +545,7 @@ public class TIFFOldJPEGDecompressor extends TIFFJPEGDecompressor { // The strategy for cases 4-5 is to concatenate a tables stream created // in initialize() with the entropy coded data in each strip or tile. // + @Override public void decodeRaw(byte[] b, int dstOffset, int bitsPerPixel, diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFPackBitsCompressor.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFPackBitsCompressor.java index 119e4ad4906..b2341f12ed7 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFPackBitsCompressor.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFPackBitsCompressor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -86,6 +86,7 @@ public class TIFFPackBitsCompressor extends TIFFCompressor { return outOffset; } + @Override public int encode(byte[] b, int off, int width, int height, int[] bitsPerSample, diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFPackBitsDecompressor.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFPackBitsDecompressor.java index 0a5dfe8308e..61026f57fed 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFPackBitsDecompressor.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFPackBitsDecompressor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -72,6 +72,7 @@ public class TIFFPackBitsDecompressor extends TIFFDecompressor { return dstIndex - dstOffset; } + @Override public void decodeRaw(byte[] b, int dstOffset, int bitsPerPixel, diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFRLECompressor.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFRLECompressor.java index 0bf59da81da..1e0270c6ecc 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFRLECompressor.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFRLECompressor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -87,6 +87,7 @@ public class TIFFRLECompressor extends TIFFFaxCompressor { return outIndex; } + @Override public int encode(byte[] b, int off, int width, int height, int[] bitsPerSample, diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFRenderedImage.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFRenderedImage.java index 77e0c9a109c..dadabac3d68 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFRenderedImage.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFRenderedImage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -130,74 +130,92 @@ public class TIFFRenderedImage implements RenderedImage { return newParam; } + @Override public Vector getSources() { return null; } + @Override public Object getProperty(String name) { return java.awt.Image.UndefinedProperty; } + @Override public String[] getPropertyNames() { return null; } + @Override public ColorModel getColorModel() { return its.getColorModel(); } + @Override public SampleModel getSampleModel() { return its.getSampleModel(); } + @Override public int getWidth() { return width; } + @Override public int getHeight() { return height; } + @Override public int getMinX() { return 0; } + @Override public int getMinY() { return 0; } + @Override public int getNumXTiles() { return (width + tileWidth - 1)/tileWidth; } + @Override public int getNumYTiles() { return (height + tileHeight - 1)/tileHeight; } + @Override public int getMinTileX() { return 0; } + @Override public int getMinTileY() { return 0; } + @Override public int getTileWidth() { return tileWidth; } + @Override public int getTileHeight() { return tileHeight; } + @Override public int getTileGridXOffset() { return 0; } + @Override public int getTileGridYOffset() { return 0; } + @Override public Raster getTile(int tileX, int tileY) { Rectangle tileRect = new Rectangle(tileX*tileWidth, tileY*tileHeight, @@ -206,10 +224,12 @@ public class TIFFRenderedImage implements RenderedImage { return getData(tileRect); } + @Override public Raster getData() { return read(new Rectangle(0, 0, getWidth(), getHeight())); } + @Override public Raster getData(Rectangle rect) { return read(rect); } @@ -236,6 +256,7 @@ public class TIFFRenderedImage implements RenderedImage { } } + @Override public WritableRaster copyData(WritableRaster raster) { if (raster == null) { return read(new Rectangle(0, 0, getWidth(), getHeight())); diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFStreamMetadata.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFStreamMetadata.java index 96592cc62fb..ac9398fd6e9 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFStreamMetadata.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFStreamMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -54,6 +54,7 @@ public class TIFFStreamMetadata extends IIOMetadata { null, null); } + @Override public boolean isReadOnly() { return false; } @@ -64,6 +65,7 @@ public class TIFFStreamMetadata extends IIOMetadata { throw new IIOInvalidTreeException(reason, node); } + @Override public Node getAsTree(String formatName) { IIOMetadataNode root = new IIOMetadataNode(nativeMetadataFormatName); @@ -103,6 +105,7 @@ public class TIFFStreamMetadata extends IIOMetadata { } } + @Override public void mergeTree(String formatName, Node root) throws IIOInvalidTreeException { if (formatName.equals(nativeMetadataFormatName)) { @@ -115,6 +118,7 @@ public class TIFFStreamMetadata extends IIOMetadata { } } + @Override public void reset() { this.byteOrder = ByteOrder.BIG_ENDIAN; } diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFStreamMetadataFormat.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFStreamMetadataFormat.java index bf35133811e..50a3d32e239 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFStreamMetadataFormat.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFStreamMetadataFormat.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,6 +31,7 @@ public class TIFFStreamMetadataFormat extends TIFFMetadataFormat { private static TIFFStreamMetadataFormat theInstance = null; + @Override public boolean canNodeAppear(String elementName, ImageTypeSpecifier imageType) { return false; diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFStreamMetadataFormatResources.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFStreamMetadataFormatResources.java index 1e0ea272200..75977c15a41 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFStreamMetadataFormatResources.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFStreamMetadataFormatResources.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -36,6 +36,7 @@ public class TIFFStreamMetadataFormatResources extends ListResourceBundle { public TIFFStreamMetadataFormatResources() { } + @Override public Object[][] getContents() { return contents.clone(); } diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFT4Compressor.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFT4Compressor.java index 55088e34dfe..99d45ce4866 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFT4Compressor.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFT4Compressor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -52,6 +52,7 @@ public class TIFFT4Compressor extends TIFFFaxCompressor { * * @see #getMetadata() */ + @Override public void setMetadata(IIOMetadata metadata) { super.setMetadata(metadata); @@ -214,6 +215,7 @@ public class TIFFT4Compressor extends TIFFFaxCompressor { return outIndex; } + @Override public int encode(byte[] b, int off, int width, int height, int[] bitsPerSample, diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFT6Compressor.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFT6Compressor.java index 517c23bde54..7e2625d87e2 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFT6Compressor.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFT6Compressor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -146,6 +146,7 @@ public class TIFFT6Compressor extends TIFFFaxCompressor { return outIndex; } + @Override public int encode(byte[] b, int off, int width, int height, int[] bitsPerSample, diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFYCbCrColorConverter.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFYCbCrColorConverter.java index a006a3a1e38..b5dae8a9298 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFYCbCrColorConverter.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFYCbCrColorConverter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -80,6 +80,7 @@ public class TIFFYCbCrColorConverter extends TIFFColorConverter { / CodingRange) + ReferenceBlack; */ + @Override public void fromRGB(float r, float g, float b, float[] result) { // Convert RGB to full-range YCbCr. float Y = (lumaRed*r + lumaGreen*g + lumaBlue*b); @@ -95,6 +96,7 @@ public class TIFFYCbCrColorConverter extends TIFFColorConverter { referenceBlackCr; } + @Override public void toRGB(float x0, float x1, float x2, float[] rgb) { // Convert YCbCr code to full-range YCbCr. float Y = (x0 - referenceBlackY)*CODING_RANGE_Y/ diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFYCbCrDecompressor.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFYCbCrDecompressor.java index 0f10904cab4..c0623885d34 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFYCbCrDecompressor.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFYCbCrDecompressor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -95,6 +95,7 @@ public class TIFFYCbCrDecompressor extends TIFFDecompressor { // "Chained" decompressor methods. // + @Override public void setReader(ImageReader reader) { if(decompressor != null) { decompressor.setReader(reader); @@ -102,6 +103,7 @@ public class TIFFYCbCrDecompressor extends TIFFDecompressor { super.setReader(reader); } + @Override public void setMetadata(IIOMetadata metadata) { if(decompressor != null) { decompressor.setMetadata(metadata); @@ -109,6 +111,7 @@ public class TIFFYCbCrDecompressor extends TIFFDecompressor { super.setMetadata(metadata); } + @Override public void setPhotometricInterpretation(int photometricInterpretation) { if(decompressor != null) { decompressor.setPhotometricInterpretation(photometricInterpretation); @@ -116,6 +119,7 @@ public class TIFFYCbCrDecompressor extends TIFFDecompressor { super.setPhotometricInterpretation(photometricInterpretation); } + @Override public void setCompression(int compression) { if(decompressor != null) { decompressor.setCompression(compression); @@ -123,6 +127,7 @@ public class TIFFYCbCrDecompressor extends TIFFDecompressor { super.setCompression(compression); } + @Override public void setPlanar(boolean planar) { if(decompressor != null) { decompressor.setPlanar(planar); @@ -130,6 +135,7 @@ public class TIFFYCbCrDecompressor extends TIFFDecompressor { super.setPlanar(planar); } + @Override public void setSamplesPerPixel(int samplesPerPixel) { if(decompressor != null) { decompressor.setSamplesPerPixel(samplesPerPixel); @@ -137,6 +143,7 @@ public class TIFFYCbCrDecompressor extends TIFFDecompressor { super.setSamplesPerPixel(samplesPerPixel); } + @Override public void setBitsPerSample(int[] bitsPerSample) { if(decompressor != null) { decompressor.setBitsPerSample(bitsPerSample); @@ -144,6 +151,7 @@ public class TIFFYCbCrDecompressor extends TIFFDecompressor { super.setBitsPerSample(bitsPerSample); } + @Override public void setSampleFormat(int[] sampleFormat) { if(decompressor != null) { decompressor.setSampleFormat(sampleFormat); @@ -151,6 +159,7 @@ public class TIFFYCbCrDecompressor extends TIFFDecompressor { super.setSampleFormat(sampleFormat); } + @Override public void setExtraSamples(int[] extraSamples) { if(decompressor != null) { decompressor.setExtraSamples(extraSamples); @@ -158,6 +167,7 @@ public class TIFFYCbCrDecompressor extends TIFFDecompressor { super.setExtraSamples(extraSamples); } + @Override public void setColorMap(char[] colorMap) { if(decompressor != null) { decompressor.setColorMap(colorMap); @@ -165,6 +175,7 @@ public class TIFFYCbCrDecompressor extends TIFFDecompressor { super.setColorMap(colorMap); } + @Override public void setStream(ImageInputStream stream) { if(decompressor != null) { decompressor.setStream(stream); @@ -173,6 +184,7 @@ public class TIFFYCbCrDecompressor extends TIFFDecompressor { } } + @Override public void setOffset(long offset) { if(decompressor != null) { decompressor.setOffset(offset); @@ -180,6 +192,7 @@ public class TIFFYCbCrDecompressor extends TIFFDecompressor { super.setOffset(offset); } + @Override public void setByteCount(int byteCount) throws IOException { if(decompressor != null) { decompressor.setByteCount(byteCount); @@ -187,6 +200,7 @@ public class TIFFYCbCrDecompressor extends TIFFDecompressor { super.setByteCount(byteCount); } + @Override public void setSrcMinX(int srcMinX) { if(decompressor != null) { decompressor.setSrcMinX(srcMinX); @@ -194,6 +208,7 @@ public class TIFFYCbCrDecompressor extends TIFFDecompressor { super.setSrcMinX(srcMinX); } + @Override public void setSrcMinY(int srcMinY) { if(decompressor != null) { decompressor.setSrcMinY(srcMinY); @@ -201,6 +216,7 @@ public class TIFFYCbCrDecompressor extends TIFFDecompressor { super.setSrcMinY(srcMinY); } + @Override public void setSrcWidth(int srcWidth) { if(decompressor != null) { decompressor.setSrcWidth(srcWidth); @@ -208,6 +224,7 @@ public class TIFFYCbCrDecompressor extends TIFFDecompressor { super.setSrcWidth(srcWidth); } + @Override public void setSrcHeight(int srcHeight) { if(decompressor != null) { decompressor.setSrcHeight(srcHeight); @@ -215,6 +232,7 @@ public class TIFFYCbCrDecompressor extends TIFFDecompressor { super.setSrcHeight(srcHeight); } + @Override public void setSourceXOffset(int sourceXOffset) { if(decompressor != null) { decompressor.setSourceXOffset(sourceXOffset); @@ -222,6 +240,7 @@ public class TIFFYCbCrDecompressor extends TIFFDecompressor { super.setSourceXOffset(sourceXOffset); } + @Override public void setDstXOffset(int dstXOffset) { if(decompressor != null) { decompressor.setDstXOffset(dstXOffset); @@ -229,6 +248,7 @@ public class TIFFYCbCrDecompressor extends TIFFDecompressor { super.setDstXOffset(dstXOffset); } + @Override public void setSourceYOffset(int sourceYOffset) { if(decompressor != null) { decompressor.setSourceYOffset(sourceYOffset); @@ -236,6 +256,7 @@ public class TIFFYCbCrDecompressor extends TIFFDecompressor { super.setSourceYOffset(sourceYOffset); } + @Override public void setDstYOffset(int dstYOffset) { if(decompressor != null) { decompressor.setDstYOffset(dstYOffset); @@ -260,6 +281,7 @@ public class TIFFYCbCrDecompressor extends TIFFDecompressor { } */ + @Override public void setSourceBands(int[] sourceBands) { if(decompressor != null) { decompressor.setSourceBands(sourceBands); @@ -267,6 +289,7 @@ public class TIFFYCbCrDecompressor extends TIFFDecompressor { super.setSourceBands(sourceBands); } + @Override public void setDestinationBands(int[] destinationBands) { if(decompressor != null) { decompressor.setDestinationBands(destinationBands); @@ -274,6 +297,7 @@ public class TIFFYCbCrDecompressor extends TIFFDecompressor { super.setDestinationBands(destinationBands); } + @Override public void setImage(BufferedImage image) { if(decompressor != null) { ColorModel cm = image.getColorModel(); @@ -287,6 +311,7 @@ public class TIFFYCbCrDecompressor extends TIFFDecompressor { super.setImage(image); } + @Override public void setDstMinX(int dstMinX) { if(decompressor != null) { decompressor.setDstMinX(dstMinX); @@ -294,6 +319,7 @@ public class TIFFYCbCrDecompressor extends TIFFDecompressor { super.setDstMinX(dstMinX); } + @Override public void setDstMinY(int dstMinY) { if(decompressor != null) { decompressor.setDstMinY(dstMinY); @@ -301,6 +327,7 @@ public class TIFFYCbCrDecompressor extends TIFFDecompressor { super.setDstMinY(dstMinY); } + @Override public void setDstWidth(int dstWidth) { if(decompressor != null) { decompressor.setDstWidth(dstWidth); @@ -308,6 +335,7 @@ public class TIFFYCbCrDecompressor extends TIFFDecompressor { super.setDstWidth(dstWidth); } + @Override public void setDstHeight(int dstHeight) { if(decompressor != null) { decompressor.setDstHeight(dstHeight); @@ -315,6 +343,7 @@ public class TIFFYCbCrDecompressor extends TIFFDecompressor { super.setDstHeight(dstHeight); } + @Override public void setActiveSrcMinX(int activeSrcMinX) { if(decompressor != null) { decompressor.setActiveSrcMinX(activeSrcMinX); @@ -322,6 +351,7 @@ public class TIFFYCbCrDecompressor extends TIFFDecompressor { super.setActiveSrcMinX(activeSrcMinX); } + @Override public void setActiveSrcMinY(int activeSrcMinY) { if(decompressor != null) { decompressor.setActiveSrcMinY(activeSrcMinY); @@ -329,6 +359,7 @@ public class TIFFYCbCrDecompressor extends TIFFDecompressor { super.setActiveSrcMinY(activeSrcMinY); } + @Override public void setActiveSrcWidth(int activeSrcWidth) { if(decompressor != null) { decompressor.setActiveSrcWidth(activeSrcWidth); @@ -336,6 +367,7 @@ public class TIFFYCbCrDecompressor extends TIFFDecompressor { super.setActiveSrcWidth(activeSrcWidth); } + @Override public void setActiveSrcHeight(int activeSrcHeight) { if(decompressor != null) { decompressor.setActiveSrcHeight(activeSrcHeight); @@ -353,6 +385,7 @@ public class TIFFYCbCrDecompressor extends TIFFDecompressor { } } + @Override public void beginDecoding() { if(decompressor != null) { decompressor.beginDecoding(); @@ -445,6 +478,7 @@ public class TIFFYCbCrDecompressor extends TIFFDecompressor { } } + @Override public void decodeRaw(byte[] buf, int dstOffset, int bitsPerPixel, diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/wbmp/WBMPImageReaderSpi.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/wbmp/WBMPImageReaderSpi.java index acff45d5b72..9cd967140c9 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/wbmp/WBMPImageReaderSpi.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/wbmp/WBMPImageReaderSpi.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -63,6 +63,7 @@ public class WBMPImageReaderSpi extends ImageReaderSpi { null, null); } + @Override public void onRegistration(ServiceRegistry registry, Class category) { if (registered) { @@ -71,10 +72,12 @@ public class WBMPImageReaderSpi extends ImageReaderSpi { registered = true; } + @Override public String getDescription(Locale locale) { return "Standard WBMP Image Reader"; } + @Override public boolean canDecodeInput(Object source) throws IOException { if (!(source instanceof ImageInputStream)) { return false; @@ -149,6 +152,7 @@ public class WBMPImageReaderSpi extends ImageReaderSpi { return result; } + @Override public ImageReader createReaderInstance(Object extension) throws IIOException { return new WBMPImageReader(this); diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/wbmp/WBMPImageWriterSpi.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/wbmp/WBMPImageWriterSpi.java index be2f2d96a38..d6d723c0110 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/wbmp/WBMPImageWriterSpi.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/wbmp/WBMPImageWriterSpi.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -63,10 +63,12 @@ public class WBMPImageWriterSpi extends ImageWriterSpi { null, null, null, null); } + @Override public String getDescription(Locale locale) { return "Standard WBMP Image Writer"; } + @Override public void onRegistration(ServiceRegistry registry, Class category) { if (registered) { @@ -76,6 +78,7 @@ public class WBMPImageWriterSpi extends ImageWriterSpi { registered = true; } + @Override public boolean canEncodeImage(ImageTypeSpecifier type) { SampleModel sm = type.getSampleModel(); if (!(sm instanceof MultiPixelPackedSampleModel)) @@ -86,6 +89,7 @@ public class WBMPImageWriterSpi extends ImageWriterSpi { return true; } + @Override public ImageWriter createWriterInstance(Object extension) throws IIOException { return new WBMPImageWriter(this); diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/wbmp/WBMPMetadata.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/wbmp/WBMPMetadata.java index 3a13bcca94f..f3a63ff7b13 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/wbmp/WBMPMetadata.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/wbmp/WBMPMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2004, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -50,10 +50,12 @@ public class WBMPMetadata extends IIOMetadata { null, null); } + @Override public boolean isReadOnly() { return true; } + @Override public Node getAsTree(String formatName) { if (formatName.equals(nativeMetadataFormatName)) { return getNativeTree(); @@ -76,14 +78,17 @@ public class WBMPMetadata extends IIOMetadata { return root; } + @Override public void setFromTree(String formatName, Node root) { throw new IllegalStateException(I18N.getString("WBMPMetadata1")); } + @Override public void mergeTree(String formatName, Node root) { throw new IllegalStateException(I18N.getString("WBMPMetadata1")); } + @Override public void reset() { throw new IllegalStateException(I18N.getString("WBMPMetadata1")); } @@ -101,6 +106,7 @@ public class WBMPMetadata extends IIOMetadata { } + @Override protected IIOMetadataNode getStandardChromaNode() { IIOMetadataNode node = new IIOMetadataNode("Chroma"); @@ -112,6 +118,7 @@ public class WBMPMetadata extends IIOMetadata { } + @Override protected IIOMetadataNode getStandardDimensionNode() { IIOMetadataNode dimension_node = new IIOMetadataNode("Dimension"); IIOMetadataNode node = null; // scratch node diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/wbmp/WBMPMetadataFormat.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/wbmp/WBMPMetadataFormat.java index 49bce89164a..0aabaa132a3 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/wbmp/WBMPMetadataFormat.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/wbmp/WBMPMetadataFormat.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2004, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -56,6 +56,7 @@ public class WBMPMetadataFormat extends IIOMetadataFormatImpl { + @Override public boolean canNodeAppear(String elementName, ImageTypeSpecifier imageType) { return true; diff --git a/src/java.desktop/share/classes/com/sun/imageio/spi/FileImageInputStreamSpi.java b/src/java.desktop/share/classes/com/sun/imageio/spi/FileImageInputStreamSpi.java index 81070ff1cb6..3b52fb2b0f0 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/spi/FileImageInputStreamSpi.java +++ b/src/java.desktop/share/classes/com/sun/imageio/spi/FileImageInputStreamSpi.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -43,10 +43,12 @@ public class FileImageInputStreamSpi extends ImageInputStreamSpi { super(vendorName, version, inputClass); } + @Override public String getDescription(Locale locale) { return "Service provider that instantiates a FileImageInputStream from a File"; } + @Override public ImageInputStream createInputStreamInstance(Object input, boolean useCache, File cacheDir) { diff --git a/src/java.desktop/share/classes/com/sun/imageio/spi/FileImageOutputStreamSpi.java b/src/java.desktop/share/classes/com/sun/imageio/spi/FileImageOutputStreamSpi.java index 1ca08005fa1..aa906197d37 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/spi/FileImageOutputStreamSpi.java +++ b/src/java.desktop/share/classes/com/sun/imageio/spi/FileImageOutputStreamSpi.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -43,10 +43,12 @@ public class FileImageOutputStreamSpi extends ImageOutputStreamSpi { super(vendorName, version, outputClass); } + @Override public String getDescription(Locale locale) { return "Service provider that instantiates a FileImageOutputStream from a File"; } + @Override public ImageOutputStream createOutputStreamInstance(Object output, boolean useCache, File cacheDir) { diff --git a/src/java.desktop/share/classes/com/sun/imageio/spi/InputStreamImageInputStreamSpi.java b/src/java.desktop/share/classes/com/sun/imageio/spi/InputStreamImageInputStreamSpi.java index 2591f77a697..266b09c6d9d 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/spi/InputStreamImageInputStreamSpi.java +++ b/src/java.desktop/share/classes/com/sun/imageio/spi/InputStreamImageInputStreamSpi.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -46,18 +46,22 @@ public class InputStreamImageInputStreamSpi extends ImageInputStreamSpi { super(vendorName, version, inputClass); } + @Override public String getDescription(Locale locale) { return "Service provider that instantiates a FileCacheImageInputStream or MemoryCacheImageInputStream from an InputStream"; } + @Override public boolean canUseCacheFile() { return true; } + @Override public boolean needsCacheFile() { return false; } + @Override public ImageInputStream createInputStreamInstance(Object input, boolean useCache, File cacheDir) diff --git a/src/java.desktop/share/classes/com/sun/imageio/spi/OutputStreamImageOutputStreamSpi.java b/src/java.desktop/share/classes/com/sun/imageio/spi/OutputStreamImageOutputStreamSpi.java index 391e00feb46..01c7df2145d 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/spi/OutputStreamImageOutputStreamSpi.java +++ b/src/java.desktop/share/classes/com/sun/imageio/spi/OutputStreamImageOutputStreamSpi.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -46,18 +46,22 @@ public class OutputStreamImageOutputStreamSpi extends ImageOutputStreamSpi { super(vendorName, version, outputClass); } + @Override public String getDescription(Locale locale) { return "Service provider that instantiates an OutputStreamImageOutputStream from an OutputStream"; } + @Override public boolean canUseCacheFile() { return true; } + @Override public boolean needsCacheFile() { return false; } + @Override public ImageOutputStream createOutputStreamInstance(Object output, boolean useCache, File cacheDir) diff --git a/src/java.desktop/share/classes/com/sun/imageio/spi/RAFImageInputStreamSpi.java b/src/java.desktop/share/classes/com/sun/imageio/spi/RAFImageInputStreamSpi.java index b5c5112c14e..43498b30dbf 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/spi/RAFImageInputStreamSpi.java +++ b/src/java.desktop/share/classes/com/sun/imageio/spi/RAFImageInputStreamSpi.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -44,10 +44,12 @@ public class RAFImageInputStreamSpi extends ImageInputStreamSpi { super(vendorName, version, inputClass); } + @Override public String getDescription(Locale locale) { return "Service provider that instantiates a FileImageInputStream from a RandomAccessFile"; } + @Override public ImageInputStream createInputStreamInstance(Object input, boolean useCache, File cacheDir) { diff --git a/src/java.desktop/share/classes/com/sun/imageio/spi/RAFImageOutputStreamSpi.java b/src/java.desktop/share/classes/com/sun/imageio/spi/RAFImageOutputStreamSpi.java index 373f8754f08..1fe6438b27d 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/spi/RAFImageOutputStreamSpi.java +++ b/src/java.desktop/share/classes/com/sun/imageio/spi/RAFImageOutputStreamSpi.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -44,10 +44,12 @@ public class RAFImageOutputStreamSpi extends ImageOutputStreamSpi { super(vendorName, version, outputClass); } + @Override public String getDescription(Locale locale) { return "Service provider that instantiates a FileImageOutputStream from a RandomAccessFile"; } + @Override public ImageOutputStream createOutputStreamInstance(Object output, boolean useCache, File cacheDir) { diff --git a/src/java.desktop/share/classes/com/sun/imageio/stream/CloseableDisposerRecord.java b/src/java.desktop/share/classes/com/sun/imageio/stream/CloseableDisposerRecord.java index 5ea52def657..370606ea08f 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/stream/CloseableDisposerRecord.java +++ b/src/java.desktop/share/classes/com/sun/imageio/stream/CloseableDisposerRecord.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -41,6 +41,7 @@ public class CloseableDisposerRecord implements DisposerRecord { this.closeable = closeable; } + @Override public synchronized void dispose() { if (closeable != null) { try { diff --git a/src/java.desktop/share/classes/com/sun/imageio/stream/StreamCloser.java b/src/java.desktop/share/classes/com/sun/imageio/stream/StreamCloser.java index 229c470335b..7de400ad199 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/stream/StreamCloser.java +++ b/src/java.desktop/share/classes/com/sun/imageio/stream/StreamCloser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -60,6 +60,7 @@ public class StreamCloser { if (streamCloser == null) { final Runnable streamCloserRunnable = new Runnable() { + @Override public void run() { if (toCloseQueue != null) { synchronized (StreamCloser.class) { diff --git a/src/java.desktop/share/classes/java/awt/image/Raster.java b/src/java.desktop/share/classes/java/awt/image/Raster.java index 9f346cb304a..053e7c1eec5 100644 --- a/src/java.desktop/share/classes/java/awt/image/Raster.java +++ b/src/java.desktop/share/classes/java/awt/image/Raster.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -265,9 +265,15 @@ public class Raster { * {@code location.x + w} or * {@code location.y + h} results in integer overflow * @throws IllegalArgumentException if {@code scanlineStride} - * is less than 0 - * @throws IllegalArgumentException if {@code pixelStride} is less than 0 + * is less than or equal to 0 + * @throws IllegalArgumentException if {@code pixelStride} is less than or equal to 0 + * @throws IllegalArgumentException if {@code w * pixelStride} is greater + * than {@code scanlineStride} + * @throws IllegalArgumentException if the data size need to store all + * lines of the image is greater than {@code Integer.MAX_VALUE} * @throws NullPointerException if {@code bandOffsets} is null + * @throws IllegalArgumentException if any element of {@code bandOffsets} is greater + * than {@code pixelStride} or the {@code scanlineStride} */ public static WritableRaster createInterleavedRaster(int dataType, int w, int h, @@ -285,14 +291,25 @@ public class Raster { throw new IllegalArgumentException("Dimensions (width="+w+ " height="+h+") are too large"); } - if (pixelStride < 0) { - throw new IllegalArgumentException("pixelStride is < 0"); + if (pixelStride <= 0) { + throw new IllegalArgumentException("pixelStride is <= 0"); } - if (scanlineStride < 0) { - throw new IllegalArgumentException("scanlineStride is < 0"); + if (scanlineStride <= 0) { + throw new IllegalArgumentException("scanlineStride is <= 0"); } - int size = scanlineStride * (h - 1) + // first (h - 1) scans - pixelStride * w; // last scan + if (bandOffsets == null) { + throw new NullPointerException("bandOffsets is null"); + } + lsz = (long)w * pixelStride; + if (lsz > scanlineStride) { + throw new IllegalArgumentException("w * pixelStride is too large"); + } + lsz = (long)scanlineStride * (long)(h - 1) + // first (h - 1) scans + (long)pixelStride * (long)w; // last scan + if (lsz > Integer.MAX_VALUE) { + throw new IllegalArgumentException("size too large to store image"); + } + int size = (int)lsz; if (location == null) { location = new Point(0, 0); @@ -415,6 +432,10 @@ public class Raster { * is less than 0 * @throws ArrayIndexOutOfBoundsException if {@code bankIndices} * is {@code null} + * @throws IllegalArgumentException if the lengths of {@code bankIndices} + * and {@code bandOffsets} are different. + * @throws IllegalArgumentException if the data size need to store all + * lines of a bank of the image is greater than {@code Integer.MAX_VALUE} * @throws NullPointerException if {@code bandOffsets} is {@code null} */ public static WritableRaster createBandedRaster(int dataType, @@ -423,9 +444,6 @@ public class Raster { int[] bankIndices, int[] bandOffsets, Point location) { - DataBuffer d; - int bands = bandOffsets.length; - if (w <= 0 || h <= 0) { throw new IllegalArgumentException("w and h must be positive"); } @@ -440,7 +458,11 @@ public class Raster { } if (bandOffsets == null) { throw new - ArrayIndexOutOfBoundsException("Band offsets array is null"); + NullPointerException("Band offsets array is null"); + } + if (bandOffsets.length != bankIndices.length) { + throw new IllegalArgumentException( + "bankIndices.length != bandOffsets.length"); } if (location != null) { if ((w + location.getX() > Integer.MAX_VALUE) || @@ -451,6 +473,9 @@ public class Raster { } } + DataBuffer d; + int bands = bandOffsets.length; + // Figure out the #banks and the largest band offset int maxBank = bankIndices[0]; int maxBandOff = bandOffsets[0]; @@ -463,9 +488,15 @@ public class Raster { } } int banks = maxBank + 1; - int size = maxBandOff + - scanlineStride * (h - 1) + // first (h - 1) scans - w; // last scan + + lsz = (long) maxBandOff + + (long)scanlineStride * (h - 1) + // first (h - 1) scans + w; // last scan + if (lsz > Integer.MAX_VALUE) { + throw new IllegalArgumentException("storage size is too large"); + } + + int size = (int)lsz; switch(dataType) { case DataBuffer.TYPE_BYTE: @@ -508,11 +539,14 @@ public class Raster { * @param location the upper-left corner of the {@code Raster} * @return a WritableRaster object with the specified data type, * width, height, and band masks. - * @throws RasterFormatException if {@code w} or {@code h} - * is less than or equal to zero, or computing either + * @throws NullPointerException if {@code bandMasks} is null + * @throws IllegalArgumentException if {@code w} and {@code h} + * are not both greater than 0 + * @throws IllegalArgumentException if the product of {@code w} + * and {@code h} is greater than {@code Integer.MAX_VALUE} + * @throws RasterFormatException if computing either * {@code location.x + w} or - * {@code location.y + h} results in integer - * overflow + * {@code location.y + h} results in integer overflow * @throws IllegalArgumentException if {@code dataType} is not * one of the supported data types, which are * {@code DataBuffer.TYPE_BYTE}, @@ -525,6 +559,24 @@ public class Raster { Point location) { DataBuffer d; + if (w <= 0 || h <= 0) { + throw new IllegalArgumentException("w and h must be positive"); + } + long lsz = (long)w * h; + if (lsz > Integer.MAX_VALUE) { + throw new IllegalArgumentException("Dimensions (width="+w+ + " height="+h+") are too large"); + } + + if (location != null) { + if ((w + location.getX() > Integer.MAX_VALUE) || + (h + location.getY() > Integer.MAX_VALUE)) { + throw new RasterFormatException( + "location.x + w and location.y + h " + + " cannot exceed Integer.MAX_VALUE"); + } + } + switch(dataType) { case DataBuffer.TYPE_BYTE: d = new DataBufferByte(w*h); @@ -573,17 +625,19 @@ public class Raster { * @param location the upper-left corner of the {@code Raster} * @return a WritableRaster object with the specified data type, * width, height, number of bands, and bits per band. - * @throws RasterFormatException if {@code w} or {@code h} - * is less than or equal to zero, or computing either - * {@code location.x + w} or - * {@code location.y + h} results in integer - * overflow + * @throws IllegalArgumentException if {@code bitsPerBand} or + * {@code bands} is not greater than zero * @throws IllegalArgumentException if the product of * {@code bitsPerBand} and {@code bands} is * greater than the number of bits held by * {@code dataType} - * @throws IllegalArgumentException if {@code bitsPerBand} or - * {@code bands} is not greater than zero + * @throws IllegalArgumentException if {@code w} and {@code h} + * are not both greater than 0 + * @throws IllegalArgumentException if the product of {@code w} + * and {@code h} is greater than {@code Integer.MAX_VALUE} + * @throws RasterFormatException if computing either + * {@code location.x + w} or + * {@code location.y + h} results in integer overflow * @throws IllegalArgumentException if {@code dataType} is not * one of the supported data types, which are * {@code DataBuffer.TYPE_BYTE}, @@ -607,18 +661,37 @@ public class Raster { ") must be greater than 0"); } + if (w <= 0 || h <= 0) { + throw new IllegalArgumentException("w and h must be positive"); + } + long lsz = (long)w * h; + if (lsz > Integer.MAX_VALUE) { + throw new IllegalArgumentException("Dimensions (width="+w+ + " height="+h+") are too large"); + } + + if (location != null) { + if ((w + location.getX() > Integer.MAX_VALUE) || + (h + location.getY() > Integer.MAX_VALUE)) { + throw new RasterFormatException( + "location.x + w and location.y + h " + + " cannot exceed Integer.MAX_VALUE"); + } + } + + int shift = (bands-1)*bitsPerBand; + + /* Make sure the total mask size will fit in the data type */ + if (shift+bitsPerBand > DataBuffer.getDataTypeSize(dataType)) { + throw new IllegalArgumentException("bitsPerBand("+ + bitsPerBand+") * bands is "+ + " greater than data type "+ + "size."); + } if (bands != 1) { int[] masks = new int[bands]; int mask = (1 << bitsPerBand) - 1; - int shift = (bands-1)*bitsPerBand; - /* Make sure the total mask size will fit in the data type */ - if (shift+bitsPerBand > DataBuffer.getDataTypeSize(dataType)) { - throw new IllegalArgumentException("bitsPerBand("+ - bitsPerBand+") * bands is "+ - " greater than data type "+ - "size."); - } switch(dataType) { case DataBuffer.TYPE_BYTE: case DataBuffer.TYPE_USHORT: @@ -693,6 +766,7 @@ public class Raster { * {@code DataBuffer.TYPE_USHORT}. * @throws RasterFormatException if {@code dataBuffer} has more * than one bank. + * @throws RasterFormatException if {@code dataBuffer} is too small. * @throws IllegalArgumentException if {@code w} and {@code h} are not * both > 0 * @throws IllegalArgumentException if the product of {@code w} @@ -704,6 +778,8 @@ public class Raster { * is less than 0 * @throws IllegalArgumentException if {@code pixelStride} is less than 0 * @throws NullPointerException if {@code bandOffsets} is null + * @throws IllegalArgumentException if any element of {@code bandOffsets} is greater + * than {@code pixelStride} or the {@code scanlineStride} */ public static WritableRaster createInterleavedRaster(DataBuffer dataBuffer, @@ -779,6 +855,10 @@ public class Raster { * bank indices and band offsets. * @throws NullPointerException if {@code dataBuffer} is null, * or {@code bankIndices} is null, or {@code bandOffsets} is null + * @throws IllegalArgumentException if the lengths of {@code bankIndices} + * and {@code bandOffsets} are different. + * @throws ArrayIndexOutOfBoundsException if any element of {@code bankIndices} + * is greater or equal to the number of bands in {@code dataBuffer} * @throws IllegalArgumentException if {@code dataType} is not * one of the supported data types, which are * {@code DataBuffer.TYPE_BYTE}, @@ -883,11 +963,14 @@ public class Raster { * @return a WritableRaster object with the specified * {@code DataBuffer}, width, height, scanline stride, * and band masks. - * @throws RasterFormatException if {@code w} or {@code h} - * is less than or equal to zero, or computing either + * @throws NullPointerException if {@code bandMasks} is null + * @throws IllegalArgumentException if {@code w} and {@code h} + * are not both greater than 0 + * @throws IllegalArgumentException if the product of {@code w} + * and {@code h} is greater than {@code Integer.MAX_VALUE} + * @throws RasterFormatException if computing either * {@code location.x + w} or - * {@code location.y + h} results in integer - * overflow + * {@code location.y + h} results in integer overflow * @throws IllegalArgumentException if {@code dataBuffer} is not * one of the supported data types, which are * {@code DataBuffer.TYPE_BYTE}, @@ -906,6 +989,25 @@ public class Raster { if (dataBuffer == null) { throw new NullPointerException("DataBuffer cannot be null"); } + + if (w <= 0 || h <= 0) { + throw new IllegalArgumentException("w and h must be positive"); + } + long lsz = (long)w * h; + if (lsz > Integer.MAX_VALUE) { + throw new IllegalArgumentException("Dimensions (width="+w+ + " height="+h+") are too large"); + } + + if (location != null) { + if ((w + location.getX() > Integer.MAX_VALUE) || + (h + location.getY() > Integer.MAX_VALUE)) { + throw new RasterFormatException( + "location.x + w and location.y + h " + + " cannot exceed Integer.MAX_VALUE"); + } + } + if (location == null) { location = new Point(0,0); } @@ -960,11 +1062,13 @@ public class Raster { * @return a WritableRaster object with the specified * {@code DataBuffer}, width, height, and * bits per pixel. - * @throws RasterFormatException if {@code w} or {@code h} - * is less than or equal to zero, or computing either + * @throws IllegalArgumentException if {@code w} and {@code h} + * are not both greater than 0 + * @throws IllegalArgumentException if the product of {@code w} + * and {@code h} is greater than {@code Integer.MAX_VALUE} + * @throws RasterFormatException if computing either * {@code location.x + w} or - * {@code location.y + h} results in integer - * overflow + * {@code location.y + h} results in integer overflow * @throws IllegalArgumentException if {@code dataType} is not * one of the supported data types, which are * {@code DataBuffer.TYPE_BYTE}, @@ -972,6 +1076,8 @@ public class Raster { * or {@code DataBuffer.TYPE_INT} * @throws RasterFormatException if {@code dataBuffer} has more * than one bank. + * @throws RasterFormatException if {@code bitsPixel} is less than 1 or + * not a power of 2 or exceeds the {@code dataBuffer} element size. * @throws NullPointerException if {@code dataBuffer} is null */ public static WritableRaster createPackedRaster(DataBuffer dataBuffer, @@ -982,6 +1088,25 @@ public class Raster { if (dataBuffer == null) { throw new NullPointerException("DataBuffer cannot be null"); } + if (w <= 0 || h <= 0) { + throw new IllegalArgumentException("w and h must be positive"); + } + long lsz = (long)w * h; + + if (lsz > Integer.MAX_VALUE) { + throw new IllegalArgumentException("Dimensions (width="+w+ + " height="+h+") are too large"); + } + + if (location != null) { + if ((w + location.getX() > Integer.MAX_VALUE) || + (h + location.getY() > Integer.MAX_VALUE)) { + throw new RasterFormatException( + "location.x + w and location.y + h " + + " cannot exceed Integer.MAX_VALUE"); + } + } + if (location == null) { location = new Point(0,0); } @@ -1000,6 +1125,12 @@ public class Raster { " must only have 1 bank."); } + if ((bitsPerPixel < 1) || (bitsPerPixel > DataBuffer.getDataTypeSize(dataType))) { + // NB MPPSM checks power of 2 condition + throw new + RasterFormatException("bitsPerPixel must be > 0 and a power of 2 that " + + "does not exceed data buffer element size"); + } MultiPixelPackedSampleModel mppsm = new MultiPixelPackedSampleModel(dataType, w, h, bitsPerPixel); diff --git a/src/java.desktop/share/classes/javax/swing/plaf/metal/MetalButtonUI.java b/src/java.desktop/share/classes/javax/swing/plaf/metal/MetalButtonUI.java index 0bbbab7b45f..c940b4dcead 100644 --- a/src/java.desktop/share/classes/javax/swing/plaf/metal/MetalButtonUI.java +++ b/src/java.desktop/share/classes/javax/swing/plaf/metal/MetalButtonUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -217,6 +217,13 @@ public class MetalButtonUI extends BasicButtonUI { // If there is an icon and no text else if ( isIcon ) { focusRect.setBounds( iconRect ); + } else { + Rectangle emptyRect = new Rectangle(); + emptyRect.x = 5; + emptyRect.y = 5; + emptyRect.width = b.getWidth() - emptyRect.x * 2; + emptyRect.height = b.getHeight() - emptyRect.y * 2; + focusRect.setBounds(emptyRect); } g.setColor(getFocusColor()); diff --git a/src/java.desktop/share/classes/sun/java2d/cmm/lcms/LCMS.java b/src/java.desktop/share/classes/sun/java2d/cmm/lcms/LCMS.java index 63a1359323f..24b50003e3c 100644 --- a/src/java.desktop/share/classes/sun/java2d/cmm/lcms/LCMS.java +++ b/src/java.desktop/share/classes/sun/java2d/cmm/lcms/LCMS.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -136,8 +136,7 @@ final class LCMS implements PCMM { static native void colorConvert(long trans, int width, int height, int srcOffset, int srcNextRowOffset, int dstOffset, int dstNextRowOffset, - Object srcData, Object dstData, - int srcType, int dstType); + Object srcData, Object dstData); private LCMS() {} diff --git a/src/java.desktop/share/classes/sun/java2d/cmm/lcms/LCMSImageLayout.java b/src/java.desktop/share/classes/sun/java2d/cmm/lcms/LCMSImageLayout.java index 663e11ff172..c7bae875282 100644 --- a/src/java.desktop/share/classes/sun/java2d/cmm/lcms/LCMSImageLayout.java +++ b/src/java.desktop/share/classes/sun/java2d/cmm/lcms/LCMSImageLayout.java @@ -31,7 +31,6 @@ import java.awt.image.ColorModel; import java.awt.image.ComponentColorModel; import java.awt.image.ComponentSampleModel; import java.awt.image.Raster; -import java.lang.annotation.Native; import java.nio.ByteOrder; import sun.awt.image.ByteComponentRaster; @@ -69,14 +68,7 @@ final class LCMSImageLayout { // private static final int PT_BGRA_8 = PT_ABGR_8 | SWAPFIRST; private static final int SWAP_ENDIAN = ByteOrder.nativeOrder() == LITTLE_ENDIAN ? DOSWAP : 0; - @Native - private static final int DT_BYTE = 0; - @Native - private static final int DT_SHORT = 1; - @Native - private static final int DT_INT = 2; int pixelType; - int dataType; int width; int height; int nextRowOffset; @@ -93,12 +85,10 @@ final class LCMSImageLayout { * @param data the storage of pixels: {@code byte[], short[] or int[]} * @param length the length of the data array * @param nc the number of color components - * @param dt the type of data array: DT_BYTE, DT_SHORT or DT_INT * @param size the size of one color component in bytes */ - private LCMSImageLayout(Object data, int length, int nc, int dt, int size) { + private LCMSImageLayout(Object data, int length, int nc, int size) { dataArray = data; - dataType = dt; dataArrayLength = length * size; pixelType = CHANNELS_SH(nc) | BYTES_SH(size); width = length / nc; @@ -109,11 +99,11 @@ final class LCMSImageLayout { } LCMSImageLayout(byte[] data, int nc) { - this(data, data.length, nc, DT_BYTE, Byte.BYTES); + this(data, data.length, nc, Byte.BYTES); } LCMSImageLayout(short[] data, int nc) { - this(data, data.length, nc, DT_SHORT, Short.BYTES); + this(data, data.length, nc, Short.BYTES); } private LCMSImageLayout() { @@ -186,7 +176,6 @@ final class LCMSImageLayout { l.offset = safeMult(4, intRaster.getDataOffset(0)); l.dataArray = intRaster.getDataStorage(); l.dataArrayLength = 4 * intRaster.getDataStorage().length; - l.dataType = DT_INT; } case BufferedImage.TYPE_BYTE_GRAY, BufferedImage.TYPE_3BYTE_BGR, BufferedImage.TYPE_4BYTE_ABGR, @@ -201,7 +190,6 @@ final class LCMSImageLayout { l.offset = byteRaster.getDataOffset(firstBand); l.dataArray = byteRaster.getDataStorage(); l.dataArrayLength = byteRaster.getDataStorage().length; - l.dataType = DT_BYTE; } case BufferedImage.TYPE_USHORT_GRAY -> { if (!(raster instanceof ShortComponentRaster shortRaster)) { @@ -212,7 +200,6 @@ final class LCMSImageLayout { l.offset = safeMult(2, shortRaster.getDataOffset(0)); l.dataArray = shortRaster.getDataStorage(); l.dataArrayLength = 2 * shortRaster.getDataStorage().length; - l.dataType = DT_SHORT; } default -> { return null; @@ -319,7 +306,6 @@ final class LCMSImageLayout { l.nextPixelOffset = br.getPixelStride(); l.offset = br.getDataOffset(firstBand); - l.dataType = DT_BYTE; byte[] data = br.getDataStorage(); l.dataArray = data; l.dataArrayLength = data.length; diff --git a/src/java.desktop/share/classes/sun/java2d/cmm/lcms/LCMSTransform.java b/src/java.desktop/share/classes/sun/java2d/cmm/lcms/LCMSTransform.java index 881a6556ace..3f4f510cf94 100644 --- a/src/java.desktop/share/classes/sun/java2d/cmm/lcms/LCMSTransform.java +++ b/src/java.desktop/share/classes/sun/java2d/cmm/lcms/LCMSTransform.java @@ -121,8 +121,7 @@ final class LCMSTransform implements ColorTransform { } LCMS.colorConvert(tfm.ID, in.width, in.height, in.offset, in.nextRowOffset, out.offset, out.nextRowOffset, - in.dataArray, out.dataArray, - in.dataType, out.dataType); + in.dataArray, out.dataArray); Reference.reachabilityFence(tfm); // prevent deallocation of "tfm.ID" } diff --git a/src/java.desktop/share/legal/libpng.md b/src/java.desktop/share/legal/libpng.md index d43ccf2e8e4..8899491c6c0 100644 --- a/src/java.desktop/share/legal/libpng.md +++ b/src/java.desktop/share/legal/libpng.md @@ -1,4 +1,4 @@ -## libpng v1.6.47 +## libpng v1.6.51 ### libpng License
@@ -9,7 +9,7 @@ COPYRIGHT NOTICE, DISCLAIMER, and LICENSE
 PNG Reference Library License version 2
 ---------------------------------------
 
-Copyright (c) 1995-2025 The PNG Reference Library Authors.
+Copyright (C) 1995-2025 The PNG Reference Library Authors.
 Copyright (C) 2018-2025 Cosmin Truta
 Copyright (C) 1998-2018 Glenn Randers-Pehrson
 Copyright (C) 1996-1997 Andreas Dilger
@@ -173,6 +173,7 @@ Authors, for copyright and licensing purposes.
  * Lucas Chollet
  * Magnus Holmgren
  * Mandar Sahastrabuddhe
+ * Manfred Schlaegl
  * Mans Rullgard
  * Matt Sarett
  * Mike Klein
@@ -184,6 +185,7 @@ Authors, for copyright and licensing purposes.
  * Samuel Williams
  * Simon-Pierre Cadieux
  * Tim Wegner
+ * Tobias Stoeckmann
  * Tom Lane
  * Tom Tanner
  * Vadim Barkov
@@ -193,8 +195,9 @@ Authors, for copyright and licensing purposes.
     - Zixu Wang (王子旭)
  * Arm Holdings
     - Richard Townsend
- * Google Inc.
+ * Google LLC
     - Dan Field
+    - Dragoș Tiselice
     - Leon Scroggins III
     - Matt Sarett
     - Mike Klein
@@ -204,6 +207,8 @@ Authors, for copyright and licensing purposes.
     - GuXiWei (顾希伟)
     - JinBo (金波)
     - ZhangLixia (张利霞)
+ * Samsung Group
+    - Filip Wasil
 
 The build projects, the build scripts, the test scripts, and other
 files in the "projects", "scripts" and "tests" directories, have
@@ -214,3 +219,4 @@ of the tools-generated files that are distributed with libpng, have
 other copyright owners, and are released under other open source
 licenses.
 ```
+
diff --git a/src/java.desktop/share/native/liblcms/LCMS.c b/src/java.desktop/share/native/liblcms/LCMS.c
index 5cf7ee6c436..5d4ace7a624 100644
--- a/src/java.desktop/share/native/liblcms/LCMS.c
+++ b/src/java.desktop/share/native/liblcms/LCMS.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2007, 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2007, 2025, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -27,7 +27,6 @@
 #include 
 #include 
 #include "sun_java2d_cmm_lcms_LCMS.h"
-#include "sun_java2d_cmm_lcms_LCMSImageLayout.h"
 #include "jni_util.h"
 #include "Trace.h"
 #include "Disposer.h"
@@ -46,10 +45,6 @@
 
 #define SigHead TagIdConst('h','e','a','d')
 
-#define DT_BYTE     sun_java2d_cmm_lcms_LCMSImageLayout_DT_BYTE
-#define DT_SHORT    sun_java2d_cmm_lcms_LCMSImageLayout_DT_SHORT
-#define DT_INT      sun_java2d_cmm_lcms_LCMSImageLayout_DT_INT
-
 /* Default temp profile list size */
 #define DF_ICC_BUF_SIZE 32
 
@@ -464,46 +459,17 @@ JNIEXPORT void JNICALL Java_sun_java2d_cmm_lcms_LCMS_setTagDataNative
     }
 }
 
-static void *getILData(JNIEnv *env, jobject data, jint type) {
-    switch (type) {
-        case DT_BYTE:
-            return (*env)->GetByteArrayElements(env, data, 0);
-        case DT_SHORT:
-            return (*env)->GetShortArrayElements(env, data, 0);
-        case DT_INT:
-            return (*env)->GetIntArrayElements(env, data, 0);
-        default:
-            return NULL;
-    }
-}
-
-static void releaseILData(JNIEnv *env, void *pData, jint type, jobject data,
-                          jint mode) {
-    switch (type) {
-        case DT_BYTE:
-            (*env)->ReleaseByteArrayElements(env, data, (jbyte *) pData, mode);
-            break;
-        case DT_SHORT:
-            (*env)->ReleaseShortArrayElements(env, data, (jshort *) pData, mode);
-            break;
-        case DT_INT:
-            (*env)->ReleaseIntArrayElements(env, data, (jint *) pData, mode);
-            break;
-    }
-}
-
 /*
  * Class:     sun_java2d_cmm_lcms_LCMS
  * Method:    colorConvert
- * Signature: (JIIIIIIZZLjava/lang/Object;Ljava/lang/Object;)V
+ * Signature: (JIIIIIILjava/lang/Object;Ljava/lang/Object;)V
  */
 JNIEXPORT void JNICALL Java_sun_java2d_cmm_lcms_LCMS_colorConvert
   (JNIEnv *env, jclass cls, jlong ID, jint width, jint height, jint srcOffset,
    jint srcNextRowOffset, jint dstOffset, jint dstNextRowOffset,
-   jobject srcData, jobject dstData, jint srcDType, jint dstDType)
+   jobject srcData, jobject dstData)
 {
     cmsHTRANSFORM sTrans = jlong_to_ptr(ID);
-
     if (sTrans == NULL) {
         J2dRlsTraceLn(J2D_TRACE_ERROR, "LCMS_colorConvert: transform == NULL");
         JNU_ThrowByName(env, "java/awt/color/CMMException",
@@ -511,28 +477,22 @@ JNIEXPORT void JNICALL Java_sun_java2d_cmm_lcms_LCMS_colorConvert
         return;
     }
 
-    void *inputBuffer = getILData(env, srcData, srcDType);
+    void *inputBuffer = (*env)->GetPrimitiveArrayCritical(env, srcData, NULL);
     if (inputBuffer == NULL) {
-        J2dRlsTraceLn(J2D_TRACE_ERROR, "");
         // An exception should have already been thrown.
         return;
     }
+    void *outputBuffer = (*env)->GetPrimitiveArrayCritical(env, dstData, NULL);
+    if (outputBuffer != NULL) {
+        char *input = (char *) inputBuffer + srcOffset;
+        char *output = (char *) outputBuffer + dstOffset;
 
-    void *outputBuffer = getILData(env, dstData, dstDType);
-    if (outputBuffer == NULL) {
-        releaseILData(env, inputBuffer, srcDType, srcData, JNI_ABORT);
-        // An exception should have already been thrown.
-        return;
+        cmsDoTransformLineStride(sTrans, input, output, width, height,
+                                 srcNextRowOffset, dstNextRowOffset, 0, 0);
+
+        (*env)->ReleasePrimitiveArrayCritical(env, dstData, outputBuffer, 0);
     }
-
-    char *input = (char *) inputBuffer + srcOffset;
-    char *output = (char *) outputBuffer + dstOffset;
-
-    cmsDoTransformLineStride(sTrans, input, output, width, height,
-                             srcNextRowOffset, dstNextRowOffset, 0, 0);
-
-    releaseILData(env, inputBuffer, srcDType, srcData, JNI_ABORT);
-    releaseILData(env, outputBuffer, dstDType, dstData, 0);
+    (*env)->ReleasePrimitiveArrayCritical(env, srcData, inputBuffer, JNI_ABORT);
 }
 
 static cmsBool _getHeaderInfo(cmsHPROFILE pf, jbyte* pBuffer, jint bufferSize)
diff --git a/src/java.desktop/share/native/libsplashscreen/libpng/CHANGES b/src/java.desktop/share/native/libsplashscreen/libpng/CHANGES
index 834b5e19277..2478fd0fc08 100644
--- a/src/java.desktop/share/native/libsplashscreen/libpng/CHANGES
+++ b/src/java.desktop/share/native/libsplashscreen/libpng/CHANGES
@@ -6251,6 +6251,59 @@ Version 1.6.47 [February 18, 2025]
     colorspace precedence rules, due to pre-existing colorspace checks.
     (Reported by Bob Friesenhahn; fixed by John Bowler)
 
+Version 1.6.48 [April 30, 2025]
+  Fixed the floating-point version of the mDCv setter `png_set_mDCv`.
+    (Reported by Mohit Bakshi; fixed by John Bowler)
+  Added #error directives to discourage the inclusion of private
+    libpng implementation header files in PNG-supporting applications.
+  Added the CMake build option `PNG_LIBCONF_HEADER`, to be used as an
+    alternative to `DFA_XTRA`.
+  Removed the Travis CI configuration files, with heartfelt thanks for
+    their generous support of our project over the past five years!
+
+Version 1.6.49 [June 12, 2025]
+  Added SIMD-optimized code for the RISC-V Vector Extension (RVV).
+    (Contributed by Manfred Schlaegl, Dragos Tiselice and Filip Wasil)
+  Added various fixes and improvements to the build scripts and to
+    the sample code.
+
+Version 1.6.50 [July 1, 2025]
+  Improved the detection of the RVV Extension on the RISC-V platform.
+    (Contributed by Filip Wasil)
+  Replaced inline ASM with C intrinsics in the RVV code.
+    (Contributed by Filip Wasil)
+  Fixed a decoder defect in which unknown chunks trailing IDAT, set
+    to go through the unknown chunk handler, incorrectly triggered
+    out-of-place IEND errors.
+    (Contributed by John Bowler)
+  Fixed the CMake file for cross-platform builds that require `libm`.
+
+Version 1.6.51 [November 21, 2025]
+  Fixed CVE-2025-64505 (moderate severity):
+    Heap buffer overflow in `png_do_quantize` via malformed palette index.
+    (Reported by Samsung; analyzed by Fabio Gritti.)
+  Fixed CVE-2025-64506 (moderate severity):
+    Heap buffer over-read in `png_write_image_8bit` with 8-bit input and
+    `convert_to_8bit` enabled.
+    (Reported by Samsung and ;
+    analyzed by Fabio Gritti.)
+  Fixed CVE-2025-64720 (high severity):
+    Buffer overflow in `png_image_read_composite` via incorrect palette
+    premultiplication.
+    (Reported by Samsung; analyzed by John Bowler.)
+  Fixed CVE-2025-65018 (high severity):
+    Heap buffer overflow in `png_combine_row` triggered via
+    `png_image_finish_read`.
+    (Reported by .)
+  Fixed a memory leak in `png_set_quantize`.
+    (Reported by Samsung; analyzed by Fabio Gritti.)
+  Removed the experimental and incomplete ERROR_NUMBERS code.
+    (Contributed by Tobias Stoeckmann.)
+  Improved the RISC-V vector extension support; required RVV 1.0 or newer.
+    (Contributed by Filip Wasil.)
+  Added GitHub Actions workflows for automated testing.
+  Performed various refactorings and cleanups.
+
 Send comments/corrections/commendations to png-mng-implement at lists.sf.net.
 Subscription is required; visit
 https://lists.sourceforge.net/lists/listinfo/png-mng-implement
diff --git a/src/java.desktop/share/native/libsplashscreen/libpng/README b/src/java.desktop/share/native/libsplashscreen/libpng/README
index 57952fb215a..5ea329ee3da 100644
--- a/src/java.desktop/share/native/libsplashscreen/libpng/README
+++ b/src/java.desktop/share/native/libsplashscreen/libpng/README
@@ -1,4 +1,4 @@
-README for libpng version 1.6.47
+README for libpng version 1.6.51
 ================================
 
 See the note about version numbers near the top of `png.h`.
@@ -147,6 +147,7 @@ Files included in this distribution
     loongarch/    =>  Optimized code for LoongArch LSX
     mips/         =>  Optimized code for MIPS MSA and MIPS MMI
     powerpc/      =>  Optimized code for PowerPC VSX
+    riscv/        =>  Optimized code for the RISC-V platform
     ci/           =>  Scripts for continuous integration
     contrib/      =>  External contributions
         arm-neon/     =>  Optimized code for the ARM-NEON platform
@@ -162,6 +163,7 @@ Files included in this distribution
                           programs demonstrating the use of pngusr.dfa
         pngminus/     =>  Simple pnm2png and png2pnm programs
         pngsuite/     =>  Test images
+        riscv-rvv/    =>  Optimized code for the RISC-V Vector platform
         testpngs/     =>  Test images
         tools/        =>  Various tools
         visupng/      =>  VisualPng, a Windows viewer for PNG images
diff --git a/src/java.desktop/share/native/libsplashscreen/libpng/png.c b/src/java.desktop/share/native/libsplashscreen/libpng/png.c
index 7b6de2f8ec3..7d85e7c8d5f 100644
--- a/src/java.desktop/share/native/libsplashscreen/libpng/png.c
+++ b/src/java.desktop/share/native/libsplashscreen/libpng/png.c
@@ -42,7 +42,7 @@
 #include "pngpriv.h"
 
 /* Generate a compiler error if there is an old png.h in the search path. */
-typedef png_libpng_version_1_6_47 Your_png_h_is_not_version_1_6_47;
+typedef png_libpng_version_1_6_51 Your_png_h_is_not_version_1_6_51;
 
 /* Sanity check the chunks definitions - PNG_KNOWN_CHUNKS from pngpriv.h and the
  * corresponding macro definitions.  This causes a compile time failure if
@@ -137,10 +137,16 @@ png_zalloc,(voidpf png_ptr, uInt items, uInt size),PNG_ALLOCATED)
    if (png_ptr == NULL)
       return NULL;
 
-   if (items >= (~(png_alloc_size_t)0)/size)
+   /* This check against overflow is vestigial, dating back from
+    * the old times when png_zalloc used to be an exported function.
+    * We're still keeping it here for now, as an extra-cautious
+    * prevention against programming errors inside zlib, although it
+    * should rather be a debug-time assertion instead.
+    */
+   if (size != 0 && items >= (~(png_alloc_size_t)0) / size)
    {
-      png_warning (png_voidcast(png_structrp, png_ptr),
-          "Potential overflow in png_zalloc()");
+      png_warning(png_voidcast(png_structrp, png_ptr),
+                  "Potential overflow in png_zalloc()");
       return NULL;
    }
 
@@ -267,10 +273,6 @@ png_user_version_check(png_structrp png_ptr, png_const_charp user_png_ver)
       png_warning(png_ptr, m);
 #endif
 
-#ifdef PNG_ERROR_NUMBERS_SUPPORTED
-      png_ptr->flags = 0;
-#endif
-
       return 0;
    }
 
@@ -729,7 +731,7 @@ png_get_io_ptr(png_const_structrp png_ptr)
  * function of your own because "FILE *" isn't necessarily available.
  */
 void PNGAPI
-png_init_io(png_structrp png_ptr, png_FILE_p fp)
+png_init_io(png_structrp png_ptr, FILE *fp)
 {
    png_debug(1, "in png_init_io");
 
@@ -844,7 +846,7 @@ png_get_copyright(png_const_structrp png_ptr)
    return PNG_STRING_COPYRIGHT
 #else
    return PNG_STRING_NEWLINE \
-      "libpng version 1.6.47" PNG_STRING_NEWLINE \
+      "libpng version 1.6.51" PNG_STRING_NEWLINE \
       "Copyright (c) 2018-2025 Cosmin Truta" PNG_STRING_NEWLINE \
       "Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson" \
       PNG_STRING_NEWLINE \
@@ -1520,7 +1522,7 @@ png_XYZ_from_xy(png_XYZ *XYZ, const png_xy *xy)
 }
 #endif /* COLORSPACE */
 
-#ifdef PNG_iCCP_SUPPORTED
+#ifdef PNG_READ_iCCP_SUPPORTED
 /* Error message generation */
 static char
 png_icc_tag_char(png_uint_32 byte)
@@ -1596,9 +1598,7 @@ png_icc_profile_error(png_const_structrp png_ptr, png_const_charp name,
 
    return 0;
 }
-#endif /* iCCP */
 
-#ifdef PNG_READ_iCCP_SUPPORTED
 /* Encoded value of D50 as an ICC XYZNumber.  From the ICC 2010 spec the value
  * is XYZ(0.9642,1.0,0.8249), which scales to:
  *
@@ -3998,7 +3998,7 @@ png_image_free_function(png_voidp argument)
 #  ifdef PNG_STDIO_SUPPORTED
       if (cp->owned_file != 0)
       {
-         FILE *fp = png_voidcast(FILE*, cp->png_ptr->io_ptr);
+         FILE *fp = png_voidcast(FILE *, cp->png_ptr->io_ptr);
          cp->owned_file = 0;
 
          /* Ignore errors here. */
diff --git a/src/java.desktop/share/native/libsplashscreen/libpng/png.h b/src/java.desktop/share/native/libsplashscreen/libpng/png.h
index ede12c34fe6..d39ff73552c 100644
--- a/src/java.desktop/share/native/libsplashscreen/libpng/png.h
+++ b/src/java.desktop/share/native/libsplashscreen/libpng/png.h
@@ -29,7 +29,7 @@
  * However, the following notice accompanied the original version of this
  * file and, per its terms, should not be removed:
  *
- * libpng version 1.6.47
+ * libpng version 1.6.51
  *
  * Copyright (c) 2018-2025 Cosmin Truta
  * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson
@@ -43,7 +43,7 @@
  *   libpng versions 0.89, June 1996, through 0.96, May 1997: Andreas Dilger
  *   libpng versions 0.97, January 1998, through 1.6.35, July 2018:
  *     Glenn Randers-Pehrson
- *   libpng versions 1.6.36, December 2018, through 1.6.47, February 2025:
+ *   libpng versions 1.6.36, December 2018, through 1.6.51, November 2025:
  *     Cosmin Truta
  *   See also "Contributing Authors", below.
  */
@@ -267,7 +267,7 @@
  *    ...
  *    1.5.30                  15    10530  15.so.15.30[.0]
  *    ...
- *    1.6.47                  16    10647  16.so.16.47[.0]
+ *    1.6.51                  16    10651  16.so.16.51[.0]
  *
  *    Henceforth the source version will match the shared-library major and
  *    minor numbers; the shared-library major version number will be used for
@@ -303,7 +303,7 @@
  */
 
 /* Version information for png.h - this should match the version in png.c */
-#define PNG_LIBPNG_VER_STRING "1.6.47"
+#define PNG_LIBPNG_VER_STRING "1.6.51"
 #define PNG_HEADER_VERSION_STRING " libpng version " PNG_LIBPNG_VER_STRING "\n"
 
 /* The versions of shared library builds should stay in sync, going forward */
@@ -314,7 +314,7 @@
 /* These should match the first 3 components of PNG_LIBPNG_VER_STRING: */
 #define PNG_LIBPNG_VER_MAJOR   1
 #define PNG_LIBPNG_VER_MINOR   6
-#define PNG_LIBPNG_VER_RELEASE 47
+#define PNG_LIBPNG_VER_RELEASE 51
 
 /* This should be zero for a public release, or non-zero for a
  * development version.
@@ -345,7 +345,7 @@
  * From version 1.0.1 it is:
  * XXYYZZ, where XX=major, YY=minor, ZZ=release
  */
-#define PNG_LIBPNG_VER 10647 /* 1.6.47 */
+#define PNG_LIBPNG_VER 10651 /* 1.6.51 */
 
 /* Library configuration: these options cannot be changed after
  * the library has been built.
@@ -455,7 +455,7 @@ extern "C" {
 /* This triggers a compiler error in png.c, if png.c and png.h
  * do not agree upon the version number.
  */
-typedef char* png_libpng_version_1_6_47;
+typedef char* png_libpng_version_1_6_51;
 
 /* Basic control structions.  Read libpng-manual.txt or libpng.3 for more info.
  *
@@ -1599,7 +1599,7 @@ PNG_EXPORT(226, void, png_set_text_compression_method, (png_structrp png_ptr,
 
 #ifdef PNG_STDIO_SUPPORTED
 /* Initialize the input/output for the PNG file to the default functions. */
-PNG_EXPORT(74, void, png_init_io, (png_structrp png_ptr, png_FILE_p fp));
+PNG_EXPORT(74, void, png_init_io, (png_structrp png_ptr, FILE *fp));
 #endif
 
 /* Replace the (error and abort), and warning functions with user
@@ -3117,7 +3117,7 @@ PNG_EXPORT(234, int, png_image_begin_read_from_file, (png_imagep image,
     */
 
 PNG_EXPORT(235, int, png_image_begin_read_from_stdio, (png_imagep image,
-   FILE* file));
+   FILE *file));
    /* The PNG header is read from the stdio FILE object. */
 #endif /* STDIO */
 
@@ -3192,7 +3192,7 @@ PNG_EXPORT(239, int, png_image_write_to_file, (png_imagep image,
 PNG_EXPORT(240, int, png_image_write_to_stdio, (png_imagep image, FILE *file,
    int convert_to_8_bit, const void *buffer, png_int_32 row_stride,
    const void *colormap));
-   /* Write the image to the given (FILE*). */
+   /* Write the image to the given FILE object. */
 #endif /* SIMPLIFIED_WRITE_STDIO */
 
 /* With all write APIs if image is in one of the linear formats with 16-bit
@@ -3332,26 +3332,45 @@ PNG_EXPORT(245, int, png_image_write_to_memory, (png_imagep image, void *memory,
  *           selected at run time.
  */
 #ifdef PNG_SET_OPTION_SUPPORTED
+
+/* HARDWARE: ARM Neon SIMD instructions supported */
 #ifdef PNG_ARM_NEON_API_SUPPORTED
-#  define PNG_ARM_NEON   0 /* HARDWARE: ARM Neon SIMD instructions supported */
-#endif
-#define PNG_MAXIMUM_INFLATE_WINDOW 2 /* SOFTWARE: force maximum window */
-#define PNG_SKIP_sRGB_CHECK_PROFILE 4 /* SOFTWARE: Check ICC profile for sRGB */
-#ifdef PNG_MIPS_MSA_API_SUPPORTED
-#  define PNG_MIPS_MSA   6 /* HARDWARE: MIPS Msa SIMD instructions supported */
-#endif
-#ifdef PNG_DISABLE_ADLER32_CHECK_SUPPORTED
-#  define PNG_IGNORE_ADLER32 8 /* SOFTWARE: disable Adler32 check on IDAT */
-#endif
-#ifdef PNG_POWERPC_VSX_API_SUPPORTED
-#  define PNG_POWERPC_VSX   10 /* HARDWARE: PowerPC VSX SIMD instructions
-                                * supported */
-#endif
-#ifdef PNG_MIPS_MMI_API_SUPPORTED
-#  define PNG_MIPS_MMI   12 /* HARDWARE: MIPS MMI SIMD instructions supported */
+#  define PNG_ARM_NEON 0
 #endif
 
-#define PNG_OPTION_NEXT  14 /* Next option - numbers must be even */
+/* SOFTWARE: Force maximum window */
+#define PNG_MAXIMUM_INFLATE_WINDOW 2
+
+/* SOFTWARE: Check ICC profile for sRGB */
+#define PNG_SKIP_sRGB_CHECK_PROFILE 4
+
+/* HARDWARE: MIPS MSA SIMD instructions supported */
+#ifdef PNG_MIPS_MSA_API_SUPPORTED
+#  define PNG_MIPS_MSA 6
+#endif
+
+/* SOFTWARE: Disable Adler32 check on IDAT */
+#ifdef PNG_DISABLE_ADLER32_CHECK_SUPPORTED
+#  define PNG_IGNORE_ADLER32 8
+#endif
+
+/* HARDWARE: PowerPC VSX SIMD instructions supported */
+#ifdef PNG_POWERPC_VSX_API_SUPPORTED
+#  define PNG_POWERPC_VSX 10
+#endif
+
+/* HARDWARE: MIPS MMI SIMD instructions supported */
+#ifdef PNG_MIPS_MMI_API_SUPPORTED
+#  define PNG_MIPS_MMI 12
+#endif
+
+/* HARDWARE: RISC-V RVV SIMD instructions supported */
+#ifdef PNG_RISCV_RVV_API_SUPPORTED
+#  define PNG_RISCV_RVV 14
+#endif
+
+/* Next option - numbers must be even */
+#define PNG_OPTION_NEXT 16
 
 /* Return values: NOTE: there are four values and 'off' is *not* zero */
 #define PNG_OPTION_UNSET   0 /* Unset - defaults to off */
diff --git a/src/java.desktop/share/native/libsplashscreen/libpng/pngconf.h b/src/java.desktop/share/native/libsplashscreen/libpng/pngconf.h
index 70bca6fa1c9..4bc5f7bb468 100644
--- a/src/java.desktop/share/native/libsplashscreen/libpng/pngconf.h
+++ b/src/java.desktop/share/native/libsplashscreen/libpng/pngconf.h
@@ -29,7 +29,7 @@
  * However, the following notice accompanied the original version of this
  * file and, per its terms, should not be removed:
  *
- * libpng version 1.6.47
+ * libpng version 1.6.51
  *
  * Copyright (c) 2018-2025 Cosmin Truta
  * Copyright (c) 1998-2002,2004,2006-2016,2018 Glenn Randers-Pehrson
@@ -248,25 +248,13 @@
   /* NOTE: PNGCBAPI always defaults to PNGCAPI. */
 
 #  if defined(PNGAPI) && !defined(PNG_USER_PRIVATEBUILD)
-#     error "PNG_USER_PRIVATEBUILD must be defined if PNGAPI is changed"
+#     error PNG_USER_PRIVATEBUILD must be defined if PNGAPI is changed
 #  endif
 
-#  if (defined(_MSC_VER) && _MSC_VER < 800) ||\
-      (defined(__BORLANDC__) && __BORLANDC__ < 0x500)
-   /* older Borland and MSC
-    * compilers used '__export' and required this to be after
-    * the type.
-    */
-#    ifndef PNG_EXPORT_TYPE
-#      define PNG_EXPORT_TYPE(type) type PNG_IMPEXP
-#    endif
-#    define PNG_DLL_EXPORT __export
-#  else /* newer compiler */
-#    define PNG_DLL_EXPORT __declspec(dllexport)
-#    ifndef PNG_DLL_IMPORT
-#      define PNG_DLL_IMPORT __declspec(dllimport)
-#    endif
-#  endif /* compiler */
+#  define PNG_DLL_EXPORT __declspec(dllexport)
+#  ifndef PNG_DLL_IMPORT
+#    define PNG_DLL_IMPORT __declspec(dllimport)
+#  endif
 
 #else /* !Windows */
 #  if (defined(__IBMC__) || defined(__IBMCPP__)) && defined(__OS2__)
@@ -508,7 +496,7 @@
 #if CHAR_BIT == 8 && UCHAR_MAX == 255
    typedef unsigned char png_byte;
 #else
-#  error "libpng requires 8-bit bytes"
+#  error libpng requires 8-bit bytes
 #endif
 
 #if INT_MIN == -32768 && INT_MAX == 32767
@@ -516,7 +504,7 @@
 #elif SHRT_MIN == -32768 && SHRT_MAX == 32767
    typedef short png_int_16;
 #else
-#  error "libpng requires a signed 16-bit type"
+#  error libpng requires a signed 16-bit integer type
 #endif
 
 #if UINT_MAX == 65535
@@ -524,7 +512,7 @@
 #elif USHRT_MAX == 65535
    typedef unsigned short png_uint_16;
 #else
-#  error "libpng requires an unsigned 16-bit type"
+#  error libpng requires an unsigned 16-bit integer type
 #endif
 
 #if INT_MIN < -2147483646 && INT_MAX > 2147483646
@@ -532,7 +520,7 @@
 #elif LONG_MIN < -2147483646 && LONG_MAX > 2147483646
    typedef long int png_int_32;
 #else
-#  error "libpng requires a signed 32-bit (or more) type"
+#  error libpng requires a signed 32-bit (or longer) integer type
 #endif
 
 #if UINT_MAX > 4294967294U
@@ -540,7 +528,7 @@
 #elif ULONG_MAX > 4294967294U
    typedef unsigned long int png_uint_32;
 #else
-#  error "libpng requires an unsigned 32-bit (or more) type"
+#  error libpng requires an unsigned 32-bit (or longer) integer type
 #endif
 
 /* Prior to 1.6.0, it was possible to disable the use of size_t and ptrdiff_t.
@@ -621,10 +609,6 @@ typedef const png_fixed_point * png_const_fixed_point_p;
 typedef size_t                * png_size_tp;
 typedef const size_t          * png_const_size_tp;
 
-#ifdef PNG_STDIO_SUPPORTED
-typedef FILE            * png_FILE_p;
-#endif
-
 #ifdef PNG_FLOATING_POINT_SUPPORTED
 typedef double       * png_doublep;
 typedef const double * png_const_doublep;
@@ -646,6 +630,15 @@ typedef double          * * png_doublepp;
 /* Pointers to pointers to pointers; i.e., pointer to array */
 typedef char            * * * png_charppp;
 
+#ifdef PNG_STDIO_SUPPORTED
+/* With PNG_STDIO_SUPPORTED it was possible to use I/O streams that were
+ * not necessarily stdio FILE streams, to allow building Windows applications
+ * before Win32 and Windows CE applications before WinCE 3.0, but that kind
+ * of support has long been discontinued.
+ */
+typedef FILE            * png_FILE_p; /* [Deprecated] */
+#endif
+
 #endif /* PNG_BUILDING_SYMBOL_TABLE */
 
 #endif /* PNGCONF_H */
diff --git a/src/java.desktop/share/native/libsplashscreen/libpng/pngdebug.h b/src/java.desktop/share/native/libsplashscreen/libpng/pngdebug.h
index 8eb5400ea9a..6ea2644dfcd 100644
--- a/src/java.desktop/share/native/libsplashscreen/libpng/pngdebug.h
+++ b/src/java.desktop/share/native/libsplashscreen/libpng/pngdebug.h
@@ -22,14 +22,14 @@
  * questions.
  */
 
-/* pngdebug.h - Debugging macros for libpng, also used in pngtest.c
+/* pngdebug.h - internal debugging macros for libpng
  *
  * This file is available under and governed by the GNU General Public
  * License version 2 only, as published by the Free Software Foundation.
  * However, the following notice accompanied the original version of this
  * file and, per its terms, should not be removed:
  *
- * Copyright (c) 2018 Cosmin Truta
+ * Copyright (c) 2018-2025 Cosmin Truta
  * Copyright (c) 1998-2002,2004,2006-2013 Glenn Randers-Pehrson
  * Copyright (c) 1996-1997 Andreas Dilger
  * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc.
@@ -39,6 +39,10 @@
  * and license in png.h
  */
 
+#ifndef PNGPRIV_H
+#  error This file must not be included by applications; please include 
+#endif
+
 /* Define PNG_DEBUG at compile time for debugging information.  Higher
  * numbers for PNG_DEBUG mean more debugging information.  This has
  * only been added since version 0.95 so it is not implemented throughout
@@ -63,9 +67,6 @@
 #define PNGDEBUG_H
 /* These settings control the formatting of messages in png.c and pngerror.c */
 /* Moved to pngdebug.h at 1.5.0 */
-#  ifndef PNG_LITERAL_SHARP
-#    define PNG_LITERAL_SHARP 0x23
-#  endif
 #  ifndef PNG_LITERAL_LEFT_SQUARE_BRACKET
 #    define PNG_LITERAL_LEFT_SQUARE_BRACKET 0x5b
 #  endif
diff --git a/src/java.desktop/share/native/libsplashscreen/libpng/pngerror.c b/src/java.desktop/share/native/libsplashscreen/libpng/pngerror.c
index ea0103331d3..44c86ebfef9 100644
--- a/src/java.desktop/share/native/libsplashscreen/libpng/pngerror.c
+++ b/src/java.desktop/share/native/libsplashscreen/libpng/pngerror.c
@@ -29,7 +29,7 @@
  * However, the following notice accompanied the original version of this
  * file and, per its terms, should not be removed:
  *
- * Copyright (c) 2018-2024 Cosmin Truta
+ * Copyright (c) 2018-2025 Cosmin Truta
  * Copyright (c) 1998-2002,2004,2006-2017 Glenn Randers-Pehrson
  * Copyright (c) 1996-1997 Andreas Dilger
  * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc.
@@ -68,46 +68,6 @@ PNG_FUNCTION(void,PNGAPI
 png_error,(png_const_structrp png_ptr, png_const_charp error_message),
     PNG_NORETURN)
 {
-#ifdef PNG_ERROR_NUMBERS_SUPPORTED
-   char msg[16];
-   if (png_ptr != NULL)
-   {
-      if ((png_ptr->flags &
-         (PNG_FLAG_STRIP_ERROR_NUMBERS|PNG_FLAG_STRIP_ERROR_TEXT)) != 0)
-      {
-         if (*error_message == PNG_LITERAL_SHARP)
-         {
-            /* Strip "#nnnn " from beginning of error message. */
-            int offset;
-            for (offset = 1; offset<15; offset++)
-               if (error_message[offset] == ' ')
-                  break;
-
-            if ((png_ptr->flags & PNG_FLAG_STRIP_ERROR_TEXT) != 0)
-            {
-               int i;
-               for (i = 0; i < offset - 1; i++)
-                  msg[i] = error_message[i + 1];
-               msg[i - 1] = '\0';
-               error_message = msg;
-            }
-
-            else
-               error_message += offset;
-         }
-
-         else
-         {
-            if ((png_ptr->flags & PNG_FLAG_STRIP_ERROR_TEXT) != 0)
-            {
-               msg[0] = '0';
-               msg[1] = '\0';
-               error_message = msg;
-            }
-         }
-      }
-   }
-#endif
    if (png_ptr != NULL && png_ptr->error_fn != NULL)
       (*(png_ptr->error_fn))(png_constcast(png_structrp,png_ptr),
           error_message);
@@ -245,21 +205,6 @@ void PNGAPI
 png_warning(png_const_structrp png_ptr, png_const_charp warning_message)
 {
    int offset = 0;
-   if (png_ptr != NULL)
-   {
-#ifdef PNG_ERROR_NUMBERS_SUPPORTED
-   if ((png_ptr->flags &
-       (PNG_FLAG_STRIP_ERROR_NUMBERS|PNG_FLAG_STRIP_ERROR_TEXT)) != 0)
-#endif
-      {
-         if (*warning_message == PNG_LITERAL_SHARP)
-         {
-            for (offset = 1; offset < 15; offset++)
-               if (warning_message[offset] == ' ')
-                  break;
-         }
-      }
-   }
    if (png_ptr != NULL && png_ptr->warning_fn != NULL)
       (*(png_ptr->warning_fn))(png_constcast(png_structrp,png_ptr),
           warning_message + offset);
@@ -741,42 +686,9 @@ png_default_error,(png_const_structrp png_ptr, png_const_charp error_message),
     PNG_NORETURN)
 {
 #ifdef PNG_CONSOLE_IO_SUPPORTED
-#ifdef PNG_ERROR_NUMBERS_SUPPORTED
-   /* Check on NULL only added in 1.5.4 */
-   if (error_message != NULL && *error_message == PNG_LITERAL_SHARP)
-   {
-      /* Strip "#nnnn " from beginning of error message. */
-      int offset;
-      char error_number[16];
-      for (offset = 0; offset<15; offset++)
-      {
-         error_number[offset] = error_message[offset + 1];
-         if (error_message[offset] == ' ')
-            break;
-      }
-
-      if ((offset > 1) && (offset < 15))
-      {
-         error_number[offset - 1] = '\0';
-         fprintf(stderr, "libpng error no. %s: %s",
-             error_number, error_message + offset + 1);
-         fprintf(stderr, PNG_STRING_NEWLINE);
-      }
-
-      else
-      {
-         fprintf(stderr, "libpng error: %s, offset=%d",
-             error_message, offset);
-         fprintf(stderr, PNG_STRING_NEWLINE);
-      }
-   }
-   else
-#endif
-   {
-      fprintf(stderr, "libpng error: %s", error_message ? error_message :
-         "undefined");
-      fprintf(stderr, PNG_STRING_NEWLINE);
-   }
+   fprintf(stderr, "libpng error: %s", error_message ? error_message :
+      "undefined");
+   fprintf(stderr, PNG_STRING_NEWLINE);
 #else
    PNG_UNUSED(error_message) /* Make compiler happy */
 #endif
@@ -814,40 +726,8 @@ static void /* PRIVATE */
 png_default_warning(png_const_structrp png_ptr, png_const_charp warning_message)
 {
 #ifdef PNG_CONSOLE_IO_SUPPORTED
-#  ifdef PNG_ERROR_NUMBERS_SUPPORTED
-   if (*warning_message == PNG_LITERAL_SHARP)
-   {
-      int offset;
-      char warning_number[16];
-      for (offset = 0; offset < 15; offset++)
-      {
-         warning_number[offset] = warning_message[offset + 1];
-         if (warning_message[offset] == ' ')
-            break;
-      }
-
-      if ((offset > 1) && (offset < 15))
-      {
-         warning_number[offset + 1] = '\0';
-         fprintf(stderr, "libpng warning no. %s: %s",
-             warning_number, warning_message + offset);
-         fprintf(stderr, PNG_STRING_NEWLINE);
-      }
-
-      else
-      {
-         fprintf(stderr, "libpng warning: %s",
-             warning_message);
-         fprintf(stderr, PNG_STRING_NEWLINE);
-      }
-   }
-   else
-#  endif
-
-   {
-      fprintf(stderr, "libpng warning: %s", warning_message);
-      fprintf(stderr, PNG_STRING_NEWLINE);
-   }
+   fprintf(stderr, "libpng warning: %s", warning_message);
+   fprintf(stderr, PNG_STRING_NEWLINE);
 #else
    PNG_UNUSED(warning_message) /* Make compiler happy */
 #endif
@@ -895,12 +775,8 @@ png_get_error_ptr(png_const_structrp png_ptr)
 void PNGAPI
 png_set_strip_error_numbers(png_structrp png_ptr, png_uint_32 strip_mode)
 {
-   if (png_ptr != NULL)
-   {
-      png_ptr->flags &=
-         ((~(PNG_FLAG_STRIP_ERROR_NUMBERS |
-         PNG_FLAG_STRIP_ERROR_TEXT))&strip_mode);
-   }
+   PNG_UNUSED(png_ptr)
+   PNG_UNUSED(strip_mode)
 }
 #endif
 
diff --git a/src/java.desktop/share/native/libsplashscreen/libpng/pngget.c b/src/java.desktop/share/native/libsplashscreen/libpng/pngget.c
index d67adbae247..ed2e7f886f5 100644
--- a/src/java.desktop/share/native/libsplashscreen/libpng/pngget.c
+++ b/src/java.desktop/share/native/libsplashscreen/libpng/pngget.c
@@ -29,7 +29,7 @@
  * However, the following notice accompanied the original version of this
  * file and, per its terms, should not be removed:
  *
- * Copyright (c) 2018-2024 Cosmin Truta
+ * Copyright (c) 2018-2025 Cosmin Truta
  * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson
  * Copyright (c) 1996-1997 Andreas Dilger
  * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc.
diff --git a/src/java.desktop/share/native/libsplashscreen/libpng/pnginfo.h b/src/java.desktop/share/native/libsplashscreen/libpng/pnginfo.h
index bc6ed3d09c9..c79c6cc780f 100644
--- a/src/java.desktop/share/native/libsplashscreen/libpng/pnginfo.h
+++ b/src/java.desktop/share/native/libsplashscreen/libpng/pnginfo.h
@@ -22,14 +22,14 @@
  * questions.
  */
 
-/* pnginfo.h - header file for PNG reference library
+/* pnginfo.h - internal structures for libpng
  *
  * This file is available under and governed by the GNU General Public
  * License version 2 only, as published by the Free Software Foundation.
  * However, the following notice accompanied the original version of this
  * file and, per its terms, should not be removed:
  *
- * Copyright (c) 2018 Cosmin Truta
+ * Copyright (c) 2018-2025 Cosmin Truta
  * Copyright (c) 1998-2002,2004,2006-2013,2018 Glenn Randers-Pehrson
  * Copyright (c) 1996-1997 Andreas Dilger
  * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc.
@@ -39,43 +39,20 @@
  * and license in png.h
  */
 
- /* png_info is a structure that holds the information in a PNG file so
- * that the application can find out the characteristics of the image.
- * If you are reading the file, this structure will tell you what is
- * in the PNG file.  If you are writing the file, fill in the information
- * you want to put into the PNG file, using png_set_*() functions, then
- * call png_write_info().
+#ifndef PNGPRIV_H
+#  error This file must not be included by applications; please include 
+#endif
+
+/* INTERNAL, PRIVATE definition of a PNG.
  *
- * The names chosen should be very close to the PNG specification, so
- * consult that document for information about the meaning of each field.
+ * png_info is a modifiable description of a PNG datastream.  The fields inside
+ * this structure are accessed through png_get_() functions and modified
+ * using png_set_() functions.
  *
- * With libpng < 0.95, it was only possible to directly set and read the
- * the values in the png_info_struct, which meant that the contents and
- * order of the values had to remain fixed.  With libpng 0.95 and later,
- * however, there are now functions that abstract the contents of
- * png_info_struct from the application, so this makes it easier to use
- * libpng with dynamic libraries, and even makes it possible to use
- * libraries that don't have all of the libpng ancillary chunk-handing
- * functionality.  In libpng-1.5.0 this was moved into a separate private
- * file that is not visible to applications.
- *
- * The following members may have allocated storage attached that should be
- * cleaned up before the structure is discarded: palette, trans, text,
- * pcal_purpose, pcal_units, pcal_params, hist, iccp_name, iccp_profile,
- * splt_palettes, scal_unit, row_pointers, and unknowns.   By default, these
- * are automatically freed when the info structure is deallocated, if they were
- * allocated internally by libpng.  This behavior can be changed by means
- * of the png_data_freer() function.
- *
- * More allocation details: all the chunk-reading functions that
- * change these members go through the corresponding png_set_*
- * functions.  A function to clear these members is available: see
- * png_free_data().  The png_set_* functions do not depend on being
- * able to point info structure members to any of the storage they are
- * passed (they make their own copies), EXCEPT that the png_set_text
- * functions use the same storage passed to them in the text_ptr or
- * itxt_ptr structure argument, and the png_set_rows and png_set_unknowns
- * functions do not make their own copies.
+ * Some functions in libpng do directly access members of png_info.  However,
+ * this should be avoided.  png_struct objects contain members which hold
+ * caches, sometimes optimised, of the values from png_info objects, and
+ * png_info is not passed to the functions which read and write image data.
  */
 #ifndef PNGINFO_H
 #define PNGINFO_H
diff --git a/src/java.desktop/share/native/libsplashscreen/libpng/pnglibconf.h b/src/java.desktop/share/native/libsplashscreen/libpng/pnglibconf.h
index 906f855db0e..4cfae474751 100644
--- a/src/java.desktop/share/native/libsplashscreen/libpng/pnglibconf.h
+++ b/src/java.desktop/share/native/libsplashscreen/libpng/pnglibconf.h
@@ -31,7 +31,7 @@
  * However, the following notice accompanied the original version of this
  * file and, per its terms, should not be removed:
  */
-/* libpng version 1.6.47 */
+/* libpng version 1.6.51 */
 
 /* Copyright (c) 2018-2025 Cosmin Truta */
 /* Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson */
diff --git a/src/java.desktop/share/native/libsplashscreen/libpng/pngmem.c b/src/java.desktop/share/native/libsplashscreen/libpng/pngmem.c
index ba9eb4df402..12b71bcbc02 100644
--- a/src/java.desktop/share/native/libsplashscreen/libpng/pngmem.c
+++ b/src/java.desktop/share/native/libsplashscreen/libpng/pngmem.c
@@ -29,7 +29,7 @@
  * However, the following notice accompanied the original version of this
  * file and, per its terms, should not be removed:
  *
- * Copyright (c) 2018 Cosmin Truta
+ * Copyright (c) 2018-2025 Cosmin Truta
  * Copyright (c) 1998-2002,2004,2006-2014,2016 Glenn Randers-Pehrson
  * Copyright (c) 1996-1997 Andreas Dilger
  * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc.
diff --git a/src/java.desktop/share/native/libsplashscreen/libpng/pngpread.c b/src/java.desktop/share/native/libsplashscreen/libpng/pngpread.c
index 86d0c7aaa64..3bfa913000f 100644
--- a/src/java.desktop/share/native/libsplashscreen/libpng/pngpread.c
+++ b/src/java.desktop/share/native/libsplashscreen/libpng/pngpread.c
@@ -29,7 +29,7 @@
  * However, the following notice accompanied the original version of this
  * file and, per its terms, should not be removed:
  *
- * Copyright (c) 2018-2024 Cosmin Truta
+ * Copyright (c) 2018-2025 Cosmin Truta
  * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson
  * Copyright (c) 1996-1997 Andreas Dilger
  * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc.
@@ -258,6 +258,14 @@ png_push_read_chunk(png_structrp png_ptr, png_inforp info_ptr)
          png_benign_error(png_ptr, "Too many IDATs found");
    }
 
+   else if ((png_ptr->mode & PNG_HAVE_IDAT) != 0)
+   {
+      /* These flags must be set consistently for all non-IDAT chunks,
+       * including the unknown chunks.
+       */
+      png_ptr->mode |= PNG_HAVE_CHUNK_AFTER_IDAT | PNG_AFTER_IDAT;
+   }
+
    if (chunk_name == png_IHDR)
    {
       if (png_ptr->push_length != 13)
diff --git a/src/java.desktop/share/native/libsplashscreen/libpng/pngpriv.h b/src/java.desktop/share/native/libsplashscreen/libpng/pngpriv.h
index 25bac4b9e69..dcd005efb34 100644
--- a/src/java.desktop/share/native/libsplashscreen/libpng/pngpriv.h
+++ b/src/java.desktop/share/native/libsplashscreen/libpng/pngpriv.h
@@ -29,7 +29,7 @@
  * However, the following notice accompanied the original version of this
  * file and, per its terms, should not be removed:
  *
- * Copyright (c) 2018-2024 Cosmin Truta
+ * Copyright (c) 2018-2025 Cosmin Truta
  * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson
  * Copyright (c) 1996-1997 Andreas Dilger
  * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc.
@@ -48,8 +48,20 @@
  * they should be well aware of the issues that may arise from doing so.
  */
 
+
+/* pngpriv.h must be included first in each translation unit inside libpng.
+ * On the other hand, it must not be included at all, directly or indirectly,
+ * by any application code that uses the libpng API.
+ */
 #ifndef PNGPRIV_H
-#define PNGPRIV_H
+#  define PNGPRIV_H
+#else
+#  error Duplicate inclusion of pngpriv.h; please check the libpng source files
+#endif
+
+#if defined(PNG_H) || defined(PNGCONF_H) || defined(PNGLCONF_H)
+#  error This file must not be included by applications; please include 
+#endif
 
 /* Feature Test Macros.  The following are defined here to ensure that correctly
  * implemented libraries reveal the APIs libpng needs to build and hide those
@@ -86,7 +98,6 @@
  */
 #if defined(HAVE_CONFIG_H) && !defined(PNG_NO_CONFIG_H)
 #  include 
-
    /* Pick up the definition of 'restrict' from config.h if it was read: */
 #  define PNG_RESTRICT restrict
 #endif
@@ -96,9 +107,7 @@
  * are not internal definitions may be required.  This is handled below just
  * before png.h is included, but load the configuration now if it is available.
  */
-#ifndef PNGLCONF_H
-#  include "pnglibconf.h"
-#endif
+#include "pnglibconf.h"
 
 /* Local renames may change non-exported API functions from png.h */
 #if defined(PNG_PREFIX) && !defined(PNGPREFIX_H)
@@ -163,6 +172,20 @@
 #  endif
 #endif
 
+#ifndef PNG_RISCV_RVV_OPT
+   /* RISCV_RVV optimizations are being controlled by the compiler settings,
+    * typically the target compiler will define __riscv but the rvv extension
+    * availability has to be explicitly stated. This is why if no
+    * PNG_RISCV_RVV_OPT was defined then a runtime check will be executed.
+    *
+    * To enable RISCV_RVV optimizations unconditionally, and compile the
+    * associated code, pass --enable-riscv-rvv=yes or --enable-riscv-rvv=on
+    * to configure or put -DPNG_RISCV_RVV_OPT=2 in CPPFLAGS.
+    */
+
+#  define PNG_RISCV_RVV_OPT 0
+#endif
+
 #if PNG_ARM_NEON_OPT > 0
    /* NEON optimizations are to be at least considered by libpng, so enable the
     * callbacks to do this.
@@ -308,6 +331,16 @@
 #   define PNG_LOONGARCH_LSX_IMPLEMENTATION 0
 #endif
 
+#if PNG_RISCV_RVV_OPT > 0 && __riscv_v >= 1000000
+#  define PNG_FILTER_OPTIMIZATIONS png_init_filter_functions_rvv
+#  ifndef PNG_RISCV_RVV_IMPLEMENTATION
+      /* Use the intrinsics code by default. */
+#     define PNG_RISCV_RVV_IMPLEMENTATION 1
+#  endif
+#else
+#  define PNG_RISCV_RVV_IMPLEMENTATION 0
+#endif /* PNG_RISCV_RVV_OPT > 0 && __riscv_v >= 1000000 */
+
 /* Is this a build of a DLL where compilation of the object modules requires
  * different preprocessor settings to those required for a simple library?  If
  * so PNG_BUILD_DLL must be set.
@@ -706,7 +739,7 @@
 /* #define PNG_FLAG_KEEP_UNKNOWN_CHUNKS      0x8000U */
 /* #define PNG_FLAG_KEEP_UNSAFE_CHUNKS      0x10000U */
 #define PNG_FLAG_LIBRARY_MISMATCH        0x20000U
-#define PNG_FLAG_STRIP_ERROR_NUMBERS     0x40000U
+                                  /*     0x40000U    unused */
 #define PNG_FLAG_STRIP_ERROR_TEXT        0x80000U
 #define PNG_FLAG_BENIGN_ERRORS_WARN     0x100000U /* Added to libpng-1.4.0 */
 #define PNG_FLAG_APP_WARNINGS_WARN      0x200000U /* Added to libpng-1.6.0 */
@@ -1020,17 +1053,15 @@
  * must match that used in the build, or we must be using pnglibconf.h.prebuilt:
  */
 #if PNG_ZLIB_VERNUM != 0 && PNG_ZLIB_VERNUM != ZLIB_VERNUM
-#  error ZLIB_VERNUM != PNG_ZLIB_VERNUM \
-      "-I (include path) error: see the notes in pngpriv.h"
-   /* This means that when pnglibconf.h was built the copy of zlib.h that it
-    * used is not the same as the one being used here.  Because the build of
-    * libpng makes decisions to use inflateInit2 and inflateReset2 based on the
-    * zlib version number and because this affects handling of certain broken
-    * PNG files the -I directives must match.
+#  error The include path of  is incorrect
+   /* When pnglibconf.h was built, the copy of zlib.h that it used was not the
+    * same as the one being used here.  Considering how libpng makes decisions
+    * to use the zlib API based on the zlib version number, the -I options must
+    * match.
     *
-    * The most likely explanation is that you passed a -I in CFLAGS. This will
-    * not work; all the preprocessor directives and in particular all the -I
-    * directives must be in CPPFLAGS.
+    * A possible cause of this mismatch is that you passed an -I option in
+    * CFLAGS, which is unlikely to work.  All the preprocessor options, and all
+    * the -I options in particular, should be in CPPFLAGS.
     */
 #endif
 
@@ -1544,6 +1575,23 @@ PNG_INTERNAL_FUNCTION(void,png_read_filter_row_paeth4_lsx,(png_row_infop
     row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY);
 #endif
 
+#if PNG_RISCV_RVV_IMPLEMENTATION == 1
+PNG_INTERNAL_FUNCTION(void,png_read_filter_row_up_rvv,(png_row_infop
+    row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY);
+PNG_INTERNAL_FUNCTION(void,png_read_filter_row_sub3_rvv,(png_row_infop
+    row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY);
+PNG_INTERNAL_FUNCTION(void,png_read_filter_row_sub4_rvv,(png_row_infop
+    row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY);
+PNG_INTERNAL_FUNCTION(void,png_read_filter_row_avg3_rvv,(png_row_infop
+    row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY);
+PNG_INTERNAL_FUNCTION(void,png_read_filter_row_avg4_rvv,(png_row_infop
+    row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY);
+PNG_INTERNAL_FUNCTION(void,png_read_filter_row_paeth3_rvv,(png_row_infop
+    row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY);
+PNG_INTERNAL_FUNCTION(void,png_read_filter_row_paeth4_rvv,(png_row_infop
+    row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY);
+#endif
+
 /* Choose the best filter to use and filter the row data */
 PNG_INTERNAL_FUNCTION(void,png_write_find_filter,(png_structrp png_ptr,
     png_row_infop row_info),PNG_EMPTY);
@@ -2156,6 +2204,11 @@ PNG_INTERNAL_FUNCTION(void, png_init_filter_functions_lsx,
     (png_structp png_ptr, unsigned int bpp), PNG_EMPTY);
 #endif
 
+#  if PNG_RISCV_RVV_IMPLEMENTATION == 1
+PNG_INTERNAL_FUNCTION(void, png_init_filter_functions_rvv,
+   (png_structp png_ptr, unsigned int bpp), PNG_EMPTY);
+#endif
+
 PNG_INTERNAL_FUNCTION(png_uint_32, png_check_keyword, (png_structrp png_ptr,
    png_const_charp key, png_bytep new_key), PNG_EMPTY);
 
@@ -2191,4 +2244,3 @@ PNG_INTERNAL_FUNCTION(int,
 #endif
 
 #endif /* PNG_VERSION_INFO_ONLY */
-#endif /* PNGPRIV_H */
diff --git a/src/java.desktop/share/native/libsplashscreen/libpng/pngread.c b/src/java.desktop/share/native/libsplashscreen/libpng/pngread.c
index 8a6381e1b3e..b53668a09ce 100644
--- a/src/java.desktop/share/native/libsplashscreen/libpng/pngread.c
+++ b/src/java.desktop/share/native/libsplashscreen/libpng/pngread.c
@@ -731,7 +731,12 @@ png_read_end(png_structrp png_ptr, png_inforp info_ptr)
       png_uint_32 chunk_name = png_ptr->chunk_name;
 
       if (chunk_name != png_IDAT)
-         png_ptr->mode |= PNG_HAVE_CHUNK_AFTER_IDAT;
+      {
+         /* These flags must be set consistently for all non-IDAT chunks,
+          * including the unknown chunks.
+          */
+         png_ptr->mode |= PNG_HAVE_CHUNK_AFTER_IDAT | PNG_AFTER_IDAT;
+      }
 
       if (chunk_name == png_IEND)
          png_handle_chunk(png_ptr, info_ptr, length);
@@ -838,7 +843,8 @@ png_read_destroy(png_structrp png_ptr)
 #endif
 
 #if defined(PNG_READ_EXPAND_SUPPORTED) && \
-    defined(PNG_ARM_NEON_IMPLEMENTATION)
+    (defined(PNG_ARM_NEON_IMPLEMENTATION) || \
+     defined(PNG_RISCV_RVV_IMPLEMENTATION))
    png_free(png_ptr, png_ptr->riffled_palette);
    png_ptr->riffled_palette = NULL;
 #endif
@@ -1357,7 +1363,7 @@ png_image_read_header(png_voidp argument)
 
 #ifdef PNG_STDIO_SUPPORTED
 int PNGAPI
-png_image_begin_read_from_stdio(png_imagep image, FILE* file)
+png_image_begin_read_from_stdio(png_imagep image, FILE *file)
 {
    if (image != NULL && image->version == PNG_IMAGE_VERSION)
    {
@@ -3152,6 +3158,54 @@ png_image_read_colormapped(png_voidp argument)
    }
 }
 
+/* Row reading for interlaced 16-to-8 bit depth conversion with local buffer. */
+static int
+png_image_read_direct_scaled(png_voidp argument)
+{
+   png_image_read_control *display = png_voidcast(png_image_read_control*,
+       argument);
+   png_imagep image = display->image;
+   png_structrp png_ptr = image->opaque->png_ptr;
+   png_bytep local_row = png_voidcast(png_bytep, display->local_row);
+   png_bytep first_row = png_voidcast(png_bytep, display->first_row);
+   ptrdiff_t row_bytes = display->row_bytes;
+   int passes;
+
+   /* Handle interlacing. */
+   switch (png_ptr->interlaced)
+   {
+      case PNG_INTERLACE_NONE:
+         passes = 1;
+         break;
+
+      case PNG_INTERLACE_ADAM7:
+         passes = PNG_INTERLACE_ADAM7_PASSES;
+         break;
+
+      default:
+         png_error(png_ptr, "unknown interlace type");
+   }
+
+   /* Read each pass using local_row as intermediate buffer. */
+   while (--passes >= 0)
+   {
+      png_uint_32 y = image->height;
+      png_bytep output_row = first_row;
+
+      for (; y > 0; --y)
+      {
+         /* Read into local_row (gets transformed 8-bit data). */
+         png_read_row(png_ptr, local_row, NULL);
+
+         /* Copy from local_row to user buffer. */
+         memcpy(output_row, local_row, (size_t)row_bytes);
+         output_row += row_bytes;
+      }
+   }
+
+   return 1;
+}
+
 /* Just the row reading part of png_image_read. */
 static int
 png_image_read_composite(png_voidp argument)
@@ -3570,6 +3624,7 @@ png_image_read_direct(png_voidp argument)
    int linear = (format & PNG_FORMAT_FLAG_LINEAR) != 0;
    int do_local_compose = 0;
    int do_local_background = 0; /* to avoid double gamma correction bug */
+   int do_local_scale = 0; /* for interlaced 16-to-8 bit conversion */
    int passes = 0;
 
    /* Add transforms to ensure the correct output format is produced then check
@@ -3703,8 +3758,16 @@ png_image_read_direct(png_voidp argument)
             png_set_expand_16(png_ptr);
 
          else /* 8-bit output */
+         {
             png_set_scale_16(png_ptr);
 
+            /* For interlaced images, use local_row buffer to avoid overflow
+             * in png_combine_row() which writes using IHDR bit-depth.
+             */
+            if (png_ptr->interlaced != 0)
+               do_local_scale = 1;
+         }
+
          change &= ~PNG_FORMAT_FLAG_LINEAR;
       }
 
@@ -3980,6 +4043,24 @@ png_image_read_direct(png_voidp argument)
       return result;
    }
 
+   else if (do_local_scale != 0)
+   {
+      /* For interlaced 16-to-8 conversion, use an intermediate row buffer
+       * to avoid buffer overflows in png_combine_row. The local_row is sized
+       * for the transformed (8-bit) output, preventing the overflow that would
+       * occur if png_combine_row wrote 16-bit data directly to the user buffer.
+       */
+      int result;
+      png_voidp row = png_malloc(png_ptr, png_get_rowbytes(png_ptr, info_ptr));
+
+      display->local_row = row;
+      result = png_safe_execute(image, png_image_read_direct_scaled, display);
+      display->local_row = NULL;
+      png_free(png_ptr, row);
+
+      return result;
+   }
+
    else
    {
       png_alloc_size_t row_bytes = (png_alloc_size_t)display->row_bytes;
diff --git a/src/java.desktop/share/native/libsplashscreen/libpng/pngrio.c b/src/java.desktop/share/native/libsplashscreen/libpng/pngrio.c
index 961d010df42..50a424d0912 100644
--- a/src/java.desktop/share/native/libsplashscreen/libpng/pngrio.c
+++ b/src/java.desktop/share/native/libsplashscreen/libpng/pngrio.c
@@ -29,7 +29,7 @@
  * However, the following notice accompanied the original version of this
  * file and, per its terms, should not be removed:
  *
- * Copyright (c) 2018 Cosmin Truta
+ * Copyright (c) 2018-2025 Cosmin Truta
  * Copyright (c) 1998-2002,2004,2006-2016,2018 Glenn Randers-Pehrson
  * Copyright (c) 1996-1997 Andreas Dilger
  * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc.
@@ -85,7 +85,7 @@ png_default_read_data(png_structp png_ptr, png_bytep data, size_t length)
    /* fread() returns 0 on error, so it is OK to store this in a size_t
     * instead of an int, which is what fread() actually returns.
     */
-   check = fread(data, 1, length, png_voidcast(png_FILE_p, png_ptr->io_ptr));
+   check = fread(data, 1, length, png_voidcast(FILE *, png_ptr->io_ptr));
 
    if (check != length)
       png_error(png_ptr, "Read Error");
diff --git a/src/java.desktop/share/native/libsplashscreen/libpng/pngrtran.c b/src/java.desktop/share/native/libsplashscreen/libpng/pngrtran.c
index 4f31f8f07bc..a19615f49fe 100644
--- a/src/java.desktop/share/native/libsplashscreen/libpng/pngrtran.c
+++ b/src/java.desktop/share/native/libsplashscreen/libpng/pngrtran.c
@@ -29,7 +29,7 @@
  * However, the following notice accompanied the original version of this
  * file and, per its terms, should not be removed:
  *
- * Copyright (c) 2018-2024 Cosmin Truta
+ * Copyright (c) 2018-2025 Cosmin Truta
  * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson
  * Copyright (c) 1996-1997 Andreas Dilger
  * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc.
@@ -57,6 +57,12 @@
 #  endif
 #endif
 
+#ifdef PNG_RISCV_RVV_IMPLEMENTATION
+#  if PNG_RISCV_RVV_IMPLEMENTATION == 1
+#    define PNG_RISCV_RVV_INTRINSICS_AVAILABLE
+#  endif
+#endif
+
 #ifdef PNG_READ_SUPPORTED
 
 /* Set the action on getting a CRC error for an ancillary or critical chunk. */
@@ -524,9 +530,19 @@ png_set_quantize(png_structrp png_ptr, png_colorp palette,
    {
       int i;
 
+      /* Initialize the array to index colors.
+       *
+       * Ensure quantize_index can fit 256 elements (PNG_MAX_PALETTE_LENGTH)
+       * rather than num_palette elements. This is to prevent buffer overflows
+       * caused by malformed PNG files with out-of-range palette indices.
+       *
+       * Be careful to avoid leaking memory. Applications are allowed to call
+       * this function more than once per png_struct.
+       */
+      png_free(png_ptr, png_ptr->quantize_index);
       png_ptr->quantize_index = (png_bytep)png_malloc(png_ptr,
-          (png_alloc_size_t)num_palette);
-      for (i = 0; i < num_palette; i++)
+          PNG_MAX_PALETTE_LENGTH);
+      for (i = 0; i < PNG_MAX_PALETTE_LENGTH; i++)
          png_ptr->quantize_index[i] = (png_byte)i;
    }
 
@@ -538,15 +554,14 @@ png_set_quantize(png_structrp png_ptr, png_colorp palette,
           * Perhaps not the best solution, but good enough.
           */
 
-         int i;
+         png_bytep quantize_sort;
+         int i, j;
 
-         /* Initialize an array to sort colors */
-         png_ptr->quantize_sort = (png_bytep)png_malloc(png_ptr,
+         /* Initialize the local array to sort colors. */
+         quantize_sort = (png_bytep)png_malloc(png_ptr,
              (png_alloc_size_t)num_palette);
-
-         /* Initialize the quantize_sort array */
          for (i = 0; i < num_palette; i++)
-            png_ptr->quantize_sort[i] = (png_byte)i;
+            quantize_sort[i] = (png_byte)i;
 
          /* Find the least used palette entries by starting a
           * bubble sort, and running it until we have sorted
@@ -558,19 +573,18 @@ png_set_quantize(png_structrp png_ptr, png_colorp palette,
          for (i = num_palette - 1; i >= maximum_colors; i--)
          {
             int done; /* To stop early if the list is pre-sorted */
-            int j;
 
             done = 1;
             for (j = 0; j < i; j++)
             {
-               if (histogram[png_ptr->quantize_sort[j]]
-                   < histogram[png_ptr->quantize_sort[j + 1]])
+               if (histogram[quantize_sort[j]]
+                   < histogram[quantize_sort[j + 1]])
                {
                   png_byte t;
 
-                  t = png_ptr->quantize_sort[j];
-                  png_ptr->quantize_sort[j] = png_ptr->quantize_sort[j + 1];
-                  png_ptr->quantize_sort[j + 1] = t;
+                  t = quantize_sort[j];
+                  quantize_sort[j] = quantize_sort[j + 1];
+                  quantize_sort[j + 1] = t;
                   done = 0;
                }
             }
@@ -582,18 +596,18 @@ png_set_quantize(png_structrp png_ptr, png_colorp palette,
          /* Swap the palette around, and set up a table, if necessary */
          if (full_quantize != 0)
          {
-            int j = num_palette;
+            j = num_palette;
 
             /* Put all the useful colors within the max, but don't
              * move the others.
              */
             for (i = 0; i < maximum_colors; i++)
             {
-               if ((int)png_ptr->quantize_sort[i] >= maximum_colors)
+               if ((int)quantize_sort[i] >= maximum_colors)
                {
                   do
                      j--;
-                  while ((int)png_ptr->quantize_sort[j] >= maximum_colors);
+                  while ((int)quantize_sort[j] >= maximum_colors);
 
                   palette[i] = palette[j];
                }
@@ -601,7 +615,7 @@ png_set_quantize(png_structrp png_ptr, png_colorp palette,
          }
          else
          {
-            int j = num_palette;
+            j = num_palette;
 
             /* Move all the used colors inside the max limit, and
              * develop a translation table.
@@ -609,13 +623,13 @@ png_set_quantize(png_structrp png_ptr, png_colorp palette,
             for (i = 0; i < maximum_colors; i++)
             {
                /* Only move the colors we need to */
-               if ((int)png_ptr->quantize_sort[i] >= maximum_colors)
+               if ((int)quantize_sort[i] >= maximum_colors)
                {
                   png_color tmp_color;
 
                   do
                      j--;
-                  while ((int)png_ptr->quantize_sort[j] >= maximum_colors);
+                  while ((int)quantize_sort[j] >= maximum_colors);
 
                   tmp_color = palette[j];
                   palette[j] = palette[i];
@@ -653,8 +667,7 @@ png_set_quantize(png_structrp png_ptr, png_colorp palette,
                }
             }
          }
-         png_free(png_ptr, png_ptr->quantize_sort);
-         png_ptr->quantize_sort = NULL;
+         png_free(png_ptr, quantize_sort);
       }
       else
       {
@@ -1797,19 +1810,51 @@ png_init_read_transformations(png_structrp png_ptr)
                   }
                   else /* if (png_ptr->trans_alpha[i] != 0xff) */
                   {
-                     png_byte v, w;
+                     if ((png_ptr->flags & PNG_FLAG_OPTIMIZE_ALPHA) != 0)
+                     {
+                        /* Premultiply only:
+                         * component = round((component * alpha) / 255)
+                         */
+                        png_uint_32 component;
 
-                     v = png_ptr->gamma_to_1[palette[i].red];
-                     png_composite(w, v, png_ptr->trans_alpha[i], back_1.red);
-                     palette[i].red = png_ptr->gamma_from_1[w];
+                        component = png_ptr->gamma_to_1[palette[i].red];
+                        component =
+                            (component * png_ptr->trans_alpha[i] + 128) / 255;
+                        palette[i].red = png_ptr->gamma_from_1[component];
 
-                     v = png_ptr->gamma_to_1[palette[i].green];
-                     png_composite(w, v, png_ptr->trans_alpha[i], back_1.green);
-                     palette[i].green = png_ptr->gamma_from_1[w];
+                        component = png_ptr->gamma_to_1[palette[i].green];
+                        component =
+                            (component * png_ptr->trans_alpha[i] + 128) / 255;
+                        palette[i].green = png_ptr->gamma_from_1[component];
 
-                     v = png_ptr->gamma_to_1[palette[i].blue];
-                     png_composite(w, v, png_ptr->trans_alpha[i], back_1.blue);
-                     palette[i].blue = png_ptr->gamma_from_1[w];
+                        component = png_ptr->gamma_to_1[palette[i].blue];
+                        component =
+                            (component * png_ptr->trans_alpha[i] + 128) / 255;
+                        palette[i].blue = png_ptr->gamma_from_1[component];
+                     }
+                     else
+                     {
+                        /* Composite with background color:
+                         * component =
+                         *    alpha * component + (1 - alpha) * background
+                         */
+                        png_byte v, w;
+
+                        v = png_ptr->gamma_to_1[palette[i].red];
+                        png_composite(w, v,
+                            png_ptr->trans_alpha[i], back_1.red);
+                        palette[i].red = png_ptr->gamma_from_1[w];
+
+                        v = png_ptr->gamma_to_1[palette[i].green];
+                        png_composite(w, v,
+                            png_ptr->trans_alpha[i], back_1.green);
+                        palette[i].green = png_ptr->gamma_from_1[w];
+
+                        v = png_ptr->gamma_to_1[palette[i].blue];
+                        png_composite(w, v,
+                            png_ptr->trans_alpha[i], back_1.blue);
+                        palette[i].blue = png_ptr->gamma_from_1[w];
+                     }
                   }
                }
                else
@@ -5032,13 +5077,8 @@ png_do_read_transformations(png_structrp png_ptr, png_row_infop row_info)
 
 #ifdef PNG_READ_QUANTIZE_SUPPORTED
    if ((png_ptr->transformations & PNG_QUANTIZE) != 0)
-   {
       png_do_quantize(row_info, png_ptr->row_buf + 1,
           png_ptr->palette_lookup, png_ptr->quantize_index);
-
-      if (row_info->rowbytes == 0)
-         png_error(png_ptr, "png_do_quantize returned rowbytes=0");
-   }
 #endif /* READ_QUANTIZE */
 
 #ifdef PNG_READ_EXPAND_16_SUPPORTED
diff --git a/src/java.desktop/share/native/libsplashscreen/libpng/pngrutil.c b/src/java.desktop/share/native/libsplashscreen/libpng/pngrutil.c
index 6cf466d182a..07d53cb2c76 100644
--- a/src/java.desktop/share/native/libsplashscreen/libpng/pngrutil.c
+++ b/src/java.desktop/share/native/libsplashscreen/libpng/pngrutil.c
@@ -29,7 +29,7 @@
  * However, the following notice accompanied the original version of this
  * file and, per its terms, should not be removed:
  *
- * Copyright (c) 2018-2024 Cosmin Truta
+ * Copyright (c) 2018-2025 Cosmin Truta
  * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson
  * Copyright (c) 1996-1997 Andreas Dilger
  * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc.
@@ -2441,10 +2441,6 @@ png_handle_tEXt(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length)
    }
 #endif
 
-   /* TODO: this doesn't work and shouldn't be necessary. */
-   if ((png_ptr->mode & PNG_HAVE_IDAT) != 0)
-      png_ptr->mode |= PNG_AFTER_IDAT;
-
    buffer = png_read_buffer(png_ptr, length+1);
 
    if (buffer == NULL)
@@ -2515,10 +2511,6 @@ png_handle_zTXt(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length)
    }
 #endif
 
-   /* TODO: should not be necessary. */
-   if ((png_ptr->mode & PNG_HAVE_IDAT) != 0)
-      png_ptr->mode |= PNG_AFTER_IDAT;
-
    /* Note, "length" is sufficient here; we won't be adding
     * a null terminator later.  The limit check in png_handle_chunk should be
     * sufficient.
@@ -2635,10 +2627,6 @@ png_handle_iTXt(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length)
    }
 #endif
 
-   /* TODO: should not be necessary. */
-   if ((png_ptr->mode & PNG_HAVE_IDAT) != 0)
-      png_ptr->mode |= PNG_AFTER_IDAT;
-
    buffer = png_read_buffer(png_ptr, length+1);
 
    if (buffer == NULL)
diff --git a/src/java.desktop/share/native/libsplashscreen/libpng/pngset.c b/src/java.desktop/share/native/libsplashscreen/libpng/pngset.c
index 1bfd292bd46..0b2844f1864 100644
--- a/src/java.desktop/share/native/libsplashscreen/libpng/pngset.c
+++ b/src/java.desktop/share/native/libsplashscreen/libpng/pngset.c
@@ -329,17 +329,14 @@ png_set_mDCV(png_const_structrp png_ptr, png_inforp info_ptr,
     double maxDL, double minDL)
 {
    png_set_mDCV_fixed(png_ptr, info_ptr,
-      /* The ITU approach is to scale by 50,000, not 100,000 so just divide
-       * the input values by 2 and use png_fixed:
-       */
-      png_fixed(png_ptr, white_x / 2, "png_set_mDCV(white(x))"),
-      png_fixed(png_ptr, white_y / 2, "png_set_mDCV(white(y))"),
-      png_fixed(png_ptr, red_x / 2, "png_set_mDCV(red(x))"),
-      png_fixed(png_ptr, red_y / 2, "png_set_mDCV(red(y))"),
-      png_fixed(png_ptr, green_x / 2, "png_set_mDCV(green(x))"),
-      png_fixed(png_ptr, green_y / 2, "png_set_mDCV(green(y))"),
-      png_fixed(png_ptr, blue_x / 2, "png_set_mDCV(blue(x))"),
-      png_fixed(png_ptr, blue_y / 2, "png_set_mDCV(blue(y))"),
+      png_fixed(png_ptr, white_x, "png_set_mDCV(white(x))"),
+      png_fixed(png_ptr, white_y, "png_set_mDCV(white(y))"),
+      png_fixed(png_ptr, red_x, "png_set_mDCV(red(x))"),
+      png_fixed(png_ptr, red_y, "png_set_mDCV(red(y))"),
+      png_fixed(png_ptr, green_x, "png_set_mDCV(green(x))"),
+      png_fixed(png_ptr, green_y, "png_set_mDCV(green(y))"),
+      png_fixed(png_ptr, blue_x, "png_set_mDCV(blue(x))"),
+      png_fixed(png_ptr, blue_y, "png_set_mDCV(blue(y))"),
       png_fixed_ITU(png_ptr, maxDL, "png_set_mDCV(maxDL)"),
       png_fixed_ITU(png_ptr, minDL, "png_set_mDCV(minDL)"));
 }
diff --git a/src/java.desktop/share/native/libsplashscreen/libpng/pngstruct.h b/src/java.desktop/share/native/libsplashscreen/libpng/pngstruct.h
index d6c446564d1..8edb4bc393a 100644
--- a/src/java.desktop/share/native/libsplashscreen/libpng/pngstruct.h
+++ b/src/java.desktop/share/native/libsplashscreen/libpng/pngstruct.h
@@ -22,14 +22,14 @@
  * questions.
  */
 
-/* pngstruct.h - header file for PNG reference library
+/* pngstruct.h - internal structures for libpng
  *
  * This file is available under and governed by the GNU General Public
  * License version 2 only, as published by the Free Software Foundation.
  * However, the following notice accompanied the original version of this
  * file and, per its terms, should not be removed:
  *
- * Copyright (c) 2018-2022 Cosmin Truta
+ * Copyright (c) 2018-2025 Cosmin Truta
  * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson
  * Copyright (c) 1996-1997 Andreas Dilger
  * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc.
@@ -39,11 +39,9 @@
  * and license in png.h
  */
 
-/* The structure that holds the information to read and write PNG files.
- * The only people who need to care about what is inside of this are the
- * people who will be modifying the library for their own special needs.
- * It should NOT be accessed directly by an application.
- */
+#ifndef PNGPRIV_H
+#  error This file must not be included by applications; please include 
+#endif
 
 #ifndef PNGSTRUCT_H
 #define PNGSTRUCT_H
@@ -406,7 +404,8 @@ struct png_struct_def
 
 /* New member added in libpng-1.6.36 */
 #if defined(PNG_READ_EXPAND_SUPPORTED) && \
-    defined(PNG_ARM_NEON_IMPLEMENTATION)
+    (defined(PNG_ARM_NEON_IMPLEMENTATION) || \
+     defined(PNG_RISCV_RVV_IMPLEMENTATION))
    png_bytep riffled_palette; /* buffer for accelerated palette expansion */
 #endif
 
@@ -435,7 +434,6 @@ struct png_struct_def
 
 #ifdef PNG_READ_QUANTIZE_SUPPORTED
 /* The following three members were added at version 1.0.14 and 1.2.4 */
-   png_bytep quantize_sort;          /* working sort array */
    png_bytep index_to_palette;       /* where the original index currently is
                                         in the palette */
    png_bytep palette_to_index;       /* which original index points to this
diff --git a/src/java.management/share/classes/sun/management/ThreadImpl.java b/src/java.management/share/classes/sun/management/ThreadImpl.java
index be54ced066d..a246e01dd63 100644
--- a/src/java.management/share/classes/sun/management/ThreadImpl.java
+++ b/src/java.management/share/classes/sun/management/ThreadImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -308,7 +308,7 @@ public class ThreadImpl implements ThreadMXBean {
                 long id = ids[0];
                 Thread thread = Thread.currentThread();
                 if (id == thread.threadId()) {
-                    times[0] = thread.isVirtual() ? -1L : getThreadTotalCpuTime0(0);
+                    times[0] = thread.isVirtual() ? -1L : getThreadUserCpuTime0(0);
                 } else {
                     times[0] = getThreadUserCpuTime0(id);
                 }
diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/ConnectionPool.java b/src/java.net.http/share/classes/jdk/internal/net/http/ConnectionPool.java
index 1d8cb013295..e1725aa92d5 100644
--- a/src/java.net.http/share/classes/jdk/internal/net/http/ConnectionPool.java
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/ConnectionPool.java
@@ -205,32 +205,38 @@ final class ConnectionPool {
 
         // it's possible that cleanup may have been called.
         HttpConnection toClose = null;
+        boolean stopping = false;
         stateLock.lock();
         try {
             if (cleanup.isDone()) {
                 return;
-            } else if (stopped) {
-                conn.close();
-                return;
-            }
-            if (MAX_POOL_SIZE > 0 && expiryList.size() >= MAX_POOL_SIZE) {
-                toClose = expiryList.removeOldest();
-                if (toClose != null) removeFromPool(toClose);
-            }
-            if (conn instanceof PlainHttpConnection) {
-                putConnection(conn, plainPool);
+            } else if (stopping = stopped) {
+                toClose = conn;
             } else {
-                assert conn.isSecure();
-                putConnection(conn, sslPool);
+                if (MAX_POOL_SIZE > 0 && expiryList.size() >= MAX_POOL_SIZE) {
+                    toClose = expiryList.removeOldest();
+                    if (toClose != null) removeFromPool(toClose);
+                }
+                if (conn instanceof PlainHttpConnection) {
+                    putConnection(conn, plainPool);
+                } else {
+                    assert conn.isSecure();
+                    putConnection(conn, sslPool);
+                }
+                expiryList.add(conn, now, keepAlive);
             }
-            expiryList.add(conn, now, keepAlive);
         } finally {
             stateLock.unlock();
         }
         if (toClose != null) {
             if (debug.on()) {
-                debug.log("Maximum pool size reached: removing oldest connection %s",
-                          toClose.dbgString());
+                if (stopping) {
+                    debug.log("Stopping: close connection %s",
+                            toClose.dbgString());
+                } else {
+                    debug.log("Maximum pool size reached: removing oldest connection %s",
+                            toClose.dbgString());
+                }
             }
             close(toClose);
         }
diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/Exchange.java b/src/java.net.http/share/classes/jdk/internal/net/http/Exchange.java
index c50a4922e80..64ff093152f 100644
--- a/src/java.net.http/share/classes/jdk/internal/net/http/Exchange.java
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/Exchange.java
@@ -27,11 +27,9 @@ package jdk.internal.net.http;
 
 import java.io.IOException;
 import java.net.ProtocolException;
-import java.net.http.HttpClient.Version;
 import java.time.Duration;
 import java.util.Optional;
 import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
@@ -708,12 +706,10 @@ final class Exchange {
                             if (s == null) {
                                 // s can be null if an exception occurred
                                 // asynchronously while sending the preface.
-                                Throwable t = c.getRecordedCause();
+                                final Http2TerminationCause tc = c.getTerminationCause();
                                 IOException ioe;
-                                if (t != null) {
-                                    if (!cached)
-                                        c.close();
-                                    ioe = new IOException("Can't get stream 1: " + t, t);
+                                if (tc != null) {
+                                    ioe = new IOException("Can't get stream 1", tc.getCloseCause());
                                 } else {
                                     ioe = new IOException("Can't get stream 1");
                                 }
diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/Http2ClientImpl.java b/src/java.net.http/share/classes/jdk/internal/net/http/Http2ClientImpl.java
index cc8a2a7142b..6121a0c1673 100644
--- a/src/java.net.http/share/classes/jdk/internal/net/http/Http2ClientImpl.java
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/Http2ClientImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -25,7 +25,6 @@
 
 package jdk.internal.net.http;
 
-import java.io.EOFException;
 import java.io.IOException;
 import java.io.UncheckedIOException;
 import java.util.Base64;
@@ -234,11 +233,8 @@ class Http2ClientImpl {
         }
     }
 
-    private EOFException STOPPED;
     void stop() {
         if (debug.on()) debug.log("stopping");
-        STOPPED = new EOFException("HTTP/2 client stopped");
-        STOPPED.setStackTrace(new StackTraceElement[0]);
         connectionPoolLock.lock();
         try {
             stopping = true;
@@ -253,10 +249,7 @@ class Http2ClientImpl {
     private boolean close(Http2Connection h2c) {
         // close all streams
         try { h2c.closeAllStreams(); } catch (Throwable t) {}
-        // send GOAWAY
         try { h2c.close(); } catch (Throwable t) {}
-        // attempt graceful shutdown
-        try { h2c.shutdown(STOPPED); } catch (Throwable t) {}
         // double check and close any new streams
         try { h2c.closeAllStreams(); } catch (Throwable t) {}
         // Allows for use of removeIf in stop()
diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/Http2Connection.java b/src/java.net.http/share/classes/jdk/internal/net/http/Http2Connection.java
index 63889fa6af2..94b8505da47 100644
--- a/src/java.net.http/share/classes/jdk/internal/net/http/Http2Connection.java
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/Http2Connection.java
@@ -25,21 +25,20 @@
 
 package jdk.internal.net.http;
 
+import java.io.Closeable;
 import java.io.EOFException;
 import java.io.IOException;
 import java.io.UncheckedIOException;
-import java.lang.invoke.MethodHandles;
-import java.lang.invoke.VarHandle;
 import java.net.InetSocketAddress;
 import java.net.ProtocolException;
 import java.net.http.HttpClient;
 import java.net.http.HttpClient.Version;
 import java.net.http.HttpHeaders;
 import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
 import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.net.http.HttpConnectTimeoutException;
 import java.time.Duration;
+import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
@@ -49,6 +48,7 @@ import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.Flow;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.atomic.AtomicReference;
@@ -56,6 +56,7 @@ import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 import java.util.function.Function;
 import java.util.function.Supplier;
+
 import javax.net.ssl.SSLEngine;
 import javax.net.ssl.SSLException;
 
@@ -130,7 +131,7 @@ import static jdk.internal.net.http.frame.SettingsFrame.MAX_HEADER_LIST_SIZE;
  * and incoming stream creation (Server push). Incoming frames destined for a
  * stream are provided by calling Stream.incoming().
  */
-class Http2Connection  {
+class Http2Connection implements Closeable {
 
     final Logger debug = Utils.getDebugLogger(this::dbgString);
     static final Logger DEBUG_LOGGER =
@@ -143,6 +144,8 @@ class Http2Connection  {
     private static final int MAX_SERVER_STREAM_ID = Integer.MAX_VALUE - 1; // 2147483646
     // may be null; must be accessed/updated with the stateLock held
     private IdleConnectionTimeoutEvent idleConnectionTimeoutEvent;
+    private final AtomicBoolean goAwaySent = new AtomicBoolean();
+    private final AtomicBoolean goAwayRecvd = new AtomicBoolean();
 
     /**
      * Flag set when no more streams to be opened on this connection.
@@ -215,7 +218,7 @@ class Http2Connection  {
         }
 
         /**
-         * {@link #shutdown(Throwable) Shuts down} the connection, unless this event is
+         * {@link #close(Http2TerminationCause) Closes} the connection, unless this event is
          * {@link #cancelled}
          */
         @Override
@@ -228,26 +231,20 @@ class Http2Connection  {
             try {
                 if (cancelled) {
                     if (debug.on()) {
-                        debug.log("Not initiating idle connection shutdown");
-                    }
-                    return;
-                }
-                if (!markIdleShutdownInitiated()) {
-                    if (debug.on()) {
-                        debug.log("Unexpected state %s, skipping idle connection shutdown",
-                                describeClosedState(closedState));
+                        debug.log("Idle timeout event already cancelled, not initiating idle connection close");
                     }
                     return;
                 }
+                // the connection has been idle long enough, we now
+                // mark a state indicating that the connection is chosen
+                // for idle termination and should not be handed out (from the pool)
+                // for newer requests.
+                connTerminator.markForIdleTermination();
             } finally {
                 stateLock.unlock();
             }
-            if (debug.on()) {
-                debug.log("Initiating shutdown of HTTP connection which is idle for too long");
-            }
-            HttpConnectTimeoutException hte = new HttpConnectTimeoutException(
-                    "HTTP connection idle, no active streams. Shutting down.");
-            shutdown(hte);
+            // terminate the connection due to being idle long enough
+            connTerminator.idleTimedOut();
         }
 
         /**
@@ -256,7 +253,7 @@ class Http2Connection  {
         void cancel() {
             assert stateLock.isHeldByCurrentThread() : "Current thread doesn't hold " + stateLock;
             // mark as cancelled to prevent potentially already triggered event from actually
-            // doing the shutdown
+            // doing the close
             this.cancelled = true;
             // cancel the timer to prevent the event from being triggered (if it hasn't already)
             client().cancelTimer(this);
@@ -376,16 +373,7 @@ class Http2Connection  {
     }
 
 
-    private static final int HALF_CLOSED_LOCAL  = 1;
-    private static final int HALF_CLOSED_REMOTE = 2;
-    private static final int SHUTDOWN_REQUESTED = 4;
-    // state when idle connection management initiates a shutdown of the connection, after
-    // which the connection will go into SHUTDOWN_REQUESTED state
-    private static final int IDLE_SHUTDOWN_INITIATED = 8;
     private final ReentrantLock stateLock = new ReentrantLock();
-    private volatile int closedState;
-
-    //-------------------------------------
     final HttpConnection connection;
     private final Http2ClientImpl client2;
     private final ConcurrentHashMap> streams = new ConcurrentHashMap<>();
@@ -403,7 +391,9 @@ class Http2Connection  {
     private final Decoder hpackIn;
     final SettingsFrame clientSettings;
     private volatile SettingsFrame serverSettings;
+
     private record PushContinuationState(PushPromiseDecoder pushContDecoder, PushPromiseFrame pushContFrame) {}
+
     private volatile PushContinuationState pushContinuationState;
     private final String key; // for HttpClientImpl.connections map
     private final FramesDecoder framesDecoder;
@@ -418,7 +408,7 @@ class Http2Connection  {
     private final FramesController framesController = new FramesController();
     private final Http2TubeSubscriber subscriber;
     final ConnectionWindowUpdateSender windowUpdater;
-    private final AtomicReference cause = new AtomicReference<>();
+    private final Terminator connTerminator = new Terminator();
     private volatile Supplier initial;
     private volatile Stream initialStream;
 
@@ -640,35 +630,31 @@ class Http2Connection  {
     }
 
     void abandonStream() {
-        boolean shouldClose = false;
         stateLock.lock();
         try {
             long reserved = --numReservedClientStreams;
             assert reserved >= 0;
-            if (finalStream && reserved == 0 && streams.isEmpty()) {
-                shouldClose = true;
-            }
         } catch (Throwable t) {
-            shutdown(t); // in case the assert fires...
+            close(Http2TerminationCause.forException(t)); // in case the assert fires...
         } finally {
             stateLock.unlock();
         }
 
-        // We should close the connection here if
-        // it's not pooled. If it's not pooled it will
-        // be marked final stream, reserved will be 0
-        // after decrementing it by one, and there should
-        // be no active request-response streams.
-        if (shouldClose) {
-            shutdown(new IOException("HTTP/2 connection abandoned"));
+        // if the connection is eligible to be closed, we close it here
+        if (shouldClose()) {
+            close(Http2TerminationCause.noErrorTermination());
         }
-
     }
 
-    boolean shouldClose() {
+    /*
+     * return true if the connection is marked as "final stream" and there
+     * are no active streams on that connection and the connection isn't
+     * reserved for a new stream.
+     */
+    final boolean shouldClose() {
         stateLock.lock();
         try {
-            return finalStream() && streams.isEmpty();
+            return finalStream() && streams.isEmpty() && numReservedClientStreams == 0;
         } finally {
             stateLock.unlock();
         }
@@ -840,22 +826,8 @@ class Http2Connection  {
         return clientSettings.getParameter(MAX_CONCURRENT_STREAMS);
     }
 
-    void close() {
-        if (markHalfClosedLocal()) {
-            // we send a GOAWAY frame only if the remote side hasn't already indicated
-            // the intention to close the connection by previously sending a GOAWAY of its own
-            if (connection.channel().isOpen() && !isMarked(closedState, HALF_CLOSED_REMOTE)) {
-                Log.logTrace("Closing HTTP/2 connection: to {0}", connection.address());
-                GoAwayFrame f = new GoAwayFrame(0,
-                        ErrorFrame.NO_ERROR,
-                        "Requested by user".getBytes(UTF_8));
-                // TODO: set last stream. For now zero ok.
-                sendFrame(f);
-            }
-        }
-    }
-
     long count;
+
     final void asyncReceive(ByteBuffer buffer) {
         // We don't need to read anything and
         // we don't want to send anything back to the server
@@ -904,44 +876,26 @@ class Http2Connection  {
         } catch (Throwable e) {
             String msg = Utils.stackTrace(e);
             Log.logTrace(msg);
-            shutdown(e);
+            close(Http2TerminationCause.forException(e));
         }
     }
 
-    Throwable getRecordedCause() {
-        return cause.get();
+    /**
+     * Closes the connection normally (with a NO_ERROR termination cause), if not already closed.
+     */
+    @Override
+    public final void close() {
+        close(Http2TerminationCause.noErrorTermination());
     }
 
-    void shutdown(Throwable t) {
-        int state = closedState;
-        if (debug.on()) debug.log(() -> "Shutting down h2c (state="+describeClosedState(state)+"): " + t);
-        stateLock.lock();
-        try {
-            if (!markShutdownRequested()) return;
-            cause.compareAndSet(null, t);
-        } finally {
-            stateLock.unlock();
-        }
-
-        if (Log.errors()) {
-            if (t!= null && (!(t instanceof EOFException) || isActive())) {
-                Log.logError(t);
-            } else if (t != null) {
-                Log.logError("Shutting down connection: {0}", t.getMessage());
-            } else {
-                Log.logError("Shutting down connection");
-            }
-        }
-        client2.removeFromPool(this);
-        subscriber.stop(cause.get());
-        for (Stream s : streams.values()) {
-            try {
-                s.connectionClosing(t);
-            } catch (Throwable e) {
-                Log.logError("Failed to close stream {0}: {1}", s.streamid, e);
-            }
-        }
-        connection.close(cause.get());
+    /**
+     * Closes the connection with the given termination cause, if not already closed.
+     *
+     * @param tc the termination cause. cannot be null.
+     */
+    final void close(final Http2TerminationCause tc) {
+        Objects.requireNonNull(tc, "termination cause cannot be null");
+        this.connTerminator.terminate(tc);
     }
 
     /**
@@ -981,15 +935,14 @@ class Http2Connection  {
         } else {
             if (frame instanceof SettingsFrame) {
                 // The stream identifier for a SETTINGS frame MUST be zero
-                framesDecoder.close(
+                protocolError(ErrorFrame.PROTOCOL_ERROR,
                         "The stream identifier for a SETTINGS frame MUST be zero");
-                protocolError(GoAwayFrame.PROTOCOL_ERROR);
                 return;
             }
 
             if (frame instanceof PushPromiseFrame && !serverPushEnabled()) {
                 String protocolError = "received a PUSH_PROMISE when SETTINGS_ENABLE_PUSH is 0";
-                protocolError(ResetFrame.PROTOCOL_ERROR, protocolError);
+                protocolError(ErrorFrame.PROTOCOL_ERROR, protocolError);
                 return;
             }
 
@@ -1144,7 +1097,9 @@ class Http2Connection  {
     // Otherwise, if the frame is dropped after having been added to the
     // inputQ, releaseUnconsumed above should be called.
     final void dropDataFrame(DataFrame df) {
-        if (isMarked(closedState, SHUTDOWN_REQUESTED)) return;
+        if (!isOpen()) {
+            return;
+        }
         if (debug.on()) {
             debug.log("Dropping data frame for stream %d (%d payload bytes)",
                     df.streamid(), df.payloadLength());
@@ -1154,7 +1109,9 @@ class Http2Connection  {
 
     final void ensureWindowUpdated(DataFrame df) {
         try {
-            if (isMarked(closedState, SHUTDOWN_REQUESTED)) return;
+            if (!isOpen()) {
+                return;
+            }
             int length = df.payloadLength();
             if (length > 0) {
                 windowUpdater.update(length);
@@ -1251,12 +1208,16 @@ class Http2Connection  {
             case AltSvcFrame.TYPE -> processAltSvcFrame(0, (AltSvcFrame) frame,
                     connection, connection.client());
 
-            default -> protocolError(ErrorFrame.PROTOCOL_ERROR);
+            default -> protocolError(ErrorFrame.PROTOCOL_ERROR, "unknown frame: " + frame);
         }
     }
 
-    boolean isOpen() {
-        return !isMarkedForShutdown() && connection.channel().isOpen();
+    /**
+     * Returns true if this connection hasn't been terminated and the underlying
+     * {@linkplain NetworkChannel#isOpen() channel is open}. false otherwise.
+     */
+    final boolean isOpen() {
+        return this.connTerminator.terminationCause.get() == null && connection.channel().isOpen();
     }
 
     void resetStream(int streamid, int code) {
@@ -1295,6 +1256,7 @@ class Http2Connection  {
         }
 
     }
+
     private void decrementStreamsCount0(int streamid) {
         Stream s = streams.get(streamid);
         if (s == null || !s.deRegister())
@@ -1346,8 +1308,7 @@ class Http2Connection  {
             // corresponding entry in the window controller.
             windowController.removeStream(streamid);
         }
-        if (finalStream() && streams.isEmpty()) {
-            // should be only 1 stream, but there might be more if server push
+        if (shouldClose()) {
             close();
         } else {
             // Start timer if property present and not already created
@@ -1372,9 +1333,7 @@ class Http2Connection  {
     /**
      * Increments this connection's send Window by the amount in the given frame.
      */
-    private void handleWindowUpdate(WindowUpdateFrame f)
-        throws IOException
-    {
+    private void handleWindowUpdate(WindowUpdateFrame f) {
         int amount = f.getUpdate();
         if (amount <= 0) {
             // ## temporarily disable to workaround a bug in Jetty where it
@@ -1383,37 +1342,19 @@ class Http2Connection  {
         } else {
             boolean success = windowController.increaseConnectionWindow(amount);
             if (!success) {
-                protocolError(ErrorFrame.FLOW_CONTROL_ERROR);  // overflow
+                protocolError(ErrorFrame.FLOW_CONTROL_ERROR, null);  // overflow
             }
         }
     }
 
-    private void protocolError(int errorCode)
-        throws IOException
-    {
-        protocolError(errorCode, null);
+    private void protocolError(final int errorCode, final String msg) {
+        final Http2TerminationCause terminationCause =
+                Http2TerminationCause.forH2Error(errorCode, msg);
+        framesDecoder.close(terminationCause.getLogMsg());
+        close(terminationCause);
     }
 
-    private void protocolError(int errorCode, String msg)
-        throws IOException
-    {
-        String protocolError = "protocol error" + (msg == null?"":(": " + msg));
-        ProtocolException protocolException =
-                new ProtocolException(protocolError);
-        this.cause.compareAndSet(null, protocolException);
-        if (markHalfClosedLocal()) {
-            framesDecoder.close(protocolError);
-            subscriber.stop(protocolException);
-            if (debug.on()) debug.log("Sending GOAWAY due to " + protocolException);
-            GoAwayFrame frame = new GoAwayFrame(0, errorCode);
-            sendFrame(frame);
-        }
-        shutdown(protocolException);
-    }
-
-    private void handleSettings(SettingsFrame frame)
-        throws IOException
-    {
+    private void handleSettings(SettingsFrame frame) {
         assert frame.streamid() == 0;
         if (!frame.getFlag(SettingsFrame.ACK)) {
             int newWindowSize = frame.getParameter(INITIAL_WINDOW_SIZE);
@@ -1430,9 +1371,7 @@ class Http2Connection  {
         }
     }
 
-    private void handlePing(PingFrame frame)
-        throws IOException
-    {
+    private void handlePing(PingFrame frame) {
         frame.setFlag(PingFrame.ACK);
         sendUnorderedFrame(frame);
     }
@@ -1442,7 +1381,7 @@ class Http2Connection  {
         assert lastProcessedStream >= 0 : "unexpected last stream id: "
                 + lastProcessedStream + " in GOAWAY frame";
 
-        markHalfClosedRemote();
+        goAwayRecvd.set(true);
         setFinalStream(); // don't allow any new streams on this connection
         if (debug.on()) {
             debug.log("processing incoming GOAWAY with last processed stream id:%s in frame %s",
@@ -1599,20 +1538,20 @@ class Http2Connection  {
         // must be done with "stateLock" held to co-ordinate idle connection management
         stateLock.lock();
         try {
-            cancelIdleShutdownEvent();
-            // consider the reservation successful only if the connection's state hasn't moved
-            // to "being closed"
-            return isOpen();
+            cancelIdleCloseEvent();
+            // consider the reservation successful only if the connection is open and
+            // hasn't been chosen for idle termination
+            return !this.connTerminator.isMarkedForIdleTermination() && isOpen();
         } finally {
             stateLock.unlock();
         }
     }
 
     /**
-     * Cancels any event that might have been scheduled to shutdown this connection. Must be called
+     * Cancels any event that might have been scheduled to close this connection. Must be called
      * with the stateLock held.
      */
-    private void cancelIdleShutdownEvent() {
+    private void cancelIdleCloseEvent() {
         assert stateLock.isHeldByCurrentThread() : "Current thread doesn't hold " + stateLock;
         if (idleConnectionTimeoutEvent == null) {
             return;
@@ -1627,20 +1566,23 @@ class Http2Connection  {
         // the stream is closed.
         stateLock.lock();
         try {
-            if (!isMarkedForShutdown()) {
+            if (isOpen() && !this.connTerminator.isMarkedForIdleTermination()) {
                 if (debug.on()) {
                     debug.log("Opened stream %d", streamid);
                 }
                 client().streamReference();
                 streams.put(streamid, stream);
-                cancelIdleShutdownEvent();
+                // don't consider the connection idle anymore
+                cancelIdleCloseEvent();
                 return;
             }
         } finally {
             stateLock.unlock();
         }
         if (debug.on()) debug.log("connection closed: closing stream %d", stream);
-        stream.cancel(new IOException("Stream " + streamid + " cancelled", cause.get()));
+        final Http2TerminationCause terminationCause = getTerminationCause();
+        assert terminationCause != null : "termination cause is null";
+        stream.cancel(new IOException("Stream " + streamid + " cancelled", terminationCause.getCloseCause()));
     }
 
     /**
@@ -1743,11 +1685,10 @@ class Http2Connection  {
         int streamid = nextstreamid;
         Throwable cause = null;
         synchronized (this) {
-            if (isMarked(closedState, SHUTDOWN_REQUESTED)) {
-                cause = this.cause.get();
-                if (cause == null) {
-                    cause = new IOException("Connection closed");
-                }
+            if (!isOpen()) {
+                final Http2TerminationCause terminationCause = getTerminationCause();
+                assert terminationCause != null : "termination cause is null";
+                cause = terminationCause.getCloseCause();
             }
         }
         if (cause != null) {
@@ -1762,7 +1703,7 @@ class Http2Connection  {
             return stream;
         } else {
             stream.cancelImpl(new IOException("Request cancelled"));
-            if (finalStream() && streams.isEmpty()) {
+            if (shouldClose()) {
                 close();
             }
             return null;
@@ -1795,14 +1736,12 @@ class Http2Connection  {
             }
             publisher.signalEnqueued();
         } catch (IOException e) {
-            if (!isMarked(closedState, SHUTDOWN_REQUESTED)) {
-                if (!client2.stopping()) {
-                    Log.logError(e);
-                    shutdown(e);
-                } else if (debug.on()) {
-                    debug.log("Failed to send %s while stopping: %s", frame, e);
-                }
+            if (!client2.stopping()) {
+                Log.logError(e);
+            } else if (debug.on()) {
+                debug.log("Failed to send %s while stopping: %s", frame, e);
             }
+            close(Http2TerminationCause.forException(e));
         }
     }
 
@@ -1817,14 +1756,12 @@ class Http2Connection  {
             publisher.enqueue(encodeFrame(frame));
             publisher.signalEnqueued();
         } catch (IOException e) {
-            if (!isMarked(closedState, SHUTDOWN_REQUESTED)) {
-                if (!client2.stopping()) {
-                    Log.logError(e);
-                    shutdown(e);
-                } else if (debug.on()) {
-                    debug.log("Failed to send %s while stopping: %s", frame, e);
-                }
+            if (!client2.stopping()) {
+                Log.logError(e);
+            } else if (debug.on()) {
+                debug.log("Failed to send %s while stopping: %s", frame, e);
             }
+            close(Http2TerminationCause.forException(e));
         }
     }
 
@@ -1839,10 +1776,12 @@ class Http2Connection  {
             publisher.enqueueUnordered(encodeFrame(frame));
             publisher.signalEnqueued();
         } catch (IOException e) {
-            if (!isMarked(closedState, SHUTDOWN_REQUESTED)) {
+            if (!client2.stopping()) {
                 Log.logError(e);
-                shutdown(e);
+            } else if (debug.on()) {
+                debug.log("Failed to send %s while stopping: %s", frame, e);
             }
+            close(Http2TerminationCause.forException(e));
         }
     }
 
@@ -1868,6 +1807,7 @@ class Http2Connection  {
             try {
                 while (!queue.isEmpty() && !scheduler.isStopped()) {
                     ByteBuffer buffer = queue.poll();
+                    assert buffer != null : "null buffer obtained from non-empty queue";
                     if (debug.on())
                         debug.log("sending %d to Http2Connection.asyncReceive",
                                   buffer.remaining());
@@ -1877,7 +1817,12 @@ class Http2Connection  {
                 errorRef.compareAndSet(null, t);
             } finally {
                 Throwable x = errorRef.get();
-                if (x != null) {
+                // if there was any error or if the TubeSubscriber completed normally,
+                // then close the connection
+                if (x != null || completed) {
+                    // although the connection terminator stops the scheduler too,
+                    // we don't want to wait that "long" and instead we should immediately
+                    // stop the scheduler so that we don't enter "processQueue" anymore.
                     scheduler.stop();
                     if (client2.stopping()) {
                         if (debug.on()) {
@@ -1888,7 +1833,11 @@ class Http2Connection  {
                             debug.log("Stopping scheduler", x);
                         }
                     }
-                    Http2Connection.this.shutdown(x);
+                    // terminate the connection
+                    final Http2TerminationCause tc = (x != null)
+                            ? Http2TerminationCause.forException(x)
+                            : Http2TerminationCause.noErrorTermination();
+                    Http2Connection.this.close(tc);
                 }
             }
         }
@@ -1938,11 +1887,17 @@ class Http2Connection  {
         @Override
         public void onComplete() {
             if (completed) return;
-            String msg = isActive()
-                    ? "EOF reached while reading"
-                    : "Idle connection closed by HTTP/2 peer";
-            if (debug.on()) debug.log(msg);
-            errorRef.compareAndSet(null, new EOFException(msg));
+            if (isActive()) {
+                final String msg = "EOF reached while reading";
+                errorRef.compareAndSet(null, new EOFException(msg));
+                if (debug.on()) {
+                    debug.log(msg);
+                }
+            } else {
+                if (debug.on()) {
+                    debug.log("HTTP/2 connection (with no active streams) closed by peer");
+                }
+            }
             completed = true;
             runOrSchedule();
         }
@@ -1955,16 +1910,13 @@ class Http2Connection  {
             dropped = true;
         }
 
-        void stop(Throwable error) {
-            if (errorRef.compareAndSet(null, error)) {
-                completed = true;
-                scheduler.stop();
-                queue.clear();
-                if (subscription != null) {
-                    subscription.cancel();
-                }
-                queue.clear();
+        private void close() {
+            scheduler.stop();
+            queue.clear();
+            if (subscription != null) {
+                subscription.cancel();
             }
+            queue.clear();
         }
     }
 
@@ -1990,6 +1942,7 @@ class Http2Connection  {
     static final class ConnectionWindowUpdateSender extends WindowUpdateSender {
 
         final int initialWindowSize;
+
         public ConnectionWindowUpdateSender(Http2Connection connection,
                                             int initialWindowSize) {
             super(connection, initialWindowSize);
@@ -2004,13 +1957,9 @@ class Http2Connection  {
         @Override
         protected boolean windowSizeExceeded(long received) {
             if (connection.isOpen()) {
-                try {
-                    connection.protocolError(ErrorFrame.FLOW_CONTROL_ERROR,
-                            "connection window exceeded (%s > %s)"
-                                    .formatted(received, windowSize));
-                } catch (IOException io) {
-                    connection.shutdown(io);
-                }
+                connection.protocolError(ErrorFrame.FLOW_CONTROL_ERROR,
+                        "connection window exceeded (%s > %s)"
+                                .formatted(received, windowSize));
             }
             return true;
         }
@@ -2033,72 +1982,144 @@ class Http2Connection  {
         }
     }
 
-    private boolean isMarked(int state, int mask) {
-        return (state & mask) == mask;
-    }
-
-    private boolean isMarkedForShutdown() {
-        final int closedSt = closedState;
-        return isMarked(closedSt, IDLE_SHUTDOWN_INITIATED)
-                || isMarked(closedSt, SHUTDOWN_REQUESTED);
-    }
-
-    private boolean markShutdownRequested() {
-        return markClosedState(SHUTDOWN_REQUESTED);
-    }
-
-    private boolean markHalfClosedLocal() {
-        return markClosedState(HALF_CLOSED_LOCAL);
-    }
-
-    private boolean markHalfClosedRemote() {
-        return markClosedState(HALF_CLOSED_REMOTE);
-    }
-
-    private boolean markIdleShutdownInitiated() {
-        return markClosedState(IDLE_SHUTDOWN_INITIATED);
-    }
-
-    private boolean markClosedState(int flag) {
-        int state, desired;
-        do {
-            state = desired = closedState;
-            if ((state & flag) == flag) return false;
-            desired = state | flag;
-        } while (!CLOSED_STATE.compareAndSet(this, state, desired));
-        return true;
-    }
-
-    String describeClosedState(int state) {
-        if (state == 0) return "active";
-        String desc = null;
-        if (isMarked(state, IDLE_SHUTDOWN_INITIATED)) {
-            desc = "idle-shutdown-initiated";
+    private void sendGoAway(final GoAwayFrame goAway) {
+        // currently we send a GOAWAY just once irrespective of what value the
+        // last stream id was in the GOAWAY frame
+        if (!goAwaySent.compareAndSet(false, true)) {
+            // already sent
+            return;
         }
-        if (isMarked(state, SHUTDOWN_REQUESTED)) {
-            desc = desc == null ? "shutdown" : desc + "+shutdown";
+        if (Log.trace()) {
+            Log.logTrace("{0} sending GOAWAY {1}", connection, goAway);
+        } else if (debug.on()) {
+            debug.log("sending GOAWAY " + goAway);
         }
-        if (isMarked(state, HALF_CLOSED_LOCAL | HALF_CLOSED_REMOTE)) {
-            if (desc == null) return "closed";
-            else return desc + "+closed";
-        }
-        if (isMarked(state, HALF_CLOSED_LOCAL)) {
-            if (desc == null) return "half-closed-local";
-            else return desc + "+half-closed-local";
-        }
-        if (isMarked(state, HALF_CLOSED_REMOTE)) {
-            if (desc == null) return "half-closed-remote";
-            else return desc + "+half-closed-remote";
-        }
-        return "0x" + Integer.toString(state, 16);
+        // this merely enqueues the frame
+        sendFrame(goAway);
     }
 
-    private static final VarHandle CLOSED_STATE;
-    static {
-        try {
-            CLOSED_STATE = MethodHandles.lookup().findVarHandle(Http2Connection.class, "closedState", int.class);
-        } catch (Exception x) {
-            throw new ExceptionInInitializerError(x);
+    /**
+     * Returns the termination cause if the connection is closed, else returns null.
+     */
+    final Http2TerminationCause getTerminationCause() {
+        return this.connTerminator.determineTerminationCause();
+    }
+
+    // Responsible for doing all the necessary work for closing a Http2Connection
+    private final class Terminator {
+        // the cause for closing the connection. Must only be set in the
+        // Terminator.terminate(Http2TerminationCause) method.
+        private final AtomicReference terminationCause = new AtomicReference<>();
+        // true if it has been decided to terminate the connection due to being idle,
+        // false otherwise. should be accessed only when holding the stateLock
+        private boolean chosenForIdleTermination;
+
+        private void terminate(final Http2TerminationCause terminationCause) {
+            Objects.requireNonNull(terminationCause, "termination cause cannot be null");
+            // allow to be terminated only once
+            stateLock.lock();
+            try {
+                final boolean success = this.terminationCause.compareAndSet(null, terminationCause);
+                if (!success) {
+                    // already terminated or is being terminated by some other thread
+                    return;
+                }
+                // disable the idle timeout event, since we are now going to terminate the
+                // connection
+                Http2Connection.this.cancelIdleCloseEvent();
+            } finally {
+                stateLock.unlock();
+            }
+            // do the actual termination
+            doTerminate();
+        }
+
+        private void doTerminate() {
+            final Http2TerminationCause tc = terminationCause.get();
+            assert tc != null : "missing termination cause";
+            // we send a GOAWAY frame only if the remote side hasn't already indicated
+            // the intention to close the connection by previously sending a GOAWAY of its own
+            if (!Http2Connection.this.goAwayRecvd.get()) {
+                final int lastStream = 0; // TODO: set last stream. For now zero is ok.
+                final String peerVisibleReason = tc.getPeerVisibleReason();
+                final GoAwayFrame goAway;
+                if (peerVisibleReason == null) {
+                    goAway = new GoAwayFrame(lastStream, tc.getCloseCode());
+                } else {
+                    goAway = new GoAwayFrame(lastStream, tc.getCloseCode(),
+                            peerVisibleReason.getBytes(UTF_8));
+                }
+                sendGoAway(goAway);
+            }
+            // now close the connection
+
+            if (Log.errors() || debug.on()) {
+                final String stateStr = "Abnormal close=" + tc.isAbnormalClose() +
+                        ", has active streams=" + isActive() +
+                        ", GOAWAY received=" + goAwayRecvd.get() +
+                        ", GOAWAY sent=" + goAwaySent.get();
+                if (Log.errors()) {
+                    Log.logError("Closing connection {0} ({1}) due to: {2}",
+                            connection, stateStr, tc);
+                } else {
+                    debug.log("Closing connection (" + stateStr + ") due to: " + tc);
+                }
+            }
+            // close the TubeSubscriber
+            subscriber.close();
+            client2.removeFromPool(Http2Connection.this);
+            // notify the HTTP/2 streams of the connection closure
+            for (final Stream s : streams.values()) {
+                try {
+                    s.connectionClosing(tc.getCloseCause());
+                } catch (Throwable e) {
+                    Log.logError("Failed to close stream {0}: {1}", s.streamid, e);
+                }
+            }
+            // close the underlying connection
+            connection.close(tc.getCloseCause());
+        }
+
+        private void markForIdleTermination() {
+            assert stateLock.isHeldByCurrentThread() : Thread.currentThread()
+                    + " not holding stateLock";
+            this.chosenForIdleTermination = true;
+        }
+
+        private boolean isMarkedForIdleTermination() {
+            assert stateLock.isHeldByCurrentThread() : Thread.currentThread()
+                    + " not holding stateLock";
+            return this.chosenForIdleTermination;
+        }
+
+        private void idleTimedOut() {
+            if (debug.on()) {
+                debug.log("closing connection due to being idle");
+            }
+            this.terminate(Http2TerminationCause.idleTimedOut());
+        }
+
+        /**
+         * Returns the termination cause for the connection. This method guarantees that if the
+         * {@linkplain Http2Connection#isOpen() connection is not open}, when this method is called,
+         * then it returns a non-null termination cause. Returns null if the connection is open.
+         */
+        private Http2TerminationCause determineTerminationCause() {
+            final Http2TerminationCause tc = this.terminationCause.get();
+            if (tc != null) {
+                // already terminated, return the cause
+                return tc;
+            }
+            if (!connection.channel().isOpen()) {
+                // if the underlying SocketChannel isn't open, then terminate the connection.
+                // that way when Http2Connection.isOpen() returns false in that situation, then this
+                // getTerminationCause() will return a termination cause.
+                terminate(Http2TerminationCause.forException(new ClosedChannelException()));
+                final Http2TerminationCause terminated = this.terminationCause.get();
+                assert terminated != null : "missing termination cause";
+                return terminated;
+            }
+            return null; // connection still open
         }
     }
 }
diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/Http2TerminationCause.java b/src/java.net.http/share/classes/jdk/internal/net/http/Http2TerminationCause.java
new file mode 100644
index 00000000000..89b08cf8888
--- /dev/null
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/Http2TerminationCause.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.internal.net.http;
+
+
+import java.io.IOException;
+import java.net.ProtocolException;
+import java.util.Objects;
+
+import jdk.internal.net.http.common.Utils;
+import jdk.internal.net.http.frame.ErrorFrame;
+
+/**
+ * Termination cause for an {@linkplain Http2Connection HTTP/2 connection}
+ */
+public abstract sealed class Http2TerminationCause {
+    private String logMsg;
+    private String peerVisibleReason;
+    private final int closeCode;
+    private final Throwable originalCause;
+    private final IOException reportedCause;
+
+    private Http2TerminationCause(final int closeCode, final Throwable closeCause) {
+        this.closeCode = closeCode;
+        this.originalCause = closeCause;
+        if (closeCause != null) {
+            this.logMsg = closeCause.toString();
+        }
+        this.reportedCause = toReportedCause(this.originalCause, this.logMsg);
+    }
+
+    private Http2TerminationCause(final int closeCode, final String loggedAs) {
+        this.closeCode = closeCode;
+        this.originalCause = null;
+        this.logMsg = loggedAs;
+        this.reportedCause = toReportedCause(null, this.logMsg);
+    }
+
+    /**
+     * Returns the error code (specified for HTTP/2 ErrorFrame) that caused the
+     * connection termination.
+     */
+    public final int getCloseCode() {
+        return this.closeCode;
+    }
+
+    /**
+     * Returns the {@link IOException} that is considered the cause of the connection termination.
+     * Even a {@linkplain #isAbnormalClose() normal} termination will have
+     * an {@code IOException} associated with it, so this method will always return a non-null instance.
+     */
+    public final IOException getCloseCause() {
+        return this.reportedCause;
+    }
+
+    /**
+     * Returns {@code true} if the connection was terminated due to some exception. {@code false}
+     * otherwise.
+     * A normal connection termination (for example, the connection idle timing out locally)
+     * is not considered as an abnormal termination and this method returns {@code false} for
+     * such cases.
+     */
+    public abstract boolean isAbnormalClose();
+
+    /**
+     * Returns the connection termination cause, represented as a string. Unlike the
+     * {@linkplain #getPeerVisibleReason() peer-visible reason}, this log message will not be
+     * sent across to the peer and it is thus allowed to include additional details that might
+     * help debugging a connection termination.
+     */
+    public final String getLogMsg() {
+        return logMsg;
+    }
+
+    /**
+     * Returns the connection termination cause, represented as a string. This represents the
+     * "debugData" that is sent to the peer in a
+     * {@linkplain  jdk.internal.net.http.frame.GoAwayFrame GOAWAY frame}.
+     */
+    public final String getPeerVisibleReason() {
+        return this.peerVisibleReason;
+    }
+
+    /**
+     * Sets the connection termination cause, represented as a string, which will be sent
+     * to the peer in a {@linkplain  jdk.internal.net.http.frame.GoAwayFrame GOAWAY frame}.
+     * Unlike the {@link #getLogMsg() log message},
+     * it is expected that this peer-visible reason will not contain anything that is not meant
+     * to be viewed by the peer.
+     */
+    protected final void setPeerVisibleReason(final String reasonPhrase) {
+        this.peerVisibleReason = reasonPhrase;
+    }
+
+    /**
+     * Returns a connection termination cause that represents an
+     * {@linkplain #isAbnormalClose() abnormal} termination due to the given {@code cause}.
+     *
+     * @param cause the termination cause, cannot be null.
+     */
+    public static Http2TerminationCause forException(final Throwable cause) {
+        Objects.requireNonNull(cause);
+        if (cause instanceof ProtocolException pe) {
+            return new ProtocolError(pe);
+        }
+        return new InternalError(cause);
+    }
+
+    /**
+     * Returns a connection termination cause that represents a
+     * {@linkplain #isAbnormalClose() normal} termination.
+     */
+    public static Http2TerminationCause noErrorTermination() {
+        return NoError.INSTANCE;
+    }
+
+    /**
+     * Returns a connection termination cause that represents a
+     * {@linkplain #isAbnormalClose() normal} termination due to the connection
+     * being idle.
+     */
+    public static Http2TerminationCause idleTimedOut() {
+        return NoError.IDLE_TIMED_OUT;
+    }
+
+    /**
+     * Returns a connection termination cause that represents an
+     * {@linkplain #isAbnormalClose() abnormal} termination due to the given {@code errorCode}.
+     * Although this method does no checks for the {@code errorCode}, it is expected to be one
+     * of the error codes specified by the HTTP/2 RFC for the ErrorFrame.
+     *
+     * @param errorCode the error code
+     * @param loggedAs  optional log message to be associated with this termination cause
+     */
+    public static Http2TerminationCause forH2Error(final int errorCode, final String loggedAs) {
+        if (errorCode == ErrorFrame.PROTOCOL_ERROR) {
+            return new ProtocolError(loggedAs);
+        } else if (errorCode == ErrorFrame.FLOW_CONTROL_ERROR) {
+            // we treat flow control error as a protocol error currently
+            return new ProtocolError(loggedAs, true);
+        }
+        return new H2StandardError(errorCode, loggedAs);
+    }
+
+    private static IOException toReportedCause(final Throwable original,
+                                               final String fallbackExceptionMsg) {
+        if (original == null) {
+            return fallbackExceptionMsg == null
+                    ? new IOException("connection terminated")
+                    : new IOException(fallbackExceptionMsg);
+        } else if (original instanceof IOException ioe) {
+            return ioe;
+        } else {
+            return Utils.toIOException(original);
+        }
+    }
+
+    private static final class NoError extends Http2TerminationCause {
+        private static final IOException NO_ERROR_MARKER =
+                new IOException("HTTP/2 connection closed normally - no error");
+        private static final IOException NO_ERROR_IDLE_TIMED_OUT_MARKER =
+                new IOException("HTTP/2 connection idle timed out - no error");
+
+        static {
+            // remove the stacktrace from the marker exception instances
+            NO_ERROR_MARKER.setStackTrace(new StackTraceElement[0]);
+            NO_ERROR_IDLE_TIMED_OUT_MARKER.setStackTrace(new StackTraceElement[0]);
+        }
+
+        private static final NoError INSTANCE = new NoError(false);
+        private static final NoError IDLE_TIMED_OUT = new NoError(true);
+
+        private final boolean idleTimedOut;
+
+        private NoError(final boolean idleTimedOut) {
+            super(ErrorFrame.NO_ERROR,
+                    idleTimedOut ? NO_ERROR_IDLE_TIMED_OUT_MARKER : NO_ERROR_MARKER);
+            this.idleTimedOut = idleTimedOut;
+            setPeerVisibleReason(idleTimedOut ? "idle timed out" : "no error");
+        }
+
+        @Override
+        public boolean isAbnormalClose() {
+            return false;
+        }
+
+        @Override
+        public String toString() {
+            return this.idleTimedOut
+                    ? "No error - idle timed out"
+                    : "No error - normal termination";
+        }
+    }
+
+    private static sealed class H2StandardError extends Http2TerminationCause {
+        private H2StandardError(final int errCode, final String msg) {
+            super(errCode, msg);
+            setPeerVisibleReason(ErrorFrame.stringForCode(errCode));
+        }
+
+        private H2StandardError(final int errCode, final Throwable cause) {
+            super(errCode, cause);
+            setPeerVisibleReason(ErrorFrame.stringForCode(errCode));
+        }
+
+        @Override
+        public boolean isAbnormalClose() {
+            return getCloseCode() != ErrorFrame.NO_ERROR;
+        }
+
+        @Override
+        public String toString() {
+            return ErrorFrame.stringForCode(this.getCloseCode());
+        }
+    }
+
+    private static final class ProtocolError extends H2StandardError {
+        private ProtocolError(final String msg) {
+            this(msg, false);
+        }
+
+        private ProtocolError(final String msg, final boolean flowControlError) {
+            super(flowControlError
+                            ? ErrorFrame.FLOW_CONTROL_ERROR
+                            : ErrorFrame.PROTOCOL_ERROR,
+                    new ProtocolException(msg));
+        }
+
+        private ProtocolError(final ProtocolException pe) {
+            super(ErrorFrame.PROTOCOL_ERROR, pe);
+        }
+
+        @Override
+        public boolean isAbnormalClose() {
+            return true;
+        }
+
+        @Override
+        public String toString() {
+            return "Protocol error - " + this.getLogMsg();
+        }
+    }
+
+    private static final class InternalError extends Http2TerminationCause {
+        private InternalError(final Throwable cause) {
+            super(ErrorFrame.INTERNAL_ERROR, cause);
+        }
+
+        @Override
+        public boolean isAbnormalClose() {
+            return true;
+        }
+
+        @Override
+        public String toString() {
+            return "Internal error - " + this.getLogMsg();
+        }
+    }
+}
diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/Http3Connection.java b/src/java.net.http/share/classes/jdk/internal/net/http/Http3Connection.java
index b97a441881d..17f230f3b15 100644
--- a/src/java.net.http/share/classes/jdk/internal/net/http/Http3Connection.java
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/Http3Connection.java
@@ -126,11 +126,16 @@ public final class Http3Connection implements AutoCloseable {
     // as per spec
     // -1 is used to imply no GOAWAY received so far
     private final AtomicLong lowestGoAwayReceipt = new AtomicLong(-1);
+
+    private final Duration idleTimeoutDuration;
     private volatile IdleConnectionTimeoutEvent idleConnectionTimeoutEvent;
     // value of true implies no more streams will be initiated on this connection,
     // and the connection will be closed once the in-progress streams complete.
     private volatile boolean finalStream;
     private volatile boolean allowOnlyOneStream;
+    // true if this connection has been placed in the HTTP/3 connection pool of the HttpClient.
+    // false otherwise.
+    private volatile boolean presentInConnPool;
     // set to true if we decide to open a new connection
     // due to stream limit reached
     private volatile boolean streamLimitReached;
@@ -220,6 +225,17 @@ public final class Http3Connection implements AutoCloseable {
                 // in case of exception. Throws in the dependent
                 // action after wrapping the exception if needed.
                 .exceptionally(this::exceptionallyAndClose);
+
+        this.idleTimeoutDuration = client.client().idleConnectionTimeout(HTTP_3).orElse(null);
+        if (idleTimeoutDuration == null) {
+            // The absence of HTTP/3 idle timeout duration is considered to mean
+            // never idle terminating the connection
+            quicConnection.connectionTerminator().appLayerMaxIdle(Duration.MAX,
+                    this::isQUICTrafficGenerationRequired);
+        } else {
+            quicConnection.connectionTerminator().appLayerMaxIdle(idleTimeoutDuration,
+                    this::isQUICTrafficGenerationRequired);
+        }
         if (Log.http3()) {
             Log.logHttp3("HTTP/3 connection created for " + quicConnectionTag() + " - local address: "
                     + quicConnection.localAddress());
@@ -732,9 +748,8 @@ public final class Http3Connection implements AutoCloseable {
             try {
                 var te = idleConnectionTimeoutEvent;
                 if (te == null && exchangeStreams.isEmpty()) {
-                    te = idleConnectionTimeoutEvent = client.client().idleConnectionTimeout(HTTP_3)
-                            .map(IdleConnectionTimeoutEvent::new).orElse(null);
-                    if (te != null) {
+                    if (idleTimeoutDuration != null) {
+                        te = idleConnectionTimeoutEvent = new IdleConnectionTimeoutEvent();
                         client.client().registerTimer(te);
                     }
                 }
@@ -873,6 +888,48 @@ public final class Http3Connection implements AutoCloseable {
         }
     }
 
+    /**
+     * Mark this connection as being present or absent from the connection pool.
+     */
+    void setPooled(final boolean present) {
+        this.presentInConnPool = present;
+    }
+
+    /**
+     * This callback method is invoked by the QUIC layer when it notices that this
+     * connection hasn't seen any traffic for certain period of time. QUIC
+     * invokes this method to ask HTTP/3 whether the QUIC layer
+     * should generate traffic to keep this connection active.
+     * This method returns true, indicating that the traffic must be generated,
+     * if this HTTP/3 connection is in pool and there's no current request/response
+     * in progress over this connection (i.e. the HTTP/3 connection is idle in the
+     * pool waiting for any new requests to be issued by the application).
+     */
+    private boolean isQUICTrafficGenerationRequired() {
+        if (!isOpen()) {
+            return false;
+        }
+        lock();
+        try {
+            // if there's no HTTP/3 request/responses in progress and the connection is
+            // in the pool (thus idle), then we instruct QUIC to generate traffic on the
+            // QUIC connection to prevent it from being idle terminated.
+            final boolean generateTraffic = this.presentInConnPool
+                    && this.exchanges.isEmpty()
+                    && this.reservedStreamCount.get() == 0
+                    // a connection in the pool could be marked as
+                    // finalStream (for example when it receives a GOAWAY). we don't want
+                    // to generate explicit QUIC traffic for such connections too.
+                    && !this.finalStream;
+            if (debug.on()) {
+                debug.log("QUIC traffic generation required = " + generateTraffic);
+            }
+            return generateTraffic;
+        } finally {
+            unlock();
+        }
+    }
+
     /**
      * Cancels any event that might have been scheduled to shutdown this connection. Must be called
      * with the stateLock held.
@@ -893,8 +950,9 @@ public final class Http3Connection implements AutoCloseable {
         private boolean cancelled;
         private boolean idleShutDownInitiated;
 
-        IdleConnectionTimeoutEvent(Duration duration) {
-            super(duration);
+        IdleConnectionTimeoutEvent() {
+            assert idleTimeoutDuration != null : "idle timeout duration is null";
+            super(idleTimeoutDuration);
         }
 
         @Override
diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/Http3ConnectionPool.java b/src/java.net.http/share/classes/jdk/internal/net/http/Http3ConnectionPool.java
index eaacd8213cb..0f1fe2b7abf 100644
--- a/src/java.net.http/share/classes/jdk/internal/net/http/Http3ConnectionPool.java
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/Http3ConnectionPool.java
@@ -155,23 +155,34 @@ class Http3ConnectionPool {
         assert key.equals(c.key());
         var altService = c.connection().getSourceAltService().orElse(null);
         if (altService != null && altService.wasAdvertised()) {
-            return advertised.putIfAbsent(key, c);
+            final var prev = advertised.putIfAbsent(key, c);
+            if (prev == null) {
+                c.setPooled(true); // mark the newly pooled connection as pooled
+            }
+            return prev;
         }
         assert altService == null || altService.originHasSameAuthority();
-        return unadvertised.putIfAbsent(key, c);
+        final var prev = unadvertised.putIfAbsent(key, c);
+        if (prev == null) {
+            c.setPooled(true); // mark the newly pooled connection as pooled
+        }
+        return prev;
     }
 
-    Http3Connection put(String key, Http3Connection c) {
+    void put(String key, Http3Connection c) {
         Objects.requireNonNull(key);
         Objects.requireNonNull(c);
         assert key.equals(c.key()) : "key mismatch %s -> %s"
                 .formatted(key, c.key());
         var altService = c.connection().getSourceAltService().orElse(null);
         if (altService != null && altService.wasAdvertised()) {
-            return advertised.put(key, c);
+            advertised.put(key, c);
+            c.setPooled(true);
+            return;
         }
         assert altService == null || altService.originHasSameAuthority();
-        return unadvertised.put(key, c);
+        unadvertised.put(key, c);
+        c.setPooled(true);
     }
 
     boolean remove(String key, Http3Connection c) {
@@ -189,11 +200,17 @@ class Http3ConnectionPool {
         }
 
         assert altService == null || altService.originHasSameAuthority();
-        return unadvertised.remove(key, c);
+        final boolean removed = unadvertised.remove(key, c);
+        if (removed) {
+            c.setPooled(false);
+        }
+        return removed;
     }
 
     void clear() {
+        advertised.values().forEach((c) -> c.setPooled(false));
         advertised.clear();
+        unadvertised.values().forEach((c) -> c.setPooled(false));
         unadvertised.clear();
     }
 
diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/HttpClientImpl.java b/src/java.net.http/share/classes/jdk/internal/net/http/HttpClientImpl.java
index 6ea196a4d1c..d02930f4f31 100644
--- a/src/java.net.http/share/classes/jdk/internal/net/http/HttpClientImpl.java
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/HttpClientImpl.java
@@ -53,6 +53,7 @@ import java.security.NoSuchAlgorithmException;
 import java.time.Duration;
 import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
@@ -98,6 +99,7 @@ import jdk.internal.net.http.common.OperationTrackers.Trackable;
 import jdk.internal.net.http.common.OperationTrackers.Tracker;
 import jdk.internal.net.http.common.Utils.SafeExecutor;
 import jdk.internal.net.http.common.Utils.SafeExecutorService;
+import jdk.internal.net.http.common.Utils.UseVTForSelector;
 import jdk.internal.net.http.websocket.BuilderImpl;
 
 import static java.net.http.HttpOption.Http3DiscoveryMode.HTTP_3_URI_ONLY;
@@ -125,6 +127,18 @@ final class HttpClientImpl extends HttpClient implements Trackable {
     // Defaults to value used for HTTP/1 Keep-Alive Timeout. Can be overridden by jdk.httpclient.keepalive.timeout.h2 property.
     static final long IDLE_CONNECTION_TIMEOUT_H2 = getTimeoutProp("jdk.httpclient.keepalive.timeout.h2", KEEP_ALIVE_TIMEOUT);
     static final long IDLE_CONNECTION_TIMEOUT_H3 = getTimeoutProp("jdk.httpclient.keepalive.timeout.h3", IDLE_CONNECTION_TIMEOUT_H2);
+    private final boolean hasRequiredH3TLS;
+    private final boolean hasRequiredH2TLS;
+
+    static final UseVTForSelector USE_VT_FOR_SELECTOR =
+        Utils.useVTForSelector("jdk.internal.httpclient.tcp.selector.useVirtualThreads", "default");
+    private static boolean useVtForSelector() {
+        return switch (USE_VT_FOR_SELECTOR) {
+            case ALWAYS -> true;
+            case NEVER -> false;
+            default -> true;
+        };
+    }
 
     // Define the default factory as a static inner class
     // that embeds all the necessary logic to avoid
@@ -293,7 +307,6 @@ final class HttpClientImpl extends HttpClient implements Trackable {
         if (pending.cf.isDone()) return res;
 
         var client = pending.client;
-        var cf = pending.cf;
         var id = pending.id;
         boolean added = client.pendingRequests.add(pending);
         // this may immediately remove `pending` from the set is the cf is already completed
@@ -342,6 +355,7 @@ final class HttpClientImpl extends HttpClient implements Trackable {
     private final boolean isDefaultExecutor;
     private final SSLContext sslContext;
     private final SSLParameters sslParams;
+    private final Thread selmgrThread;
     private final SelectorManager selmgr;
     private final FilterFactory filters;
     private final Http2ClientImpl client2;
@@ -466,10 +480,17 @@ final class HttpClientImpl extends HttpClient implements Trackable {
                     "HTTP3 is not supported"));
         }
         sslParams = requireNonNullElseGet(builder.sslParams, sslContext::getDefaultSSLParameters);
-        boolean sslParamsSupportedForH3 = sslParams.getProtocols() == null
-                || sslParams.getProtocols().length == 0
-                || isQuicCompatible(sslParams);
-        if (version == Version.HTTP_3 && !sslParamsSupportedForH3) {
+        String[] sslProtocols = sslParams.getProtocols();
+        if (sslProtocols == null) {
+            sslProtocols = requireNonNullElseGet(sslContext.getDefaultSSLParameters().getProtocols(),
+                    () -> new String[0]);
+        }
+        // HTTP/3 MUST use TLS version 1.3 or higher
+        hasRequiredH3TLS = Arrays.asList(sslProtocols).contains("TLSv1.3");
+        // HTTP/2 MUST use TLS version 1.2 or higher for HTTP/2 over TLS
+        hasRequiredH2TLS = hasRequiredH3TLS || Arrays.asList(sslProtocols).contains("TLSv1.2");
+
+        if (version == Version.HTTP_3 && !hasRequiredH3TLS) {
             throw new UncheckedIOException(new UnsupportedProtocolVersionException(
                     "HTTP3 is not supported - TLSv1.3 isn't configured on SSLParameters"));
         }
@@ -496,7 +517,7 @@ final class HttpClientImpl extends HttpClient implements Trackable {
             debug.log("proxySelector is %s (user-supplied=%s)",
                       this.proxySelector, userProxySelector != null);
         authenticator = builder.authenticator;
-        boolean h3Supported = sslCtxSupportedForH3 && sslParamsSupportedForH3;
+        boolean h3Supported = sslCtxSupportedForH3 && hasRequiredH3TLS;
         registry = new AltServicesRegistry(id);
         connections = new ConnectionPool(id);
         client2 = new Http2ClientImpl(this);
@@ -509,12 +530,32 @@ final class HttpClientImpl extends HttpClient implements Trackable {
             // unlikely
             throw new UncheckedIOException(e);
         }
-        selmgr.setDaemon(true);
+        selmgrThread = useVtForSelector()
+                ? Thread.ofVirtual().name("HttpClient-" + id + "-SelectorManager")
+                .inheritInheritableThreadLocals(false).unstarted(selmgr)
+                : Thread.ofPlatform().name("HttpClient-" + id + "-SelectorManager")
+                .inheritInheritableThreadLocals(false).daemon().unstarted(selmgr);
         filters = new FilterFactory();
         initFilters();
         assert facadeRef.get() != null;
     }
 
+    /**
+     * Returns true if the SSL parameter protocols contains at
+     * least one TLS version that HTTP/3 requires.
+     */
+    boolean hasRequiredHTTP3TLSVersion() {
+        return hasRequiredH3TLS;
+    }
+
+    /**
+     * Returns true if the SSL parameter protocols contains at
+     * least one TLS version that HTTP/2 requires.
+     */
+    boolean hasRequiredHTTP2TLSVersion() {
+        return hasRequiredH2TLS;
+    }
+
     // called when the facade is GC'ed.
     // Just wakes up the selector to cleanup...
     void facadeCleanup() {
@@ -528,7 +569,7 @@ final class HttpClientImpl extends HttpClient implements Trackable {
 
     private void start() {
         try {
-            selmgr.start();
+            selmgrThread.start();
         } catch (Throwable t) {
             isStarted.set(true);
             throw t;
@@ -635,7 +676,7 @@ final class HttpClientImpl extends HttpClient implements Trackable {
     @Override
     public boolean awaitTermination(Duration duration) throws InterruptedException {
         // Implicit NPE will be thrown if duration is null
-        return selmgr.join(duration);
+        return selmgrThread.join(duration);
     }
 
     @Override
@@ -927,7 +968,7 @@ final class HttpClientImpl extends HttpClient implements Trackable {
     }
 
     boolean isSelectorThread() {
-        return Thread.currentThread() == selmgr;
+        return Thread.currentThread() == selmgrThread;
     }
 
     AltServicesRegistry registry() { return registry; }
@@ -1157,7 +1198,7 @@ final class HttpClientImpl extends HttpClient implements Trackable {
     }
 
     // Main loop for this client's selector
-    private static final class SelectorManager extends Thread {
+    private static final class SelectorManager implements Runnable {
 
         // For testing purposes we have an internal System property that
         // can control the frequency at which the selector manager will wake
@@ -1196,9 +1237,6 @@ final class HttpClientImpl extends HttpClient implements Trackable {
         private final ReentrantLock lock = new ReentrantLock();
 
         SelectorManager(HttpClientImpl ref) throws IOException {
-            super(null, null,
-                  "HttpClient-" + ref.id + "-SelectorManager",
-                  0, false);
             owner = ref;
             debug = ref.debug;
             debugtimeout = ref.debugtimeout;
@@ -1221,7 +1259,7 @@ final class HttpClientImpl extends HttpClient implements Trackable {
         }
 
         void eventUpdated(AsyncEvent e) throws ClosedChannelException {
-            if (Thread.currentThread() == this) {
+            if (owner.isSelectorThread()) {
                 SelectionKey key = e.channel().keyFor(selector);
                 if (key != null && key.isValid()) {
                     SelectorAttachment sa = (SelectorAttachment) key.attachment();
@@ -1315,8 +1353,19 @@ final class HttpClientImpl extends HttpClient implements Trackable {
             if (!inSelectorThread) selector.wakeup();
         }
 
+        String getName() {
+            return owner.selmgrThread.getName();
+        }
+
         // Only called by the selector manager thread
         private void shutdown() {
+            // first stop the client to avoid seeing exceptions
+            // about "selector manager closed"
+            Log.logTrace("{0}: stopping", owner.dbgTag);
+            try {
+                owner.stop();
+            } catch (Throwable ignored) {
+            }
             try {
                 lock.lock();
                 try {
@@ -1329,6 +1378,7 @@ final class HttpClientImpl extends HttpClient implements Trackable {
                 }
             } catch (IOException ignored) {
             } finally {
+                // cleanup anything that might have been left behind
                 owner.stop();
             }
         }
diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/HttpConnection.java b/src/java.net.http/share/classes/jdk/internal/net/http/HttpConnection.java
index 0219b0960d7..0c1388cec8a 100644
--- a/src/java.net.http/share/classes/jdk/internal/net/http/HttpConnection.java
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/HttpConnection.java
@@ -32,7 +32,6 @@ import java.net.http.HttpResponse;
 import java.nio.ByteBuffer;
 import java.nio.channels.NetworkChannel;
 import java.nio.channels.SocketChannel;
-import java.util.Arrays;
 import java.util.Comparator;
 import java.util.IdentityHashMap;
 import java.util.List;
@@ -43,8 +42,6 @@ import java.util.concurrent.ConcurrentLinkedDeque;
 import java.util.concurrent.Flow;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.BiPredicate;
-import java.util.function.Predicate;
-import java.net.http.HttpClient;
 import java.net.http.HttpClient.Version;
 import java.net.http.HttpHeaders;
 
@@ -285,23 +282,6 @@ abstract class HttpConnection implements Closeable {
      */
     abstract HttpPublisher publisher();
 
-    // HTTP/2 MUST use TLS version 1.2 or higher for HTTP/2 over TLS
-    private static final Predicate testRequiredHTTP2TLSVersion = proto ->
-            proto.equals("TLSv1.2") || proto.equals("TLSv1.3");
-
-   /**
-    * Returns true if the given client's SSL parameter protocols contains at
-    * least one TLS version that HTTP/2 requires.
-    */
-   private static final boolean hasRequiredHTTP2TLSVersion(HttpClient client) {
-       String[] protos = client.sslParameters().getProtocols();
-       if (protos != null) {
-           return Arrays.stream(protos).filter(testRequiredHTTP2TLSVersion).findAny().isPresent();
-       } else {
-           return false;
-       }
-   }
-
     /**
      * Factory for retrieving HttpConnections. A connection can be retrieved
      * from the connection pool, or a new one created if none available.
@@ -359,7 +339,7 @@ abstract class HttpConnection implements Closeable {
             } else {
                 assert !request.isHttp3Only(version); // should have failed before
                 String[] alpn = null;
-                if (version == HTTP_2 && hasRequiredHTTP2TLSVersion(client)) {
+                if (version == HTTP_2 && client.hasRequiredHTTP2TLSVersion()) {
                     // We only come here after we have checked the HTTP/2 connection pool.
                     // We will not negotiate HTTP/2 if we don't have the appropriate TLS version
                     alpn = new String[] { Alpns.H2, Alpns.HTTP_1_1 };
@@ -540,9 +520,7 @@ abstract class HttpConnection implements Closeable {
      * Closes this connection due to the given cause.
      * @param cause the cause for which the connection is closed, may be null
      */
-    void close(Throwable cause) {
-        close();
-    }
+    abstract void close(Throwable cause);
 
     /**
      * {@return the underlying connection flow, if applicable}
diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/HttpQuicConnection.java b/src/java.net.http/share/classes/jdk/internal/net/http/HttpQuicConnection.java
index 0a22256f6c6..36f191fbd67 100644
--- a/src/java.net.http/share/classes/jdk/internal/net/http/HttpQuicConnection.java
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/HttpQuicConnection.java
@@ -31,11 +31,9 @@ import java.net.InetSocketAddress;
 import java.net.SocketAddress;
 import java.net.SocketOption;
 import java.net.URI;
-import java.net.http.HttpClient;
 import java.net.http.HttpConnectTimeoutException;
 import java.nio.channels.NetworkChannel;
 import java.time.Duration;
-import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
@@ -43,7 +41,6 @@ import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Function;
-import java.util.function.Predicate;
 
 import javax.net.ssl.SNIServerName;
 import javax.net.ssl.SSLParameters;
@@ -81,9 +78,6 @@ abstract class HttpQuicConnection extends HttpConnection {
     // the alt-service which was advertised, from some origin, for this connection co-ordinates.
     // can be null, which indicates this wasn't created because of an alt-service
     private final AltService sourceAltService;
-    // HTTP/2 MUST use TLS version 1.3 or higher for HTTP/3 over TLS
-    private static final Predicate testRequiredHTTP3TLSVersion = proto ->
-            proto.equals("TLSv1.3");
 
 
     HttpQuicConnection(Origin originServer, InetSocketAddress address, HttpClientImpl client,
@@ -178,19 +172,6 @@ abstract class HttpQuicConnection extends HttpConnection {
         return quicConnection;
     }
 
-    /**
-     * Returns true if the given client's SSL parameter protocols contains at
-     * least one TLS version that HTTP/3 requires.
-     */
-    private static boolean hasRequiredHTTP3TLSVersion(HttpClient client) {
-        String[] protos = client.sslParameters().getProtocols();
-        if (protos != null) {
-            return Arrays.stream(protos).anyMatch(testRequiredHTTP3TLSVersion);
-        } else {
-            return false;
-        }
-    }
-
     /**
      * Called when the HTTP/3 connection is established, either successfully or
      * unsuccessfully
@@ -260,7 +241,7 @@ abstract class HttpQuicConnection extends HttpConnection {
         // to using HTTP/2
         var debug = h3client.debug();
         var where = "HttpQuicConnection.getHttpQuicConnection";
-        if (proxy != null || !hasRequiredHTTP3TLSVersion(client)) {
+        if (proxy != null || !client.hasRequiredHTTP3TLSVersion()) {
             if (debug.on())
                 debug.log("%s: proxy required or SSL version mismatch", where);
             return null;
diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/PlainHttpConnection.java b/src/java.net.http/share/classes/jdk/internal/net/http/PlainHttpConnection.java
index e705aae72a1..45d242df671 100644
--- a/src/java.net.http/share/classes/jdk/internal/net/http/PlainHttpConnection.java
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/PlainHttpConnection.java
@@ -311,20 +311,20 @@ class PlainHttpConnection extends HttpConnection {
             var connectTimerEvent = this.connectTimerEvent;
             if (connectTimerEvent != null)
                 client().cancelTimer(connectTimerEvent);
-            if (Log.channel()) {
-                Log.logChannel("Closing channel: " + chan);
-            }
-            try {
-                tube.signalClosed(errorRef.get());
-                chan.close();
-            } finally {
-                client().connectionClosed(this);
-            }
+        } finally {
+            stateLock.unlock();
+        }
+        if (Log.channel()) {
+            Log.logChannel("Closing channel: " + chan);
+        }
+        try {
+            tube.signalClosed(errorRef.get());
+            chan.close();
         } catch (IOException e) {
             debug.log("Closing resulted in " + e);
             Log.logTrace("Closing resulted in " + e);
         } finally {
-            stateLock.unlock();
+            client().connectionClosed(this);
         }
     }
 
diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/common/Utils.java b/src/java.net.http/share/classes/jdk/internal/net/http/common/Utils.java
index a02506cff5c..20b0338215c 100644
--- a/src/java.net.http/share/classes/jdk/internal/net/http/common/Utils.java
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/Utils.java
@@ -242,6 +242,15 @@ public final class Utils {
                 return true;
             };
 
+    public enum UseVTForSelector { ALWAYS, NEVER, DEFAULT }
+
+    public static UseVTForSelector useVTForSelector(String property, String defval) {
+        String useVtForSelector = System.getProperty(property, defval);
+        return Stream.of(UseVTForSelector.values())
+                .filter((v) -> v.name().equalsIgnoreCase(useVtForSelector))
+                .findFirst().orElse(UseVTForSelector.DEFAULT);
+    }
+
     public static  T addSuppressed(T x, Throwable suppressed) {
         if (x != suppressed && suppressed != null) {
             var sup = x.getSuppressed();
diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/quic/ConnectionTerminator.java b/src/java.net.http/share/classes/jdk/internal/net/http/quic/ConnectionTerminator.java
index 24230a0883d..14559db4a11 100644
--- a/src/java.net.http/share/classes/jdk/internal/net/http/quic/ConnectionTerminator.java
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/quic/ConnectionTerminator.java
@@ -24,15 +24,77 @@
  */
 package jdk.internal.net.http.quic;
 
-// responsible for managing the connection termination of a QUIC connection
+import java.time.Duration;
+import java.util.function.Supplier;
+
+/**
+ * Responsible for managing the connection termination of a QUIC connection
+ */
 public sealed interface ConnectionTerminator permits ConnectionTerminatorImpl {
 
-    // lets the terminator know that the connection is still alive and should not be
-    // idle timed out
-    void keepAlive();
+    /**
+     * Instructs the connection terminator to consider the connection as active
+     * at the present point in time. The connection terminator will then restart its
+     * idle timeout timer from this point in time.
+     * 

+ * This method must be called when an incoming packet is processed successfully + * or when an ack-eliciting packet is sent by the local endpoint on the connection. + */ + void markActive(); + /** + * Terminates the connection, if not already terminated, with the given cause. + *

+ * A connection is terminated only once with a {@link TerminationCause}. However, this method + * can be called any number of times. If the connection is not already terminated, + * then this method does the necessary work to terminate the connection. Any subsequent + * invocations of this method, after the connection has been terminated, will not + * change the termination cause of the connection. + * + * @param cause the termination cause + */ void terminate(TerminationCause cause); + /** + * Returns {@code true} if the connection is allowed for use, {@code false} otherwise. + *

+ * This method is typically called when a connection that has been idle, is about to be used + * for handling some request. This method allows for co-ordination between the connection usage + * and the connection terminator to prevent the connection from being idle timed out when it is + * about to be used for some request. The connection must only be used if this method + * returns {@code true}. + * + * @return true if the connection can be used, false otherwise + */ boolean tryReserveForUse(); + /** + * Instructs the connection terminator that the application layer allows the + * connection to stay idle for the given {@code maxIdle} duration. If the QUIC + * layer has negotiated an idle timeout for the connection, that's lower than + * the application's {@code maxIdle} duration, then the connection terminator + * upon noticing absence of traffic over the connection for certain duration, + * calls the {@code trafficGenerationCheck} to check if the QUIC layer should + * explicitly generate some traffic to prevent the connection + * from idle terminating. + *

+ * When the {@code trafficGenerationCheck} is invoked, the application layer + * must return {@code true} only if explicit traffic generation is necessary + * to keep the connection alive. + *

+ * If the application layer wishes to never idle terminate the connection, then + * a {@code maxIdle} duration of {@linkplain Duration#MAX Duration.MAX} is recommended. + * + * @param maxIdle the maximum idle duration of the connection, + * at the application layer + * @param trafficGenerationCheck the callback that will be invoked by the connection + * terminator to decide if the QUIC layer should generate + * any traffic to prevent the connection from idle terminating + * @throws NullPointerException if either {@code maxIdle} or {@code trafficGenerationCheck} + * is null + * @throws IllegalArgumentException if {@code maxIdle} is + * {@linkplain Duration#isNegative() negative} or + * {@linkplain Duration#isZero() zero} + */ + void appLayerMaxIdle(Duration maxIdle, Supplier trafficGenerationCheck); } diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/quic/ConnectionTerminatorImpl.java b/src/java.net.http/share/classes/jdk/internal/net/http/quic/ConnectionTerminatorImpl.java index 150d6233953..7870f4f1d8e 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/quic/ConnectionTerminatorImpl.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/quic/ConnectionTerminatorImpl.java @@ -25,11 +25,13 @@ package jdk.internal.net.http.quic; import java.nio.ByteBuffer; +import java.time.Duration; import java.util.List; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; +import java.util.function.Supplier; import jdk.internal.net.http.common.Log; import jdk.internal.net.http.common.Logger; @@ -52,7 +54,6 @@ import static jdk.internal.net.http.quic.QuicConnectionImpl.QuicConnectionState. import static jdk.internal.net.http.quic.TerminationCause.appLayerClose; import static jdk.internal.net.http.quic.TerminationCause.forSilentTermination; import static jdk.internal.net.http.quic.TerminationCause.forTransportError; -import static jdk.internal.net.quic.QuicTransportErrors.INTERNAL_ERROR; import static jdk.internal.net.quic.QuicTransportErrors.NO_ERROR; final class ConnectionTerminatorImpl implements ConnectionTerminator { @@ -70,8 +71,8 @@ final class ConnectionTerminatorImpl implements ConnectionTerminator { } @Override - public void keepAlive() { - this.connection.idleTimeoutManager.keepAlive(); + public void markActive() { + this.connection.idleTimeoutManager.markActive(); } @Override @@ -79,6 +80,11 @@ final class ConnectionTerminatorImpl implements ConnectionTerminator { return this.connection.idleTimeoutManager.tryReserveForUse(); } + @Override + public void appLayerMaxIdle(final Duration maxIdle, final Supplier trafficGenerationCheck) { + this.connection.idleTimeoutManager.appLayerMaxIdle(maxIdle, trafficGenerationCheck); + } + @Override public void terminate(final TerminationCause cause) { Objects.requireNonNull(cause); @@ -143,8 +149,9 @@ final class ConnectionTerminatorImpl implements ConnectionTerminator { Log.logError("{0}: stateless reset from peer ({1})", connection.logTag(), (peerIsServer ? "server" : "client")); } + var label = "quic:" + connection.uniqueId(); final SilentTermination st = forSilentTermination("stateless reset from peer (" - + (peerIsServer ? "server" : "client") + ")"); + + (peerIsServer ? "server" : "client") + ") on " + label); terminate(st); } diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/quic/IdleTimeoutManager.java b/src/java.net.http/share/classes/jdk/internal/net/http/quic/IdleTimeoutManager.java index a7469f18ed8..375a0dc8e16 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/quic/IdleTimeoutManager.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/quic/IdleTimeoutManager.java @@ -24,12 +24,15 @@ */ package jdk.internal.net.http.quic; +import java.time.Duration; import java.util.Objects; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Supplier; import jdk.internal.net.http.common.Deadline; import jdk.internal.net.http.common.Log; @@ -58,17 +61,21 @@ final class IdleTimeoutManager { private IdleTimeoutEvent idleTimeoutEvent; // must be accessed only when holding stateLock private StreamDataBlockedEvent streamDataBlockedEvent; - // the time at which the last outgoing packet was sent or an + // the time (in nanos) at which the last outgoing packet was sent or an // incoming packet processed on the connection - private volatile long lastPacketActivityAt; + private volatile long lastPacketActivityNanos; private final ReentrantLock idleTerminationLock = new ReentrantLock(); // true if it has been decided to terminate the connection due to being idle, // false otherwise. should be accessed only when holding the idleTerminationLock private boolean chosenForIdleTermination; - // the time at which the connection was last reserved for use. + // the time (in nanos) at which the connection was last reserved for use. // should be accessed only when holding the idleTerminationLock - private long lastUsageReservationAt; + private long lastUsageReservationNanos; + + private final AtomicReference> trafficGenerationCheck = new AtomicReference<>(); + // must be accessed only when holding stateLock + private PingEvent pingEvent; IdleTimeoutManager(final QuicConnectionImpl connection) { this.connection = Objects.requireNonNull(connection, "connection"); @@ -79,28 +86,12 @@ final class IdleTimeoutManager { * Starts the idle timeout management for the connection. This should be called * after the handshake is complete for the connection. * - * @throw IllegalStateException if handshake hasn't yet completed or if the handshake - * has failed for the connection + * @throws IllegalStateException if handshake hasn't yet completed or if the handshake + * has failed for the connection */ void start() { - final CompletableFuture handshakeCF = - this.connection.handshakeFlow().handshakeCF(); // start idle management only for successfully completed handshake - if (!handshakeCF.isDone()) { - throw new IllegalStateException("handshake isn't yet complete," - + " cannot start idle connection management"); - } - if (handshakeCF.isCompletedExceptionally()) { - throw new IllegalStateException("cannot start idle connection management for a failed" - + " connection"); - } - startTimers(); - } - - /** - * Starts the idle timeout timer of the QUIC connection, if not already started. - */ - private void startTimers() { + requireSuccessfulHandshake(); if (shutdown.get()) { return; } @@ -116,6 +107,19 @@ final class IdleTimeoutManager { } } + private void requireSuccessfulHandshake() { + final CompletableFuture handshakeCF = + this.connection.handshakeFlow().handshakeCF(); + if (!handshakeCF.isDone()) { + throw new IllegalStateException("handshake isn't yet complete," + + " cannot use idle connection management"); + } + if (handshakeCF.isCompletedExceptionally()) { + throw new IllegalStateException("cannot use idle connection management for a failed" + + " connection"); + } + } + private void startIdleTerminationTimer() { assert stateLock.isHeldByCurrentThread() : "not holding state lock"; final Optional idleTimeoutMillis = getIdleTimeout(); @@ -154,18 +158,14 @@ final class IdleTimeoutManager { } final QuicEndpoint endpoint = this.connection.endpoint(); assert endpoint != null : "QUIC endpoint is null"; - // disable the event (refreshDeadline() of IdleTimeoutEvent will return Deadline.MAX) - final Deadline nextDeadline = this.idleTimeoutEvent.nextDeadline; - if (!nextDeadline.equals(Deadline.MAX)) { - this.idleTimeoutEvent.nextDeadline = Deadline.MAX; - endpoint.timer().reschedule(this.idleTimeoutEvent, Deadline.MIN); - } + // disable the idle timeout timer event + disableTimedEvent(endpoint.timer(), this.idleTimeoutEvent); this.idleTimeoutEvent = null; } private void startStreamDataBlockedTimer() { assert stateLock.isHeldByCurrentThread() : "not holding state lock"; - // 75% of idle timeout or if idle timeout is not configured, then 30 seconds + // 75% of QUIC idle timeout or if QUIC idle timeout is not configured, then 30 seconds final long timeoutMillis = getIdleTimeout() .map((v) -> (long) (0.75 * v)) .orElse(30000L); @@ -194,22 +194,95 @@ final class IdleTimeoutManager { } final QuicEndpoint endpoint = this.connection.endpoint(); assert endpoint != null : "QUIC endpoint is null"; - // disable the event (refreshDeadline() of StreamDataBlockedEvent will return Deadline.MAX) - final Deadline nextDeadline = this.streamDataBlockedEvent.nextDeadline; - if (!nextDeadline.equals(Deadline.MAX)) { - this.streamDataBlockedEvent.nextDeadline = Deadline.MAX; - endpoint.timer().reschedule(this.streamDataBlockedEvent, Deadline.MIN); - } + // disable the stream data blocked timer event + disableTimedEvent(endpoint.timer(), this.streamDataBlockedEvent); this.streamDataBlockedEvent = null; } + // set up a PING timer if the application layer's max idle duration for the connection + // is larger than that of the negotiated QUIC idle timeout for that connection + void appLayerMaxIdle(final Duration maxIdle, final Supplier trafficGenerationCheck) { + Objects.requireNonNull(maxIdle, "maxIdle"); + Objects.requireNonNull(trafficGenerationCheck, "trafficGenerationCheck"); + if (maxIdle.isZero() || maxIdle.isNegative()) { + throw new IllegalArgumentException("invalid maxIdle duration: " + maxIdle); + } + // the application layer must not configure its max idle duration + // until the QUIC connection's handshake has successfully completed + requireSuccessfulHandshake(); + + if (!this.trafficGenerationCheck.compareAndSet(null, trafficGenerationCheck)) { + throw new IllegalStateException("app layer max inactivity already set"); + } + final Optional quicIdleTimeout = getIdleTimeout(); + if (quicIdleTimeout.isEmpty()) { + // the QUIC connection will never idle timeout, nothing more to do + return; + } + // we start the PING sending timer event only if the QUIC layer idle timeout + // is lesser than the app layer's desired idle time + if (Duration.ofMillis(quicIdleTimeout.get()).compareTo(maxIdle) < 0) { + this.stateLock.lock(); + try { + if (shutdown.get()) { + return; + } + // QUIC connection has a lower idle timeout than the app layer. start a timer + // which checks with the app layer at regular intervals to decide whether to + // send a PING to keep the QUIC connection active. + startPingTimer(); + } finally { + this.stateLock.unlock(); + } + } + } + + private void startPingTimer() { + assert stateLock.isHeldByCurrentThread() : "not holding state lock"; + // we don't expect the timer to be started more than once + assert this.pingEvent == null : "PING timer already started"; + final Optional quicIdleTimeout = getIdleTimeout(); + assert quicIdleTimeout.isPresent() : "QUIC idle timeout is disabled, no need to start PING timer"; + // potential PING generation every 75% of QUIC idle timeout + final long pingFrequencyMillis = (long) (0.75 * quicIdleTimeout.get()); + assert pingFrequencyMillis > 0 : "unexpected ping frequency: " + pingFrequencyMillis; + final QuicTimerQueue timerQueue = connection.endpoint().timer(); + final Deadline deadline = timeLine().instant().plusMillis(pingFrequencyMillis); + // create the timeout event and register with the QuicTimerQueue. + this.pingEvent = new PingEvent(deadline, pingFrequencyMillis); + timerQueue.offer(this.pingEvent); + if (debug.on()) { + debug.log("started periodic PING for connection," + + " ping event: " + this.pingEvent + + " deadline: " + deadline); + } else { + Log.logQuic("{0} started periodic PING for connection," + + " ping event: {1} deadline: {2}", + connection.logTag(), this.pingEvent, deadline); + } + } + + private void stopPingTimer() { + assert stateLock.isHeldByCurrentThread() : "not holding state lock"; + if (this.pingEvent == null) { + return; + } + final QuicEndpoint endpoint = this.connection.endpoint(); + assert endpoint != null : "QUIC endpoint is null"; + // disable the ping timer event + disableTimedEvent(endpoint.timer(), this.pingEvent); + this.pingEvent = null; + this.trafficGenerationCheck.set(null); + } + + /** * Attempts to notify the idle connection management that this connection should * be considered "in use". This way the idle connection management doesn't close * this connection during the time the connection is handed out from the pool and any * new stream created on that connection. * - * @return true if the connection has been successfully reserved and is {@link #isOpen()}. false + * @return true if the connection has been successfully reserved. false * otherwise; in which case the connection must not be handed out from the pool. */ boolean tryReserveForUse() { @@ -221,7 +294,7 @@ final class IdleTimeoutManager { } // if the connection is nearing idle timeout due to lack of traffic then // don't use it - final long lastPktActivity = lastPacketActivityAt; + final long lastPktActivity = lastPacketActivityNanos; final long currentNanos = System.nanoTime(); final long inactivityMs = MILLISECONDS.convert((currentNanos - lastPktActivity), NANOSECONDS); @@ -232,7 +305,7 @@ final class IdleTimeoutManager { return false; } // express interest in using the connection - this.lastUsageReservationAt = System.nanoTime(); + this.lastUsageReservationNanos = System.nanoTime(); return true; } finally { this.idleTerminationLock.unlock(); @@ -256,8 +329,9 @@ final class IdleTimeoutManager { return val == NO_IDLE_TIMEOUT ? Optional.empty() : Optional.of(val); } - void keepAlive() { - lastPacketActivityAt = System.nanoTime(); // TODO: timeline().instant()? + // consider the connection as active as of this moment + void markActive() { + lastPacketActivityNanos = System.nanoTime(); // TODO: timeline().instant()? } void shutdown() { @@ -270,6 +344,7 @@ final class IdleTimeoutManager { // unregister the timeout events from the QuicTimerQueue stopIdleTerminationTimer(); stopStreamDataBlockedTimer(); + stopPingTimer(); } finally { this.stateLock.unlock(); } @@ -318,6 +393,15 @@ final class IdleTimeoutManager { return this.connection.endpoint().timeSource(); } + private static void disableTimedEvent(final QuicTimerQueue timer, final TimedEvent te) { + // disable the event (refreshDeadline() of TimedEvent will return Deadline.MAX) + final Deadline nextDeadline = te.nextDeadline; + if (!nextDeadline.equals(Deadline.MAX)) { + te.nextDeadline = Deadline.MAX; + timer.reschedule(te, Deadline.MIN); + } + } + // called when the connection has been idle past its idle timeout duration private void idleTimedOut() { if (shutdown.get()) { @@ -346,41 +430,59 @@ final class IdleTimeoutManager { } } // silently close the connection and discard all its state - final TerminationCause cause = forSilentTermination("connection idle timed out (" - + timeoutMillis + " milli seconds)"); + var type = connection.isClientConnection() ? "client" : "server"; + var label = "quic:" + connection.uniqueId(); + final TerminationCause cause = forSilentTermination(type + " connection idle timed out (" + + timeoutMillis + " milli seconds) on " + label); connection.terminator.terminate(cause); } private long computeInactivityMillis() { + assert idleTerminationLock.isHeldByCurrentThread() : "not holding idle termination lock"; final long currentNanos = System.nanoTime(); - final long lastActiveNanos = Math.max(lastPacketActivityAt, lastUsageReservationAt); + final long lastActiveNanos = Math.max(lastPacketActivityNanos, lastUsageReservationNanos); return MILLISECONDS.convert((currentNanos - lastActiveNanos), NANOSECONDS); } - final class IdleTimeoutEvent implements QuicTimedEvent { - private final long eventId; - private volatile Deadline deadline; - private volatile Deadline nextDeadline; + sealed abstract class TimedEvent implements QuicTimedEvent { + protected final long eventId; + protected volatile Deadline deadline; + protected volatile Deadline nextDeadline; - private IdleTimeoutEvent(final Deadline deadline) { + private TimedEvent(final Deadline deadline) { assert deadline != null : "timeout deadline is null"; this.deadline = this.nextDeadline = deadline; this.eventId = QuicTimerQueue.newEventId(); } @Override - public Deadline deadline() { + public final Deadline deadline() { return this.deadline; } @Override - public Deadline refreshDeadline() { + public final Deadline refreshDeadline() { if (shutdown.get()) { return this.deadline = this.nextDeadline = Deadline.MAX; } return this.deadline = this.nextDeadline; } + @Override + public final long eventId() { + return this.eventId; + } + + @Override + public abstract Deadline handle(); + } + + final class IdleTimeoutEvent extends TimedEvent { + + private IdleTimeoutEvent(final Deadline deadline) { + super(deadline); + } + @Override public Deadline handle() { if (shutdown.get()) { @@ -440,41 +542,18 @@ final class IdleTimeoutManager { } } - @Override - public long eventId() { - return this.eventId; - } - @Override public String toString() { return "QuicIdleTimeoutEvent-" + this.eventId; } } - final class StreamDataBlockedEvent implements QuicTimedEvent { - private final long eventId; + final class StreamDataBlockedEvent extends TimedEvent { private final long timeoutMillis; - private volatile Deadline deadline; - private volatile Deadline nextDeadline; private StreamDataBlockedEvent(final Deadline deadline, final long timeoutMillis) { - assert deadline != null : "timeout deadline is null"; - this.deadline = this.nextDeadline = deadline; + super(deadline); this.timeoutMillis = timeoutMillis; - this.eventId = QuicTimerQueue.newEventId(); - } - - @Override - public Deadline deadline() { - return this.deadline; - } - - @Override - public Deadline refreshDeadline() { - if (shutdown.get()) { - return this.deadline = this.nextDeadline = Deadline.MAX; - } - return this.deadline = this.nextDeadline; } @Override @@ -515,14 +594,95 @@ final class IdleTimeoutManager { } } - @Override - public long eventId() { - return this.eventId; - } - @Override public String toString() { return "StreamDataBlockedEvent-" + this.eventId; } } + + + final class PingEvent extends TimedEvent { + private final long pingFrequencyNanos; + private final long idleTimeoutNanos; + + private PingEvent(final Deadline deadline, final long pingFrequencyMillis) { + super(deadline); + this.pingFrequencyNanos = MILLISECONDS.toNanos(pingFrequencyMillis); + if (this.pingFrequencyNanos <= 0) { + throw new IllegalArgumentException("ping frequency is too small: " + + pingFrequencyMillis + " milliseconds"); + } + this.idleTimeoutNanos = MILLISECONDS.toNanos(getIdleTimeout().get()); + } + + @Override + public Deadline handle() { + if (shutdown.get()) { + // timeout manager is shutdown, nothing more to do + return this.nextDeadline = Deadline.MAX; + } + if (!shouldInitiateAppLayerCheck()) { + // reschedule for next round + this.nextDeadline = timeLine().instant().plusNanos(this.pingFrequencyNanos); + return this.nextDeadline; + } + // check with the app layer if traffic generation is required + final Supplier check = trafficGenerationCheck.get(); + if (check == null) { + // generateTrafficCheck can be null if the timeout manager was shutdown + // when this event handling was in progress. don't send a PING frame + // in that case. + assert shutdown.get() : "trafficGenerationCheck is absent"; + return this.nextDeadline = Deadline.MAX; + } + if (check.get()) { + // app layer OKed sending a PING + connection.requestSendPing(); + if (debug.on()) { + debug.log("enqueued a PING frame"); + } else { + Log.logQuic("{0} enqueued a PING frame", connection.logTag()); + } + } else { + // app layer told us not to send a PING. + // we skip the PING generation only for the current round, no need + // to disable future PING checks + if (debug.on()) { + debug.log("skipping PING generation"); + } else { + Log.logQuic("{0} skipping PING generation", connection.logTag()); + } + } + this.nextDeadline = timeLine().instant().plusNanos(this.pingFrequencyNanos); + return this.nextDeadline; + } + + @Override + public String toString() { + return "PingEvent-" + this.eventId; + } + + // returns true if the app layer traffic generation check needs to be invoked, + // false otherwise. + private boolean shouldInitiateAppLayerCheck() { + final long lastPktAt = lastPacketActivityNanos; + final long now = System.nanoTime(); + if ((now - lastPktAt) >= this.pingFrequencyNanos) { + // no traffic during the ping interval, initiate a app layer check + // to see if explicit traffic needs to be generated + return true; + } + // check if the connection will potentially idle terminate before the next + // ping check is scheduled, if yes, then initiate a app layer traffic + // generation check now + final long idleTerminationAt = lastPktAt + this.idleTimeoutNanos; + final long nextPingCheck = now + this.pingFrequencyNanos; + if (idleTerminationAt - nextPingCheck <= 0) { + return true; + } + // connection appears to be receiving traffic, no need to initiate app layer + // traffic generation check + return false; + } + } } diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/quic/PacketSpaceManager.java b/src/java.net.http/share/classes/jdk/internal/net/http/quic/PacketSpaceManager.java index 90031decdb4..487a8a186f6 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/quic/PacketSpaceManager.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/quic/PacketSpaceManager.java @@ -385,10 +385,10 @@ public sealed class PacketSpaceManager implements PacketSpace } packetEmitter.checkAbort(PacketSpaceManager.this.packetNumberSpace); // Handle is called from within the executor - var nextDeadline = this.nextDeadline; + Deadline newDeadline; Deadline now = now(); - congestionController.updatePacer(now); do { + congestionController.updatePacer(now); transmitNow = false; var closed = !isOpenForTransmission(); if (closed) { @@ -534,16 +534,17 @@ public sealed class PacketSpaceManager implements PacketSpace packetEmitter.ptoBackoffIncreased(PacketSpaceManager.this, backoff); } - // if nextDeadline is not Deadline.MAX the task will be + // if newDeadline is not Deadline.MAX the task will be // automatically rescheduled. if (debug.on()) debug.log("handle: refreshing deadline"); - nextDeadline = computeNextDeadline(); - } while(!nextDeadline.isAfter(now)); + newDeadline = computeNextDeadline(); + now = now(); + } while(!newDeadline.isAfter(now)); - logNoDeadline(nextDeadline, true); - if (Deadline.MAX.equals(nextDeadline)) return; + logNoDeadline(newDeadline, true); + if (Deadline.MAX.equals(newDeadline)) return; // we have a new deadline - packetEmitter.reschedule(this, nextDeadline); + packetEmitter.reschedule(this, newDeadline); } /** diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicBaseCongestionController.java b/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicBaseCongestionController.java new file mode 100644 index 00000000000..7dd3276f0d3 --- /dev/null +++ b/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicBaseCongestionController.java @@ -0,0 +1,318 @@ +/* + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.net.http.quic; + +import jdk.internal.net.http.common.Deadline; +import jdk.internal.net.http.common.Log; +import jdk.internal.net.http.common.TimeLine; +import jdk.internal.net.http.common.TimeSource; +import jdk.internal.net.http.common.Utils; +import jdk.internal.net.http.quic.frames.AckFrame; +import jdk.internal.net.http.quic.packets.QuicPacket; + +import java.util.Collection; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Implementation of the common parts of a QUIC congestion controller based on RFC 9002. + * + * This class implements the common parts of a congestion controller: + * - slow start + * - loss recovery + * - cooperation with pacer + * + * Subclasses implement congestion window growth in congestion avoidance phase. + * + * @spec https://www.rfc-editor.org/info/rfc9002 + * RFC 9002: QUIC Loss Detection and Congestion Control + */ +abstract class QuicBaseCongestionController implements QuicCongestionController { + // higher of 14720 and 2*maxDatagramSize; we use fixed maxDatagramSize + private static final int INITIAL_WINDOW = Math.max(14720, 2 * QuicConnectionImpl.DEFAULT_DATAGRAM_SIZE); + private static final int MAX_BYTES_IN_FLIGHT = Math.clamp( + Utils.getLongProperty("jdk.httpclient.quic.maxBytesInFlight", 1 << 24), + 1 << 14, 1 << 24); + final TimeLine timeSource; + final String dbgTag; + final Lock lock = new ReentrantLock(); + long congestionWindow = INITIAL_WINDOW; + int maxDatagramSize = QuicConnectionImpl.DEFAULT_DATAGRAM_SIZE; + int minimumWindow = 2 * maxDatagramSize; + long bytesInFlight; + // maximum bytes in flight seen since the last congestion event + long maxBytesInFlight; + Deadline congestionRecoveryStartTime; + long ssThresh = Long.MAX_VALUE; + + private final QuicPacer pacer; + + QuicBaseCongestionController(String dbgTag, QuicRttEstimator rttEstimator) { + this(dbgTag, TimeSource.source(), rttEstimator); + } + + // Allows to pass a custom timeline for testing + QuicBaseCongestionController(String dbgTag, TimeLine source, QuicRttEstimator rttEstimator) { + this.dbgTag = dbgTag; + this.timeSource = source; + this.pacer = new QuicPacer(rttEstimator, this); + } + + boolean inCongestionRecovery(Deadline sentTime) { + return (congestionRecoveryStartTime != null && + !sentTime.isAfter(congestionRecoveryStartTime)); + } + + abstract void onCongestionEvent(Deadline sentTime); + + private static boolean inFlight(QuicPacket packet) { + // packet is in flight if it contains anything other than a single ACK frame + // specifically, a packet containing padding is considered to be in flight. + return packet.frames().size() != 1 || + !(packet.frames().get(0) instanceof AckFrame); + } + + @Override + public boolean canSendPacket() { + lock.lock(); + try { + if (bytesInFlight >= MAX_BYTES_IN_FLIGHT) { + return false; + } + if (isCwndLimited() || isPacerLimited()) { + return false; + } + return true; + } finally { + lock.unlock(); + } + } + + @Override + public void updateMaxDatagramSize(int newSize) { + lock.lock(); + try { + if (minimumWindow != newSize * 2) { + minimumWindow = newSize * 2; + maxDatagramSize = newSize; + congestionWindow = Math.max(congestionWindow, minimumWindow); + } + } finally { + lock.unlock(); + } + } + + @Override + public void packetSent(int packetBytes) { + lock.lock(); + try { + bytesInFlight += packetBytes; + if (bytesInFlight > maxBytesInFlight) { + maxBytesInFlight = bytesInFlight; + } + pacer.packetSent(packetBytes); + } finally { + lock.unlock(); + } + } + + @Override + public void packetAcked(int packetBytes, Deadline sentTime) { + lock.lock(); + try { + long oldWindow = congestionWindow; + assert oldWindow >= minimumWindow : + "Congestion window lower than minimum: %s < %s".formatted(oldWindow, minimumWindow); + bytesInFlight -= packetBytes; + // RFC 9002 says we should not increase cwnd when application limited. + // The concept itself is poorly defined. + // Here we limit cwnd growth based on the maximum bytes in flight + // observed since the last congestion event + if (inCongestionRecovery(sentTime)) { + if (Log.quicCC() && Log.trace()) { + Log.logQuic(dbgTag + " Acked, in recovery: bytes: " + packetBytes + + ", in flight: " + bytesInFlight); + } + return; + } + boolean isAppLimited; + if (congestionWindow < ssThresh) { + isAppLimited = congestionWindow >= 2 * maxBytesInFlight; + if (!isAppLimited) { + congestionWindow += packetBytes; + } + } else { + isAppLimited = congestionAvoidanceAcked(packetBytes, sentTime); + } + if (Log.quicCC() && Log.trace()) { + if (isAppLimited) { + Log.logQuic(dbgTag + " Acked, not blocked: bytes: " + packetBytes + + ", in flight: " + bytesInFlight); + } else { + Log.logQuic(dbgTag + " Acked, increased: bytes: " + packetBytes + + ", in flight: " + bytesInFlight + + ", new cwnd:" + congestionWindow); + } + } + assert congestionWindow >= oldWindow : + "Window size decreased on ACK: %s to %s".formatted(oldWindow, congestionWindow); + } finally { + lock.unlock(); + } + } + + abstract boolean congestionAvoidanceAcked(int packetBytes, Deadline sentTime); + + @Override + public void packetLost(Collection lostPackets, Deadline sentTime, boolean persistent) { + lock.lock(); + try { + for (QuicPacket packet : lostPackets) { + if (inFlight(packet)) { + bytesInFlight -= packet.size(); + } + } + onCongestionEvent(sentTime); + if (persistent) { + congestionWindow = minimumWindow; + congestionRecoveryStartTime = null; + if (Log.quicCC()) { + Log.logQuic(dbgTag + " Persistent congestion: ssThresh: " + ssThresh + + ", in flight: " + bytesInFlight + + ", cwnd:" + congestionWindow); + } + } + assert congestionWindow >= minimumWindow : + "Congestion window lower than minimum: %s < %s".formatted(congestionWindow, minimumWindow); + } finally { + lock.unlock(); + } + } + + @Override + public void packetDiscarded(Collection discardedPackets) { + lock.lock(); + try { + for (QuicPacket packet : discardedPackets) { + if (inFlight(packet)) { + bytesInFlight -= packet.size(); + } + } + } finally { + lock.unlock(); + } + } + + @Override + public long congestionWindow() { + lock.lock(); + try { + return congestionWindow; + } finally { + lock.unlock(); + } + } + + @Override + public long initialWindow() { + lock.lock(); + try { + return Math.max(14720, 2 * maxDatagramSize); + } finally { + lock.unlock(); + } + } + + @Override + public long maxDatagramSize() { + lock.lock(); + try { + return maxDatagramSize; + } finally { + lock.unlock(); + } + } + + @Override + public boolean isSlowStart() { + lock.lock(); + try { + return congestionWindow < ssThresh; + } finally { + lock.unlock(); + } + } + + @Override + public void updatePacer(Deadline now) { + lock.lock(); + try { + pacer.updateQuota(now); + } finally { + lock.unlock(); + } + } + + @Override + public boolean isPacerLimited() { + lock.lock(); + try { + return !pacer.canSend(); + } finally { + lock.unlock(); + } + } + + @Override + public boolean isCwndLimited() { + lock.lock(); + try { + return congestionWindow - bytesInFlight < maxDatagramSize; + } finally { + lock.unlock(); + } + } + + @Override + public Deadline pacerDeadline() { + lock.lock(); + try { + return pacer.twoPacketDeadline(); + } finally { + lock.unlock(); + } + } + + @Override + public void appLimited() { + lock.lock(); + try { + pacer.appLimited(); + } finally { + lock.unlock(); + } + } +} diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicConnectionImpl.java b/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicConnectionImpl.java index 7ebe09e008e..240f90852bc 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicConnectionImpl.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicConnectionImpl.java @@ -334,7 +334,7 @@ public class QuicConnectionImpl extends QuicConnection implements QuicPacketRece this.connectionId = this.endpoint.idFactory().newConnectionId(); this.logTag = logTagFormat.formatted(labelId); this.dbgTag = dbgTag(quicInstance, logTag); - this.congestionController = new QuicRenoCongestionController(dbgTag, rttEstimator); + this.congestionController = createCongestionController(dbgTag, rttEstimator); this.originalVersion = this.quicVersion = firstFlightVersion == null ? QuicVersion.firstFlightVersion(quicInstance.getAvailableVersions()) : firstFlightVersion; @@ -366,6 +366,16 @@ public class QuicConnectionImpl extends QuicConnection implements QuicPacketRece if (debug.on()) debug.log("Quic Connection Created"); } + private static QuicCongestionController createCongestionController + (String dbgTag, QuicRttEstimator rttEstimator) { + String algo = System.getProperty("jdk.internal.httpclient.quic.congestionController", "cubic"); + if (algo.equalsIgnoreCase("reno")) { + return new QuicRenoCongestionController(dbgTag, rttEstimator); + } else { + return new QuicCubicCongestionController(dbgTag, rttEstimator); + } + } + @Override public final long uniqueId() { return labelId; @@ -1977,7 +1987,7 @@ public class QuicConnectionImpl extends QuicConnection implements QuicPacketRece case NONE -> throw new InternalError("Unrecognized packet type"); } // packet has been processed successfully - connection isn't idle (RFC-9000, section 10.1) - this.terminator.keepAlive(); + this.terminator.markActive(); if (packetSpace != null) { packetSpace.packetReceived( packetType, @@ -2809,7 +2819,7 @@ public class QuicConnectionImpl extends QuicConnection implements QuicPacketRece // RFC-9000, section 10.1: An endpoint also restarts its idle timer when sending // an ack-eliciting packet ... if (packet.isAckEliciting()) { - this.terminator.keepAlive(); + this.terminator.markActive(); } } diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicCubicCongestionController.java b/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicCubicCongestionController.java new file mode 100644 index 00000000000..a7a1cd0c0bc --- /dev/null +++ b/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicCubicCongestionController.java @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.net.http.quic; + +import jdk.internal.net.http.common.Deadline; +import jdk.internal.net.http.common.Log; +import jdk.internal.net.http.common.TimeLine; + +import java.util.concurrent.TimeUnit; + +/** + * Implementation of the CUBIC congestion controller + * based on RFC 9438. + * + * @spec https://www.rfc-editor.org/rfc/rfc9438.html + * RFC 9438: CUBIC for Fast and Long-Distance Networks + */ +public final class QuicCubicCongestionController extends QuicBaseCongestionController { + + public static final double BETA = 0.7; + public static final double ALPHA = 3 * (1 - BETA) / (1 + BETA); + private static final double C = 0.4; + private final QuicRttEstimator rttEstimator; + // Cubic curve inflection point, in bytes + private long wMaxBytes; + // cwnd before the most recent congestion event + private long cwndPriorBytes; + // "t" from RFC 9438 + private long timeNanos; + // "K" from RFC 9438 + private long kNanos; + // estimate for the Reno-friendly congestion window + private long wEstBytes; + // the most recent time when the congestion window was filled + private Deadline lastFullWindow; + + public QuicCubicCongestionController(String dbgTag, QuicRttEstimator rttEstimator) { + super(dbgTag, rttEstimator); + this.rttEstimator = rttEstimator; + } + + // for testing + public QuicCubicCongestionController(TimeLine source, QuicRttEstimator rttEstimator) { + super("TEST", source, rttEstimator); + this.rttEstimator = rttEstimator; + } + + @Override + public void packetSent(int packetBytes) { + lock.lock(); + try { + super.packetSent(packetBytes); + if (isCwndLimited()) { + Deadline now = timeSource.instant(); + if (lastFullWindow == null) { + lastFullWindow = now; + } else { + long timePassedNanos = Deadline.between(lastFullWindow, now).toNanos(); + if (timePassedNanos > 0) { + /* "The elapsed time MUST NOT include periods during which cwnd + has not been updated due to application-limited behavior" + "A flow is application limited if it is currently sending less + than what is allowed by the congestion window." + + We are sending asynchronously; one thread is sending data, + a separate thread is processing the acknowledgements. + We can't rely on cwnd being fully utilized when we process an ack, because + most of the time it won't be. + + Instead, we assume that if we filled the cwnd, we were not application-limited + in the last RTT (which is a pretty good approximation because of pacing), + and acknowledgements for all packets sent prior to filling the cwnd + count towards cwnd increase. + */ + long rttNanos = TimeUnit.MICROSECONDS.toNanos(rttEstimator.state().smoothedRttMicros()); + timeNanos += Math.min(timePassedNanos, rttNanos); + lastFullWindow = now; + } + } + } + } finally { + lock.unlock(); + } + } + + + boolean congestionAvoidanceAcked(int packetBytes, Deadline sentTime) { + boolean isAppLimited = sentTime.isAfter(lastFullWindow); + if (!isAppLimited) { + if (wEstBytes < cwndPriorBytes) { + wEstBytes += Math.max((long) (ALPHA * maxDatagramSize * packetBytes / congestionWindow), 1); + } else { + wEstBytes += Math.max((long)maxDatagramSize * packetBytes / congestionWindow, 1); + } + // target = Wcubic(t + RTT) + long rttNanos = TimeUnit.MICROSECONDS.toNanos(rttEstimator.state().smoothedRttMicros()); + double dblTargetBytes = wCubicBytes(timeNanos + rttNanos); + assert dblTargetBytes > 0 : "Unexpected negative target bytes"; + long targetBytes = (long) Math.min(dblTargetBytes, 1.5 * congestionWindow); + if (targetBytes > congestionWindow) { + long oldWindow = congestionWindow; + congestionWindow += Math.max((targetBytes - congestionWindow) * packetBytes / congestionWindow, 1L); + assert congestionWindow > oldWindow : + "Window size decreased: %s to %s".formatted(oldWindow, congestionWindow); + } + if (wEstBytes > congestionWindow) { + congestionWindow = wEstBytes; + } + } + return isAppLimited; + } + + // Wcubic(t) = C * (t-K [seconds])^3 + Wmax (segments) + private double wCubicBytes(long timeNanos) { + return (C * maxDatagramSize * Math.pow((timeNanos - kNanos) / 1e9, 3)) + wMaxBytes; + } + + void onCongestionEvent(Deadline sentTime) { + if (inCongestionRecovery(sentTime)) { + return; + } + if (congestionWindow < wMaxBytes) { + // fast convergence + wMaxBytes = (long) ((1 + BETA) * congestionWindow / 2); + } else { + wMaxBytes = congestionWindow; + } + cwndPriorBytes = congestionWindow; + congestionRecoveryStartTime = timeSource.instant(); + ssThresh = (long)(congestionWindow * BETA); + wEstBytes = congestionWindow = Math.max(minimumWindow, ssThresh); + maxBytesInFlight = 0; + timeNanos = 0; + // set lastFullWindow to prevent rapid timeNanos growth + lastFullWindow = congestionRecoveryStartTime; + // ((wmax_segments - cwnd_segments) / C) ^ (1/3) seconds + kNanos = (long)(Math.cbrt((wMaxBytes - congestionWindow) / C / maxDatagramSize) * 1_000_000_000); + // kNanos may be negative if we reduced the window below minimum, + // and fast convergence was used. This is acceptable. + if (Log.quicCC()) { + Log.logQuic(dbgTag + " Congestion: ssThresh: " + ssThresh + + ", in flight: " + bytesInFlight + + ", cwnd:" + congestionWindow + + ", K: " + TimeUnit.NANOSECONDS.toMillis(kNanos) + " ms"); + } + } +} diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicEndpoint.java b/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicEndpoint.java index d9bf5fe6dcf..b1de5ef4bfd 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicEndpoint.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicEndpoint.java @@ -62,6 +62,7 @@ import jdk.internal.net.http.common.SequentialScheduler; import jdk.internal.net.http.common.TimeLine; import jdk.internal.net.http.common.TimeSource; import jdk.internal.net.http.common.Utils; +import jdk.internal.net.http.common.Utils.UseVTForSelector; import jdk.internal.net.http.quic.QuicSelector.QuicNioSelector; import jdk.internal.net.http.quic.QuicSelector.QuicVirtualThreadPoller; import jdk.internal.net.http.quic.packets.QuicPacket.HeadersType; @@ -116,7 +117,6 @@ public abstract sealed class QuicEndpoint implements AutoCloseable static final boolean DGRAM_SEND_ASYNC; static final int MAX_BUFFERED_HIGH; static final int MAX_BUFFERED_LOW; - enum UseVTForSelector { ALWAYS, NEVER, DEFAULT } static final UseVTForSelector USE_VT_FOR_SELECTOR; static { // This default value is the maximum payload size of @@ -144,11 +144,8 @@ public abstract sealed class QuicEndpoint implements AutoCloseable if (maxBufferLow >= maxBufferHigh) maxBufferLow = maxBufferHigh >> 1; MAX_BUFFERED_HIGH = maxBufferHigh; MAX_BUFFERED_LOW = maxBufferLow; - String useVtForSelector = - System.getProperty("jdk.internal.httpclient.quic.selector.useVirtualThreads", "default"); - USE_VT_FOR_SELECTOR = Stream.of(UseVTForSelector.values()) - .filter((v) -> v.name().equalsIgnoreCase(useVtForSelector)) - .findFirst().orElse(UseVTForSelector.DEFAULT); + var property = "jdk.internal.httpclient.quic.selector.useVirtualThreads"; + USE_VT_FOR_SELECTOR = Utils.useVTForSelector(property, "default"); } /** diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicPacer.java b/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicPacer.java index 0ba7d78038b..50d8485785f 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicPacer.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicPacer.java @@ -30,10 +30,8 @@ import jdk.internal.net.http.common.Log; import jdk.internal.net.http.common.Utils; import jdk.internal.util.OperatingSystem; -import java.time.Duration; import java.time.temporal.ChronoUnit; import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.ReentrantLock; /** * Implementation of pacing. diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicRenoCongestionController.java b/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicRenoCongestionController.java index ff51aafc131..2594c00055f 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicRenoCongestionController.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicRenoCongestionController.java @@ -27,15 +27,6 @@ package jdk.internal.net.http.quic; import jdk.internal.net.http.common.Deadline; import jdk.internal.net.http.common.Log; -import jdk.internal.net.http.common.TimeLine; -import jdk.internal.net.http.common.TimeSource; -import jdk.internal.net.http.common.Utils; -import jdk.internal.net.http.quic.frames.AckFrame; -import jdk.internal.net.http.quic.packets.QuicPacket; - -import java.util.Collection; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; /** * Implementation of QUIC congestion controller based on RFC 9002. @@ -46,38 +37,20 @@ import java.util.concurrent.locks.ReentrantLock; * @spec https://www.rfc-editor.org/info/rfc9002 * RFC 9002: QUIC Loss Detection and Congestion Control */ -class QuicRenoCongestionController implements QuicCongestionController { - // higher of 14720 and 2*maxDatagramSize; we use fixed maxDatagramSize - private static final int INITIAL_WINDOW = Math.max(14720, 2 * QuicConnectionImpl.DEFAULT_DATAGRAM_SIZE); - private static final int MAX_BYTES_IN_FLIGHT = Math.clamp( - Utils.getLongProperty("jdk.httpclient.quic.maxBytesInFlight", 1 << 24), - 1 << 14, 1 << 24); - private final TimeLine timeSource; - private final String dbgTag; - private final Lock lock = new ReentrantLock(); - private long congestionWindow = INITIAL_WINDOW; - private int maxDatagramSize = QuicConnectionImpl.DEFAULT_DATAGRAM_SIZE; - private int minimumWindow = 2 * maxDatagramSize; - private long bytesInFlight; - // maximum bytes in flight seen since the last congestion event - private long maxBytesInFlight; - private Deadline congestionRecoveryStartTime; - private long ssThresh = Long.MAX_VALUE; - - private final QuicPacer pacer; - +final class QuicRenoCongestionController extends QuicBaseCongestionController { public QuicRenoCongestionController(String dbgTag, QuicRttEstimator rttEstimator) { - this.dbgTag = dbgTag; - this.timeSource = TimeSource.source(); - this.pacer = new QuicPacer(rttEstimator, this); + super(dbgTag, rttEstimator); } - private boolean inCongestionRecovery(Deadline sentTime) { - return (congestionRecoveryStartTime != null && - !sentTime.isAfter(congestionRecoveryStartTime)); + boolean congestionAvoidanceAcked(int packetBytes, Deadline sentTime) { + boolean isAppLimited = congestionWindow > maxBytesInFlight + 2L * maxDatagramSize; + if (!isAppLimited) { + congestionWindow += Math.max((long) maxDatagramSize * packetBytes / congestionWindow, 1L); + } + return isAppLimited; } - private void onCongestionEvent(Deadline sentTime) { + void onCongestionEvent(Deadline sentTime) { if (inCongestionRecovery(sentTime)) { return; } @@ -91,226 +64,4 @@ class QuicRenoCongestionController implements QuicCongestionController { ", cwnd:" + congestionWindow); } } - - private static boolean inFlight(QuicPacket packet) { - // packet is in flight if it contains anything other than a single ACK frame - // specifically, a packet containing padding is considered to be in flight. - return packet.frames().size() != 1 || - !(packet.frames().get(0) instanceof AckFrame); - } - - @Override - public boolean canSendPacket() { - lock.lock(); - try { - if (bytesInFlight >= MAX_BYTES_IN_FLIGHT) { - return false; - } - if (isCwndLimited() || isPacerLimited()) { - return false; - } - return true; - } finally { - lock.unlock(); - } - } - - @Override - public void updateMaxDatagramSize(int newSize) { - lock.lock(); - try { - if (minimumWindow != newSize * 2) { - minimumWindow = newSize * 2; - maxDatagramSize = newSize; - congestionWindow = Math.max(congestionWindow, minimumWindow); - } - } finally { - lock.unlock(); - } - } - - @Override - public void packetSent(int packetBytes) { - lock.lock(); - try { - bytesInFlight += packetBytes; - if (bytesInFlight > maxBytesInFlight) { - maxBytesInFlight = bytesInFlight; - } - pacer.packetSent(packetBytes); - } finally { - lock.unlock(); - } - } - - @Override - public void packetAcked(int packetBytes, Deadline sentTime) { - lock.lock(); - try { - bytesInFlight -= packetBytes; - // RFC 9002 says we should not increase cwnd when application limited. - // The concept itself is poorly defined. - // Here we limit cwnd growth based on the maximum bytes in flight - // observed since the last congestion event - if (inCongestionRecovery(sentTime)) { - if (Log.quicCC() && Log.trace()) { - Log.logQuic(dbgTag + " Acked, in recovery: bytes: " + packetBytes + - ", in flight: " + bytesInFlight); - } - return; - } - boolean isAppLimited; - if (congestionWindow < ssThresh) { - isAppLimited = congestionWindow >= 2 * maxBytesInFlight; - if (!isAppLimited) { - congestionWindow += packetBytes; - } - } else { - isAppLimited = congestionWindow > maxBytesInFlight + 2L * maxDatagramSize; - if (!isAppLimited) { - congestionWindow += Math.max((long) maxDatagramSize * packetBytes / congestionWindow, 1L); - } - } - if (Log.quicCC() && Log.trace()) { - if (isAppLimited) { - Log.logQuic(dbgTag + " Acked, not blocked: bytes: " + packetBytes + - ", in flight: " + bytesInFlight); - } else { - Log.logQuic(dbgTag + " Acked, increased: bytes: " + packetBytes + - ", in flight: " + bytesInFlight + - ", new cwnd:" + congestionWindow); - } - } - } finally { - lock.unlock(); - } - } - - @Override - public void packetLost(Collection lostPackets, Deadline sentTime, boolean persistent) { - lock.lock(); - try { - for (QuicPacket packet : lostPackets) { - if (inFlight(packet)) { - bytesInFlight -= packet.size(); - } - } - onCongestionEvent(sentTime); - if (persistent) { - congestionWindow = minimumWindow; - congestionRecoveryStartTime = null; - if (Log.quicCC()) { - Log.logQuic(dbgTag + " Persistent congestion: ssThresh: " + ssThresh + - ", in flight: " + bytesInFlight + - ", cwnd:" + congestionWindow); - } - } - } finally { - lock.unlock(); - } - } - - @Override - public void packetDiscarded(Collection discardedPackets) { - lock.lock(); - try { - for (QuicPacket packet : discardedPackets) { - if (inFlight(packet)) { - bytesInFlight -= packet.size(); - } - } - } finally { - lock.unlock(); - } - } - - @Override - public long congestionWindow() { - lock.lock(); - try { - return congestionWindow; - } finally { - lock.unlock(); - } - } - - @Override - public long initialWindow() { - lock.lock(); - try { - return Math.max(14720, 2 * maxDatagramSize); - } finally { - lock.unlock(); - } - } - - @Override - public long maxDatagramSize() { - lock.lock(); - try { - return maxDatagramSize; - } finally { - lock.unlock(); - } - } - - @Override - public boolean isSlowStart() { - lock.lock(); - try { - return congestionWindow < ssThresh; - } finally { - lock.unlock(); - } - } - - @Override - public void updatePacer(Deadline now) { - lock.lock(); - try { - pacer.updateQuota(now); - } finally { - lock.unlock(); - } - } - - @Override - public boolean isPacerLimited() { - lock.lock(); - try { - return !pacer.canSend(); - } finally { - lock.unlock(); - } - } - - @Override - public boolean isCwndLimited() { - lock.lock(); - try { - return congestionWindow - bytesInFlight < maxDatagramSize; - } finally { - lock.unlock(); - } - } - - @Override - public Deadline pacerDeadline() { - lock.lock(); - try { - return pacer.twoPacketDeadline(); - } finally { - lock.unlock(); - } - } - - @Override - public void appLimited() { - lock.lock(); - try { - pacer.appLimited(); - } finally { - lock.unlock(); - } - } } diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicSelector.java b/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicSelector.java index 9fa825459ff..02db895d27c 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicSelector.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicSelector.java @@ -45,9 +45,9 @@ import jdk.internal.net.http.common.Logger; import jdk.internal.net.http.common.TimeLine; import jdk.internal.net.http.common.TimeSource; import jdk.internal.net.http.common.Utils; +import jdk.internal.net.http.common.Utils.UseVTForSelector; import jdk.internal.net.http.quic.QuicEndpoint.QuicVirtualThreadedEndpoint; import jdk.internal.net.http.quic.QuicEndpoint.QuicSelectableEndpoint; -import jdk.internal.net.http.quic.QuicEndpoint.UseVTForSelector; import static jdk.internal.net.http.quic.QuicEndpoint.USE_VT_FOR_SELECTOR; diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicTimedEvent.java b/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicTimedEvent.java index 9269b12bf64..ac885c1950e 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicTimedEvent.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicTimedEvent.java @@ -45,8 +45,7 @@ sealed interface QuicTimedEvent permits PacketSpaceManager.PacketTransmissionTask, QuicTimerQueue.Marker, QuicEndpoint.ClosedConnection, - IdleTimeoutManager.IdleTimeoutEvent, - IdleTimeoutManager.StreamDataBlockedEvent, + IdleTimeoutManager.TimedEvent, QuicConnectionImpl.MaxInitialTimer { /** diff --git a/src/java.sql/share/classes/java/sql/Array.java b/src/java.sql/share/classes/java/sql/Array.java index 1c42d3e5b79..8fe84f4f930 100644 --- a/src/java.sql/share/classes/java/sql/Array.java +++ b/src/java.sql/share/classes/java/sql/Array.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -61,13 +61,19 @@ package java.sql; * If the connection's type map or a type map supplied to a method has no entry * for the base type, the elements are mapped according to the standard mapping. *

+ * To release resources used by the {@code Array} object, applications must call + * either the {@link #free()} or the {@link #close()} method. Any attempt to + * invoke a method other than {@link #free()} or {@link #close()} after the + * {@code Array} object has been closed, will result in a {@link SQLException} + * being thrown. + *

* All methods on the {@code Array} interface must be fully implemented if the * JDBC driver supports the data type. * * @since 1.2 */ -public interface Array { +public interface Array extends AutoCloseable { /** * Retrieves the SQL type name of the elements in @@ -345,21 +351,35 @@ public interface Array { java.util.Map> map) throws SQLException; /** - * This method frees the {@code Array} object and releases the resources that - * it holds. The object is invalid once the {@code free} - * method is called. + * Closes and releases the resources held by this {@code Array} object. *

- * After {@code free} has been called, any attempt to invoke a - * method other than {@code free} will result in a {@code SQLException} - * being thrown. If {@code free} is called multiple times, the subsequent - * calls to {@code free} are treated as a no-op. + * If the {@code Array} object is already closed, then invoking this method + * has no effect. * * @throws SQLException if an error occurs releasing * the Array's resources * @throws SQLFeatureNotSupportedException if the JDBC driver does not support * this method * @since 1.6 + * @see #close() */ void free() throws SQLException; + /** + * Closes and releases the resources held by this {@code Array} object. + *

+ * If the {@code Array} object is already closed, then invoking this method + * has no effect. + * + * @throws SQLException if an error occurs releasing + * the Array's resources + * @throws SQLFeatureNotSupportedException if the JDBC driver + * does not support this method + * @implSpec The default implementation calls the {@link #free()} method. + * @see #free() + * @since 26 + */ + default void close() throws SQLException { + free(); + }; } diff --git a/src/java.sql/share/classes/java/sql/Blob.java b/src/java.sql/share/classes/java/sql/Blob.java index 5bf9cfcb7ad..4f5619616eb 100644 --- a/src/java.sql/share/classes/java/sql/Blob.java +++ b/src/java.sql/share/classes/java/sql/Blob.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -32,7 +32,7 @@ import java.io.InputStream; * the Java programming language of an SQL * {@code BLOB} value. An SQL {@code BLOB} is a built-in type * that stores a Binary Large Object as a column value in a row of - * a database table. By default drivers implement {@code Blob} using + * a database table. By default, drivers implement {@code Blob} using * an SQL {@code locator(BLOB)}, which means that a * {@code Blob} object contains a logical pointer to the * SQL {@code BLOB} data rather than the data itself. @@ -50,13 +50,19 @@ import java.io.InputStream; * {@code BLOB} value. In addition, this interface has methods for updating * a {@code BLOB} value. *

+ * To release resources used by the {@code Blob} object, applications must call + * either the {@link #free()} or the {@link #close()} method. Any attempt to + * invoke a method other than {@link #free()} or {@link #close()} after the + * {@code Blob} object has been closed, will result in a {@link SQLException} + * being thrown. + *

* All methods on the {@code Blob} interface must be fully implemented if the * JDBC driver supports the data type. * * @since 1.2 */ -public interface Blob { +public interface Blob extends AutoCloseable { /** * Returns the number of bytes in the {@code BLOB} value @@ -266,20 +272,17 @@ public interface Blob { void truncate(long len) throws SQLException; /** - * This method frees the {@code Blob} object and releases the resources that - * it holds. The object is invalid once the {@code free} - * method is called. + * Closes and releases the resources held by this {@code Blob} object. *

- * After {@code free} has been called, any attempt to invoke a - * method other than {@code free} will result in an {@code SQLException} - * being thrown. If {@code free} is called multiple times, the subsequent - * calls to {@code free} are treated as a no-op. + * If the {@code Blob} object is already closed, then invoking this method + * has no effect. * * @throws SQLException if an error occurs releasing * the Blob's resources * @throws SQLFeatureNotSupportedException if the JDBC driver * does not support this method * @since 1.6 + * @see #close() */ void free() throws SQLException; @@ -303,4 +306,23 @@ public interface Blob { * @since 1.6 */ InputStream getBinaryStream(long pos, long length) throws SQLException; + + /** + * Closes and releases the resources held by this {@code Blob} object. + *

+ * If the {@code Blob} object is already closed, then invoking this method + * has no effect. + * + * @implSpec The default implementation calls the {@link #free()} method. + * + * @throws SQLException if an error occurs releasing + * the Blob's resources + * @throws SQLFeatureNotSupportedException if the JDBC driver + * does not support this method + * @since 26 + * @see #free() + */ + default void close() throws SQLException { + free(); + }; } diff --git a/src/java.sql/share/classes/java/sql/Clob.java b/src/java.sql/share/classes/java/sql/Clob.java index 15e6897f711..141f7de81af 100644 --- a/src/java.sql/share/classes/java/sql/Clob.java +++ b/src/java.sql/share/classes/java/sql/Clob.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -33,7 +33,7 @@ import java.io.Reader; * An SQL {@code CLOB} is a built-in type * that stores a Character Large Object as a column value in a row of * a database table. - * By default drivers implement a {@code Clob} object using an SQL + * By default, drivers implement a {@code Clob} object using an SQL * {@code locator(CLOB)}, which means that a {@code Clob} object * contains a logical pointer to the SQL {@code CLOB} data rather than * the data itself. A {@code Clob} object is valid for the duration @@ -49,13 +49,19 @@ import java.io.Reader; * access an SQL {@code CLOB} value. In addition, this interface * has methods for updating a {@code CLOB} value. *

+ * To release resources used by the {@code Clob} object, applications must call + * either the {@link #free()} or the {@link #close()} method. Any attempt to + * invoke a method other than {@link #free()} or {@link #close()} after the + * {@code Clob} object has been closed, will result in a {@link SQLException} + * being thrown. + *

* All methods on the {@code Clob} interface must be * fully implemented if the JDBC driver supports the data type. * * @since 1.2 */ -public interface Clob { +public interface Clob extends AutoCloseable { /** * Retrieves the number of characters @@ -310,14 +316,10 @@ public interface Clob { void truncate(long len) throws SQLException; /** - * This method releases the resources that the {@code Clob} object - * holds. The object is invalid once the {@code free} method - * is called. + * Closes and releases the resources held by this {@code Clob} object. *

- * After {@code free} has been called, any attempt to invoke a - * method other than {@code free} will result in a {@code SQLException} - * being thrown. If {@code free} is called multiple times, the subsequent - * calls to {@code free} are treated as a no-op. + * If the {@code Clob} object is already closed, then invoking this method + * has no effect. * * @throws SQLException if an error occurs releasing * the Clob's resources @@ -325,6 +327,7 @@ public interface Clob { * @throws SQLFeatureNotSupportedException if the JDBC driver * does not support this method * @since 1.6 + * @see #close() */ void free() throws SQLException; @@ -350,4 +353,21 @@ public interface Clob { */ Reader getCharacterStream(long pos, long length) throws SQLException; + /** + * Closes and releases the resources held by this {@code Clob} object. + *

+ * If the {@code Clob} object is already closed, then invoking this method + * has no effect. + * + * @throws SQLException if an error occurs releasing + * the Clob's resources + * @throws SQLFeatureNotSupportedException if the JDBC driver + * does not support this method + * @implSpec The default implementation calls the {@link #free()} method. + * @see #free() + * @since 26 + */ + default void close() throws SQLException { + free(); + }; } diff --git a/src/java.sql/share/classes/java/sql/Connection.java b/src/java.sql/share/classes/java/sql/Connection.java index 19f283f5762..946459adf7b 100644 --- a/src/java.sql/share/classes/java/sql/Connection.java +++ b/src/java.sql/share/classes/java/sql/Connection.java @@ -43,8 +43,8 @@ import java.util.Map; * should use the appropriate {@code Connection} method such as * {@code setAutoCommit} or {@code setTransactionIsolation}. * Applications should not invoke SQL commands directly to change the connection's - * configuration when there is a JDBC method available. By default a {@code Connection} object is in - * auto-commit mode, which means that it automatically commits changes + * configuration when there is a JDBC method available. By default, a {@code Connection} + * object is in auto-commit mode, which means that it automatically commits changes * after executing each statement. If auto-commit mode has been * disabled, the method {@code commit} must be called explicitly in * order to commit changes; otherwise, database changes will not be saved. @@ -77,7 +77,7 @@ import java.util.Map; * con.setTypeMap(map); *

* - * @see DriverManager#getConnection + * @see DriverManager#getConnection(String) * @see Statement * @see ResultSet * @see DatabaseMetaData @@ -1505,7 +1505,7 @@ public interface Connection extends Wrapper, AutoCloseable { * * @throws SQLException if an error occurs * @since 9 - * @see endRequest + * @see #endRequest() * @see javax.sql.PooledConnection */ default void beginRequest() throws SQLException { @@ -1548,7 +1548,7 @@ public interface Connection extends Wrapper, AutoCloseable { * * @throws SQLException if an error occurs * @since 9 - * @see beginRequest + * @see #beginRequest() * @see javax.sql.PooledConnection */ default void endRequest() throws SQLException { @@ -1676,5 +1676,311 @@ public interface Connection extends Wrapper, AutoCloseable { default void setShardingKey(ShardingKey shardingKey) throws SQLException { throw new SQLFeatureNotSupportedException("setShardingKey not implemented"); } + // JDBC 4.5 + /** + * Returns a {@code String} enclosed in single quotes. Any occurrence of a + * single quote within the string will be replaced by two single quotes. + * + *
+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Examples of the conversion:
ValueResult
Hello 'Hello'
G'Day 'G''Day'
'G''Day''''G''''Day'''
I'''M 'I''''''M'
+ *
+ * @implSpec + * The default implementation creates the literal as: + * {@code "'" + val.replace("'", "''") + "'"}. + * @implNote + * JDBC driver implementations may need to provide their own implementation + * of this method in order to meet the requirements of the underlying + * datasource. + * @param val a character string + * @return A string enclosed by single quotes with every single quote + * converted to two single quotes + * @throws NullPointerException if val is {@code null} + * @throws SQLException if a database access error occurs + * + * @since 26 + */ + default String enquoteLiteral(String val) throws SQLException { + return SQLUtils.enquoteLiteral(val); + } + + /** + * Returns a {@link #isSimpleIdentifier(String) simple SQL identifier} or a + * delimited identifier. A delimited identifier represents the name of a + * database object such as a table, column, or view that is enclosed by a + * delimiter, which is typically a double quote as defined by the SQL standard. + *

+ * If {@code identifier} is a simple SQL identifier: + *

    + *
  • If {@code alwaysDelimit} is {@code false}, return the original value
  • + *
  • if {@code alwaysDelimit} is {@code true}, enquote the original value + * and return as a delimited identifier
  • + *
+ * + * If {@code identifier} is not a simple SQL identifier, the delimited + * {@code identifier} to be returned must be enclosed by the delimiter + * returned from {@link DatabaseMetaData#getIdentifierQuoteString}. If + * the datasource does not support delimited identifiers, a + * {@code SQLFeatureNotSupportedException} is thrown. + *

+ * A {@code SQLException} will be thrown if {@code identifier} contains any + * invalid characters within a delimited identifier or the identifier length + * is invalid for the datasource. + * + * @implSpec + * The default implementation uses the following criteria to + * determine a valid simple SQL identifier: + *

    + *
  • The string is not enclosed in double quotes
  • + *
  • The first character is an alphabetic character from a ({@code '\u005C0061'}) + * through z ({@code '\u005Cu007A'}), or from A ({@code '\u005Cu0041'}) + * through Z ({@code '\u005Cu005A'})
  • + *
  • The name only contains alphanumeric characters([0-9A-Za-z]) + * or the character "_"
  • + *
+ * + * The default implementation will throw a {@code SQLException} if: + *
    + *
  • {@link DatabaseMetaData#getIdentifierQuoteString} does not return a + * double quote
  • + *
  • {@code identifier} contains a {@code null} character or double quote
  • + *
  • The length of {@code identifier} is less than 1 or greater than 128 characters + *
+ *
+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Examples of the conversion:
identifieralwaysDelimitResult
HellofalseHello
Hellotrue"Hello"
G'Dayfalse"G'Day"
"Bruce Wayne"false"Bruce Wayne"
"Bruce Wayne"true"Bruce Wayne"
"select"false"select"
"select"true"select"
GoodDay$false"GoodDay$"
Hello"WorldfalseSQLException
"Hello"World"falseSQLException
+ *
+ * @implNote + * JDBC driver implementations may need to provide their own implementation + * of this method in order to meet the requirements of the underlying + * datasource. + * @param identifier a SQL identifier + * @param alwaysDelimit indicates if a simple SQL identifier should be + * returned as a delimited identifier + * @return A simple SQL identifier or a delimited identifier + * @throws SQLException if identifier is not a valid identifier + * @throws SQLFeatureNotSupportedException if the datasource does not support + * delimited identifiers + * @throws NullPointerException if identifier is {@code null} + * + * @since 26 + */ + default String enquoteIdentifier(String identifier, boolean alwaysDelimit) throws SQLException { + String delimiter = this.getMetaData().getIdentifierQuoteString(); + return SQLUtils.enquoteIdentifier(delimiter, identifier, alwaysDelimit); + } + + /** + * Returns whether {@code identifier} is a simple SQL identifier. + * A simple SQL identifier is referred to as regular (or ordinary) identifier + * within the SQL standard. A regular identifier represents the name of a database + * object such as a table, column, or view. + *

+ * The rules for a regular Identifier are: + *

    + *
  • The first character is an alphabetic character from a ({@code '\u005Cu0061'}) + * through z ({@code '\u005Cu007A'}), or from A ({@code '\u005Cu0041'}) + * through Z ({@code '\u005Cu005A'})
  • + *
  • The name only contains alphanumeric characters([0-9A-Za-z]) + * or the character "_"
  • + *
  • It cannot be a SQL reserved word
  • + *
+ *

+ * A datasource may have additional rules for a regular identifier such as: + *

    + *
  • Supports additional characters within the name based on + * the locale being used
  • + *
  • Supports a different maximum length for the identifier
  • + *
+ * + * @implSpec The default implementation uses the following criteria to + * determine a valid simple SQL identifier: + *
    + *
  • The identifier is not enclosed in double quotes
  • + *
  • The first character is an alphabetic character from a through z, or + * from A through Z
  • + *
  • The identifier only contains alphanumeric characters([0-9A-Za-z]) or + * the character "_"
  • + *
  • The identifier is not a SQL reserved word
  • + *
  • The identifier is between 1 and 128 characters in length inclusive
  • + *
+ * + *
+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Examples of the conversion:
identifierSimple Identifier
Hellotrue
G'Dayfalse
"Bruce Wayne"false
GoodDay$false
Hello"Worldfalse
"Hello"World"false
"select"false
"from"false
+ *
+ * @implNote JDBC driver implementations may need to provide their own + * implementation of this method in order to meet the requirements of the + * underlying datasource. + * @param identifier a SQL identifier + * @return true if a simple SQL identifier, false otherwise + * @throws NullPointerException if identifier is {@code null} + * @throws SQLException if a database access error occurs + * + * @since 26 + */ + default boolean isSimpleIdentifier(String identifier) throws SQLException { + return SQLUtils.isSimpleIdentifier(identifier); + } + + /** + * Returns a {@code String} representing a National Character Set Literal + * enclosed in single quotes and prefixed with a upper case letter N. + * Any occurrence of a single quote within the string will be replaced + * by two single quotes. + * + *
+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Examples of the conversion:
ValueResult
Hello N'Hello'
G'Day N'G''Day'
'G''Day'N'''G''''Day'''
I'''M N'I''''''M'
N'Hello' N'N''Hello'''
+ *
+ * @implSpec + * The default implementation creates the literal as: + * {@code "N'" + val.replace("'", "''") + "'"}. + * @implNote + * JDBC driver implementations may need to provide their own implementation + * of this method in order to meet the requirements of the underlying + * datasource. An implementation of enquoteNCharLiteral may accept a different + * set of characters than that accepted by the same drivers implementation of + * enquoteLiteral. + * @param val a character string + * @return the result of replacing every single quote character in the + * argument by two single quote characters where this entire result is + * then prefixed with 'N'. + * @throws NullPointerException if val is {@code null} + * @throws SQLException if a database access error occurs + * + * @since 26 + */ + default String enquoteNCharLiteral(String val) throws SQLException { + return SQLUtils.enquoteNCharLiteral(val); + } } diff --git a/src/java.sql/share/classes/java/sql/DriverPropertyInfo.java b/src/java.sql/share/classes/java/sql/DriverPropertyInfo.java index c5f5fc30ee9..426a49b7ae0 100644 --- a/src/java.sql/share/classes/java/sql/DriverPropertyInfo.java +++ b/src/java.sql/share/classes/java/sql/DriverPropertyInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,12 +25,13 @@ package java.sql; +import java.util.Properties; + /** *

Driver properties for making a connection. The - * {@code DriverPropertyInfo} class is of interest only to advanced programmers - * who need to interact with a Driver via the method - * {@code getDriverProperties} to discover - * and supply properties for connections. + * {@code DriverPropertyInfo} class is of interest only to advanced programmers. + * The method {@link Driver#getPropertyInfo(String, Properties)} may be used + * to discover Driver properties. * * @since 1.1 */ diff --git a/src/java.sql/share/classes/java/sql/JDBCType.java b/src/java.sql/share/classes/java/sql/JDBCType.java index 9c9d314b7f1..9da51c27925 100644 --- a/src/java.sql/share/classes/java/sql/JDBCType.java +++ b/src/java.sql/share/classes/java/sql/JDBCType.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -200,7 +200,21 @@ public enum JDBCType implements SQLType { /** * Identifies the generic SQL type {@code TIMESTAMP_WITH_TIMEZONE}. */ - TIMESTAMP_WITH_TIMEZONE(Types.TIMESTAMP_WITH_TIMEZONE); + TIMESTAMP_WITH_TIMEZONE(Types.TIMESTAMP_WITH_TIMEZONE), + + /* JDBC 4.5 Types */ + + /** + * Identifies the generic SQL type {@code DECFLOAT}. + * @since 26 + */ + DECFLOAT(Types.DECFLOAT), + + /** + * Identifies the generic SQL type {@code JSON}. + * @since 26 + */ + JSON(Types.JSON); /** * The Integer value for the JDBCType. It maps to a value in diff --git a/src/java.sql/share/classes/java/sql/NClob.java b/src/java.sql/share/classes/java/sql/NClob.java index 587f6e1f843..0639f894146 100644 --- a/src/java.sql/share/classes/java/sql/NClob.java +++ b/src/java.sql/share/classes/java/sql/NClob.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,7 +31,8 @@ package java.sql; * An SQL {@code NCLOB} is a built-in type * that stores a Character Large Object using the National Character Set * as a column value in a row of a database table. - *

The {@code NClob} interface extends the {@code Clob} interface + *

+ * The {@code NClob} interface extends the {@code Clob} interface * which provides methods for getting the * length of an SQL {@code NCLOB} value, * for materializing a {@code NCLOB} value on the client, and for @@ -44,6 +45,12 @@ package java.sql; * access an SQL {@code NCLOB} value. In addition, this interface * has methods for updating a {@code NCLOB} value. *

+ * To release resources used by the {@code NClob} object, applications must call + * either the {@link #free()} or the {@link #close()} method. Any attempt to + * invoke a method other than {@link #free()} or {@link #close()} after the + * {@code NClob} object has been closed, will result in a {@link SQLException} + * being thrown. + *

* All methods on the {@code NClob} interface must be fully implemented if the * JDBC driver supports the data type. * diff --git a/src/java.sql/share/classes/java/sql/SQLPermission.java b/src/java.sql/share/classes/java/sql/SQLPermission.java index 84e41c51a33..f9a0c7e2ed6 100644 --- a/src/java.sql/share/classes/java/sql/SQLPermission.java +++ b/src/java.sql/share/classes/java/sql/SQLPermission.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,30 +29,14 @@ package java.sql; import java.security.*; /** - * A {@code SQLPermission} object contains - * a name (also referred to as a "target name") but no actions - * list; there is either a named permission or there is not. - * The target name is the name of the permission. The - * naming convention follows the hierarchical property naming convention. - * In addition, an asterisk - * may appear at the end of the name, following a ".", or by itself, to - * signify a wildcard match. For example: {@code loadLibrary.*} - * and {@code *} signify a wildcard match, - * while {@code *loadLibrary} and {@code a*b} do not. - * - * @apiNote - * This permission cannot be used for controlling access to resources - * as the Security Manager is no longer supported. + * This class was only useful in conjunction with the {@link java.lang.SecurityManager}, + * which is no longer supported. There is no replacement for this class. * * @since 1.3 - * @see java.security.BasicPermission - * @see java.security.Permission - * @see java.security.Permissions - * @see java.security.PermissionCollection - * @see java.lang.SecurityManager * + * @deprecated There is no replacement for this class. */ - +@Deprecated(since="26", forRemoval=true) public final class SQLPermission extends BasicPermission { /** diff --git a/src/java.sql/share/classes/java/sql/SQLUtils.java b/src/java.sql/share/classes/java/sql/SQLUtils.java new file mode 100644 index 00000000000..49757d7e8ad --- /dev/null +++ b/src/java.sql/share/classes/java/sql/SQLUtils.java @@ -0,0 +1,431 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package java.sql; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Pattern; + +/** + * Utility class used by the Connection & Statement interfaces for their + * shared default methods. + */ +class SQLUtils { + // Pattern used to verify if an identifier is a Simple SQL identifier + private static final Pattern SIMPLE_IDENTIFIER_PATTERN + = Pattern.compile("[\\p{Alpha}][\\p{Alnum}_]*"); + // Pattern to check if an identifier contains a null character or a double quote + private static final Pattern INVALID_IDENTIFIER_CHARACTERS_PATTERN + = Pattern.compile("[^\u0000\"]+"); + // SQL 2023 reserved words + private static final String[] SQL2023_RESERVED_WORDS = { + "ABS", "ABSENT", "ACOS", "ALL", "ALLOCATE", "ALTER", "AND", "ANY", + "ANY_VALUE", "ARE", "ARRAY", "ARRAY_AGG", "ARRAY_MAX_CARDINALITY", + "AS", "ASENSITIVE", "ASIN", "ASYMMETRIC", "AT", "ATAN", + "ATOMIC", "AUTHORIZATION", "AVG", + "BEGIN", "BEGIN_FRAME", "BEGIN_PARTITION", "BETWEEN", "BIGINT", + "BINARY", "BLOB", "BOOLEAN", "BOTH", "BTRIM", "BY", + "CALL", "CALLED", "CARDINALITY", "CASCADED", "CASE", "CAST", "CEIL", + "CEILING", "CHAR", "CHAR_LENGTH", + "CHARACTER", "CHARACTER_LENGTH", "CHECK", "CLASSIFIER", "CLOB", + "CLOSE", "COALESCE", "COLLATE", "COLLECT", "COLUMN", "COMMIT", "CONDITION", + "CONNECT", "CONSTRAINT", "CONTAINS", "CONVERT", "COPY", "CORR", "CORRESPONDING", + "COS", "COSH", "COUNT", "COVAR_POP", "COVAR_SAMP", "CREATE", "CROSS", "CUBE", + "CUME_DIST", "CURRENT", + "CURRENT_CATALOG", "CURRENT_DATE", "CURRENT_DEFAULT_TRANSFORM_GROUP", "CURRENT_PATH", + "CURRENT_ROLE", "CURRENT_SCHEMA", "CURRENT_TIME", "CURRENT_TIMESTAMP", + "CURRENT_TRANSFORM_GROUP_FOR_TYPE", "CURRENT_USER", "CURSOR", "CYCLE", + "DATE", "DAY", "DEALLOCATE", "DEC", "DECFLOAT", "DECIMAL", "DECLARE", "DEFAULT", + "DEFINE", "DELETE", "DENSE_RANK", "DEREF", "DESCRIBE", "DETERMINISTIC", + "DISCONNECT", "DISTINCT", "DOUBLE", "DROP", "DYNAMIC", + "EACH", "ELEMENT", "ELSE", "EMPTY", "END", "END_FRAME", "END_PARTITION", + "END-EXEC", "EQUALS", "ESCAPE", "EVERY", "EXCEPT", "EXEC", "EXECUTE", + "EXISTS", "EXP", "EXTERNAL", "EXTRACT", + "FALSE", "FETCH", "FILTER", "FIRST_VALUE", "FLOAT", "FLOOR", "FOR", "FOREIGN", "FRAME_ROW", + "FREE", "FROM", "FULL", "FUNCTION", "FUSION", + "GET", "GLOBAL", "GRANT", "GREATEST", "GROUP", "GROUPING", "GROUPS", + "HAVING", "HOLD", "HOUR", + "IDENTITY", "IN", "INDICATOR", "INITIAL", "INNER", "INOUT", "INSENSITIVE", + "INSERT", "INT", "INTEGER", + "INTERSECT", "INTERSECTION", "INTERVAL", "INTO", "IS", + "JOIN", "JSON", "JSON_ARRAY", "JSON_ARRAYAGG", "JSON_EXISTS", + "JSON_OBJECT", "JSON_OBJECTAGG", "JSON_QUERY", "JSON_SCALAR", + "JSON_SERIALIZE", "JSON_TABLE", "JSON_TABLE_PRIMITIVE", "JSON_VALUE", + "LAG", "LANGUAGE", "LARGE", " LAST_VALUE", "LATERAL", "LEAD", + "LEADING", "LEAST", "LEFT", "LIKE", "LIKE_REGEX", "LISTAGG", + "LN", "LOCAL", "LOCALTIME", "LOCALTIMESTAMP", "LOG", "LOG10", + "LOWER", "LPAD", "LTRIM", + "MATCH", "MATCH_NUMBER", "MATCH_RECOGNIZE", "MATCHES", "MAX", + "MEMBER", "MERGE", "METHOD", "MIN", "MINUTE", "MOD", "MODIFIES", + "MODULE", "MONTH", "MULTISET", + "NATIONAL", "NATURAL", "NCHAR", "NCLOB", "NEW", "NO", "NONE", + "NORMALIZE", "NOT", "NTH_VALUE", "NTILE", "NULL", "NULLIF", "NUMERIC", + "OCCURRENCES_REGEX", "OCTET_LENGTH", "OF", "OFFSET", "OLD", "OMIT", + "ON", "ONE", "ONLY", "OPEN", "OR", "ORDER", "OUT", "OUTER", "OUTPUT", + "OVER", "OVERLAPS", "OVERLAY", + "PARAMETER", "PARTITION", "PATTERN", "PER", "PERCENT", "PERCENT_RANK", + "PERCENTILE_CONT", "PERCENTILE_DISC", "PERIOD", "PORTION", "POSITION", + "POSITION_REGEX", "POWER", "PRECEDES", + "PRECISION", "PREPARE", "PRIMARY", "PROCEDURE", "PTF", + "RANGE", "RANK", "READS", "REAL", "RECURSIVE", "REF", "REFERENCES", + "REFERENCING", "REGR_AVGX", "REGR_AVGY", "REGR_COUNT", "REGR_INTERCEPT", + "REGR_R2", "REGR_SLOPE", "REGR_SXX", "REGR_SXY", "REGR_SYY", + "RELEASE", "RESULT", "RETURN", "RETURNS", "REVOKE", "RIGHT", + "ROLLBACK", "ROLLUP", "ROW", "ROW_NUMBER", "ROWS", "RPAD", "RTRIM", + "RUNNING", + "SAVEPOINT", "SCOPE", "SCROLL", "SEARCH", "SECOND", "SEEK", + "SELECT", "SENSITIVE", "SESSION_USER", "SET", "SHOW", "SIMILAR", + "SIN", "SINH", "SKIP", "SMALLINT", + "SOME", "SPECIFIC", "SPECIFICTYPE", "SQL", "SQLEXCEPTION", "SQLSTATE", + "SQLWARNING", "SQRT", "START", "STATIC", "STDDEV_POP", "STDDEV_SAMP", + "SUBMULTISET", "SUBSET", "SUBSTRING", "SUBSTRING_REGEX", "SUCCEEDS", + "SUM", "SYMMETRIC", "SYSTEM", "SYSTEM_TIME", "SYSTEM_USER", + "TABLE", "TABLESAMPLE", "TAN", "TANH", "THEN", "TIME", "TIMESTAMP", + "TIMEZONE_HOUR", "TIMEZONE_MINUTE", "TO", "TRAILING", "TRANSLATE", + "TRANSLATE_REGEX", "TRANSLATION", "TREAT", "TRIGGER", "TRIM", + "TRIM_ARRAY", "TRUE", "TRUNCATE", + "UESCAPE", "UNION", "UNIQUE", "UNKNOWN", "UNNEST", "UPDATE", "UPPER", + "USER", "USING", + "VALUE", "VALUES", "VALUE_OF", "VAR_POP", "VAR_SAMP", "VARBINARY", + "VARCHAR", "VARYING", "VERSIONING", + "WHEN", "WHENEVER", "WHERE", "WHILE", "WIDTH_BUCKET", "WINDOW", + "WITH", "WITHIN", "WITHOUT", + "YEAR" + }; + private static final Set SQL_RESERVED_WORDS = + new HashSet<>(Arrays.asList(SQL2023_RESERVED_WORDS)); + + /** + * Returns a {@code String} enclosed in single quotes. Any occurrence of a + * single quote within the string will be replaced by two single quotes. + * + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Examples of the conversion:
ValueResult
Hello 'Hello'
G'Day 'G''Day'
'G''Day''''G''''Day'''
I'''M 'I''''''M'
+ *
+ * + * @param val a character string + * @return A string enclosed by single quotes with every single quote + * converted to two single quotes + * @throws NullPointerException if val is {@code null} + * @throws SQLException if a database access error occurs + * @implNote JDBC driver implementations may need to provide their own implementation + * of this method in order to meet the requirements of the underlying + * datasource. + */ + static String enquoteLiteral(String val) throws SQLException { + return "'" + val.replace("'", "''") + "'"; + } + + /** + * Returns a {@link #isSimpleIdentifier(String) simple SQL identifier} or a + * delimited identifier. A delimited identifier represents the name of a + * database object such as a table, column, or view that is enclosed by a + * delimiter, which is typically a double quote as defined by the SQL standard. + *

+ * If {@code identifier} is a simple SQL identifier: + *

    + *
  • If {@code alwaysDelimit} is {@code false}, return the original value
  • + *
  • if {@code alwaysDelimit} is {@code true}, enquote the original value + * and return as a delimited identifier
  • + *
+ * + * If {@code identifier} is not a simple SQL identifier, the delimited + * {@code identifier} to be returned must be enclosed by the delimiter + * returned from {@link DatabaseMetaData#getIdentifierQuoteString}. If + * the datasource does not support delimited identifiers, a + * {@code SQLFeatureNotSupportedException} is thrown. + *

+ * A {@code SQLException} will be thrown if {@code identifier} contains any + * invalid characters within a delimited identifier or the identifier length + * is invalid for the datasource. + * + * @implSpec + * The default implementation uses the following criteria to + * determine a valid simple SQL identifier: + *

    + *
  • The string is not enclosed in double quotes
  • + *
  • The first character is an alphabetic character from a ({@code '\u005C0061'}) + * through z ({@code '\u005Cu007A'}), or from A ({@code '\u005Cu0041'}) + * through Z ({@code '\u005Cu005A'})
  • + *
  • The name only contains alphanumeric characters or the character "_"
  • + *
+ * + * The default implementation will throw a {@code SQLException} if: + *
    + *
  • {@link DatabaseMetaData#getIdentifierQuoteString} does not return a + * double quote
  • + *
  • {@code identifier} contains a {@code null} character or double quote
  • + *
  • The length of {@code identifier} is less than 1 or greater than 128 characters + *
+ *
+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Examples of the conversion:
identifieralwaysDelimitResult
HellofalseHello
Hellotrue"Hello"
G'Dayfalse"G'Day"
"Bruce Wayne"false"Bruce Wayne"
"Bruce Wayne"true"Bruce Wayne"
"select"false"select"
"select"true"select"
GoodDay$false"GoodDay$"
Hello"WorldfalseSQLException
"Hello"World"falseSQLException
+ *
+ * @implNote + * JDBC driver implementations may need to provide their own implementation + * of this method in order to meet the requirements of the underlying + * datasource. + * @param identifier a SQL identifier + * @param alwaysDelimit indicates if a simple SQL identifier should be + * returned as a delimited identifier + * @return A simple SQL identifier or a delimited identifier + * @throws SQLException if identifier is not a valid identifier + * @throws SQLFeatureNotSupportedException if the datasource does not support + * delimited identifiers + * @throws NullPointerException if identifier is {@code null} + */ + static String enquoteIdentifier(String delimiter, String identifier, boolean alwaysDelimit) throws SQLException { + int len = identifier.length(); + if (len < 1 || len > 128) { + throw new SQLException("Invalid identifier length"); + } + if (!delimiter.equals("\"")) { + throw new SQLException("Unsupported delimiter"); + } + if (isSimpleIdentifier(identifier)) { + return alwaysDelimit ? "\"" + identifier + "\"" : identifier; + } + if (identifier.matches("^\".+\"$")) { + identifier = identifier.substring(1, len - 1); + } + // Enclose the identifier in double quotes. If the identifier + // contains a null character or a double quote, throw a SQLException + if (INVALID_IDENTIFIER_CHARACTERS_PATTERN.matcher(identifier).matches()) { + return "\"" + identifier + "\""; + } else { + throw new SQLException("Invalid name"); + } + } + + /** + * Returns whether {@code identifier} is a simple SQL identifier. + * A simple SQL identifier is referred to as regular (or ordinary) identifier + * within the SQL standard. A regular identifier represents the name of a database + * object such as a table, column, or view. + *

+ * The rules for a regular Identifier are: + *

    + *
  • The first character is an alphabetic character from a ({@code '\u005Cu0061'}) + * through z ({@code '\u005Cu007A'}), or from A ({@code '\u005Cu0041'}) + * through Z ({@code '\u005Cu005A'})
  • + *
  • The name only contains alphanumeric characters or the character "_"
  • + *
  • It cannot be a SQL reserved word
  • + *
+ *

+ * A datasource may have additional rules for a regular identifier such as: + *

    + *
  • Supports additional characters within the name based on + * the locale being used
  • + *
  • Supports a different maximum length for the identifier
  • + *
+ * + * @implSpec The default implementation uses the following criteria to + * determine a valid simple SQL identifier: + *
    + *
  • The identifier is not enclosed in double quotes
  • + *
  • The first character is an alphabetic character from a through z, or + * from A through Z
  • + *
  • The identifier only contains alphanumeric characters or the character + * "_"
  • + *
  • The identifier is not a SQL reserved word
  • + *
  • The identifier is between 1 and 128 characters in length inclusive
  • + *
+ * + *
+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Examples of the conversion:
identifierSimple Identifier
Hellotrue
G'Dayfalse
"Bruce Wayne"false
GoodDay$false
Hello"Worldfalse
"Hello"World"false
"select"false
"from"false
+ *
+ * @implNote JDBC driver implementations may need to provide their own + * implementation of this method in order to meet the requirements of the + * underlying datasource. + * @param identifier a SQL identifier + * @return true if a simple SQL identifier, false otherwise + * @throws NullPointerException if identifier is {@code null} + * @throws SQLException if a database access error occurs + */ + static boolean isSimpleIdentifier(String identifier) throws SQLException { + int len = identifier.length(); + return !SQL_RESERVED_WORDS.contains(identifier.toUpperCase()) && + len >= 1 && len <= 128 + && SIMPLE_IDENTIFIER_PATTERN.matcher(identifier).matches(); + } + + /** + * Returns a {@code String} representing a National Character Set Literal + * enclosed in single quotes and prefixed with an upper case letter N. + * Any occurrence of a single quote within the string will be replaced + * by two single quotes. + * + *
+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Examples of the conversion:
ValueResult
Hello N'Hello'
G'Day N'G''Day'
'G''Day'N'''G''''Day'''
I'''M N'I''''''M'
N'Hello' N'N''Hello'''
+ *
+ * + * @param val a character string + * @return the result of replacing every single quote character in the + * argument by two single quote characters where this entire result is + * then prefixed with 'N'. + * @throws NullPointerException if val is {@code null} + * @throws SQLException if a database access error occurs + * @implNote JDBC driver implementations may need to provide their own implementation + * of this method in order to meet the requirements of the underlying + * datasource. An implementation of enquoteNCharLiteral may accept a different + * set of characters than that accepted by the same drivers implementation of + * enquoteLiteral. + */ + static String enquoteNCharLiteral(String val) throws SQLException { + return "N'" + val.replace("'", "''") + "'"; + } +} diff --git a/src/java.sql/share/classes/java/sql/SQLXML.java b/src/java.sql/share/classes/java/sql/SQLXML.java index 3edae8a9508..70d481822e2 100644 --- a/src/java.sql/share/classes/java/sql/SQLXML.java +++ b/src/java.sql/share/classes/java/sql/SQLXML.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -170,14 +170,19 @@ import javax.xml.transform.Source; * The conceptual states of writable and not writable determine if one * of the writing APIs will set a value or throw an exception. *

- * The state moves from readable to not readable once free() or any of the + * The state moves from readable to not readable once close(), free() or any of the * reading APIs are called: getBinaryStream(), getCharacterStream(), getSource(), and getString(). * Implementations may also change the state to not writable when this occurs. *

- * The state moves from writable to not writable once free() or any of the + * The state moves from writable to not writable once close(), free() or any of the * writing APIs are called: setBinaryStream(), setCharacterStream(), setResult(), and setString(). * Implementations may also change the state to not readable when this occurs. - * + *

+ * To release resources used by the {@code SQLXML} object, applications must call + * either the {@link #free()} or the {@link #close()} method. Any attempt to + * invoke a method other than {@link #free()} or {@link #close()} after the + * {@code SQLXML} object has been closed, will result in a {@link SQLException} + * being thrown. *

* All methods on the {@code SQLXML} interface must be fully implemented if the * JDBC driver supports the data type. @@ -188,21 +193,19 @@ import javax.xml.transform.Source; * @see javax.xml.xpath * @since 1.6 */ -public interface SQLXML +public interface SQLXML extends AutoCloseable { /** - * This method closes this object and releases the resources that it held. - * The SQL XML object becomes invalid and neither readable or writable - * when this method is called. + * Closes and releases the resources held by this {@code SQLXML} object. + *

+ * If the {@code SQLXML} object is already closed, then invoking this method + * has no effect. * - * After {@code free} has been called, any attempt to invoke a - * method other than {@code free} will result in a {@code SQLException} - * being thrown. If {@code free} is called multiple times, the subsequent - * calls to {@code free} are treated as a no-op. * @throws SQLException if there is an error freeing the XML value. * @throws SQLFeatureNotSupportedException if the JDBC driver does not support * this method * @since 1.6 + * @see #close() */ void free() throws SQLException; @@ -424,4 +427,21 @@ public interface SQLXML */ T setResult(Class resultClass) throws SQLException; + /** + * Closes and releases the resources held by this {@code SQLXML} object. + *

+ * If the {@code SQLXML} object is already closed, then invoking this method + * has no effect. + * + * @throws SQLException if an error occurs releasing + * the SQLXML's resources + * @throws SQLFeatureNotSupportedException if the JDBC driver + * does not support this method + * @implSpec The default implementation calls the {@link #free()} method. + * @see #free() + * @since 26 + */ + default void close() throws SQLException { + free(); + }; } diff --git a/src/java.sql/share/classes/java/sql/Statement.java b/src/java.sql/share/classes/java/sql/Statement.java index bb5a5cfd64d..4da510f6749 100644 --- a/src/java.sql/share/classes/java/sql/Statement.java +++ b/src/java.sql/share/classes/java/sql/Statement.java @@ -25,9 +25,6 @@ package java.sql; -import java.util.regex.Pattern; -import static java.util.stream.Collectors.joining; - /** *

The object used for executing a static SQL statement * and returning the results it produces. @@ -1395,6 +1392,9 @@ public interface Statement extends Wrapper, AutoCloseable { * * * + * @implSpec + * The default implementation creates the literal as: + * {@code "'" + val.replace("'", "''") + "'"}. * @implNote * JDBC driver implementations may need to provide their own implementation * of this method in order to meet the requirements of the underlying @@ -1407,46 +1407,50 @@ public interface Statement extends Wrapper, AutoCloseable { * * @since 9 */ - default String enquoteLiteral(String val) throws SQLException { - return "'" + val.replace("'", "''") + "'"; + default String enquoteLiteral(String val) throws SQLException { + return SQLUtils.enquoteLiteral(val); } - - /** - * Returns a SQL identifier. If {@code identifier} is a simple SQL identifier: + /** + * Returns a {@link #isSimpleIdentifier(String) simple SQL identifier} or a + * delimited identifier. A delimited identifier represents the name of a + * database object such as a table, column, or view that is enclosed by a + * delimiter, which is typically a double quote as defined by the SQL standard. + *

+ * If {@code identifier} is a simple SQL identifier: *

    - *
  • Return the original value if {@code alwaysQuote} is - * {@code false}
  • - *
  • Return a delimited identifier if {@code alwaysQuote} is - * {@code true}
  • + *
  • If {@code alwaysDelimit} is {@code false}, return the original value
  • + *
  • if {@code alwaysDelimit} is {@code true}, enquote the original value + * and return as a delimited identifier
  • *
* - * If {@code identifier} is not a simple SQL identifier, {@code identifier} will be - * enclosed in double quotes if not already present. If the datasource does - * not support double quotes for delimited identifiers, the - * identifier should be enclosed by the string returned from - * {@link DatabaseMetaData#getIdentifierQuoteString}. If the datasource - * does not support delimited identifiers, a - * {@code SQLFeatureNotSupportedException} should be thrown. + * If {@code identifier} is not a simple SQL identifier, the delimited + * {@code identifier} to be returned must be enclosed by the delimiter + * returned from {@link DatabaseMetaData#getIdentifierQuoteString}. If + * the datasource does not support delimited identifiers, a + * {@code SQLFeatureNotSupportedException} is thrown. *

* A {@code SQLException} will be thrown if {@code identifier} contains any - * characters invalid in a delimited identifier or the identifier length is - * invalid for the datasource. + * invalid characters within a delimited identifier or the identifier length + * is invalid for the datasource. * * @implSpec * The default implementation uses the following criteria to * determine a valid simple SQL identifier: *

    *
  • The string is not enclosed in double quotes
  • - *
  • The first character is an alphabetic character from a through z, or - * from A through Z
  • - *
  • The name only contains alphanumeric characters or the character "_"
  • + *
  • The first character is an alphabetic character from a ({@code '\u005C0061'}) + * through z ({@code '\u005Cu007A'}), or from A ({@code '\u005Cu0041'}) + * through Z ({@code '\u005Cu005A'})
  • + *
  • The name only contains alphanumeric characters([0-9A-Za-z]) + * or the character "_"
  • *
* * The default implementation will throw a {@code SQLException} if: *
    - *
  • {@code identifier} contains a {@code null} character or double quote and is not - * a simple SQL identifier.
  • + *
  • {@link DatabaseMetaData#getIdentifierQuoteString} does not return a + * double quote
  • + *
  • {@code identifier} contains a {@code null} character or double quote
  • *
  • The length of {@code identifier} is less than 1 or greater than 128 characters *
*
@@ -1455,7 +1459,7 @@ public interface Statement extends Wrapper, AutoCloseable { * * * identifier - * alwaysQuote + * alwaysDelimit * Result * * @@ -1485,6 +1489,16 @@ public interface Statement extends Wrapper, AutoCloseable { * "Bruce Wayne" * * + * "select" + * false + * "select" + * + * + * "select" + * true + * "select" + * + * * GoodDay$ * false * "GoodDay$" @@ -1507,8 +1521,8 @@ public interface Statement extends Wrapper, AutoCloseable { * of this method in order to meet the requirements of the underlying * datasource. * @param identifier a SQL identifier - * @param alwaysQuote indicates if a simple SQL identifier should be - * returned as a quoted identifier + * @param alwaysDelimit indicates if a simple SQL identifier should be + * returned as a delimited identifier * @return A simple SQL identifier or a delimited identifier * @throws SQLException if identifier is not a valid identifier * @throws SQLFeatureNotSupportedException if the datasource does not support @@ -1517,36 +1531,43 @@ public interface Statement extends Wrapper, AutoCloseable { * * @since 9 */ - default String enquoteIdentifier(String identifier, boolean alwaysQuote) throws SQLException { - int len = identifier.length(); - if (len < 1 || len > 128) { - throw new SQLException("Invalid name"); - } - if (Pattern.compile("[\\p{Alpha}][\\p{Alnum}_]*").matcher(identifier).matches()) { - return alwaysQuote ? "\"" + identifier + "\"" : identifier; - } - if (identifier.matches("^\".+\"$")) { - identifier = identifier.substring(1, len - 1); - } - if (Pattern.compile("[^\u0000\"]+").matcher(identifier).matches()) { - return "\"" + identifier + "\""; - } else { - throw new SQLException("Invalid name"); - } + default String enquoteIdentifier(String identifier, boolean alwaysDelimit) throws SQLException { + return getConnection().enquoteIdentifier(identifier,alwaysDelimit); } /** - * Retrieves whether {@code identifier} is a simple SQL identifier. + * Returns whether {@code identifier} is a simple SQL identifier. + * A simple SQL identifier is referred to as regular (or ordinary) identifier + * within the SQL standard. A regular identifier represents the name of a database + * object such as a table, column, or view. + *

+ * The rules for a regular Identifier are: + *

    + *
  • The first character is an alphabetic character from a ({@code '\u005Cu0061'}) + * through z ({@code '\u005Cu007A'}), or from A ({@code '\u005Cu0041'}) + * through Z ({@code '\u005Cu005A'})
  • + *
  • The name only contains alphanumeric characters([0-9A-Za-z]) or the + * character "_"
  • + *
  • It cannot be a SQL reserved word
  • + *
+ *

+ * A datasource may have additional rules for a regular identifier such as: + *

    + *
  • Supports additional characters within the name based on + * the locale being used
  • + *
  • Supports a different maximum length for the identifier
  • + *
* * @implSpec The default implementation uses the following criteria to * determine a valid simple SQL identifier: *
    - *
  • The string is not enclosed in double quotes
  • + *
  • The identifier is not enclosed in double quotes
  • *
  • The first character is an alphabetic character from a through z, or * from A through Z
  • - *
  • The string only contains alphanumeric characters or the character - * "_"
  • - *
  • The string is between 1 and 128 characters in length inclusive
  • + *
  • The identifier only contains alphanumeric characters([0-9A-Za-z]) + * or the character "_"
  • + *
  • The identifier is not a SQL reserved word
  • + *
  • The identifier is between 1 and 128 characters in length inclusive
  • *
* *
@@ -1583,6 +1604,13 @@ public interface Statement extends Wrapper, AutoCloseable { * "Hello"World" * false * + * + * "select" + * false + * + * "from" + * false + * * * *
@@ -1590,16 +1618,14 @@ public interface Statement extends Wrapper, AutoCloseable { * implementation of this method in order to meet the requirements of the * underlying datasource. * @param identifier a SQL identifier - * @return true if a simple SQL identifier, false otherwise + * @return true if a simple SQL identifier, false otherwise * @throws NullPointerException if identifier is {@code null} * @throws SQLException if a database access error occurs * * @since 9 */ default boolean isSimpleIdentifier(String identifier) throws SQLException { - int len = identifier.length(); - return len >= 1 && len <= 128 - && Pattern.compile("[\\p{Alpha}][\\p{Alnum}_]*").matcher(identifier).matches(); + return SQLUtils.isSimpleIdentifier(identifier); } /** @@ -1628,7 +1654,10 @@ public interface Statement extends Wrapper, AutoCloseable { * * *
- * @implNote + * @implSpec + * The default implementation creates the literal as: + * {@code "N'" + val.replace("'", "''") + "'"}. + * @implNote * JDBC driver implementations may need to provide their own implementation * of this method in order to meet the requirements of the underlying * datasource. An implementation of enquoteNCharLiteral may accept a different @@ -1643,7 +1672,7 @@ public interface Statement extends Wrapper, AutoCloseable { * * @since 9 */ - default String enquoteNCharLiteral(String val) throws SQLException { - return "N'" + val.replace("'", "''") + "'"; + default String enquoteNCharLiteral(String val) throws SQLException { + return SQLUtils.enquoteNCharLiteral(val); } } diff --git a/src/java.sql/share/classes/java/sql/Timestamp.java b/src/java.sql/share/classes/java/sql/Timestamp.java index a91ab7210c5..c550291bb95 100644 --- a/src/java.sql/share/classes/java/sql/Timestamp.java +++ b/src/java.sql/share/classes/java/sql/Timestamp.java @@ -54,10 +54,7 @@ import java.time.LocalDateTime; * because the nanos component of a date is unknown. * As a result, the {@code Timestamp.equals(Object)} * method is not symmetric with respect to the - * {@code java.util.Date.equals(Object)} - * method. Also, the {@code hashCode} method uses the underlying - * {@code java.util.Date} - * implementation and therefore does not include nanos in its computation. + * {@code java.util.Date.equals(Object)} method. *

* Due to the differences between the {@code Timestamp} class * and the {@code java.util.Date} @@ -465,10 +462,15 @@ public class Timestamp extends java.util.Date { } /** - * {@inheritDoc} + * Returns a hash code value for this Timestamp. The result is the + * exclusive OR of the two halves of the primitive {@code long} + * value returned by the {@link #getTime} method. That is, + * the hash code is the value of the expression: + * {@snippet : + * (int)(this.getTime()^(this.getTime() >>> 32)) + * } * - * The {@code hashCode} method uses the underlying {@code java.util.Date} - * implementation and therefore does not include nanos in its computation. + * @return a hash code value for this Timestamp. * */ @Override diff --git a/src/java.sql/share/classes/java/sql/Types.java b/src/java.sql/share/classes/java/sql/Types.java index 2b3b571647b..778d782ddaf 100644 --- a/src/java.sql/share/classes/java/sql/Types.java +++ b/src/java.sql/share/classes/java/sql/Types.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -339,6 +339,27 @@ public class Types { */ public static final int TIMESTAMP_WITH_TIMEZONE = 2014; + + //--------------------------JDBC 4.5 ----------------------------- + + /** + * The constant in the Java programming language, sometimes referred to + * as a type code, that identifies the generic SQL type + * {@code DECFLOAT}. + * + * @since 26 + */ + public static final int DECFLOAT = 2015; + + /** + * The constant in the Java programming language, sometimes referred to + * as a type code, that identifies the generic SQL type + * {@code JSON}. + * + * @since 26 + */ + public static final int JSON = 2016; + // Prevent instantiation private Types() {} } diff --git a/src/java.sql/share/classes/java/sql/package-info.java b/src/java.sql/share/classes/java/sql/package-info.java index 495119effac..64b23dba76f 100644 --- a/src/java.sql/share/classes/java/sql/package-info.java +++ b/src/java.sql/share/classes/java/sql/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -38,20 +38,22 @@ * use and update data from a spread sheet, flat file, or any other tabular * data source. * - *

What the JDBC 4.3 API Includes

- * The JDBC 4.3 API includes both + *

What the JDBC 4.5 API Includes

+ * The JDBC 4.5 API includes both * the {@code java.sql} package, referred to as the JDBC core API, * and the {@code javax.sql} package, referred to as the JDBC Optional * Package API. This complete JDBC API - * is included in the Java Standard Edition (Java SE), version 7. + * is included in the Java Standard Edition (Java SE). * The {@code javax.sql} package extends the functionality of the JDBC API * from a client-side API to a server-side API, and it is an essential part * of the Java Enterprise Edition * (Java EE) technology. * *

Versions

- * The JDBC 4.3 API incorporates all of the previous JDBC API versions: + * The JDBC 4.5 API incorporates all the previous JDBC API versions: *
    + *
  • The JDBC 4.4 API
  • + *
  • The JDBC 4.3 API
  • *
  • The JDBC 4.2 API
  • *
  • The JDBC 4.1 API
  • *
  • The JDBC 4.0 API
  • @@ -70,6 +72,10 @@ * Javadoc comments for the JDBC API, * they indicate the following: *
      + *
    • Since 26 -- new in the JDBC 4.5 API and part of the Java SE platform, + * version 26
    • + *
    • Since 24 -- new in the JDBC 4.4 API and part of the Java SE platform, + * version 24
    • *
    • Since 9 -- new in the JDBC 4.3 API and part of the Java SE platform, * version 9
    • *
    • Since 1.8 -- new in the JDBC 4.2 API and part of the Java SE platform, @@ -126,6 +132,7 @@ *
    • {@code Blob} interface -- mapping for SQL {@code BLOB} *
    • {@code Clob} interface -- mapping for SQL {@code CLOB} *
    • {@code Date} class -- mapping for SQL {@code DATE} + *
    • {@code JDBCType} class -- provides enum constants for SQL types *
    • {@code NClob} interface -- mapping for SQL {@code NCLOB} *
    • {@code Ref} interface -- mapping for SQL {@code REF} *
    • {@code RowId} interface -- mapping for SQL {@code ROWID} @@ -166,6 +173,26 @@ *
    *
* + *

{@code java.sql} and {@code javax.sql} Features Introduced in the JDBC 4.5 API

+ *
    + *
  • The interfaces {@code Array}, {@code Blob}, {@code Clob}, {@code NClob} + * and {@code SQLXML} now extend the {@code AutoCloseable} interface and + * include a default {@code close} method implementation
  • + *
  • Added support to {@code Connection} for enquoting literals + * and simple identifiers
  • + *
  • {@code SQLPermissions} has been deprecated for removal
  • + *
  • The SQL Types {@code JSON} and {@code DECFLOAT} have been added to + * {@code JDBCType} and {@code Types}
  • + *
+ *

{@code java.sql} and {@code javax.sql} Features Introduced in the JDBC 4.4 API

+ *
    + *
  • Remove mention of {@code SecurityManager} and {@code SecurityException} + * as the {@code SecurityManager} is no longer supported
  • + *
  • {@code SQLPermissions} can no longer be used to control access to + * resources as the {@code SecurityManager} is no longer supported
  • + *
  • Added support to {@code Connection} for enquoting literals + * and simple identifiers
  • + *
*

{@code java.sql} and {@code javax.sql} Features Introduced in the JDBC 4.3 API

*
    *
  • Added {@code Sharding} support
  • @@ -232,7 +259,6 @@ * *
* - * *

{@code java.sql} and {@code javax.sql} Features Introduced in the JDBC 3.0 API

*
    *
  • Pooled statements -- reuse of statements associated with a pooled @@ -288,7 +314,6 @@ * handling and passing data *
* - * *

Custom Mapping of UDTs

* A user-defined type (UDT) defined in SQL can be mapped to a class in the Java * programming language. An SQL structured type or an SQL {@code DISTINCT} @@ -317,7 +342,7 @@ *

Package Specification

* * * *

Related Documentation

@@ -326,7 +351,6 @@ *
  • * Lesson:JDBC Basics(The Java Tutorials > JDBC Database Access) * - *
  • JDBC API Tutorial and Reference, Third Edition” * * @since 1.1 */ diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTrees.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTrees.java index f933ef36565..22ee2393a02 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTrees.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTrees.java @@ -413,7 +413,18 @@ public class JavacTrees extends DocTrees { } if (ref.qualifierExpression == null) { - tsym = env.enclClass.sym; + // Resolve target for unqualified reference based on declaring element + tsym = switch (path.getLeaf().getKind()) { + case PACKAGE -> env.toplevel.packge; + case MODULE -> env.toplevel.modle; + case COMPILATION_UNIT -> + // Treat unqualified reference in legacy package.html as package reference. + // Unqualified references in doc-files only need to work locally, so null is fine. + path.getCompilationUnit().getSourceFile().isNameCompatible("package", JavaFileObject.Kind.HTML) + ? env.toplevel.packge + : null; + default -> env.enclClass.sym; // Class or class member reference + }; memberName = (Name) ref.memberName; } else { // Check if qualifierExpression is a type or package, using the methods javac provides. @@ -470,8 +481,11 @@ public class JavacTrees extends DocTrees { } } - if (memberName == null) + if (memberName == null) { return tsym; + } else if (tsym == null || tsym.getKind() == ElementKind.PACKAGE || tsym.getKind() == ElementKind.MODULE) { + return null; // Non-null member name in non-class context + } if (tsym.type.isPrimitive()) { return null; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java index c723caf1843..3f72ada94e8 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java @@ -176,7 +176,7 @@ public class Attr extends JCTree.Visitor { Feature.UNCONDITIONAL_PATTERN_IN_INSTANCEOF.allowedInSource(source); sourceName = source.name; useBeforeDeclarationWarning = options.isSet("useBeforeDeclarationWarning"); - captureMRefReturnType = Source.Feature.ERASE_POLY_SIG_RETURN_TYPE.allowedInSource(source); + captureMRefReturnType = Source.Feature.CAPTURE_MREF_RETURN_TYPE.allowedInSource(source); statInfo = new ResultInfo(KindSelector.NIL, Type.noType); varAssignmentInfo = new ResultInfo(KindSelector.ASG, Type.noType); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java index eea766f57c1..07f2a742bcb 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java @@ -2810,7 +2810,12 @@ public class Resolve { Symbol resolveQualifiedMethod(DiagnosticPosition pos, Env env, Symbol location, Type site, Name name, List argtypes, List typeargtypes) { - return resolveQualifiedMethod(new MethodResolutionContext(), pos, env, location, site, name, argtypes, typeargtypes); + try { + return resolveQualifiedMethod(new MethodResolutionContext(), pos, env, location, site, name, argtypes, typeargtypes); + } catch (CompletionFailure cf) { + chk.completionError(pos, cf); + return methodNotFound.access(name, site.tsym); + } } private Symbol resolveQualifiedMethod(MethodResolutionContext resolveContext, DiagnosticPosition pos, Env env, 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 0436f68b9e3..6501fd5d96c 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 @@ -505,7 +505,7 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition { // for default DiagnosticPosition public int getStartPosition() { - return TreeInfo.getStartPos(this); + return noNoPos(TreeInfo.getStartPos(this)); } // for default DiagnosticPosition @@ -515,7 +515,14 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition { // for default DiagnosticPosition public int getEndPosition(EndPosTable endPosTable) { - return TreeInfo.getEndPos(this, endPosTable); + return noNoPos(TreeInfo.getEndPos(this, endPosTable)); + } + + private int noNoPos(int position) { + if (position == JCDiagnostic.NOPOS) { + return pos; + } + return position; } /** diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeInfo.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeInfo.java index af823024fab..3f73bfd2296 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeInfo.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeInfo.java @@ -651,6 +651,11 @@ public class TreeInfo { if (tree == null) return Position.NOPOS; + if (endPosTable == null) { + // fall back on limited info in the tree + return endPos(tree); + } + int mapPos = endPosTable.getEndPos(tree); if (mapPos != Position.NOPOS) return mapPos; diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/code/NMethod.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/code/NMethod.java index 91302dba0f6..f935c56b536 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/code/NMethod.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/code/NMethod.java @@ -48,7 +48,7 @@ public class NMethod extends CodeBlob { /** Offsets for different nmethod parts */ private static CIntegerField exceptionOffsetField; - private static CIntegerField deoptHandlerOffsetField; + private static CIntegerField deoptHandlerEntryOffsetField; private static CIntegerField origPCOffsetField; private static CIntegerField stubOffsetField; private static CIntField handlerTableOffsetField; @@ -86,7 +86,7 @@ public class NMethod extends CodeBlob { immutableDataField = type.getAddressField("_immutable_data"); immutableDataSizeField = type.getCIntegerField("_immutable_data_size"); exceptionOffsetField = type.getCIntegerField("_exception_offset"); - deoptHandlerOffsetField = type.getCIntegerField("_deopt_handler_offset"); + deoptHandlerEntryOffsetField = type.getCIntegerField("_deopt_handler_entry_offset"); origPCOffsetField = type.getCIntegerField("_orig_pc_offset"); stubOffsetField = type.getCIntegerField("_stub_offset"); scopesPCsOffsetField = type.getCIntegerField("_scopes_pcs_offset"); @@ -121,16 +121,16 @@ public class NMethod extends CodeBlob { public boolean isOSRMethod() { return getEntryBCI() != VM.getVM().getInvocationEntryBCI(); } /** Boundaries for different parts */ - public Address constantsBegin() { return contentBegin(); } - public Address constantsEnd() { return codeBegin(); } - public Address instsBegin() { return codeBegin(); } - public Address instsEnd() { return headerBegin().addOffsetTo(getStubOffset()); } - public Address exceptionBegin() { return headerBegin().addOffsetTo(getExceptionOffset()); } - public Address deoptHandlerBegin() { return headerBegin().addOffsetTo(getDeoptHandlerOffset()); } - public Address stubBegin() { return headerBegin().addOffsetTo(getStubOffset()); } - public Address stubEnd() { return dataBegin(); } - public Address oopsBegin() { return dataBegin(); } - public Address oopsEnd() { return dataEnd(); } + public Address constantsBegin() { return contentBegin(); } + public Address constantsEnd() { return codeBegin(); } + public Address instsBegin() { return codeBegin(); } + public Address instsEnd() { return headerBegin().addOffsetTo(getStubOffset()); } + public Address exceptionBegin() { return headerBegin().addOffsetTo(getExceptionOffset()); } + public Address deoptHandlerEntry() { return headerBegin().addOffsetTo(getDeoptHandlerEntryOffset()); } + public Address stubBegin() { return headerBegin().addOffsetTo(getStubOffset()); } + public Address stubEnd() { return dataBegin(); } + public Address oopsBegin() { return dataBegin(); } + public Address oopsEnd() { return dataEnd(); } public Address immutableDataBegin() { return immutableDataField.getValue(addr); } public Address immutableDataEnd() { return immutableDataBegin().addOffsetTo(getImmutableDataSize()); } @@ -262,7 +262,7 @@ public class NMethod extends CodeBlob { // Deopt // Return true is the PC is one would expect if the frame is being deopted. public boolean isDeoptPc (Address pc) { return isDeoptEntry(pc); } - public boolean isDeoptEntry (Address pc) { return pc == deoptHandlerBegin(); } + public boolean isDeoptEntry (Address pc) { return pc == deoptHandlerEntry(); } /** Tells whether frames described by this nmethod can be deoptimized. Note: native wrappers cannot be deoptimized. */ @@ -490,7 +490,7 @@ public class NMethod extends CodeBlob { private int getEntryBCI() { return (int) entryBCIField .getValue(addr); } private int getExceptionOffset() { return (int) exceptionOffsetField .getValue(addr); } - private int getDeoptHandlerOffset() { return (int) deoptHandlerOffsetField .getValue(addr); } + private int getDeoptHandlerEntryOffset() { return (int) deoptHandlerEntryOffsetField .getValue(addr); } private int getStubOffset() { return (int) stubOffsetField .getValue(addr); } private int getScopesDataOffset() { return (int) scopesDataOffsetField .getValue(addr); } private int getScopesPCsOffset() { return (int) scopesPCsOffsetField .getValue(addr); } diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ConstantPool.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ConstantPool.java index 563d9d3ac4a..3a4ea5546a1 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ConstantPool.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ConstantPool.java @@ -88,8 +88,11 @@ public class ConstantPool extends Metadata implements ClassConstants { private static synchronized void initialize(TypeDataBase db) throws WrongTypeException { Type type = db.lookupType("ConstantPool"); tags = type.getAddressField("_tags"); - operands = type.getAddressField("_operands"); cache = type.getAddressField("_cache"); + bsm_entries = type.getField("_bsm_entries").getOffset(); + Type bsmae_type = db.lookupType("BSMAttributeEntries"); + bsm_entries_offsets = bsmae_type.getAddressField("_offsets"); + bsm_entries_bootstrap_methods = bsmae_type.getAddressField("_bootstrap_methods"); poolHolder = new MetadataField(type.getAddressField("_pool_holder"), 0); length = new CIntField(type.getCIntegerField("_length"), 0); resolved_klasses = type.getAddressField("_resolved_klasses"); @@ -112,9 +115,11 @@ public class ConstantPool extends Metadata implements ClassConstants { public boolean isConstantPool() { return true; } private static AddressField tags; - private static AddressField operands; private static AddressField cache; private static AddressField resolved_klasses; + private static long bsm_entries; // Offset in the constantpool where the Bsm_Entries are found + private static AddressField bsm_entries_offsets; + private static AddressField bsm_entries_bootstrap_methods; private static MetadataField poolHolder; private static CIntField length; // number of elements in oop private static CIntField majorVersion; @@ -130,10 +135,6 @@ public class ConstantPool extends Metadata implements ClassConstants { private static int INDY_ARGV_OFFSET; public U1Array getTags() { return new U1Array(tags.getValue(getAddress())); } - public U2Array getOperands() { - Address addr = operands.getValue(getAddress()); - return VMObjectFactory.newObject(U2Array.class, addr); - } public ConstantPoolCache getCache() { Address addr = cache.getValue(getAddress()); return VMObjectFactory.newObject(ConstantPoolCache.class, addr); @@ -435,26 +436,23 @@ public class ConstantPool extends Metadata implements ClassConstants { return res; } + private U4Array getOffsets() { + Address a = getAddress().addOffsetTo(bsm_entries); + if (a == null) return null; + a = bsm_entries_offsets.getValue(a); + return VMObjectFactory.newObject(U4Array.class, a); + } + private U2Array getBootstrapMethods() { + Address a = getAddress().addOffsetTo(bsm_entries); + if (a == null) return null; + return VMObjectFactory.newObject(U2Array.class, bsm_entries_bootstrap_methods.getValue(a)); + } + public int getBootstrapMethodsCount() { - U2Array operands = getOperands(); + U4Array offsets = getOffsets(); int count = 0; - if (operands != null) { - // Operands array consists of two parts. First part is an array of 32-bit values which denote - // index of the bootstrap method data in the operands array. Note that elements of operands array are of type short. - // So each element of first part occupies two slots in the array. - // Second part is the bootstrap methods data. - // This layout allows us to get BSM count by getting the index of first BSM and dividing it by 2. - // - // The example below shows layout of operands array with 3 bootstrap methods. - // First part has 3 32-bit values indicating the index of the respective bootstrap methods in - // the operands array. - // The first BSM is at index 6. So the count in this case is 6/2=3. - // - // <-----first part----><-------second part-------> - // index: 0 2 4 6 i2 i3 - // operands: | 6 | i2 | i3 | bsm1 | bsm2 | bsm3 | - // - count = getOperandOffsetAt(operands, 0) / 2; + if (offsets != null) { + count = offsets.length(); } if (DEBUG) { System.err.println("ConstantPool.getBootstrapMethodsCount: count = " + count); @@ -463,12 +461,12 @@ public class ConstantPool extends Metadata implements ClassConstants { } public int getBootstrapMethodArgsCount(int bsmIndex) { - U2Array operands = getOperands(); + U4Array offs = getOffsets(); + U2Array bsms = getBootstrapMethods(); if (Assert.ASSERTS_ENABLED) { - Assert.that(operands != null, "Operands is not present"); + Assert.that(offs != null && bsms != null, "BSM attribute is not present"); } - int bsmOffset = getOperandOffsetAt(operands, bsmIndex); - int argc = operands.at(bsmOffset + INDY_ARGC_OFFSET); + int argc = bsms.at(offs.at(bsmIndex) + INDY_ARGC_OFFSET); if (DEBUG) { System.err.println("ConstantPool.getBootstrapMethodArgsCount: bsm index = " + bsmIndex + ", args count = " + argc); } @@ -476,15 +474,16 @@ public class ConstantPool extends Metadata implements ClassConstants { } public short[] getBootstrapMethodAt(int bsmIndex) { - U2Array operands = getOperands(); - if (operands == null) return null; // safety first - int basePos = getOperandOffsetAt(operands, bsmIndex); + U4Array offs = getOffsets(); + U2Array bsms = getBootstrapMethods(); + if (offs == null || bsms == null) return null; // safety first + int basePos = offs.at(bsmIndex); int argv = basePos + INDY_ARGV_OFFSET; - int argc = operands.at(basePos + INDY_ARGC_OFFSET); + int argc = getBootstrapMethodArgsCount(bsmIndex); int endPos = argv + argc; short[] values = new short[endPos - basePos]; for (int j = 0; j < values.length; j++) { - values[j] = operands.at(basePos+j); + values[j] = bsms.at(basePos+j); } return values; } @@ -773,8 +772,7 @@ public class ConstantPool extends Metadata implements ClassConstants { // Return the offset of the requested Bootstrap Method in the operands array private int getOperandOffsetAt(U2Array operands, int bsmIndex) { - return VM.getVM().buildIntFromShorts(operands.at(bsmIndex * 2), - operands.at(bsmIndex * 2 + 1)); + return 0; } } diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/Frame.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/Frame.java index 27efb631f79..ee9e0ecdafd 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/Frame.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/Frame.java @@ -87,7 +87,7 @@ public abstract class Frame implements Cloneable { CodeBlob cb = VM.getVM().getCodeCache().findBlob(pc); if (cb != null && cb.isJavaMethod()) { NMethod nm = (NMethod) cb; - if (pc.equals(nm.deoptHandlerBegin())) { + if (pc.equals(nm.deoptHandlerEntry())) { if (Assert.ASSERTS_ENABLED) { Assert.that(this.getUnextendedSP() != null, "null SP in Java frame"); } diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/U4Array.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/U4Array.java new file mode 100644 index 00000000000..9836614d2c9 --- /dev/null +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/U4Array.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +package sun.jvm.hotspot.utilities; + +import sun.jvm.hotspot.debugger.Address; +import sun.jvm.hotspot.runtime.VM; +import sun.jvm.hotspot.types.Type; +import sun.jvm.hotspot.types.TypeDataBase; +import sun.jvm.hotspot.types.WrongTypeException; +import sun.jvm.hotspot.utilities.Observable; +import sun.jvm.hotspot.utilities.Observer; + +public class U4Array extends GenericArray { + static { + VM.registerVMInitializedObserver(new Observer() { + public void update(Observable o, Object data) { + initialize(VM.getVM().getTypeDataBase()); + } + }); + } + + private static synchronized void initialize(TypeDataBase db) throws WrongTypeException { + elemType = db.lookupType("u4"); + Type type = db.lookupType("Array"); + dataFieldOffset = type.getAddressField("_data").getOffset(); + } + + private static long dataFieldOffset; + protected static Type elemType; + + public U4Array(Address addr) { + super(addr, dataFieldOffset); + } + + public int at(int i) { + return (int)getIntegerAt(i); + } + + public Type getElemType() { + return elemType; + } +} diff --git a/src/jdk.httpserver/share/classes/com/sun/net/httpserver/Headers.java b/src/jdk.httpserver/share/classes/com/sun/net/httpserver/Headers.java index 84535027eb4..7d11bd42c94 100644 --- a/src/jdk.httpserver/share/classes/com/sun/net/httpserver/Headers.java +++ b/src/jdk.httpserver/share/classes/com/sun/net/httpserver/Headers.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -109,29 +109,69 @@ public class Headers implements Map> { } /** - * Normalize the key by converting to following form. - * First {@code char} upper case, rest lower case. - * key is presumed to be {@code ASCII}. + * {@return the normalized header name of the following form: the first + * character in upper-case, the rest in lower-case} + * The input header name is assumed to be encoded in ASCII. + * + * @implSpec + * This method is performance-sensitive; update with care. + * + * @param key an ASCII-encoded header name + * @throws NullPointerException on null {@code key} + * @throws IllegalArgumentException if {@code key} contains {@code \r} or {@code \n} */ - private String normalize(String key) { + private static String normalize(String key) { + + // Fast path for the empty key Objects.requireNonNull(key); - int len = key.length(); - if (len == 0) { + int l = key.length(); + if (l == 0) { return key; } - char[] b = key.toCharArray(); - if (b[0] >= 'a' && b[0] <= 'z') { - b[0] = (char)(b[0] - ('a' - 'A')); - } else if (b[0] == '\r' || b[0] == '\n') - throw new IllegalArgumentException("illegal character in key"); - for (int i=1; i= 'A' && b[i] <= 'Z') { - b[i] = (char) (b[i] + ('a' - 'A')); - } else if (b[i] == '\r' || b[i] == '\n') - throw new IllegalArgumentException("illegal character in key"); + // Find the first non-normalized `char` + int i = 0; + char c = key.charAt(i); + if (!(c == '\r' || c == '\n' || (c >= 'a' && c <= 'z'))) { + i++; + for (; i < l; i++) { + c = key.charAt(i); + if (c == '\r' || c == '\n' || (c >= 'A' && c <= 'Z')) { + break; + } + } } - return new String(b); + + // Fast path for the already normalized key + if (i == l) { + return key; + } + + // Upper-case the first `char` + char[] cs = key.toCharArray(); + int o = 'a' - 'A'; + if (i == 0) { + if (c == '\r' || c == '\n') { + throw new IllegalArgumentException("illegal character in key at index " + i); + } + if (c >= 'a' && c <= 'z') { + cs[0] = (char) (c - o); + } + i++; + } + + // Lower-case the secondary `char`s + for (; i < l; i++) { + c = cs[i]; + if (c >= 'A' && c <= 'Z') { + cs[i] = (char) (c + o); + } else if (c == '\r' || c == '\n') { + throw new IllegalArgumentException("illegal character in key at index " + i); + } + } + + return new String(cs); + } @Override diff --git a/src/jdk.internal.opt/share/classes/module-info.java b/src/jdk.internal.opt/share/classes/module-info.java index ba6987f1ea9..728c2de500d 100644 --- a/src/jdk.internal.opt/share/classes/module-info.java +++ b/src/jdk.internal.opt/share/classes/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -32,6 +32,7 @@ module jdk.internal.opt { exports jdk.internal.joptsimple to jdk.jlink, jdk.jshell, + jdk.jpackage, jdk.jdeps; exports jdk.internal.opt to jdk.compiler, diff --git a/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java b/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java index f2c76058434..12967972a88 100644 --- a/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java +++ b/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java @@ -167,6 +167,7 @@ public class Main { char[] storepass; // keystore password boolean protectedPath; // protected authentication path String storetype; // keystore type + String realStoreType; String providerName; // provider name List providers = null; // list of provider names List providerClasses = null; // list of provider classes @@ -240,6 +241,7 @@ public class Main { private boolean signerSelfSigned = false; private boolean allAliasesFound = true; private boolean hasMultipleManifests = false; + private boolean weakKeyStore = false; private Throwable chainNotValidatedReason = null; private Throwable tsaChainNotValidatedReason = null; @@ -1482,6 +1484,12 @@ public class Main { warnings.add(rb.getString("external.file.attributes.detected")); } + if (weakKeyStore) { + warnings.add(String.format(rb.getString( + "jks.storetype.warning"), + realStoreType, keystore)); + } + if ((strict) && (!errors.isEmpty())) { result = isSigning ? rb.getString("jar.signed.with.signer.errors.") @@ -2422,6 +2430,23 @@ public class Main { is.close(); } } + + File storeFile = new File(keyStoreName); + if (storeFile.exists()) { + // Probe for real type. A JKS can be loaded as PKCS12 because + // DualFormat support, vice versa. + try { + KeyStore keyStore = KeyStore.getInstance(storeFile, storepass); + realStoreType = keyStore.getType(); + if (realStoreType.equalsIgnoreCase("JKS") + || realStoreType.equalsIgnoreCase("JCEKS")) { + weakKeyStore = true; + } + } catch (KeyStoreException e) { + // Probing not supported, therefore cannot be JKS or JCEKS. + // Skip the legacy type warning at all. + } + } } Enumeration aliases = store.aliases(); while (aliases.hasMoreElements()) { diff --git a/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/resources/jarsigner.properties b/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/resources/jarsigner.properties index b490d386e59..a16420daf8f 100644 --- a/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/resources/jarsigner.properties +++ b/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/resources/jarsigner.properties @@ -222,3 +222,5 @@ entry.1.is.signed.in.jarinputstream.but.is.not.signed.in.jarfile=Entry %s is sig jar.contains.internal.inconsistencies.result.in.different.contents.via.jarfile.and.jarinputstream=This JAR file contains internal inconsistencies that may result in different contents when reading via JarFile and JarInputStream: signature.verification.failed.on.entry.1.when.reading.via.jarinputstream=Signature verification failed on entry %s when reading via JarInputStream signature.verification.failed.on.entry.1.when.reading.via.jarfile=Signature verification failed on entry %s when reading via JarFile +jks.storetype.warning=%1$s uses outdated cryptographic algorithms and will be removed in a future release. Migrate to PKCS12 using:\n\ +keytool -importkeystore -srckeystore %2$s -destkeystore %2$s -deststoretype pkcs12 diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDocletWriter.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDocletWriter.java index 6896b86279f..1f2c4d97dd3 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDocletWriter.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDocletWriter.java @@ -908,12 +908,13 @@ public abstract class HtmlDocletWriter { * @param refMemName the name of the member being referenced. This should * be null or empty string if no member is being referenced. * @param label the label for the external link. + * @param title the title for the link * @param style optional style for the link. * @param code true if the label should be code font. * @return the link */ public Content getCrossClassLink(TypeElement classElement, String refMemName, - Content label, HtmlStyle style, boolean code) { + Content label, String title, HtmlStyle style, boolean code) { if (classElement != null) { String className = utils.getSimpleName(classElement); PackageElement packageElement = utils.containingPackage(classElement); @@ -931,9 +932,7 @@ public abstract class HtmlDocletWriter { DocLink link = configuration.extern.getExternalLink(packageElement, pathToRoot, className + ".html", refMemName); return links.createLink(link, - (label == null) || label.isEmpty() ? defaultLabel : label, style, - resources.getText("doclet.Href_Class_Or_Interface_Title", - getLocalizedPackageName(packageElement)), true); + (label == null) || label.isEmpty() ? defaultLabel : label, style, title, true); } } return null; diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlLinkFactory.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlLinkFactory.java index 4dbbd5e172a..cb8b3dbfd40 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlLinkFactory.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlLinkFactory.java @@ -305,7 +305,7 @@ public class HtmlLinkFactory { } else { Content crossLink = m_writer.getCrossClassLink( typeElement, linkInfo.getFragment(), - label, linkInfo.getStyle(), true); + label, linkInfo.getTitle(), linkInfo.getStyle(), true); if (crossLink != null) { link.add(crossLink); addSuperscript(link, flags, null, typeElement, previewTarget, restrictedTarget); @@ -361,7 +361,7 @@ public class HtmlLinkFactory { if (fileName != null) { return m_writer.links.createLink(fileName.fragment(id.name()), label); } else if (typeElement != null) { - return (m_writer.getCrossClassLink(typeElement, id.name(), label, null, false)); + return (m_writer.getCrossClassLink(typeElement, id.name(), label, null, null, false)); } else { return label; } diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/Links.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/Links.java index 0af29135654..285ab260e0e 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/Links.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/Links.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -197,7 +197,7 @@ public class Links { if (style != null) { l.setStyle(style); } - if (title != null && title.length() != 0) { + if (title != null && !title.isEmpty()) { l.put(HtmlAttr.TITLE, title); } if (isExternal) { diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard.properties b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard.properties index df3c2fc3a53..4366295477b 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard.properties +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard.properties @@ -51,7 +51,6 @@ doclet.Href_Annotation_Interface_Title=annotation interface in {0} doclet.Href_Enum_Title=enum in {0} doclet.Href_Enum_Class_Title=enum class in {0} doclet.Href_Type_Param_Title=type parameter in {0} -doclet.Href_Class_Or_Interface_Title=class or interface in {0} doclet.Summary=Summary: doclet.Detail=Detail: doclet.Module_Sub_Nav=Module: diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard_de.properties b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard_de.properties index 2669aa9bdc0..4cbb4b97774 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard_de.properties +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard_de.properties @@ -51,7 +51,6 @@ doclet.Href_Annotation_Interface_Title=Annotationsschnittstelle in {0} doclet.Href_Enum_Title=Enum in {0} doclet.Href_Enum_Class_Title=Enum-Klasse in {0} doclet.Href_Type_Param_Title=Typparameter in {0} -doclet.Href_Class_Or_Interface_Title=Klasse oder Schnittstelle in {0} doclet.Summary=Übersicht: doclet.Detail=Details: doclet.Module_Sub_Nav=Modul: diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard_ja.properties b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard_ja.properties index 1694dc980bc..2151b3f4a2e 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard_ja.properties +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard_ja.properties @@ -51,7 +51,6 @@ doclet.Href_Annotation_Interface_Title={0}内の注釈インタフェース doclet.Href_Enum_Title={0}内の列挙型 doclet.Href_Enum_Class_Title={0}の列挙クラス doclet.Href_Type_Param_Title={0}内の型パラメータ -doclet.Href_Class_Or_Interface_Title={0}内のクラスまたはインタフェース doclet.Summary=概要: doclet.Detail=詳細: doclet.Module_Sub_Nav=モジュール: diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard_zh_CN.properties b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard_zh_CN.properties index 8881171351e..66620d158bb 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard_zh_CN.properties +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard_zh_CN.properties @@ -51,7 +51,6 @@ doclet.Href_Annotation_Interface_Title={0} 中的批注接口 doclet.Href_Enum_Title={0}中的枚举 doclet.Href_Enum_Class_Title={0} 中的枚举类 doclet.Href_Type_Param_Title={0}中的类型参数 -doclet.Href_Class_Or_Interface_Title={0}中的类或接口 doclet.Summary=概要: doclet.Detail=详细资料: doclet.Module_Sub_Nav=模块: diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/LinkTaglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/LinkTaglet.java index 914f70ced47..62b003afd96 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/LinkTaglet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/LinkTaglet.java @@ -50,6 +50,7 @@ import jdk.javadoc.internal.doclets.toolkit.util.CommentHelper; import jdk.javadoc.internal.doclets.toolkit.util.DocLink; import jdk.javadoc.internal.doclets.toolkit.util.VisibleMemberTable; import jdk.javadoc.internal.html.Content; +import jdk.javadoc.internal.html.HtmlId; import jdk.javadoc.internal.html.HtmlTree; import jdk.javadoc.internal.html.Text; @@ -159,6 +160,10 @@ public class LinkTaglet extends BaseTaglet { Optional.of(refSignature)); } refFragment = refFragment.substring(1); + if (ref == null && refSignature.startsWith("##")) { + // Unqualified local anchor link in doc-file + return htmlWriter.links.createLink(HtmlId.of(refFragment), labelContent); + } } if (refClass == null) { ModuleElement refModule = ch.getReferencedModule(ref); diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclint/Checker.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclint/Checker.java index 3669c800a8e..43405c3ebb0 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclint/Checker.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclint/Checker.java @@ -1004,12 +1004,15 @@ public class Checker extends DocTreePathScanner { @Override @DefinedBy(Api.COMPILER_TREE) public Void visitReference(ReferenceTree tree, Void ignore) { - Element e = env.trees.getElement(getCurrentPath()); - if (e == null) { - reportBadReference(tree); - } else if ((inLink || inSee) - && e.getKind() == ElementKind.CLASS && e.asType().getKind() != TypeKind.DECLARED) { - reportBadReference(tree); + // Exclude same-file anchor links from reference checks + if (!tree.getSignature().startsWith("##")) { + Element e = env.trees.getElement(getCurrentPath()); + if (e == null) { + reportBadReference(tree); + } else if ((inLink || inSee) + && e.getKind() == ElementKind.CLASS && e.asType().getKind() != TypeKind.DECLARED) { + reportBadReference(tree); + } } return super.visitReference(tree, ignore); } diff --git a/src/jdk.jdwp.agent/share/native/libjdwp/threadControl.c b/src/jdk.jdwp.agent/share/native/libjdwp/threadControl.c index bfeffe85678..491182a583f 100644 --- a/src/jdk.jdwp.agent/share/native/libjdwp/threadControl.c +++ b/src/jdk.jdwp.agent/share/native/libjdwp/threadControl.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -137,6 +137,9 @@ typedef struct { static DeferredEventModeList deferredEventModes; +static ThreadNode * +insertThread(JNIEnv *env, ThreadList *list, jthread thread); + /* Get the state of the thread direct from JVMTI */ static jvmtiError threadState(jthread thread, jint *pstate) @@ -277,6 +280,35 @@ findThread(ThreadList *list, jthread thread) return node; } +/* Creates a new ThreadNode for a vthread if one is needed. */ +static ThreadNode * +createVThreadNodeIfNeeded(jthread thread) { + ThreadNode *node = findThread(&otherThreads, thread); + if (node != NULL) { + //tty_message("createVThreadNodeIfNeeded: thread is on otherThreads"); + // Don't create a new node if it is on otherThreads list. Also don't return + // the existing node because if it is on otherThreads, it is not running. + return NULL; + } + + // See if we have a vthread that is alive. If we do, create a ThreadNode + // for it. Otherwise just return NULL. + jint vthread_state = 0; + jvmtiError error = threadState(thread, &vthread_state); + if (error != JVMTI_ERROR_NONE) { + EXIT_ERROR(error, "getting vthread state"); + } + if ((vthread_state & JVMTI_THREAD_STATE_ALIVE) == 0) { + return NULL; // Don't create a new ThreadNode if thread is not alive + } + node = insertThread(getEnv(), &runningVThreads, thread); + if (node->suspendCount > 0 && !node->suspendOnStart) { + node->toBeResumed = JNI_TRUE; + } + + return node; +} + /* Search for a running thread, including vthreads. */ static ThreadNode * findRunningThread(jthread thread) @@ -284,6 +316,16 @@ findRunningThread(jthread thread) ThreadNode *node; if (isVThread(thread)) { node = findThread(&runningVThreads, thread); + if (node == NULL && !gdata->includeVThreads) { + // Unlike platform threads, we don't always have a ThreadNode for all vthreads. + // They can be freed if not holding on to any relevant state info. It's also + // possible that the vthread was created before the debugger attached. Also + // in the future we won't be enabling VIRTUAL_THREAD_START events in some + // cases, which means we won't be creating a ThreadNode when the vthread is + // created. If for any of the above reasons the ThreadNode lookup failed, + // we'll create one for the vthread now, but only if really needed. + node = createVThreadNodeIfNeeded(thread); + } } else { node = findThread(&runningThreads, thread); } @@ -370,6 +412,23 @@ insertThread(JNIEnv *env, ThreadList *list, jthread thread) EXIT_ERROR(AGENT_ERROR_OUT_OF_MEMORY,"thread table entry"); return NULL; } + +#ifdef DEBUG_THREADNAME + { + /* Set the thread name */ + jvmtiThreadInfo info; + jvmtiError error; + + memset(&info, 0, sizeof(info)); + error = JVMTI_FUNC_PTR(gdata->jvmti,GetThreadInfo) + (gdata->jvmti, node->thread, &info); + if (info.name != NULL) { + strncpy(node->name, info.name, sizeof(node->name) - 1); + jvmtiDeallocate(info.name); + } + } +#endif + if (!is_vthread) { if (threadControl_isDebugThread(node->thread)) { /* Remember if it is a debug thread */ @@ -418,22 +477,6 @@ insertThread(JNIEnv *env, ThreadList *list, jthread thread) node->eventBag = eventBag; addNode(list, node); -#ifdef DEBUG_THREADNAME - { - /* Set the thread name */ - jvmtiThreadInfo info; - jvmtiError error; - - memset(&info, 0, sizeof(info)); - error = JVMTI_FUNC_PTR(gdata->jvmti,GetThreadInfo) - (gdata->jvmti, node->thread, &info); - if (info.name != NULL) { - strncpy(node->name, info.name, sizeof(node->name) - 1); - jvmtiDeallocate(info.name); - } - } -#endif - /* Set thread local storage for quick thread -> node access. * Threads that are not yet started do not allow setting of TLS. These * threads go on the otherThreads list and have their TLS set @@ -491,8 +534,7 @@ removeResumed(JNIEnv *env, ThreadList *list) static void removeVThreads(JNIEnv *env) { - ThreadList *list = &runningVThreads; - ThreadNode *node = list->first; + ThreadNode *node = runningVThreads.first; while (node != NULL) { ThreadNode *temp = node->next; removeNode(node); @@ -501,6 +543,94 @@ removeVThreads(JNIEnv *env) } } +/* + * Free (garbage collect) a vthread node if it is unused. This can only be called when + * locks are held so we don't need to worry about other threads changing the state + * of the ThreadNode while we are looking at it. We also need to make sure the + * ThreadNode is not in use for operations like single stepping or invoking. + */ +static void +freeUnusedVThreadNode(JNIEnv *env, ThreadNode* node) +{ + if (gdata->includeVThreads) { + return; + } + + /* + * node->suspendCount requires special handling to see if it triggers having + * to keep the node around. It's possible for it to be 0 yet we still need to + * keep the node around. Also, it's possbile for it to be non-zero yet we + * don't need to keep the node around. More details in the comments below. + */ + if (node->suspendCount == 0) { + /* + * Normally a suspendCount of 0 does not result in having to keep the + * node. However, if the suspendAllCount is not 0, then we do. Otherwise + * when the node is recreated later it will end up assuming suspendAllCount + * as its suspendCount rather than 0, which would be incorrect. This + * mismatch in suspend counts happens when there is a suspendAll in place, + * and ThreadReference.resume() is used to decrement the thread's + * suspendCount to 0. + */ + if (suspendAllCount > 0) { + return; + } + } else { + /* + * Although at first it might seem that a non-zero suspendCount would require + * keeping the node, we don't have to if node->suspendCount == suspendAllCount, + * because when the node is recreated it will get suspendAllCount assigned to it. + * So we only worry about keeping the node around if the two counts are not equal. + */ + if (node->suspendCount != suspendAllCount) { + return; + } + + } + + // All of the following conditions must be met to free this node. Note + // suspendCount checks were already made above, so are not included below. If + // we got here, then suspendCount checks passed w.r.t. being able to free the node. + // Also note we don't need to check node->toBeResumed. If it is set, that implies + // node->suspendCount > 0, and that will trigger toBeResumed getting set when + // the ThreadNode is recreated. See createVThreadNodeIfNeeded(). + if (!node->suspendOnStart && + node->current_ei == 0 && + node->cleInfo.ei == 0 && + !node->currentInvoke.pending && + !node->currentInvoke.started && + !node->currentInvoke.available && + !node->currentStep.pending && + node->instructionStepMode != JVMTI_ENABLE && + !node->pendingInterrupt && + !node->popFrameEvent && + !node->popFrameProceed && + !node->popFrameThread && + node->pendingStop == NULL) + { + removeNode(node); + clearThread(env, node); + } +} + +/* + * Free (garbage collect) vthread nodes if unused. See freeUnusedVThreadNode() above. + */ +static void +freeUnusedVThreadNodes(JNIEnv *env) +{ + if (gdata->includeVThreads) { + return; + } + + ThreadNode *node = runningVThreads.first; + while (node != NULL) { + ThreadNode *temp = node->next; + freeUnusedVThreadNode(env, node); + node = temp; + } +} + static void moveNode(ThreadList *source, ThreadList *dest, ThreadNode *node) { @@ -1139,6 +1269,7 @@ commonResumeList(JNIEnv *env) /* * This function must be called after preSuspend and before postSuspend. + * Only called for platform threads. */ static jvmtiError commonSuspendList(JNIEnv *env, jint initCount, jthread *initList) @@ -1160,15 +1291,17 @@ commonSuspendList(JNIEnv *env, jint initCount, jthread *initList) */ for (i = 0; i < initCount; i++) { ThreadNode *node; + jthread thread = initList[i]; + JDI_ASSERT(!isVThread(thread)); /* * If the thread is not between its start and end events, we should * still suspend it. To keep track of things, add the thread * to a separate list of threads so that we'll resume it later. */ - node = findThread(&runningThreads, initList[i]); + node = findThread(&runningThreads, thread); if (node == NULL) { - node = insertThread(env, &otherThreads, initList[i]); + node = insertThread(env, &otherThreads, thread); } if (node->isDebugThread) { @@ -1187,7 +1320,7 @@ commonSuspendList(JNIEnv *env, jint initCount, jthread *initList) if (node->suspendCount == 0) { /* thread is not suspended yet so put it on the request list */ - reqList[reqCnt++] = initList[i]; + reqList[reqCnt++] = thread; } } @@ -1255,10 +1388,16 @@ commonResume(jthread thread) ThreadNode *node; /* - * The thread is normally between its start and end events, but if - * not, check the auxiliary list used by threadControl_suspendThread. + * We need to call findRunningThread(thread) here instead of just calling + * findThread(NULL, thread) because it's possible that there is not currently a + * ThreadNode for the thread, and findRunningThread() will create one in that case. */ - node = findThread(NULL, thread); + node = findRunningThread(thread); + if (node == NULL) { + // The thread is normally between its start and end events, but if not, + // check the auxiliary list used by commonSuspend() and commonSuspendList(). + node = findThread(&otherThreads, thread); + } #if 0 tty_message("commonResume: node(%p) suspendCount(%d) %s", node, node->suspendCount, node->name); #endif @@ -1336,24 +1475,9 @@ threadControl_suspendCount(jthread thread, jint *count) } else { /* * If the node is in neither list, the debugger never suspended - * this thread, so the suspend count is 0, unless it is a vthread. + * this thread, so the suspend count is 0. */ - if (isVThread(thread)) { - jint vthread_state = 0; - jvmtiError error = threadState(thread, &vthread_state); - if (error != JVMTI_ERROR_NONE) { - EXIT_ERROR(error, "getting vthread state"); - } - if (vthread_state == 0) { - // If state == 0, then this is a new vthread that has not been started yet. - *count = 0; - } else { - // This is a started vthread that we are not tracking. Use suspendAllCount. - *count = suspendAllCount; - } - } else { - *count = 0; - } + *count = 0; } debugMonitorExit(threadLock); @@ -1406,9 +1530,6 @@ threadControl_suspendAll(void) { jvmtiError error; JNIEnv *env; -#if 0 - tty_message("threadControl_suspendAll: suspendAllCount(%d)", suspendAllCount); -#endif env = getEnv(); @@ -1416,6 +1537,8 @@ threadControl_suspendAll(void) preSuspend(); + //tty_message("threadControl_suspendAll: suspendAllCount(%d)", suspendAllCount); + /* * Get a list of all threads and suspend them. */ @@ -1425,6 +1548,11 @@ threadControl_suspendAll(void) jint count; if (gdata->vthreadsSupported) { + // Now is a good time to garbage collect vthread nodes. We want to do it before + // any suspendAll because it will prevent the suspended nodes from being freed. + if (!gdata->includeVThreads) { + freeUnusedVThreadNodes(env); + } /* Tell JVMTI to suspend all virtual threads. */ if (suspendAllCount == 0) { error = JVMTI_FUNC_PTR(gdata->jvmti, SuspendAllVirtualThreads) @@ -1528,9 +1656,6 @@ threadControl_resumeAll(void) { jvmtiError error; JNIEnv *env; -#if 0 - tty_message("threadControl_resumeAll: suspendAllCount(%d)", suspendAllCount); -#endif env = getEnv(); @@ -1539,6 +1664,8 @@ threadControl_resumeAll(void) eventHandler_lock(); /* for proper lock order */ debugMonitorEnter(threadLock); + //tty_message("threadControl_resumeAll: suspendAllCount(%d)", suspendAllCount); + if (gdata->vthreadsSupported) { if (suspendAllCount == 1) { jint excludeCnt = 0; @@ -2092,7 +2219,7 @@ threadControl_onEventHandlerEntry(jbyte sessionID, EventInfo *evinfo, jobject cu processDeferredEventModes(env, thread, node); } if (ei == EI_THREAD_END) { - // If the node was previously freed, then it was just recreated and we need + // If the node was previously freed and was just now recreated, we need // to mark it as started. node->isStarted = JNI_TRUE; } @@ -2148,17 +2275,30 @@ threadControl_onEventHandlerExit(EventIndex ei, jthread thread, log_debugee_location("threadControl_onEventHandlerExit()", thread, NULL, 0); - if (ei == EI_THREAD_END) { + if (ei == EI_THREAD_END || ei == EI_THREAD_START) { eventHandler_lock(); /* for proper lock order - see removeThread() call below */ debugMonitorEnter(threadLock); node = findRunningThread(thread); if (node == NULL) { EXIT_ERROR(AGENT_ERROR_NULL_POINTER,"thread list corrupted"); } - removeThread(env, node); // Grabs handlerLock, thus the reason for first grabbing it above. - node = NULL; // We exiting the threadLock. No longer safe to access. - debugMonitorExit(threadLock); - eventHandler_unlock(); + // Remove nodes for exiting threads, but also remove nodes for vthreads + // that are just starting to help prevent their accumulation. There can't + // possibly be any state related information in the node at this point. A new + // one will be created later if necessary, such as when a new event arrives. + if (ei == EI_THREAD_END || (node->is_vthread && !gdata->includeVThreads)) { + removeThread(env, node); // Grabs handlerLock, thus the reason for first grabbing it above. + node = NULL; // Node has been freed. No longer safe to access. + } else { + // EI_THREAD_START for a platform thread, or we are tracking vthreads. + // In either case we are not removing the thread node. + // Just clear these two fields. Others are not set yet. Also no need to + // worry about pending tasks like we do below for other event types. + node->eventBag = eventBag; + node->current_ei = 0; + } + debugMonitorExit(threadLock); + eventHandler_unlock(); } else { debugMonitorEnter(threadLock); node = findRunningThread(thread); @@ -2173,7 +2313,7 @@ threadControl_onEventHandlerExit(EventIndex ei, jthread thread, node->pendingStop = NULL; node->eventBag = eventBag; node->current_ei = 0; - node = NULL; // We exiting the threadLock. No longer safe to access. + node = NULL; // We're exiting the threadLock. No longer safe to access. // doPendingTasks() may do an upcall to java, and we don't want to hold any // locks when doing that. Thus we got all our node updates done first // and can now exit the threadLock. @@ -2189,6 +2329,8 @@ threadControl_onEventHandlerExit(EventIndex ei, jthread thread, // until now. Otherwise there are complaints when JNI IsVirtualThread is called. JNI_FUNC_PTR(env,Throw)(env, currentException); } + // Note: Threads that were just created or are about to die don't have pending + // tasks, which is why this is the only code path where we call doPendingTasks(). doPendingTasks(env, thread, pendingInterrupt, pendingStop); if (pendingStop != NULL) { tossGlobalRef(env, &pendingStop); @@ -2207,6 +2349,7 @@ threadControl_applicationThreadStatus(jthread thread, log_debugee_location("threadControl_applicationThreadStatus()", thread, NULL, 0); + eventHandler_lock(); // Needed to safely access HANDLING_EVENT(node) debugMonitorEnter(threadLock); error = threadState(thread, &state); @@ -2229,6 +2372,7 @@ threadControl_applicationThreadStatus(jthread thread, } debugMonitorExit(threadLock); + eventHandler_unlock(); return error; } @@ -2334,6 +2478,7 @@ threadControl_stop(jthread thread, jobject throwable) log_debugee_location("threadControl_stop()", thread, NULL, 0); + eventHandler_lock(); // Needed to safely access HANDLING_EVENT(node) debugMonitorEnter(threadLock); node = findThread(&runningThreads, thread); @@ -2351,6 +2496,7 @@ threadControl_stop(jthread thread, jobject throwable) } debugMonitorExit(threadLock); + eventHandler_unlock(); return error; } @@ -2579,7 +2725,7 @@ threadControl_dumpAllThreads() tty_message("suspendAllCount: %d", suspendAllCount); tty_message("Dumping runningThreads:"); dumpThreadList(&runningThreads); - tty_message("\nDumping runningVThreads:"); + tty_message("\nDumping runningVThreads(numRunningVThreads=%d):", numRunningVThreads); dumpThreadList(&runningVThreads); tty_message("\nDumping otherThreads:"); dumpThreadList(&otherThreads); @@ -2650,9 +2796,11 @@ dumpThread(ThreadNode *node) { // kept small so it doesn't generate too much output. tty_message("\tsuspendCount: %d", node->suspendCount); #if 0 + tty_message("\ttoBeResumed: %d", node->toBeResumed); + tty_message("\tisStarted: %d", node->isStarted); + tty_message("\tsuspendOnStart: %d", node->suspendOnStart); tty_message("\tsuspendAllCount: %d", suspendAllCount); tty_message("\tthreadState: 0x%x", getThreadState(node)); - tty_message("\ttoBeResumed: %d", node->toBeResumed); tty_message("\tis_vthread: %d", node->is_vthread); tty_message("\tpendingInterrupt: %d", node->pendingInterrupt); tty_message("\tcurrentInvoke.pending: %d", node->currentInvoke.pending); diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/ReleaseInfoPlugin.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/ReleaseInfoPlugin.java index b4301ea8e45..25789b4d172 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/ReleaseInfoPlugin.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/ReleaseInfoPlugin.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,11 +25,13 @@ package jdk.tools.jlink.internal.plugins; import java.io.ByteArrayOutputStream; -import java.io.FileInputStream; import java.io.IOException; import java.io.PrintWriter; +import java.io.Reader; import java.io.UncheckedIOException; import java.lang.module.ModuleDescriptor; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.EnumSet; import java.util.HashMap; import java.util.Map; @@ -107,8 +109,8 @@ public final class ReleaseInfoPlugin extends AbstractPlugin { default: { // --release-info Properties props = new Properties(); - try (FileInputStream fis = new FileInputStream(operation)) { - props.load(fis); + try (Reader reader = Files.newBufferedReader(Path.of(operation))) { + props.load(reader); // Use reader API so as to read in as UTF-8 } catch (IOException exp) { throw new UncheckedIOException(exp); } diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins.properties b/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins.properties index a4b780a15c3..e9be0b4e587 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins.properties +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -39,6 +39,7 @@ release-info.argument=|add:=:=:...|del: option is to load release properties from the supplied file.\n\ +\ The specified file is expected to be encoded in UTF-8.\n\ add: is to add properties to the 'release' file.\n\ Any number of = pairs can be passed.\n\ del: is to delete the list of keys in release file. @@ -46,7 +47,8 @@ del: is to delete the list of keys in release file. release-info.usage=\ \ --release-info |add:=:=:...|del:\n\ \ option is to load release properties from\n\ -\ the supplied file.\n\ +\ the supplied file. The specified file is expected\n\ +\ to be encoded in UTF-8.\n\ \ add: is to add properties to the 'release' file.\n\ \ Any number of = pairs can be passed.\n\ \ del: is to delete the list of keys in release file. diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxAppBundler.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxAppBundler.java deleted file mode 100644 index fe8d6bcf34f..00000000000 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxAppBundler.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.jpackage.internal; - -import java.util.Optional; - -public class LinuxAppBundler extends AppImageBundler { - public LinuxAppBundler() { - setAppImageSupplier((params, output) -> { - // Order is important! - var app = LinuxFromParams.APPLICATION.fetchFrom(params); - var env = BuildEnvFromParams.BUILD_ENV.fetchFrom(params); - LinuxPackagingPipeline.build(Optional.empty()) - .excludeDirFromCopying(output.getParent()) - .create().execute(BuildEnv.withAppImageDir(env, output), app); - }); - } -} diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxBundlingEnvironment.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxBundlingEnvironment.java new file mode 100644 index 00000000000..cfd8ab391bb --- /dev/null +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxBundlingEnvironment.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.jpackage.internal; + +import static java.util.stream.Collectors.toMap; +import static jdk.jpackage.internal.LinuxFromOptions.createLinuxApplication; +import static jdk.jpackage.internal.LinuxPackagingPipeline.APPLICATION_LAYOUT; +import static jdk.jpackage.internal.cli.StandardBundlingOperation.CREATE_LINUX_APP_IMAGE; +import static jdk.jpackage.internal.cli.StandardBundlingOperation.CREATE_LINUX_DEB; +import static jdk.jpackage.internal.cli.StandardBundlingOperation.CREATE_LINUX_RPM; + +import java.util.Map; +import java.util.Optional; +import java.util.stream.Stream; +import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.cli.StandardBundlingOperation; +import jdk.jpackage.internal.model.BundlingOperationDescriptor; +import jdk.jpackage.internal.model.LinuxPackage; +import jdk.jpackage.internal.model.PackageType; +import jdk.jpackage.internal.util.Result; + +public class LinuxBundlingEnvironment extends DefaultBundlingEnvironment { + + public LinuxBundlingEnvironment() { + super(build() + .defaultOperation(() -> { + return LazyLoad.SYS_ENV.value().map(LinuxSystemEnvironment::nativePackageType).map(DESCRIPTORS::get); + }) + .bundler(CREATE_LINUX_APP_IMAGE, LinuxBundlingEnvironment::createAppImage) + .bundler(CREATE_LINUX_DEB, LazyLoad::debSysEnv, LinuxBundlingEnvironment::createDebPackage) + .bundler(CREATE_LINUX_RPM, LazyLoad::rpmSysEnv, LinuxBundlingEnvironment::createRpmPackage)); + } + + private static void createDebPackage(Options options, LinuxDebSystemEnvironment sysEnv) { + + createNativePackage(options, + LinuxFromOptions::createLinuxDebPackage, + buildEnv()::create, + LinuxBundlingEnvironment::buildPipeline, + (env, pkg, outputDir) -> { + return new LinuxDebPackager(env, pkg, outputDir, sysEnv); + }); + } + + private static void createRpmPackage(Options options, LinuxRpmSystemEnvironment sysEnv) { + + createNativePackage(options, + LinuxFromOptions::createLinuxRpmPackage, + buildEnv()::create, + LinuxBundlingEnvironment::buildPipeline, + (env, pkg, outputDir) -> { + return new LinuxRpmPackager(env, pkg, outputDir, sysEnv); + }); + } + + private static void createAppImage(Options options) { + + final var app = createLinuxApplication(options); + + createApplicationImage(options, app, LinuxPackagingPipeline.build(Optional.empty())); + } + + private static PackagingPipeline.Builder buildPipeline(LinuxPackage pkg) { + return LinuxPackagingPipeline.build(Optional.of(pkg)); + } + + private static BuildEnvFromOptions buildEnv() { + return new BuildEnvFromOptions().predefinedAppImageLayout(APPLICATION_LAYOUT); + } + + private static final class LazyLoad { + + static Result debSysEnv() { + return DEB_SYS_ENV; + } + + static Result rpmSysEnv() { + return RPM_SYS_ENV; + } + + private static final Result SYS_ENV = LinuxSystemEnvironment.create(); + + private static final Result DEB_SYS_ENV = LinuxDebSystemEnvironment.create(SYS_ENV); + + private static final Result RPM_SYS_ENV = LinuxRpmSystemEnvironment.create(SYS_ENV); + } + + private static final Map DESCRIPTORS = Stream.of( + CREATE_LINUX_DEB, + CREATE_LINUX_RPM + ).collect(toMap(StandardBundlingOperation::packageType, StandardBundlingOperation::descriptor)); +} diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebBundler.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebBundler.java deleted file mode 100644 index 76a08519b48..00000000000 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebBundler.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.jpackage.internal; - -import java.nio.file.Path; -import java.util.Map; -import java.util.Optional; -import jdk.jpackage.internal.model.LinuxDebPackage; -import jdk.jpackage.internal.model.PackagerException; -import jdk.jpackage.internal.model.StandardPackageType; -import jdk.jpackage.internal.util.Result; - -public class LinuxDebBundler extends LinuxPackageBundler { - - public LinuxDebBundler() { - super(LinuxFromParams.DEB_PACKAGE); - } - - @Override - public String getName() { - return I18N.getString("deb.bundler.name"); - } - - @Override - public String getID() { - return "deb"; - } - - @Override - public Path execute(Map params, Path outputParentDir) throws PackagerException { - - var pkg = LinuxFromParams.DEB_PACKAGE.fetchFrom(params); - - return Packager.build().outputDir(outputParentDir) - .pkg(pkg) - .env(BuildEnvFromParams.BUILD_ENV.fetchFrom(params)) - .pipelineBuilderMutatorFactory((env, _, outputDir) -> { - return new LinuxDebPackager(env, pkg, outputDir, sysEnv.orElseThrow()); - }).execute(LinuxPackagingPipeline.build(Optional.of(pkg))); - } - - @Override - protected Result sysEnv() { - return sysEnv; - } - - @Override - public boolean isDefault() { - return sysEnv.value() - .map(LinuxSystemEnvironment::nativePackageType) - .map(StandardPackageType.LINUX_DEB::equals) - .orElse(false); - } - - private final Result sysEnv = LinuxDebSystemEnvironment.create(SYS_ENV); -} diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebPackageBuilder.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebPackageBuilder.java index 50e371d5c76..d7b6559abe1 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebPackageBuilder.java +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebPackageBuilder.java @@ -26,7 +26,6 @@ package jdk.jpackage.internal; import java.util.Objects; import java.util.Optional; -import jdk.jpackage.internal.model.ConfigException; import jdk.jpackage.internal.model.LinuxDebPackage; import jdk.jpackage.internal.model.LinuxDebPackageMixin; @@ -36,7 +35,7 @@ final class LinuxDebPackageBuilder { this.pkgBuilder = Objects.requireNonNull(pkgBuilder); } - LinuxDebPackage create() throws ConfigException { + LinuxDebPackage create() { if (pkgBuilder.category().isEmpty()) { pkgBuilder.category(DEFAULTS.category()); } diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxFromOptions.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxFromOptions.java new file mode 100644 index 00000000000..799c92ce2e1 --- /dev/null +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxFromOptions.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.jpackage.internal; + +import static jdk.jpackage.internal.FromOptions.buildApplicationBuilder; +import static jdk.jpackage.internal.FromOptions.createPackageBuilder; +import static jdk.jpackage.internal.LinuxPackagingPipeline.APPLICATION_LAYOUT; +import static jdk.jpackage.internal.cli.StandardOption.LINUX_APP_CATEGORY; +import static jdk.jpackage.internal.cli.StandardOption.LINUX_DEB_MAINTAINER_EMAIL; +import static jdk.jpackage.internal.cli.StandardOption.LINUX_MENU_GROUP; +import static jdk.jpackage.internal.cli.StandardOption.LINUX_PACKAGE_DEPENDENCIES; +import static jdk.jpackage.internal.cli.StandardOption.LINUX_PACKAGE_NAME; +import static jdk.jpackage.internal.cli.StandardOption.LINUX_RELEASE; +import static jdk.jpackage.internal.cli.StandardOption.LINUX_RPM_LICENSE_TYPE; +import static jdk.jpackage.internal.cli.StandardOption.LINUX_SHORTCUT_HINT; +import static jdk.jpackage.internal.model.StandardPackageType.LINUX_DEB; +import static jdk.jpackage.internal.model.StandardPackageType.LINUX_RPM; + +import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.model.Launcher; +import jdk.jpackage.internal.model.LinuxApplication; +import jdk.jpackage.internal.model.LinuxDebPackage; +import jdk.jpackage.internal.model.LinuxLauncher; +import jdk.jpackage.internal.model.LinuxLauncherMixin; +import jdk.jpackage.internal.model.LinuxRpmPackage; +import jdk.jpackage.internal.model.StandardPackageType; + +final class LinuxFromOptions { + + static LinuxApplication createLinuxApplication(Options options) { + + final var launcherFromOptions = new LauncherFromOptions().faWithDefaultDescription(); + + final var appBuilder = buildApplicationBuilder().create(options, launcherOptions -> { + + final var launcher = launcherFromOptions.create(launcherOptions); + + final var shortcut = LINUX_SHORTCUT_HINT.findIn(launcherOptions); + + return LinuxLauncher.create(launcher, new LinuxLauncherMixin.Stub(shortcut)); + + }, (LinuxLauncher linuxLauncher, Launcher launcher) -> { + return LinuxLauncher.create(launcher, linuxLauncher); + }, APPLICATION_LAYOUT); + + appBuilder.launchers().map(LinuxPackagingPipeline::normalizeShortcuts).ifPresent(appBuilder::launchers); + + return LinuxApplication.create(appBuilder.create()); + } + + static LinuxRpmPackage createLinuxRpmPackage(Options options) { + + final var superPkgBuilder = createLinuxPackageBuilder(options, LINUX_RPM); + + final var pkgBuilder = new LinuxRpmPackageBuilder(superPkgBuilder); + + LINUX_RPM_LICENSE_TYPE.ifPresentIn(options, pkgBuilder::licenseType); + + return pkgBuilder.create(); + } + + static LinuxDebPackage createLinuxDebPackage(Options options) { + + final var superPkgBuilder = createLinuxPackageBuilder(options, LINUX_DEB); + + final var pkgBuilder = new LinuxDebPackageBuilder(superPkgBuilder); + + LINUX_DEB_MAINTAINER_EMAIL.ifPresentIn(options, pkgBuilder::maintainerEmail); + + final var pkg = pkgBuilder.create(); + + // Show warning if license file is missing + if (pkg.licenseFile().isEmpty()) { + Log.verbose(I18N.getString("message.debs-like-licenses")); + } + + return pkg; + } + + private static LinuxPackageBuilder createLinuxPackageBuilder(Options options, StandardPackageType type) { + + final var app = createLinuxApplication(options); + + final var superPkgBuilder = createPackageBuilder(options, app, type); + + final var pkgBuilder = new LinuxPackageBuilder(superPkgBuilder); + + LINUX_PACKAGE_DEPENDENCIES.ifPresentIn(options, pkgBuilder::additionalDependencies); + LINUX_APP_CATEGORY.ifPresentIn(options, pkgBuilder::category); + LINUX_MENU_GROUP.ifPresentIn(options, pkgBuilder::menuGroupName); + LINUX_RELEASE.ifPresentIn(options, pkgBuilder::release); + LINUX_PACKAGE_NAME.ifPresentIn(options, pkgBuilder::literalName); + + return pkgBuilder; + } + +} diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxFromParams.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxFromParams.java deleted file mode 100644 index e9d1416b5c3..00000000000 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxFromParams.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package jdk.jpackage.internal; - -import static jdk.jpackage.internal.BundlerParamInfo.createStringBundlerParam; -import static jdk.jpackage.internal.FromParams.createApplicationBuilder; -import static jdk.jpackage.internal.FromParams.createApplicationBundlerParam; -import static jdk.jpackage.internal.FromParams.createPackageBuilder; -import static jdk.jpackage.internal.FromParams.createPackageBundlerParam; -import static jdk.jpackage.internal.FromParams.findLauncherShortcut; -import static jdk.jpackage.internal.LinuxPackagingPipeline.APPLICATION_LAYOUT; -import static jdk.jpackage.internal.model.StandardPackageType.LINUX_DEB; -import static jdk.jpackage.internal.model.StandardPackageType.LINUX_RPM; -import static jdk.jpackage.internal.util.function.ThrowingFunction.toFunction; - -import java.io.IOException; -import java.util.Map; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.model.LinuxApplication; -import jdk.jpackage.internal.model.LinuxDebPackage; -import jdk.jpackage.internal.model.LinuxLauncher; -import jdk.jpackage.internal.model.LinuxLauncherMixin; -import jdk.jpackage.internal.model.LinuxRpmPackage; -import jdk.jpackage.internal.model.Launcher; -import jdk.jpackage.internal.model.StandardPackageType; - -final class LinuxFromParams { - - private static LinuxApplication createLinuxApplication( - Map params) throws ConfigException, IOException { - final var launcherFromParams = new LauncherFromParams(); - - final var app = createApplicationBuilder(params, toFunction(launcherParams -> { - final var launcher = launcherFromParams.create(launcherParams); - final var shortcut = findLauncherShortcut(LINUX_SHORTCUT_HINT, params, launcherParams); - return LinuxLauncher.create(launcher, new LinuxLauncherMixin.Stub(shortcut)); - }), (LinuxLauncher linuxLauncher, Launcher launcher) -> { - return LinuxLauncher.create(launcher, linuxLauncher); - }, APPLICATION_LAYOUT).create(); - return LinuxApplication.create(app); - } - - private static LinuxPackageBuilder createLinuxPackageBuilder( - Map params, StandardPackageType type) throws ConfigException, IOException { - - final var app = APPLICATION.fetchFrom(params); - - final var superPkgBuilder = createPackageBuilder(params, app, type); - - final var pkgBuilder = new LinuxPackageBuilder(superPkgBuilder); - - LINUX_PACKAGE_DEPENDENCIES.copyInto(params, pkgBuilder::additionalDependencies); - LINUX_CATEGORY.copyInto(params, pkgBuilder::category); - LINUX_MENU_GROUP.copyInto(params, pkgBuilder::menuGroupName); - RELEASE.copyInto(params, pkgBuilder::release); - LINUX_PACKAGE_NAME.copyInto(params, pkgBuilder::literalName); - - return pkgBuilder; - } - - private static LinuxRpmPackage createLinuxRpmPackage( - Map params) throws ConfigException, IOException { - - final var superPkgBuilder = createLinuxPackageBuilder(params, LINUX_RPM); - - final var pkgBuilder = new LinuxRpmPackageBuilder(superPkgBuilder); - - LICENSE_TYPE.copyInto(params, pkgBuilder::licenseType); - - return pkgBuilder.create(); - } - - private static LinuxDebPackage createLinuxDebPackage( - Map params) throws ConfigException, IOException { - - final var superPkgBuilder = createLinuxPackageBuilder(params, LINUX_DEB); - - final var pkgBuilder = new LinuxDebPackageBuilder(superPkgBuilder); - - MAINTAINER_EMAIL.copyInto(params, pkgBuilder::maintainerEmail); - - final var pkg = pkgBuilder.create(); - - // Show warning if license file is missing - if (pkg.licenseFile().isEmpty()) { - Log.verbose(I18N.getString("message.debs-like-licenses")); - } - - return pkg; - } - - static final BundlerParamInfo APPLICATION = createApplicationBundlerParam( - LinuxFromParams::createLinuxApplication); - - static final BundlerParamInfo RPM_PACKAGE = createPackageBundlerParam( - LinuxFromParams::createLinuxRpmPackage); - - static final BundlerParamInfo DEB_PACKAGE = createPackageBundlerParam( - LinuxFromParams::createLinuxDebPackage); - - private static final BundlerParamInfo LINUX_SHORTCUT_HINT = createStringBundlerParam( - Arguments.CLIOptions.LINUX_SHORTCUT_HINT.getId()); - - private static final BundlerParamInfo LINUX_CATEGORY = createStringBundlerParam( - Arguments.CLIOptions.LINUX_CATEGORY.getId()); - - private static final BundlerParamInfo LINUX_PACKAGE_DEPENDENCIES = createStringBundlerParam( - Arguments.CLIOptions.LINUX_PACKAGE_DEPENDENCIES.getId()); - - private static final BundlerParamInfo LINUX_MENU_GROUP = createStringBundlerParam( - Arguments.CLIOptions.LINUX_MENU_GROUP.getId()); - - private static final BundlerParamInfo RELEASE = createStringBundlerParam( - Arguments.CLIOptions.RELEASE.getId()); - - private static final BundlerParamInfo LINUX_PACKAGE_NAME = createStringBundlerParam( - Arguments.CLIOptions.LINUX_BUNDLE_NAME.getId()); - - private static final BundlerParamInfo LICENSE_TYPE = createStringBundlerParam( - Arguments.CLIOptions.LINUX_RPM_LICENSE_TYPE.getId()); - - private static final BundlerParamInfo MAINTAINER_EMAIL = createStringBundlerParam( - Arguments.CLIOptions.LINUX_DEB_MAINTAINER.getId()); -} diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBuilder.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBuilder.java index 4cfb8a26c8f..bc7c301ace2 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBuilder.java +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBuilder.java @@ -32,12 +32,11 @@ import java.util.Optional; import java.util.regex.Pattern; import jdk.jpackage.internal.model.AppImageLayout; import jdk.jpackage.internal.model.ApplicationLayout; -import jdk.jpackage.internal.model.ConfigException; import jdk.jpackage.internal.model.LinuxApplication; import jdk.jpackage.internal.model.LinuxPackage; -import jdk.jpackage.internal.model.RuntimeLayout; import jdk.jpackage.internal.model.LinuxPackageMixin; import jdk.jpackage.internal.model.Package; +import jdk.jpackage.internal.model.RuntimeLayout; import jdk.jpackage.internal.model.StandardPackageType; final class LinuxPackageBuilder { @@ -46,20 +45,17 @@ final class LinuxPackageBuilder { this.pkgBuilder = Objects.requireNonNull(pkgBuilder); } - LinuxPackage create() throws ConfigException { - if (literalName != null) { - pkgBuilder.name(literalName); - } else { + LinuxPackage create() { + pkgBuilder.name(Optional.ofNullable(literalName).orElseGet(() -> { // Lower case and turn spaces/underscores into dashes - pkgBuilder.name(pkgBuilder.create().packageName().toLowerCase().replaceAll("[ _]", "-")); - } + return pkgBuilder.create().packageName().toLowerCase().replaceAll("[ _]", "-"); + })); final var tmpPkg = pkgBuilder.create(); - final var stdPkgType = tmpPkg.asStandardPackageType(); - if (stdPkgType.isPresent()) { - validatePackageName(tmpPkg.packageName(), stdPkgType.orElseThrow()); - } + tmpPkg.asStandardPackageType().ifPresent(stdPkgType -> { + validatePackageName(tmpPkg.packageName(), stdPkgType); + }); final AppImageLayout relativeInstalledLayout; if (create(tmpPkg).isInstallDirInUsrTree()) { @@ -81,7 +77,7 @@ final class LinuxPackageBuilder { .create()); } - private LinuxPackage create(Package pkg) throws ConfigException { + private LinuxPackage create(Package pkg) { return LinuxPackage.create(pkg, new LinuxPackageMixin.Stub( Optional.ofNullable(menuGroupName).orElseGet(DEFAULTS::menuGroupName), category(), @@ -137,8 +133,7 @@ final class LinuxPackageBuilder { lib.resolve("lib/libapplauncher.so")); } - private static void validatePackageName(String packageName, - StandardPackageType pkgType) throws ConfigException { + private static void validatePackageName(String packageName, StandardPackageType pkgType) { switch (pkgType) { case LINUX_DEB -> { // diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBundler.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBundler.java deleted file mode 100644 index 1f674c0be11..00000000000 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBundler.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package jdk.jpackage.internal; - -import java.util.Map; -import java.util.Objects; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.model.LinuxPackage; -import jdk.jpackage.internal.util.Result; - -abstract class LinuxPackageBundler extends AbstractBundler { - - LinuxPackageBundler(BundlerParamInfo pkgParam) { - this.pkgParam = Objects.requireNonNull(pkgParam); - } - - @Override - public final boolean validate(Map params) - throws ConfigException { - - // Order is important! - pkgParam.fetchFrom(params); - BuildEnvFromParams.BUILD_ENV.fetchFrom(params); - - LinuxSystemEnvironment sysEnv; - try { - sysEnv = sysEnv().orElseThrow(); - } catch (RuntimeException ex) { - throw ConfigException.rethrowConfigException(ex); - } - - if (!isDefault()) { - Log.verbose(I18N.format( - "message.not-default-bundler-no-dependencies-lookup", - getName())); - } else if (!sysEnv.soLookupAvailable()) { - final String advice; - if ("deb".equals(getID())) { - advice = "message.deb-ldd-not-available.advice"; - } else { - advice = "message.rpm-ldd-not-available.advice"; - } - // Let user know package dependencies will not be generated. - Log.error(String.format("%s\n%s", I18N.getString( - "message.ldd-not-available"), I18N.getString(advice))); - } - - return true; - } - - @Override - public final String getBundleType() { - return "INSTALLER"; - } - - @Override - public boolean supported(boolean runtimeInstaller) { - return sysEnv().hasValue(); - } - - protected abstract Result sysEnv(); - - private final BundlerParamInfo pkgParam; - - static final Result SYS_ENV = LinuxSystemEnvironment.create(); -} diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackager.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackager.java index 806592904d1..af7f5288cc5 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackager.java +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackager.java @@ -40,7 +40,6 @@ import jdk.jpackage.internal.PackagingPipeline.PrimaryTaskID; import jdk.jpackage.internal.PackagingPipeline.TaskID; import jdk.jpackage.internal.model.ConfigException; import jdk.jpackage.internal.model.LinuxPackage; -import jdk.jpackage.internal.model.PackagerException; abstract class LinuxPackager implements Consumer { @@ -95,7 +94,7 @@ abstract class LinuxPackager implements Consumer { + // Return "true" if shortcut is not configured for the launcher. + return launcher.shortcut().isEmpty(); + }, (LinuxLauncher launcher) -> { + return launcher.shortcut().flatMap(LauncherShortcut::startupDirectory); + }, (launcher, shortcut) -> { + return LinuxLauncher.create(launcher, new LinuxLauncherMixin.Stub(Optional.of(new LauncherShortcut(shortcut)))); + }); + } + private static void writeLauncherLib( AppImageBuildEnv env) throws IOException { @@ -90,6 +106,15 @@ final class LinuxPackagingPipeline { }); } + private static final ApplicationLayout LINUX_APPLICATION_LAYOUT = ApplicationLayout.build() + .launchersDirectory("bin") + .appDirectory("lib/app") + .runtimeDirectory("lib/runtime") + .desktopIntegrationDirectory("lib") + .appModsDirectory("lib/app/mods") + .contentDirectory("lib") + .create(); + static final LinuxApplicationLayout APPLICATION_LAYOUT = LinuxApplicationLayout.create( - ApplicationLayoutUtils.PLATFORM_APPLICATION_LAYOUT, Path.of("lib/libapplauncher.so")); + LINUX_APPLICATION_LAYOUT, Path.of("lib/libapplauncher.so")); } diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmBundler.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmBundler.java deleted file mode 100644 index c134aa91d6a..00000000000 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmBundler.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.jpackage.internal; - -import java.nio.file.Path; -import java.util.Map; -import java.util.Optional; -import jdk.jpackage.internal.model.LinuxRpmPackage; -import jdk.jpackage.internal.model.PackagerException; -import jdk.jpackage.internal.model.StandardPackageType; -import jdk.jpackage.internal.util.Result; - - -public class LinuxRpmBundler extends LinuxPackageBundler { - - public LinuxRpmBundler() { - super(LinuxFromParams.RPM_PACKAGE); - } - - @Override - public String getName() { - return I18N.getString("rpm.bundler.name"); - } - - @Override - public String getID() { - return "rpm"; - } - - @Override - public Path execute(Map params, Path outputParentDir) throws PackagerException { - - var pkg = LinuxFromParams.RPM_PACKAGE.fetchFrom(params); - - return Packager.build().outputDir(outputParentDir) - .pkg(pkg) - .env(BuildEnvFromParams.BUILD_ENV.fetchFrom(params)) - .pipelineBuilderMutatorFactory((env, _, outputDir) -> { - return new LinuxRpmPackager(env, pkg, outputDir, sysEnv.orElseThrow()); - }).execute(LinuxPackagingPipeline.build(Optional.of(pkg))); - } - - @Override - protected Result sysEnv() { - return sysEnv; - } - - @Override - public boolean isDefault() { - return sysEnv.value() - .map(LinuxSystemEnvironment::nativePackageType) - .map(StandardPackageType.LINUX_RPM::equals) - .orElse(false); - } - - private final Result sysEnv = LinuxRpmSystemEnvironment.create(SYS_ENV); -} diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmPackageBuilder.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmPackageBuilder.java index bc361eac759..f97a7741e42 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmPackageBuilder.java +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmPackageBuilder.java @@ -26,7 +26,6 @@ package jdk.jpackage.internal; import java.util.Objects; import java.util.Optional; -import jdk.jpackage.internal.model.ConfigException; import jdk.jpackage.internal.model.LinuxRpmPackage; import jdk.jpackage.internal.model.LinuxRpmPackageMixin; @@ -36,7 +35,7 @@ final class LinuxRpmPackageBuilder { this.pkgBuilder = Objects.requireNonNull(pkgBuilder); } - LinuxRpmPackage create() throws ConfigException { + LinuxRpmPackage create() { if (pkgBuilder.release().isEmpty()) { pkgBuilder.release("1"); } diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/model/LinuxLauncher.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/model/LinuxLauncher.java index c84b5e3bbf5..3c654f604c2 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/model/LinuxLauncher.java +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/model/LinuxLauncher.java @@ -24,6 +24,8 @@ */ package jdk.jpackage.internal.model; +import static jdk.jpackage.internal.cli.StandardAppImageFileOption.LINUX_LAUNCHER_SHORTCUT; + import java.util.HashMap; import java.util.Map; import jdk.jpackage.internal.util.CompositeProxy; @@ -39,7 +41,7 @@ public interface LinuxLauncher extends Launcher, LinuxLauncherMixin { default Map extraAppImageFileData() { Map map = new HashMap<>(); shortcut().ifPresent(shortcut -> { - shortcut.store(SHORTCUT_ID, map::put); + shortcut.store(LINUX_LAUNCHER_SHORTCUT.getName(), map::put); }); return map; } @@ -55,6 +57,4 @@ public interface LinuxLauncher extends Launcher, LinuxLauncherMixin { public static LinuxLauncher create(Launcher launcher, LinuxLauncherMixin mixin) { return CompositeProxy.create(LinuxLauncher.class, launcher, mixin); } - - public static final String SHORTCUT_ID = "linux-shortcut"; } diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources.properties b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources.properties index a732d02c7d1..3aabe1f4ba5 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources.properties +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources.properties @@ -23,10 +23,6 @@ # questions. # # -app.bundler.name=Linux Application Image -deb.bundler.name=DEB Bundle -rpm.bundler.name=RPM Bundle - param.license-type.default=Unknown resource.deb-control-file=DEB control file @@ -58,7 +54,6 @@ message.output-to-location=Package (.deb) saved to: {0}. message.debs-like-licenses=Debian packages should specify a license. The absence of a license will cause some linux distributions to complain about the quality of the application. message.outputting-bundle-location=Generating RPM for installer to: {0}. message.output-bundle-location=Package (.rpm) saved to: {0}. -message.creating-association-with-null-extension=Creating association with null extension. message.ldd-not-available=ldd command not found. Package dependencies will not be generated. message.deb-ldd-not-available.advice=Install "libc-bin" DEB package to get ldd. diff --git a/src/jdk.jpackage/linux/classes/module-info.java.extra b/src/jdk.jpackage/linux/classes/module-info.java.extra index d32314b0429..7bef2286214 100644 --- a/src/jdk.jpackage/linux/classes/module-info.java.extra +++ b/src/jdk.jpackage/linux/classes/module-info.java.extra @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,8 +23,5 @@ * questions. */ -provides jdk.jpackage.internal.Bundler with - jdk.jpackage.internal.LinuxAppBundler, - jdk.jpackage.internal.LinuxDebBundler, - jdk.jpackage.internal.LinuxRpmBundler; - +provides jdk.jpackage.internal.cli.CliBundlingEnvironment with + jdk.jpackage.internal.LinuxBundlingEnvironment; diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/AppImageSigningConfigBuilder.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/AppImageSigningConfigBuilder.java index bb043830f3c..6fc7fe004c2 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/AppImageSigningConfigBuilder.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/AppImageSigningConfigBuilder.java @@ -28,7 +28,6 @@ import java.nio.file.Path; import java.util.Objects; import java.util.Optional; import jdk.jpackage.internal.model.AppImageSigningConfig; -import jdk.jpackage.internal.model.ConfigException; import jdk.jpackage.internal.model.LauncherStartupInfo; final class AppImageSigningConfigBuilder { @@ -62,21 +61,20 @@ final class AppImageSigningConfigBuilder { return this; } - Optional create() throws ConfigException { - final var identityCfg = signingIdentityBuilder.create(); - if (identityCfg.isEmpty()) { - return Optional.empty(); - } else { + Optional create() { + return signingIdentityBuilder.create().map(cfg -> { final var validatedEntitlements = validatedEntitlements(); - return identityCfg.map(cfg -> { - return new AppImageSigningConfig.Stub(cfg.identity(), signingIdentifierPrefix, - validatedEntitlements, cfg.keychain().map(Keychain::name), - Optional.ofNullable(entitlementsResourceName).orElse("entitlements.plist")); - }); - } + return new AppImageSigningConfig.Stub( + Objects.requireNonNull(cfg.identity()), + Objects.requireNonNull(signingIdentifierPrefix), + validatedEntitlements, + cfg.keychain().map(Keychain::name), + Optional.ofNullable(entitlementsResourceName).orElse("entitlements.plist") + ); + }); } - private Optional validatedEntitlements() throws ConfigException { + private Optional validatedEntitlements() { return Optional.ofNullable(entitlements); } diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppBundler.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppBundler.java deleted file mode 100644 index cce35ece117..00000000000 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppBundler.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.jpackage.internal; - -import static jdk.jpackage.internal.StandardBundlerParam.OUTPUT_DIR; -import static jdk.jpackage.internal.StandardBundlerParam.SIGN_BUNDLE; - -import java.util.Map; -import java.util.Optional; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.util.function.ExceptionBox; - -public class MacAppBundler extends AppImageBundler { - public MacAppBundler() { - setAppImageSupplier((params, output) -> { - - // Order is important! - final var app = MacFromParams.APPLICATION.fetchFrom(params); - final BuildEnv env; - - if (StandardBundlerParam.hasPredefinedAppImage(params)) { - env = MacBuildEnvFromParams.BUILD_ENV.fetchFrom(params); - final var pkg = MacPackagingPipeline.createSignAppImagePackage(app, env); - MacPackagingPipeline.build(Optional.of(pkg)).create().execute(env, pkg, output); - } else { - env = BuildEnv.withAppImageDir(MacBuildEnvFromParams.BUILD_ENV.fetchFrom(params), output); - MacPackagingPipeline.build(Optional.empty()) - .excludeDirFromCopying(output.getParent()) - .excludeDirFromCopying(OUTPUT_DIR.fetchFrom(params)).create().execute(env, app); - } - - }); - setParamsValidator(MacAppBundler::doValidate); - } - - private static void doValidate(Map params) - throws ConfigException { - - try { - MacFromParams.APPLICATION.fetchFrom(params); - } catch (ExceptionBox ex) { - if (ex.getCause() instanceof ConfigException cfgEx) { - throw cfgEx; - } else { - throw ex; - } - } - - if (StandardBundlerParam.hasPredefinedAppImage(params)) { - if (!Optional.ofNullable( - SIGN_BUNDLE.fetchFrom(params)).orElse(Boolean.FALSE)) { - throw new ConfigException( - I18N.getString("error.app-image.mac-sign.required"), - null); - } - } - } -} diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacApplicationBuilder.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacApplicationBuilder.java index 226bb9e8134..5f126305aed 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacApplicationBuilder.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacApplicationBuilder.java @@ -28,17 +28,15 @@ import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.List; -import java.util.Set; import java.util.Objects; import java.util.Optional; +import java.util.Set; +import jdk.jpackage.internal.model.AppImageLayout; +import jdk.jpackage.internal.model.AppImageSigningConfig; import jdk.jpackage.internal.model.Application; -import jdk.jpackage.internal.model.ConfigException; import jdk.jpackage.internal.model.Launcher; import jdk.jpackage.internal.model.MacApplication; import jdk.jpackage.internal.model.MacApplicationMixin; -import jdk.jpackage.internal.model.AppImageLayout; -import jdk.jpackage.internal.model.AppImageSigningConfig; final class MacApplicationBuilder { @@ -92,7 +90,7 @@ final class MacApplicationBuilder { return this; } - MacApplication create() throws ConfigException { + MacApplication create() { if (externalInfoPlistFile != null) { return createCopyForExternalInfoPlistFile().create(); } @@ -136,7 +134,7 @@ final class MacApplicationBuilder { return true; } - private static void validateAppVersion(Application app) throws ConfigException { + private static void validateAppVersion(Application app) { try { CFBundleVersion.of(app.version()); } catch (IllegalArgumentException ex) { @@ -156,7 +154,7 @@ final class MacApplicationBuilder { } } - private MacApplicationBuilder createCopyForExternalInfoPlistFile() throws ConfigException { + private MacApplicationBuilder createCopyForExternalInfoPlistFile() { try { final var plistFile = AppImageInfoPListFile.loadFromInfoPList(externalInfoPlistFile); @@ -187,15 +185,11 @@ final class MacApplicationBuilder { } } - private Optional createSigningConfig() throws ConfigException { - if (signingBuilder != null) { - return signingBuilder.create(); - } else { - return Optional.empty(); - } + private Optional createSigningConfig() { + return Optional.ofNullable(signingBuilder).flatMap(AppImageSigningConfigBuilder::create); } - private String validatedBundleName() throws ConfigException { + private String validatedBundleName() { final var value = Optional.ofNullable(bundleName).orElseGet(() -> { final var appName = app.name(); // Commented out for backward compatibility @@ -214,7 +208,7 @@ final class MacApplicationBuilder { return value; } - private String validatedBundleIdentifier() throws ConfigException { + private String validatedBundleIdentifier() { final var value = Optional.ofNullable(bundleIdentifier).orElseGet(() -> { return app.mainLauncher() .flatMap(Launcher::startupInfo) @@ -238,16 +232,12 @@ final class MacApplicationBuilder { return value; } - private String validatedCategory() throws ConfigException { + private String validatedCategory() { return "public.app-category." + Optional.ofNullable(category).orElseGet(DEFAULTS::category); } - private Optional validatedIcon() throws ConfigException { - if (icon != null) { - LauncherBuilder.validateIcon(icon); - } - - return Optional.ofNullable(icon); + private Optional validatedIcon() { + return Optional.ofNullable(icon).map(LauncherBuilder::validateIcon); } private record Defaults(String category) { diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBundlingEnvironment.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBundlingEnvironment.java new file mode 100644 index 00000000000..371a3c7307a --- /dev/null +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBundlingEnvironment.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.jpackage.internal; + +import static jdk.jpackage.internal.MacFromOptions.createMacApplication; +import static jdk.jpackage.internal.MacPackagingPipeline.APPLICATION_LAYOUT; +import static jdk.jpackage.internal.MacPackagingPipeline.createSignAppImagePackage; +import static jdk.jpackage.internal.cli.StandardBundlingOperation.CREATE_MAC_APP_IMAGE; +import static jdk.jpackage.internal.cli.StandardBundlingOperation.CREATE_MAC_DMG; +import static jdk.jpackage.internal.cli.StandardBundlingOperation.CREATE_MAC_PKG; +import static jdk.jpackage.internal.cli.StandardBundlingOperation.SIGN_MAC_APP_IMAGE; + +import java.util.Optional; +import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.model.MacPackage; +import jdk.jpackage.internal.model.Package; +import jdk.jpackage.internal.util.Result; + +public class MacBundlingEnvironment extends DefaultBundlingEnvironment { + + public MacBundlingEnvironment() { + super(build() + .defaultOperation(CREATE_MAC_DMG) + .bundler(SIGN_MAC_APP_IMAGE, MacBundlingEnvironment::signAppImage) + .bundler(CREATE_MAC_APP_IMAGE, MacBundlingEnvironment::createAppImage) + .bundler(CREATE_MAC_DMG, LazyLoad::dmgSysEnv, MacBundlingEnvironment::createDmdPackage) + .bundler(CREATE_MAC_PKG, MacBundlingEnvironment::createPkgPackage)); + } + + private static void createDmdPackage(Options options, MacDmgSystemEnvironment sysEnv) { + createNativePackage(options, + MacFromOptions::createMacDmgPackage, + buildEnv()::create, + MacBundlingEnvironment::buildPipeline, + (env, pkg, outputDir) -> { + Log.verbose(I18N.format("message.building-dmg", pkg.app().name())); + return new MacDmgPackager(env, pkg, outputDir, sysEnv); + }); + } + + private static void createPkgPackage(Options options) { + createNativePackage(options, + MacFromOptions::createMacPkgPackage, + buildEnv()::create, + MacBundlingEnvironment::buildPipeline, + (env, pkg, outputDir) -> { + Log.verbose(I18N.format("message.building-pkg", pkg.app().name())); + return new MacPkgPackager(env, pkg, outputDir); + }); + } + + private static void signAppImage(Options options) { + + final var app = createMacApplication(options); + + final var env = buildEnv().create(options, app); + + final var pkg = createSignAppImagePackage(app, env); + + buildPipeline(pkg).create().execute(env, pkg, env.appImageDir()); + } + + private static void createAppImage(Options options) { + + final var app = createMacApplication(options); + + createApplicationImage(options, app, MacPackagingPipeline.build(Optional.empty())); + } + + private static PackagingPipeline.Builder buildPipeline(Package pkg) { + return MacPackagingPipeline.build(Optional.of(pkg)); + } + + private static BuildEnvFromOptions buildEnv() { + return new BuildEnvFromOptions() + .predefinedAppImageLayout(APPLICATION_LAYOUT) + .predefinedRuntimeImageLayout(MacPackage::guessRuntimeLayout); + } + + private static final class LazyLoad { + + static Result dmgSysEnv() { + return DMG_SYS_ENV; + } + + private static final Result DMG_SYS_ENV = MacDmgSystemEnvironment.create(); + } +} diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgBundler.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgBundler.java deleted file mode 100644 index 0ddb987dbee..00000000000 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgBundler.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.jpackage.internal; - -import java.nio.file.Path; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.model.MacDmgPackage; -import jdk.jpackage.internal.model.PackagerException; -import jdk.jpackage.internal.util.Result; - -public class MacDmgBundler extends MacBaseInstallerBundler { - - @Override - public String getName() { - return I18N.getString("dmg.bundler.name"); - } - - @Override - public String getID() { - return "dmg"; - } - - @Override - public boolean validate(Map params) - throws ConfigException { - try { - Objects.requireNonNull(params); - - MacFromParams.DMG_PACKAGE.fetchFrom(params); - - //run basic validation to ensure requirements are met - //we are not interested in return code, only possible exception - validateAppImageAndBundeler(params); - - return true; - } catch (RuntimeException re) { - if (re.getCause() instanceof ConfigException) { - throw (ConfigException) re.getCause(); - } else { - throw new ConfigException(re); - } - } - } - - @Override - public Path execute(Map params, - Path outputParentDir) throws PackagerException { - - var pkg = MacFromParams.DMG_PACKAGE.fetchFrom(params); - - Log.verbose(I18N.format("message.building-dmg", pkg.app().name())); - - return Packager.build().outputDir(outputParentDir) - .pkg(pkg) - .env(MacBuildEnvFromParams.BUILD_ENV.fetchFrom(params)) - .pipelineBuilderMutatorFactory((env, _, outputDir) -> { - return new MacDmgPackager(env, pkg, outputDir, sysEnv.orElseThrow()); - }).execute(MacPackagingPipeline.build(Optional.of(pkg))); - } - - @Override - public boolean supported(boolean runtimeInstaller) { - return sysEnv.hasValue(); - } - - @Override - public boolean isDefault() { - return true; - } - - private final Result sysEnv = MacDmgSystemEnvironment.create(); -} diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgPackageBuilder.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgPackageBuilder.java index c2b9c25f327..10754b1f1b6 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgPackageBuilder.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgPackageBuilder.java @@ -28,7 +28,6 @@ import java.nio.file.Path; import java.util.List; import java.util.Objects; import java.util.Optional; -import jdk.jpackage.internal.model.ConfigException; import jdk.jpackage.internal.model.MacDmgPackage; import jdk.jpackage.internal.model.MacDmgPackageMixin; @@ -52,7 +51,7 @@ final class MacDmgPackageBuilder { return Optional.ofNullable(dmgContent).orElseGet(List::of); } - MacDmgPackage create() throws ConfigException { + MacDmgPackage create() { final var pkg = pkgBuilder.create(); return MacDmgPackage.create(pkg, new MacDmgPackageMixin.Stub( diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFileAssociationBuilder.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFileAssociationBuilder.java index c86b3a15cbb..7b9e5478f74 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFileAssociationBuilder.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFileAssociationBuilder.java @@ -27,15 +27,13 @@ package jdk.jpackage.internal; import java.util.List; import java.util.Objects; import java.util.Optional; - -import jdk.jpackage.internal.model.ConfigException; import jdk.jpackage.internal.model.FileAssociation; import jdk.jpackage.internal.model.MacFileAssociation; import jdk.jpackage.internal.model.MacFileAssociationMixin; final class MacFileAssociationBuilder { - MacFileAssociation create(FileAssociation fa) throws ConfigException { + MacFileAssociation create(FileAssociation fa) { Objects.requireNonNull(fa); final var mixin = new MacFileAssociationMixin.Stub( diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromOptions.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromOptions.java new file mode 100644 index 00000000000..074014dede0 --- /dev/null +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromOptions.java @@ -0,0 +1,331 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.jpackage.internal; + +import static jdk.jpackage.internal.FromOptions.buildApplicationBuilder; +import static jdk.jpackage.internal.FromOptions.createPackageBuilder; +import static jdk.jpackage.internal.MacPackagingPipeline.APPLICATION_LAYOUT; +import static jdk.jpackage.internal.MacRuntimeValidator.validateRuntimeHasJliLib; +import static jdk.jpackage.internal.MacRuntimeValidator.validateRuntimeHasNoBinDir; +import static jdk.jpackage.internal.cli.StandardBundlingOperation.SIGN_MAC_APP_IMAGE; +import static jdk.jpackage.internal.cli.StandardOption.ICON; +import static jdk.jpackage.internal.cli.StandardOption.APPCLASS; +import static jdk.jpackage.internal.cli.StandardOption.MAC_APP_CATEGORY; +import static jdk.jpackage.internal.cli.StandardOption.MAC_APP_IMAGE_SIGN_IDENTITY; +import static jdk.jpackage.internal.cli.StandardOption.MAC_APP_STORE; +import static jdk.jpackage.internal.cli.StandardOption.MAC_BUNDLE_IDENTIFIER; +import static jdk.jpackage.internal.cli.StandardOption.MAC_BUNDLE_NAME; +import static jdk.jpackage.internal.cli.StandardOption.MAC_BUNDLE_SIGNING_PREFIX; +import static jdk.jpackage.internal.cli.StandardOption.MAC_DMG_CONTENT; +import static jdk.jpackage.internal.cli.StandardOption.MAC_ENTITLEMENTS; +import static jdk.jpackage.internal.cli.StandardOption.MAC_INSTALLER_SIGN_IDENTITY; +import static jdk.jpackage.internal.cli.StandardOption.MAC_SIGN; +import static jdk.jpackage.internal.cli.StandardOption.MAC_SIGNING_KEYCHAIN; +import static jdk.jpackage.internal.cli.StandardOption.MAC_SIGNING_KEY_NAME; +import static jdk.jpackage.internal.cli.StandardOption.PREDEFINED_APP_IMAGE; +import static jdk.jpackage.internal.cli.StandardOption.PREDEFINED_RUNTIME_IMAGE; +import static jdk.jpackage.internal.model.MacPackage.RUNTIME_BUNDLE_LAYOUT; +import static jdk.jpackage.internal.model.StandardPackageType.MAC_DMG; +import static jdk.jpackage.internal.model.StandardPackageType.MAC_PKG; +import static jdk.jpackage.internal.util.function.ExceptionBox.rethrowUnchecked; + +import java.nio.file.Path; +import java.util.Objects; +import java.util.Optional; +import jdk.jpackage.internal.ApplicationBuilder.MainLauncherStartupInfo; +import jdk.jpackage.internal.SigningIdentityBuilder.ExpiredCertificateException; +import jdk.jpackage.internal.SigningIdentityBuilder.StandardCertificateSelector; +import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.cli.StandardFaOption; +import jdk.jpackage.internal.model.ApplicationLaunchers; +import jdk.jpackage.internal.model.ExternalApplication; +import jdk.jpackage.internal.model.FileAssociation; +import jdk.jpackage.internal.model.Launcher; +import jdk.jpackage.internal.model.MacApplication; +import jdk.jpackage.internal.model.MacDmgPackage; +import jdk.jpackage.internal.model.MacFileAssociation; +import jdk.jpackage.internal.model.MacLauncher; +import jdk.jpackage.internal.model.MacPackage; +import jdk.jpackage.internal.model.MacPkgPackage; +import jdk.jpackage.internal.model.PackageType; +import jdk.jpackage.internal.model.RuntimeLayout; +import jdk.jpackage.internal.util.Result; +import jdk.jpackage.internal.util.function.ExceptionBox; + + +final class MacFromOptions { + + static MacApplication createMacApplication(Options options) { + return createMacApplicationInternal(options).app(); + } + + static MacDmgPackage createMacDmgPackage(Options options) { + + final var app = createMacApplicationInternal(options); + + final var superPkgBuilder = createMacPackageBuilder(options, app, MAC_DMG); + + final var pkgBuilder = new MacDmgPackageBuilder(superPkgBuilder); + + MAC_DMG_CONTENT.ifPresentIn(options, pkgBuilder::dmgContent); + + return pkgBuilder.create(); + } + + static MacPkgPackage createMacPkgPackage(Options options) { + + // + // One of "MacSignTest.testExpiredCertificate" test cases expects + // two error messages about expired certificates in the output: one for + // certificate for signing an app image, another certificate for signing a PKG. + // So creation of a PKG package is a bit messy. + // + + final boolean sign = MAC_SIGN.findIn(options).orElse(false); + final boolean appStore = MAC_APP_STORE.findIn(options).orElse(false); + + final var appResult = Result.create(() -> createMacApplicationInternal(options)); + + final Optional pkgBuilder; + if (appResult.hasValue()) { + final var superPkgBuilder = createMacPackageBuilder(options, appResult.orElseThrow(), MAC_PKG); + pkgBuilder = Optional.of(new MacPkgPackageBuilder(superPkgBuilder)); + } else { + // Failed to create an app. Is it because of the expired certificate? + rethrowIfNotExpiredCertificateException(appResult); + // Yes, the certificate for signing the app image has expired. + // Keep going, try to create a signing config for the package. + pkgBuilder = Optional.empty(); + } + + if (sign) { + final var signingIdentityBuilder = createSigningIdentityBuilder(options); + MAC_INSTALLER_SIGN_IDENTITY.ifPresentIn(options, signingIdentityBuilder::signingIdentity); + MAC_SIGNING_KEY_NAME.findIn(options).ifPresent(userName -> { + final StandardCertificateSelector domain; + if (appStore) { + domain = StandardCertificateSelector.APP_STORE_PKG_INSTALLER; + } else { + domain = StandardCertificateSelector.PKG_INSTALLER; + } + + signingIdentityBuilder.certificateSelector(StandardCertificateSelector.create(userName, domain)); + }); + + if (pkgBuilder.isPresent()) { + pkgBuilder.orElseThrow().signingBuilder(signingIdentityBuilder); + } else { + // + // The certificate for signing the app image has expired. Can not create a + // package because there is no app. + // Try to create a signing config for the package and see if the certificate for + // signing the package is also expired. + // + + final var expiredAppCertException = appResult.firstError().orElseThrow(); + + final var pkgSignConfigResult = Result.create(signingIdentityBuilder::create); + try { + rethrowIfNotExpiredCertificateException(pkgSignConfigResult); + // The certificate for the package signing config is also expired! + } catch (RuntimeException ex) { + // Some error occurred trying to configure the signing config for the package. + // Ignore it, bail out with the first error. + rethrowUnchecked(expiredAppCertException); + } + + Log.error(pkgSignConfigResult.firstError().orElseThrow().getMessage()); + rethrowUnchecked(expiredAppCertException); + } + } + + return pkgBuilder.orElseThrow().create(); + } + + private record ApplicationWithDetails(MacApplication app, Optional externalApp) { + ApplicationWithDetails { + Objects.requireNonNull(app); + Objects.requireNonNull(externalApp); + } + } + + private static ApplicationWithDetails createMacApplicationInternal(Options options) { + + final var predefinedRuntimeLayout = PREDEFINED_RUNTIME_IMAGE.findIn(options) + .map(MacPackage::guessRuntimeLayout); + + predefinedRuntimeLayout.ifPresent(layout -> { + validateRuntimeHasJliLib(layout); + if (MAC_APP_STORE.containsIn(options)) { + validateRuntimeHasNoBinDir(layout); + } + }); + + final var launcherFromOptions = new LauncherFromOptions().faMapper(MacFromOptions::createMacFa); + + final var superAppBuilder = buildApplicationBuilder() + .runtimeLayout(RUNTIME_BUNDLE_LAYOUT) + .predefinedRuntimeLayout(predefinedRuntimeLayout.map(RuntimeLayout::unresolve).orElse(null)) + .create(options, launcherOptions -> { + var launcher = launcherFromOptions.create(launcherOptions); + return MacLauncher.create(launcher); + }, (MacLauncher _, Launcher launcher) -> { + return MacLauncher.create(launcher); + }, APPLICATION_LAYOUT); + + if (PREDEFINED_APP_IMAGE.containsIn(options)) { + // Set the main launcher start up info. + // AppImageFile assumes the main launcher start up info is available when + // it is constructed from Application instance. + // This happens when jpackage signs predefined app image. + final var appImageFileOptions = superAppBuilder.externalApplication().orElseThrow().extra(); + final var mainLauncherStartupInfo = new MainLauncherStartupInfo(APPCLASS.getFrom(appImageFileOptions)); + final var launchers = superAppBuilder.launchers().orElseThrow(); + final var mainLauncher = ApplicationBuilder.overrideLauncherStartupInfo(launchers.mainLauncher(), mainLauncherStartupInfo); + superAppBuilder.launchers(new ApplicationLaunchers(MacLauncher.create(mainLauncher), launchers.additionalLaunchers())); + } + + final var app = superAppBuilder.create(); + + final var appBuilder = new MacApplicationBuilder(app); + + PREDEFINED_APP_IMAGE.findIn(options) + .map(MacBundle::new) + .map(MacBundle::infoPlistFile) + .ifPresent(appBuilder::externalInfoPlistFile); + + ICON.ifPresentIn(options, appBuilder::icon); + MAC_BUNDLE_NAME.ifPresentIn(options, appBuilder::bundleName); + MAC_BUNDLE_IDENTIFIER.ifPresentIn(options, appBuilder::bundleIdentifier); + MAC_APP_CATEGORY.ifPresentIn(options, appBuilder::category); + + final boolean sign; + final boolean appStore; + + if (PREDEFINED_APP_IMAGE.containsIn(options) && OptionUtils.bundlingOperation(options) != SIGN_MAC_APP_IMAGE) { + final var appImageFileOptions = superAppBuilder.externalApplication().orElseThrow().extra(); + sign = MAC_SIGN.getFrom(appImageFileOptions); + appStore = MAC_APP_STORE.getFrom(appImageFileOptions); + } else { + sign = MAC_SIGN.getFrom(options); + appStore = MAC_APP_STORE.getFrom(options); + } + + appBuilder.appStore(appStore); + + if (sign) { + final var signingIdentityBuilder = createSigningIdentityBuilder(options); + MAC_APP_IMAGE_SIGN_IDENTITY.ifPresentIn(options, signingIdentityBuilder::signingIdentity); + MAC_SIGNING_KEY_NAME.findIn(options).ifPresent(userName -> { + final StandardCertificateSelector domain; + if (appStore) { + domain = StandardCertificateSelector.APP_STORE_APP_IMAGE; + } else { + domain = StandardCertificateSelector.APP_IMAGE; + } + + signingIdentityBuilder.certificateSelector(StandardCertificateSelector.create(userName, domain)); + }); + + final var signingBuilder = new AppImageSigningConfigBuilder(signingIdentityBuilder); + if (appStore) { + signingBuilder.entitlementsResourceName("sandbox.plist"); + } + + app.mainLauncher().flatMap(Launcher::startupInfo).ifPresentOrElse( + signingBuilder::signingIdentifierPrefix, + () -> { + // Runtime installer does not have the main launcher, use + // 'bundleIdentifier' as the prefix by default. + var bundleIdentifier = appBuilder.create().bundleIdentifier(); + signingBuilder.signingIdentifierPrefix(bundleIdentifier + "."); + }); + MAC_BUNDLE_SIGNING_PREFIX.ifPresentIn(options, signingBuilder::signingIdentifierPrefix); + + MAC_ENTITLEMENTS.ifPresentIn(options, signingBuilder::entitlements); + + appBuilder.signingBuilder(signingBuilder); + } + + return new ApplicationWithDetails(appBuilder.create(), superAppBuilder.externalApplication()); + } + + private static MacPackageBuilder createMacPackageBuilder(Options options, ApplicationWithDetails app, PackageType type) { + + final var builder = new MacPackageBuilder(createPackageBuilder(options, app.app(), type)); + + app.externalApp() + .map(ExternalApplication::extra) + .flatMap(MAC_SIGN::findIn) + .ifPresent(builder::predefinedAppImageSigned); + + PREDEFINED_RUNTIME_IMAGE.findIn(options) + .map(MacBundle::new) + .filter(MacBundle::isValid) + .map(MacBundle::isSigned) + .ifPresent(builder::predefinedAppImageSigned); + + return builder; + } + + private static void rethrowIfNotExpiredCertificateException(Result result) { + final var ex = result.firstError().orElseThrow(); + + if (ex instanceof ExpiredCertificateException) { + return; + } + + if (ex instanceof ExceptionBox box) { + if (box.getCause() instanceof Exception cause) { + rethrowIfNotExpiredCertificateException(Result.ofError(cause)); + } + } + + rethrowUnchecked(ex); + } + + private static SigningIdentityBuilder createSigningIdentityBuilder(Options options) { + final var builder = new SigningIdentityBuilder(); + MAC_SIGNING_KEYCHAIN.findIn(options).map(Path::toString).ifPresent(builder::keychain); + return builder; + } + + private static MacFileAssociation createMacFa(Options options, FileAssociation fa) { + + final var builder = new MacFileAssociationBuilder(); + + StandardFaOption.MAC_CFBUNDLETYPEROLE.ifPresentIn(options, builder::cfBundleTypeRole); + StandardFaOption.MAC_LSHANDLERRANK.ifPresentIn(options, builder::lsHandlerRank); + StandardFaOption.MAC_NSSTORETYPEKEY.ifPresentIn(options, builder::nsPersistentStoreTypeKey); + StandardFaOption.MAC_NSDOCUMENTCLASS.ifPresentIn(options, builder::nsDocumentClass); + StandardFaOption.MAC_LSTYPEISPACKAGE.ifPresentIn(options, builder::lsTypeIsPackage); + StandardFaOption.MAC_LSDOCINPLACE.ifPresentIn(options, builder::lsSupportsOpeningDocumentsInPlace); + StandardFaOption.MAC_UIDOCBROWSER.ifPresentIn(options, builder::uiSupportsDocumentBrowser); + StandardFaOption.MAC_NSEXPORTABLETYPES.ifPresentIn(options, builder::nsExportableTypes); + StandardFaOption.MAC_UTTYPECONFORMSTO.ifPresentIn(options, builder::utTypeConformsTo); + + return builder.create(fa); + } +} diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromParams.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromParams.java deleted file mode 100644 index 72c33ef6475..00000000000 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromParams.java +++ /dev/null @@ -1,384 +0,0 @@ -/* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package jdk.jpackage.internal; - -import static jdk.jpackage.internal.BundlerParamInfo.createBooleanBundlerParam; -import static jdk.jpackage.internal.BundlerParamInfo.createPathBundlerParam; -import static jdk.jpackage.internal.BundlerParamInfo.createStringBundlerParam; -import static jdk.jpackage.internal.FromParams.createApplicationBuilder; -import static jdk.jpackage.internal.FromParams.createApplicationBundlerParam; -import static jdk.jpackage.internal.FromParams.createPackageBuilder; -import static jdk.jpackage.internal.FromParams.createPackageBundlerParam; -import static jdk.jpackage.internal.MacPackagingPipeline.APPLICATION_LAYOUT; -import static jdk.jpackage.internal.MacRuntimeValidator.validateRuntimeHasJliLib; -import static jdk.jpackage.internal.MacRuntimeValidator.validateRuntimeHasNoBinDir; -import static jdk.jpackage.internal.StandardBundlerParam.DMG_CONTENT; -import static jdk.jpackage.internal.StandardBundlerParam.ICON; -import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE; -import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE_FILE; -import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_RUNTIME_IMAGE; -import static jdk.jpackage.internal.StandardBundlerParam.SIGN_BUNDLE; -import static jdk.jpackage.internal.StandardBundlerParam.hasPredefinedAppImage; -import static jdk.jpackage.internal.model.MacPackage.RUNTIME_BUNDLE_LAYOUT; -import static jdk.jpackage.internal.model.StandardPackageType.MAC_DMG; -import static jdk.jpackage.internal.model.StandardPackageType.MAC_PKG; -import static jdk.jpackage.internal.util.function.ThrowingFunction.toFunction; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.Callable; -import java.util.function.Predicate; -import jdk.jpackage.internal.ApplicationBuilder.MainLauncherStartupInfo; -import jdk.jpackage.internal.SigningIdentityBuilder.ExpiredCertificateException; -import jdk.jpackage.internal.SigningIdentityBuilder.StandardCertificateSelector; -import jdk.jpackage.internal.model.ApplicationLaunchers; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.model.FileAssociation; -import jdk.jpackage.internal.model.Launcher; -import jdk.jpackage.internal.model.MacApplication; -import jdk.jpackage.internal.model.MacDmgPackage; -import jdk.jpackage.internal.model.MacFileAssociation; -import jdk.jpackage.internal.model.MacLauncher; -import jdk.jpackage.internal.model.MacPackage; -import jdk.jpackage.internal.model.MacPkgPackage; -import jdk.jpackage.internal.model.PackageType; -import jdk.jpackage.internal.model.RuntimeLayout; -import jdk.jpackage.internal.util.function.ExceptionBox; - - -final class MacFromParams { - - private static MacApplication createMacApplication( - Map params) throws ConfigException, IOException { - - final var predefinedRuntimeLayout = PREDEFINED_RUNTIME_IMAGE.findIn(params) - .map(MacPackage::guessRuntimeLayout); - - if (predefinedRuntimeLayout.isPresent()) { - validateRuntimeHasJliLib(predefinedRuntimeLayout.orElseThrow()); - if (APP_STORE.findIn(params).orElse(false)) { - validateRuntimeHasNoBinDir(predefinedRuntimeLayout.orElseThrow()); - } - } - - final var launcherFromParams = new LauncherFromParams(Optional.of(MacFromParams::createMacFa)); - - final var superAppBuilder = createApplicationBuilder(params, toFunction(launcherParams -> { - var launcher = launcherFromParams.create(launcherParams); - return MacLauncher.create(launcher); - }), (MacLauncher _, Launcher launcher) -> { - return MacLauncher.create(launcher); - }, APPLICATION_LAYOUT, RUNTIME_BUNDLE_LAYOUT, predefinedRuntimeLayout.map(RuntimeLayout::unresolve)); - - if (hasPredefinedAppImage(params)) { - // Set the main launcher start up info. - // AppImageFile assumes the main launcher start up info is available when - // it is constructed from Application instance. - // This happens when jpackage signs predefined app image. - final var mainLauncherStartupInfo = new MainLauncherStartupInfo(superAppBuilder.mainLauncherClassName().orElseThrow()); - final var launchers = superAppBuilder.launchers().orElseThrow(); - final var mainLauncher = ApplicationBuilder.overrideLauncherStartupInfo(launchers.mainLauncher(), mainLauncherStartupInfo); - superAppBuilder.launchers(new ApplicationLaunchers(MacLauncher.create(mainLauncher), launchers.additionalLaunchers())); - } - - final var app = superAppBuilder.create(); - - final var appBuilder = new MacApplicationBuilder(app); - - if (hasPredefinedAppImage(params)) { - appBuilder.externalInfoPlistFile(PREDEFINED_APP_IMAGE.findIn(params).map(MacBundle::new).orElseThrow().infoPlistFile()); - } - - ICON.copyInto(params, appBuilder::icon); - MAC_CF_BUNDLE_NAME.copyInto(params, appBuilder::bundleName); - MAC_CF_BUNDLE_IDENTIFIER.copyInto(params, appBuilder::bundleIdentifier); - APP_CATEGORY.copyInto(params, appBuilder::category); - - final boolean sign; - final boolean appStore; - - if (hasPredefinedAppImage(params) && PACKAGE_TYPE.findIn(params).filter(Predicate.isEqual("app-image")).isEmpty()) { - final var appImageFileExtras = new MacAppImageFileExtras(superAppBuilder.externalApplication().orElseThrow()); - sign = appImageFileExtras.signed(); - appStore = appImageFileExtras.appStore(); - } else { - sign = SIGN_BUNDLE.findIn(params).orElse(false); - appStore = APP_STORE.findIn(params).orElse(false); - } - - appBuilder.appStore(appStore); - - if (sign) { - final var signingIdentityBuilder = createSigningIdentityBuilder(params); - APP_IMAGE_SIGN_IDENTITY.copyInto(params, signingIdentityBuilder::signingIdentity); - SIGNING_KEY_USER.findIn(params).ifPresent(userName -> { - final StandardCertificateSelector domain; - if (appStore) { - domain = StandardCertificateSelector.APP_STORE_APP_IMAGE; - } else { - domain = StandardCertificateSelector.APP_IMAGE; - } - - signingIdentityBuilder.certificateSelector(StandardCertificateSelector.create(userName, domain)); - }); - - final var signingBuilder = new AppImageSigningConfigBuilder(signingIdentityBuilder); - if (appStore) { - signingBuilder.entitlementsResourceName("sandbox.plist"); - } - - final var bundleIdentifier = appBuilder.create().bundleIdentifier(); - app.mainLauncher().flatMap(Launcher::startupInfo).ifPresentOrElse( - signingBuilder::signingIdentifierPrefix, - () -> { - // Runtime installer does not have main launcher, so use - // 'bundleIdentifier' as prefix by default. - signingBuilder.signingIdentifierPrefix( - bundleIdentifier + "."); - }); - SIGN_IDENTIFIER_PREFIX.copyInto(params, signingBuilder::signingIdentifierPrefix); - - ENTITLEMENTS.copyInto(params, signingBuilder::entitlements); - - appBuilder.signingBuilder(signingBuilder); - } - - return appBuilder.create(); - } - - private static MacPackageBuilder createMacPackageBuilder( - Map params, MacApplication app, - PackageType type) throws ConfigException { - final var builder = new MacPackageBuilder(createPackageBuilder(params, app, type)); - - PREDEFINED_APP_IMAGE_FILE.findIn(params) - .map(MacAppImageFileExtras::new) - .map(MacAppImageFileExtras::signed) - .ifPresent(builder::predefinedAppImageSigned); - - PREDEFINED_RUNTIME_IMAGE.findIn(params) - .map(MacBundle::new) - .filter(MacBundle::isValid) - .map(MacBundle::isSigned) - .ifPresent(builder::predefinedAppImageSigned); - - return builder; - } - - private static MacDmgPackage createMacDmgPackage( - Map params) throws ConfigException, IOException { - - final var app = APPLICATION.fetchFrom(params); - - final var superPkgBuilder = createMacPackageBuilder(params, app, MAC_DMG); - - final var pkgBuilder = new MacDmgPackageBuilder(superPkgBuilder); - - DMG_CONTENT.copyInto(params, pkgBuilder::dmgContent); - - return pkgBuilder.create(); - } - - private record WithExpiredCertificateException(Optional obj, Optional certEx) { - WithExpiredCertificateException { - if (obj.isEmpty() == certEx.isEmpty()) { - throw new IllegalArgumentException(); - } - } - - static WithExpiredCertificateException of(Callable callable) { - try { - return new WithExpiredCertificateException<>(Optional.of(callable.call()), Optional.empty()); - } catch (ExpiredCertificateException ex) { - return new WithExpiredCertificateException<>(Optional.empty(), Optional.of(ex)); - } catch (ExceptionBox ex) { - if (ex.getCause() instanceof ExpiredCertificateException certEx) { - return new WithExpiredCertificateException<>(Optional.empty(), Optional.of(certEx)); - } - throw ex; - } catch (RuntimeException ex) { - throw ex; - } catch (Throwable t) { - throw ExceptionBox.rethrowUnchecked(t); - } - } - } - - private static MacPkgPackage createMacPkgPackage( - Map params) throws ConfigException, IOException { - - // This is over complicated to make "MacSignTest.testExpiredCertificate" test pass. - - final boolean sign = SIGN_BUNDLE.findIn(params).orElse(false); - final boolean appStore = APP_STORE.findIn(params).orElse(false); - - final var appOrExpiredCertEx = WithExpiredCertificateException.of(() -> { - return APPLICATION.fetchFrom(params); - }); - - final Optional pkgBuilder; - if (appOrExpiredCertEx.obj().isPresent()) { - final var superPkgBuilder = createMacPackageBuilder(params, appOrExpiredCertEx.obj().orElseThrow(), MAC_PKG); - pkgBuilder = Optional.of(new MacPkgPackageBuilder(superPkgBuilder)); - } else { - pkgBuilder = Optional.empty(); - } - - if (sign) { - final var signingIdentityBuilder = createSigningIdentityBuilder(params); - INSTALLER_SIGN_IDENTITY.copyInto(params, signingIdentityBuilder::signingIdentity); - SIGNING_KEY_USER.findIn(params).ifPresent(userName -> { - final StandardCertificateSelector domain; - if (appStore) { - domain = StandardCertificateSelector.APP_STORE_PKG_INSTALLER; - } else { - domain = StandardCertificateSelector.PKG_INSTALLER; - } - - signingIdentityBuilder.certificateSelector(StandardCertificateSelector.create(userName, domain)); - }); - - if (pkgBuilder.isPresent()) { - pkgBuilder.orElseThrow().signingBuilder(signingIdentityBuilder); - } else { - final var expiredPkgCert = WithExpiredCertificateException.of(() -> { - return signingIdentityBuilder.create(); - }).certEx(); - expiredPkgCert.map(ConfigException::getMessage).ifPresent(Log::error); - throw appOrExpiredCertEx.certEx().orElseThrow(); - } - } - - return pkgBuilder.orElseThrow().create(); - } - - private static SigningIdentityBuilder createSigningIdentityBuilder(Map params) { - final var builder = new SigningIdentityBuilder(); - SIGNING_KEYCHAIN.copyInto(params, builder::keychain); - return builder; - } - - private static MacFileAssociation createMacFa(FileAssociation fa, Map params) { - - final var builder = new MacFileAssociationBuilder(); - - FA_MAC_CFBUNDLETYPEROLE.copyInto(params, builder::cfBundleTypeRole); - FA_MAC_LSHANDLERRANK.copyInto(params, builder::lsHandlerRank); - FA_MAC_NSSTORETYPEKEY.copyInto(params, builder::nsPersistentStoreTypeKey); - FA_MAC_NSDOCUMENTCLASS.copyInto(params, builder::nsDocumentClass); - FA_MAC_LSTYPEISPACKAGE.copyInto(params, builder::lsTypeIsPackage); - FA_MAC_LSDOCINPLACE.copyInto(params, builder::lsSupportsOpeningDocumentsInPlace); - FA_MAC_UIDOCBROWSER.copyInto(params, builder::uiSupportsDocumentBrowser); - FA_MAC_NSEXPORTABLETYPES.copyInto(params, builder::nsExportableTypes); - FA_MAC_UTTYPECONFORMSTO.copyInto(params, builder::utTypeConformsTo); - - return toFunction(builder::create).apply(fa); - } - - static final BundlerParamInfo APPLICATION = createApplicationBundlerParam( - MacFromParams::createMacApplication); - - static final BundlerParamInfo DMG_PACKAGE = createPackageBundlerParam( - MacFromParams::createMacDmgPackage); - - static final BundlerParamInfo PKG_PACKAGE = createPackageBundlerParam( - MacFromParams::createMacPkgPackage); - - private static final BundlerParamInfo MAC_CF_BUNDLE_NAME = createStringBundlerParam( - Arguments.CLIOptions.MAC_BUNDLE_NAME.getId()); - - private static final BundlerParamInfo APP_CATEGORY = createStringBundlerParam( - Arguments.CLIOptions.MAC_CATEGORY.getId()); - - private static final BundlerParamInfo ENTITLEMENTS = createPathBundlerParam( - Arguments.CLIOptions.MAC_ENTITLEMENTS.getId()); - - private static final BundlerParamInfo MAC_CF_BUNDLE_IDENTIFIER = createStringBundlerParam( - Arguments.CLIOptions.MAC_BUNDLE_IDENTIFIER.getId()); - - private static final BundlerParamInfo SIGN_IDENTIFIER_PREFIX = createStringBundlerParam( - Arguments.CLIOptions.MAC_BUNDLE_SIGNING_PREFIX.getId()); - - private static final BundlerParamInfo APP_IMAGE_SIGN_IDENTITY = createStringBundlerParam( - Arguments.CLIOptions.MAC_APP_IMAGE_SIGN_IDENTITY.getId()); - - private static final BundlerParamInfo INSTALLER_SIGN_IDENTITY = createStringBundlerParam( - Arguments.CLIOptions.MAC_INSTALLER_SIGN_IDENTITY.getId()); - - private static final BundlerParamInfo SIGNING_KEY_USER = createStringBundlerParam( - Arguments.CLIOptions.MAC_SIGNING_KEY_NAME.getId()); - - private static final BundlerParamInfo SIGNING_KEYCHAIN = createStringBundlerParam( - Arguments.CLIOptions.MAC_SIGNING_KEYCHAIN.getId()); - - private static final BundlerParamInfo PACKAGE_TYPE = createStringBundlerParam( - Arguments.CLIOptions.PACKAGE_TYPE.getId()); - - private static final BundlerParamInfo APP_STORE = createBooleanBundlerParam( - Arguments.CLIOptions.MAC_APP_STORE.getId()); - - private static final BundlerParamInfo FA_MAC_CFBUNDLETYPEROLE = createStringBundlerParam( - Arguments.MAC_CFBUNDLETYPEROLE); - - private static final BundlerParamInfo FA_MAC_LSHANDLERRANK = createStringBundlerParam( - Arguments.MAC_LSHANDLERRANK); - - private static final BundlerParamInfo FA_MAC_NSSTORETYPEKEY = createStringBundlerParam( - Arguments.MAC_NSSTORETYPEKEY); - - private static final BundlerParamInfo FA_MAC_NSDOCUMENTCLASS = createStringBundlerParam( - Arguments.MAC_NSDOCUMENTCLASS); - - private static final BundlerParamInfo FA_MAC_LSTYPEISPACKAGE = createBooleanBundlerParam( - Arguments.MAC_LSTYPEISPACKAGE); - - private static final BundlerParamInfo FA_MAC_LSDOCINPLACE = createBooleanBundlerParam( - Arguments.MAC_LSDOCINPLACE); - - private static final BundlerParamInfo FA_MAC_UIDOCBROWSER = createBooleanBundlerParam( - Arguments.MAC_UIDOCBROWSER); - - @SuppressWarnings("unchecked") - private static final BundlerParamInfo> FA_MAC_NSEXPORTABLETYPES = - new BundlerParamInfo<>( - Arguments.MAC_NSEXPORTABLETYPES, - (Class>) (Object) List.class, - null, - (s, p) -> Arrays.asList(s.split("(,|\\s)+")) - ); - - @SuppressWarnings("unchecked") - private static final BundlerParamInfo> FA_MAC_UTTYPECONFORMSTO = - new BundlerParamInfo<>( - Arguments.MAC_UTTYPECONFORMSTO, - (Class>) (Object) List.class, - null, - (s, p) -> Arrays.asList(s.split("(,|\\s)+")) - ); -} diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackageBuilder.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackageBuilder.java index 9576f6a6a99..e19f234d0d6 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackageBuilder.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackageBuilder.java @@ -29,7 +29,6 @@ import static jdk.jpackage.internal.MacPackagingPipeline.LayoutUtils.packagerLay import java.nio.file.Files; import java.util.Objects; -import jdk.jpackage.internal.model.ConfigException; import jdk.jpackage.internal.model.MacApplication; import jdk.jpackage.internal.model.MacPackage; import jdk.jpackage.internal.model.MacPackageMixin; @@ -49,7 +48,7 @@ final class MacPackageBuilder { return pkgBuilder; } - MacPackage create() throws ConfigException { + MacPackage create() { final var app = (MacApplication)pkgBuilder.app(); diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackagingPipeline.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackagingPipeline.java index b82b20c0c36..53f297282ba 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackagingPipeline.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackagingPipeline.java @@ -75,7 +75,6 @@ import jdk.jpackage.internal.model.MacFileAssociation; import jdk.jpackage.internal.model.MacPackage; import jdk.jpackage.internal.model.Package; import jdk.jpackage.internal.model.PackageType; -import jdk.jpackage.internal.model.PackagerException; import jdk.jpackage.internal.util.FileUtils; import jdk.jpackage.internal.util.PathUtils; import jdk.jpackage.internal.util.function.ThrowingConsumer; @@ -225,10 +224,12 @@ final class MacPackagingPipeline { if (!app.sign()) { throw new IllegalArgumentException(); } - return toSupplier(() -> { - return new PackageBuilder(app, SignAppImagePackageType.VALUE).predefinedAppImage( - Objects.requireNonNull(env.appImageDir())).installDir(Path.of("/foo")).create(); - }).get(); + return new PackageBuilder( + app, + SignAppImagePackageType.VALUE + ).predefinedAppImage( + Objects.requireNonNull(env.appImageDir()) + ).installDir(Path.of("/foo")).create(); } static final class LayoutUtils { @@ -268,7 +269,7 @@ final class MacPackagingPipeline { static AppImageTaskAction withBundleLayout(AppImageTaskAction action) { return new AppImageTaskAction<>() { @Override - public void execute(AppImageBuildEnv env) throws IOException, PackagerException { + public void execute(AppImageBuildEnv env) throws IOException { if (!env.envLayout().runtimeDirectory().getName(0).equals(Path.of("Contents"))) { env = LayoutUtils.fromPackagerLayout(env); } @@ -610,11 +611,20 @@ final class MacPackagingPipeline { } @Override - public void execute(TaskAction taskAction) throws IOException, PackagerException { + public void execute(TaskAction taskAction) throws IOException { delegate.execute(taskAction); } } + private static final ApplicationLayout MAC_APPLICATION_LAYOUT = ApplicationLayout.build() + .launchersDirectory("Contents/MacOS") + .appDirectory("Contents/app") + .runtimeDirectory("Contents/runtime/Contents/Home") + .desktopIntegrationDirectory("Contents/Resources") + .appModsDirectory("Contents/app/mods") + .contentDirectory("Contents") + .create(); + static final MacApplicationLayout APPLICATION_LAYOUT = MacApplicationLayout.create( - ApplicationLayoutUtils.PLATFORM_APPLICATION_LAYOUT, Path.of("Contents/runtime")); + MAC_APPLICATION_LAYOUT, Path.of("Contents/runtime")); } diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgBundler.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgBundler.java deleted file mode 100644 index e827f238db3..00000000000 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgBundler.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.jpackage.internal; - -import java.nio.file.Path; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.model.MacPkgPackage; -import jdk.jpackage.internal.model.PackagerException; - -public class MacPkgBundler extends MacBaseInstallerBundler { - - @Override - public String getName() { - return I18N.getString("pkg.bundler.name"); - } - - @Override - public String getID() { - return "pkg"; - } - - @Override - public boolean validate(Map params) - throws ConfigException { - try { - Objects.requireNonNull(params); - - final var pkg = MacFromParams.PKG_PACKAGE.fetchFrom(params); - - // run basic validation to ensure requirements are met - // we are not interested in return code, only possible exception - validateAppImageAndBundeler(params); - - // hdiutil is always available so there's no need - // to test for availability. - - return true; - } catch (RuntimeException re) { - if (re.getCause() instanceof ConfigException) { - throw (ConfigException) re.getCause(); - } else { - throw new ConfigException(re); - } - } - } - - @Override - public Path execute(Map params, - Path outputParentDir) throws PackagerException { - - var pkg = MacFromParams.PKG_PACKAGE.fetchFrom(params); - - Log.verbose(I18N.format("message.building-pkg", pkg.app().name())); - - return Packager.build().outputDir(outputParentDir) - .pkg(pkg) - .env(MacBuildEnvFromParams.BUILD_ENV.fetchFrom(params)) - .pipelineBuilderMutatorFactory((env, _, outputDir) -> { - return new MacPkgPackager(env, pkg, outputDir); - }).execute(MacPackagingPipeline.build(Optional.of(pkg))); - } - - @Override - public boolean supported(boolean runtimeInstaller) { - return true; - } - - @Override - public boolean isDefault() { - return false; - } - -} diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgPackageBuilder.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgPackageBuilder.java index 663b8b16265..b81340354e1 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgPackageBuilder.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgPackageBuilder.java @@ -26,7 +26,6 @@ package jdk.jpackage.internal; import java.util.Objects; import java.util.Optional; -import jdk.jpackage.internal.model.ConfigException; import jdk.jpackage.internal.model.MacPkgPackage; import jdk.jpackage.internal.model.MacPkgPackageMixin; import jdk.jpackage.internal.model.PkgSigningConfig; @@ -42,20 +41,16 @@ final class MacPkgPackageBuilder { return this; } - MacPkgPackage create() throws ConfigException { + MacPkgPackage create() { var pkg = MacPkgPackage.create(pkgBuilder.create(), new MacPkgPackageMixin.Stub(createSigningConfig())); validatePredefinedAppImage(pkg); return pkg; } - private Optional createSigningConfig() throws ConfigException { - if (signingBuilder != null) { - return signingBuilder.create().map(cfg -> { - return new PkgSigningConfig.Stub(cfg.identity(), cfg.keychain().map(Keychain::name)); - }); - } else { - return Optional.empty(); - } + private Optional createSigningConfig() { + return Optional.ofNullable(signingBuilder).flatMap(SigningIdentityBuilder::create).map(cfg -> { + return new PkgSigningConfig.Stub(cfg.identity(), cfg.keychain().map(Keychain::name)); + }); } private static void validatePredefinedAppImage(MacPkgPackage pkg) { diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacRuntimeValidator.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacRuntimeValidator.java index e7ef6a23fa7..cfab12cbcba 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacRuntimeValidator.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacRuntimeValidator.java @@ -30,12 +30,11 @@ import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.util.function.Predicate; -import jdk.jpackage.internal.model.ConfigException; import jdk.jpackage.internal.model.RuntimeLayout; final class MacRuntimeValidator { - static void validateRuntimeHasJliLib(RuntimeLayout runtimeLayout) throws ConfigException { + static void validateRuntimeHasJliLib(RuntimeLayout runtimeLayout) { final var jliName = Path.of("libjli.dylib"); try (var walk = Files.walk(runtimeLayout.runtimeDirectory().resolve("lib"))) { if (walk.map(Path::getFileName).anyMatch(Predicate.isEqual(jliName))) { @@ -51,7 +50,7 @@ final class MacRuntimeValidator { runtimeLayout.unresolve().runtimeDirectory().resolve("lib/**").resolve(jliName)).create(); } - static void validateRuntimeHasNoBinDir(RuntimeLayout runtimeLayout) throws ConfigException { + static void validateRuntimeHasNoBinDir(RuntimeLayout runtimeLayout) { if (Files.isDirectory(runtimeLayout.runtimeDirectory().resolve("bin"))) { throw I18N.buildConfigException() .message("error.invalid-runtime-image-bin-dir", runtimeLayout.rootDirectory()) diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/SigningIdentityBuilder.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/SigningIdentityBuilder.java index f90e76bb23d..9a57ce1e697 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/SigningIdentityBuilder.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/SigningIdentityBuilder.java @@ -82,7 +82,7 @@ final class SigningIdentityBuilder { return this; } - Optional create() throws ConfigException { + Optional create() { if (signingIdentity == null && certificateSelector == null) { return Optional.empty(); } else { @@ -90,11 +90,11 @@ final class SigningIdentityBuilder { } } - private Optional validatedKeychain() throws ConfigException { + private Optional validatedKeychain() { return Optional.ofNullable(keychain).map(Keychain::new); } - private SigningIdentity validatedSigningIdentity() throws ConfigException { + private SigningIdentity validatedSigningIdentity() { if (signingIdentity != null) { return new SigningIdentityImpl(signingIdentity); } @@ -142,7 +142,7 @@ final class SigningIdentityBuilder { } private static X509Certificate selectSigningIdentity(List certs, - CertificateSelector certificateSelector, Optional keychain) throws ConfigException { + CertificateSelector certificateSelector, Optional keychain) { Objects.requireNonNull(certificateSelector); Objects.requireNonNull(keychain); switch (certs.size()) { diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/model/MacApplication.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/model/MacApplication.java index 04ab7042ac5..cfe10e8a012 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/model/MacApplication.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/model/MacApplication.java @@ -25,13 +25,18 @@ package jdk.jpackage.internal.model; import static java.util.stream.Collectors.joining; -import static java.util.stream.Collectors.toMap; +import static java.util.stream.Collectors.toUnmodifiableMap; +import static jdk.jpackage.internal.cli.StandardAppImageFileOption.MAC_APP_STORE; +import static jdk.jpackage.internal.cli.StandardAppImageFileOption.MAC_MAIN_CLASS; +import static jdk.jpackage.internal.cli.StandardAppImageFileOption.MAC_SIGNED; import java.nio.file.Path; import java.util.Map; +import java.util.Optional; import java.util.function.Function; import java.util.stream.IntStream; import java.util.stream.Stream; +import jdk.jpackage.internal.cli.OptionValue; import jdk.jpackage.internal.util.CompositeProxy; public interface MacApplication extends Application, MacApplicationMixin { @@ -76,7 +81,14 @@ public interface MacApplication extends Application, MacApplicationMixin { @Override default Map extraAppImageFileData() { - return Stream.of(ExtraAppImageFileField.values()).collect(toMap(ExtraAppImageFileField::fieldName, x -> x.asString(this))); + return Stream.of(ExtraAppImageFileField.values()).map(field -> { + return field.findStringValue(this).map(value -> { + return Map.entry(field.fieldName(), value); + }); + }) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue)); } public static MacApplication create(Application app, MacApplicationMixin mixin) { @@ -84,23 +96,31 @@ public interface MacApplication extends Application, MacApplicationMixin { } public enum ExtraAppImageFileField { - SIGNED("signed", app -> Boolean.toString(app.sign())), - APP_STORE("app-store", app -> Boolean.toString(app.appStore())); + SIGNED(MAC_SIGNED, app -> { + return Optional.of(Boolean.toString(app.sign())); + }), + APP_STORE(MAC_APP_STORE, app -> { + return Optional.of(Boolean.toString(app.appStore())); + }), + APP_CLASS(MAC_MAIN_CLASS, app -> { + return app.mainLauncher().flatMap(Launcher::startupInfo).map(LauncherStartupInfo::qualifiedClassName); + }), + ; - ExtraAppImageFileField(String fieldName, Function getter) { - this.fieldName = fieldName; + ExtraAppImageFileField(OptionValue option, Function> getter) { + this.fieldName = option.getName(); this.getter = getter; } - public String fieldName() { + String fieldName() { return fieldName; } - String asString(MacApplication app) { + Optional findStringValue(MacApplication app) { return getter.apply(app); } private final String fieldName; - private final Function getter; + private final Function> getter; } } diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources.properties b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources.properties index afa71d84d5c..d01297aa814 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources.properties +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources.properties @@ -23,26 +23,17 @@ # questions. # # - -app.bundler.name=Mac Application Image -store.bundler.name=Mac App Store Ready Bundler -dmg.bundler.name=Mac DMG Package -pkg.bundler.name=Mac PKG Package - error.invalid-cfbundle-version.advice=Set a compatible 'app-version' value. Valid versions are one to three integers separated by dots. error.explicit-sign-no-cert=Signature explicitly requested but no signing certificate found error.explicit-sign-no-cert.advice=Specify a valid mac-signing-key-user-name and mac-signing-keychain -error.must-sign-app-store=Mac App Store apps must be signed, and signing has been disabled by bundler configuration -error.must-sign-app-store.advice=Use --mac-sign option with appropriate user-name and keychain -error.certificate.expired=Error: Certificate expired {0} +error.certificate.expired=Certificate expired {0} error.cert.not.found=No certificate found matching [{0}] using keychain [{1}] error.multiple.certs.found=Multiple certificates matching name [{0}] found in keychain [{1}] -error.app-image.mac-sign.required=Error: --mac-sign option is required with predefined application image and with type [app-image] -error.tool.failed.with.output=Error: "{0}" failed with following output: +error.app-image.mac-sign.required=--mac-sign option is required with predefined application image and with type [app-image] +error.tool.failed.with.output="{0}" failed with following output: error.invalid-runtime-image-missing-file=Runtime image "{0}" is missing "{1}" file error.invalid-runtime-image-bin-dir=Runtime image "{0}" should not contain "bin" folder error.invalid-runtime-image-bin-dir.advice=Use --strip-native-commands jlink option when generating runtime image used with {0} option -resource.bundle-config-file=Bundle config file resource.app-info-plist=Application Info.plist resource.app-runtime-info-plist=Embedded Java Runtime Info.plist resource.runtime-info-plist=Java Runtime Info.plist @@ -60,21 +51,15 @@ resource.pkg-background-image=pkg background image resource.pkg-pdf=project definition file resource.launchd-plist-file=launchd plist file - message.bundle-name-too-long-warning={0} is set to ''{1}'', which is longer than 16 characters. For a better Mac experience consider shortening it. message.preparing-info-plist=Preparing Info.plist: {0}. message.icon-not-icns= The specified icon "{0}" is not an ICNS file and will not be used. The default icon will be used in it's place. message.version-string-too-many-components='app-version' may have between 1 and 3 numbers: 1, 1.2, 1.2.3. message.version-string-first-number-not-zero=The first number in an app-version cannot be zero or negative. -message.creating-association-with-null-extension=Creating association with null extension. -message.ignoring.symlink=Warning: codesign is skipping the symlink {0}. -message.already.signed=File already signed: {0}. -message.keychain.error=Error: unable to get keychain list. -message.building-bundle=Building Mac App Store Package for {0}. -message.invalid-identifier=invalid mac bundle identifier [{0}]. +message.keychain.error=Unable to get keychain list. +message.invalid-identifier=Invalid mac bundle identifier [{0}]. message.invalid-identifier.advice=specify identifier with "--mac-package-identifier". message.building-dmg=Building DMG package for {0}. -message.running-script=Running shell script on application image [{0}]. message.preparing-dmg-setup=Preparing dmg setup: {0}. message.creating-dmg-file=Creating DMG file: {0}. message.dmg-cannot-be-overwritten=Dmg file exists [{0}] and can not be removed. diff --git a/src/jdk.jpackage/macosx/classes/module-info.java.extra b/src/jdk.jpackage/macosx/classes/module-info.java.extra index 1496167cd4a..e6202dd1156 100644 --- a/src/jdk.jpackage/macosx/classes/module-info.java.extra +++ b/src/jdk.jpackage/macosx/classes/module-info.java.extra @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,8 +23,5 @@ * questions. */ -provides jdk.jpackage.internal.Bundler with - jdk.jpackage.internal.MacAppBundler, - jdk.jpackage.internal.MacDmgBundler, - jdk.jpackage.internal.MacPkgBundler; - +provides jdk.jpackage.internal.cli.CliBundlingEnvironment with + jdk.jpackage.internal.MacBundlingEnvironment; diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AddLauncherArguments.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AddLauncherArguments.java deleted file mode 100644 index 93d037c6a45..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AddLauncherArguments.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.jpackage.internal; - -import java.nio.file.Path; -import java.util.HashMap; -import java.util.Map; -import java.util.List; -import java.util.Optional; - -import jdk.internal.util.OperatingSystem; - -import jdk.jpackage.internal.Arguments.CLIOptions; -import static jdk.jpackage.internal.StandardBundlerParam.LAUNCHER_DATA; -import static jdk.jpackage.internal.StandardBundlerParam.APP_NAME; - -/* - * AddLauncherArguments - * - * Processes a add-launcher properties file to create the Map of - * bundle params applicable to the add-launcher: - * - * BundlerParams p = (new AddLauncherArguments(file)).getLauncherMap(); - * - * A add-launcher is another executable program generated by either the - * create-app-image mode or the create-installer mode. - * The add-launcher may be the same program with different configuration, - * or a completely different program created from the same files. - * - * There may be multiple add-launchers, each created by using the - * command line arg "--add-launcher - * - * The add-launcher properties file may have any of: - * - * appVersion - * description - * module - * main-jar - * main-class - * icon - * arguments - * java-options - * launcher-as-service - * win-console - * win-shortcut - * win-menu - * linux-app-category - * linux-shortcut - * - */ -class AddLauncherArguments { - - private final String name; - private final String filename; - private Map allArgs; - private Map bundleParams; - - AddLauncherArguments(String name, String filename) { - this.name = name; - this.filename = filename; - } - - private void initLauncherMap() { - if (bundleParams != null) { - return; - } - - allArgs = Arguments.getPropertiesFromFile(filename); - allArgs.put(CLIOptions.NAME.getId(), name); - - bundleParams = new HashMap<>(); - String mainJar = getOptionValue(CLIOptions.MAIN_JAR); - String mainClass = getOptionValue(CLIOptions.APPCLASS); - String module = getOptionValue(CLIOptions.MODULE); - - if (module != null && mainClass != null) { - Arguments.putUnlessNull(bundleParams, CLIOptions.MODULE.getId(), - module + "/" + mainClass); - } else if (module != null) { - Arguments.putUnlessNull(bundleParams, CLIOptions.MODULE.getId(), - module); - } else { - Arguments.putUnlessNull(bundleParams, CLIOptions.MAIN_JAR.getId(), - mainJar); - Arguments.putUnlessNull(bundleParams, CLIOptions.APPCLASS.getId(), - mainClass); - } - - Arguments.putUnlessNull(bundleParams, CLIOptions.NAME.getId(), - getOptionValue(CLIOptions.NAME)); - - Arguments.putUnlessNull(bundleParams, CLIOptions.VERSION.getId(), - getOptionValue(CLIOptions.VERSION)); - - Arguments.putUnlessNull(bundleParams, CLIOptions.DESCRIPTION.getId(), - getOptionValue(CLIOptions.DESCRIPTION)); - - Arguments.putUnlessNull(bundleParams, CLIOptions.RELEASE.getId(), - getOptionValue(CLIOptions.RELEASE)); - - Arguments.putUnlessNull(bundleParams, CLIOptions.ICON.getId(), - Optional.ofNullable(getOptionValue(CLIOptions.ICON)).map( - Path::of).orElse(null)); - - Arguments.putUnlessNull(bundleParams, - CLIOptions.LAUNCHER_AS_SERVICE.getId(), getOptionValue( - CLIOptions.LAUNCHER_AS_SERVICE)); - - if (OperatingSystem.isWindows()) { - Arguments.putUnlessNull(bundleParams, - CLIOptions.WIN_CONSOLE_HINT.getId(), - getOptionValue(CLIOptions.WIN_CONSOLE_HINT)); - Arguments.putUnlessNull(bundleParams, CLIOptions.WIN_SHORTCUT_HINT.getId(), - getOptionValue(CLIOptions.WIN_SHORTCUT_HINT)); - Arguments.putUnlessNull(bundleParams, CLIOptions.WIN_MENU_HINT.getId(), - getOptionValue(CLIOptions.WIN_MENU_HINT)); - } - - if (OperatingSystem.isLinux()) { - Arguments.putUnlessNull(bundleParams, CLIOptions.LINUX_CATEGORY.getId(), - getOptionValue(CLIOptions.LINUX_CATEGORY)); - Arguments.putUnlessNull(bundleParams, CLIOptions.LINUX_SHORTCUT_HINT.getId(), - getOptionValue(CLIOptions.LINUX_SHORTCUT_HINT)); - } - - // "arguments" and "java-options" even if value is null: - if (allArgs.containsKey(CLIOptions.ARGUMENTS.getId())) { - String argumentStr = getOptionValue(CLIOptions.ARGUMENTS); - bundleParams.put(CLIOptions.ARGUMENTS.getId(), - Arguments.getArgumentList(argumentStr)); - } - - if (allArgs.containsKey(CLIOptions.JAVA_OPTIONS.getId())) { - String jvmargsStr = getOptionValue(CLIOptions.JAVA_OPTIONS); - bundleParams.put(CLIOptions.JAVA_OPTIONS.getId(), - Arguments.getArgumentList(jvmargsStr)); - } - } - - private String getOptionValue(CLIOptions option) { - if (option == null || allArgs == null) { - return null; - } - - String id = option.getId(); - - if (allArgs.containsKey(id)) { - return allArgs.get(id); - } - - return null; - } - - Map getLauncherMap() { - initLauncherMap(); - return bundleParams; - } - - static Map merge( - Map original, - Map additional, String... exclude) { - Map tmp = new HashMap<>(original); - List.of(exclude).forEach(tmp::remove); - - // remove LauncherData from map so it will be re-computed - tmp.remove(LAUNCHER_DATA.getID()); - // remove "application-name" so it will be re-computed - tmp.remove(APP_NAME.getID()); - - if (additional.containsKey(CLIOptions.MODULE.getId())) { - tmp.remove(CLIOptions.MAIN_JAR.getId()); - tmp.remove(CLIOptions.APPCLASS.getId()); - } else if (additional.containsKey(CLIOptions.MAIN_JAR.getId())) { - tmp.remove(CLIOptions.MODULE.getId()); - } - if (additional.containsKey(CLIOptions.ARGUMENTS.getId())) { - // if add launcher properties file contains "arguments", even with - // null value, disregard the "arguments" from command line - tmp.remove(CLIOptions.ARGUMENTS.getId()); - } - if (additional.containsKey(CLIOptions.JAVA_OPTIONS.getId())) { - // same thing for java-options - tmp.remove(CLIOptions.JAVA_OPTIONS.getId()); - } - tmp.putAll(additional); - return tmp; - } - -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageBundler.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageBundler.java deleted file mode 100644 index 192630a5656..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageBundler.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.jpackage.internal; - -import static jdk.jpackage.internal.StandardBundlerParam.APP_NAME; -import static jdk.jpackage.internal.StandardBundlerParam.LAUNCHER_DATA; -import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.text.MessageFormat; -import java.util.Map; -import java.util.Objects; -import jdk.internal.util.OperatingSystem; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.model.PackagerException; - - -class AppImageBundler extends AbstractBundler { - - @Override - public final String getName() { - return I18N.getString("app.bundler.name"); - } - - @Override - public final String getID() { - return "app"; - } - - @Override - public final String getBundleType() { - return "IMAGE"; - } - - @Override - public final boolean validate(Map params) - throws ConfigException { - try { - Objects.requireNonNull(params); - - if (!params.containsKey(PREDEFINED_APP_IMAGE.getID()) - && !StandardBundlerParam.isRuntimeInstaller(params)) { - LAUNCHER_DATA.fetchFrom(params); - } - - if (paramsValidator != null) { - paramsValidator.validate(params); - } - } catch (RuntimeException re) { - if (re.getCause() instanceof ConfigException) { - throw (ConfigException) re.getCause(); - } else { - throw new ConfigException(re); - } - } - - return true; - } - - @Override - public final Path execute(Map params, - Path outputParentDir) throws PackagerException { - - final var predefinedAppImage = PREDEFINED_APP_IMAGE.fetchFrom(params); - - try { - if (predefinedAppImage == null) { - Path rootDirectory = createRoot(params, outputParentDir); - appImageSupplier.prepareApplicationFiles(params, rootDirectory); - return rootDirectory; - } else { - appImageSupplier.prepareApplicationFiles(params, predefinedAppImage); - return predefinedAppImage; - } - } catch (PackagerException pe) { - throw pe; - } catch (RuntimeException|IOException ex) { - Log.verbose(ex); - throw new PackagerException(ex); - } - } - - @Override - public final boolean supported(boolean runtimeInstaller) { - return true; - } - - @Override - public final boolean isDefault() { - return false; - } - - @FunctionalInterface - static interface AppImageSupplier { - - void prepareApplicationFiles(Map params, - Path root) throws PackagerException, IOException; - } - - final AppImageBundler setAppImageSupplier(AppImageSupplier v) { - appImageSupplier = v; - return this; - } - - final AppImageBundler setParamsValidator(ParamsValidator v) { - paramsValidator = v; - return this; - } - - @FunctionalInterface - interface ParamsValidator { - void validate(Map params) throws ConfigException; - } - - private Path createRoot(Map params, - Path outputDirectory) throws PackagerException, IOException { - - IOUtils.writableOutputDir(outputDirectory); - - String imageName = APP_NAME.fetchFrom(params); - if (OperatingSystem.isMacOS()) { - imageName = imageName + ".app"; - } - - Log.verbose(MessageFormat.format( - I18N.getString("message.creating-app-bundle"), - imageName, outputDirectory.toAbsolutePath())); - - // Create directory structure - Path rootDirectory = outputDirectory.resolve(imageName); - if (Files.exists(rootDirectory)) { - throw new PackagerException("error.root-exists", - rootDirectory.toAbsolutePath().toString()); - } - - Files.createDirectories(rootDirectory); - - return rootDirectory; - } - - private ParamsValidator paramsValidator; - private AppImageSupplier appImageSupplier; -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageFile.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageFile.java index 8b8a22edc56..b6b4322302e 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageFile.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageFile.java @@ -25,148 +25,149 @@ package jdk.jpackage.internal; import static java.util.stream.Collectors.joining; -import static java.util.stream.Collectors.toMap; -import static java.util.stream.Collectors.toSet; +import static java.util.stream.Collectors.toUnmodifiableMap; +import static java.util.stream.Collectors.toUnmodifiableSet; +import static jdk.jpackage.internal.cli.StandardAppImageFileOption.APP_VERSION; +import static jdk.jpackage.internal.cli.StandardAppImageFileOption.LAUNCHER_AS_SERVICE; +import static jdk.jpackage.internal.cli.StandardAppImageFileOption.DESCRIPTION; +import static jdk.jpackage.internal.cli.StandardAppImageFileOption.LAUNCHER_NAME; import static jdk.jpackage.internal.util.function.ThrowingFunction.toFunction; import java.io.IOException; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import java.util.Objects; -import java.util.Optional; import java.util.Set; import java.util.stream.Stream; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import jdk.internal.util.OperatingSystem; +import jdk.jpackage.internal.cli.OptionValue; +import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.cli.StandardAppImageFileOption.AppImageFileOptionScope; +import jdk.jpackage.internal.cli.StandardAppImageFileOption.InvalidOptionValueException; +import jdk.jpackage.internal.cli.StandardAppImageFileOption.MissingMandatoryOptionException; import jdk.jpackage.internal.model.Application; import jdk.jpackage.internal.model.ApplicationLayout; -import jdk.jpackage.internal.model.ConfigException; import jdk.jpackage.internal.model.ExternalApplication; +import jdk.jpackage.internal.model.JPackageException; import jdk.jpackage.internal.model.Launcher; import jdk.jpackage.internal.util.XmlUtils; +import jdk.jpackage.internal.util.function.ExceptionBox; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.xml.sax.SAXException; -final class AppImageFile implements ExternalApplication { +final class AppImageFile { AppImageFile(Application app) { - this(new ApplicationData(app)); - } - - private AppImageFile(ApplicationData app) { - - appVersion = app.version(); - launcherName = app.mainLauncherName(); - mainClass = app.mainLauncherMainClassName(); - extra = app.extra; - creatorVersion = getVersion(); - creatorPlatform = getPlatform(); - addLauncherInfos = app.additionalLaunchers; - } - - @Override - public List getAddLaunchers() { - return addLauncherInfos; - } - - @Override - public String getAppVersion() { - return appVersion; - } - - @Override - public String getAppName() { - return launcherName; - } - - @Override - public String getLauncherName() { - return launcherName; - } - - @Override - public String getMainClass() { - return mainClass; - } - - @Override - public Map getExtra() { - return extra; + appVersion = Objects.requireNonNull(app.version()); + extra = Objects.requireNonNull(app.extraAppImageFileData()); + launcherInfos = app.launchers().stream().map(LauncherInfo::new).toList(); } /** - * Saves file with application image info in application image using values - * from this instance. + * Writes the values captured in this instance into the application image info + * file in the given application layout. + *

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

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

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

    + * The scope of bundling operations. E.g., app image or native package bundling. + */ +interface BundlingOperationOptionScope extends OptionScope { + BundlingOperationDescriptor descriptor(); +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/CliBundlingEnvironment.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/CliBundlingEnvironment.java new file mode 100644 index 00000000000..09ff0997c14 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/CliBundlingEnvironment.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.jpackage.internal.cli; + +import java.util.NoSuchElementException; +import jdk.jpackage.internal.model.BundlingEnvironment; +import jdk.jpackage.internal.model.BundlingOperationDescriptor; + +/** + * CLI bundling environment. + */ +public interface CliBundlingEnvironment extends BundlingEnvironment { + + /** + * Requests to run a bundling operation denoted with the given descriptor with + * the given values of command line options. + * + * @param op the descriptor of the requested bundling operation + * @param cmdline the validated values of the command line options + * @throws NoSuchElementException if the specified descriptor is not one of the + * items in the list returned by + * {@link #supportedOperations()} method + */ + void createBundle(BundlingOperationDescriptor op, Options cmdline); +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/DefaultOptions.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/DefaultOptions.java new file mode 100644 index 00000000000..91342500277 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/DefaultOptions.java @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.jpackage.internal.cli; + +import static java.util.stream.Collectors.toUnmodifiableMap; +import static java.util.stream.Collectors.toUnmodifiableSet; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + + +final class DefaultOptions implements Options { + + DefaultOptions(Map values) { + this(values, Optional.empty()); + } + + DefaultOptions( + Map values, + Predicate optionNamesFilter) { + + this(values, Optional.of(optionNamesFilter)); + } + + DefaultOptions( + Map values, + Optional> optionNamesFilter) { + + map = values.entrySet().stream().collect(toUnmodifiableMap(e -> { + return e.getKey().id(); + }, e -> { + return new OptionIdentifierWithValue(e.getKey(), e.getValue()); + })); + + var optionNamesStream = optionNames(values.keySet().stream()); + optionNames = optionNamesFilter.map(optionNamesStream::filter).orElse(optionNamesStream) + .collect(toUnmodifiableSet()); + } + + private DefaultOptions(Snapshot snapshot) { + map = snapshot.map(); + optionNames = snapshot.optionNames(); + } + + static DefaultOptions create(Snapshot snapshot) { + var options = new DefaultOptions(snapshot); + + var mapOptionNames = optionNames( + options.map.values().stream().map(OptionIdentifierWithValue::withId) + ).collect(toUnmodifiableSet()); + + for (var e : options.map.entrySet()) { + if (e.getKey() != e.getValue().withId().id()) { + throw new IllegalArgumentException("Corrupted options map"); + } + } + + if (!mapOptionNames.containsAll(snapshot.optionNames())) { + throw new IllegalArgumentException("Unexpected option names"); + } + return options; + } + + @Override + public Optional find(OptionIdentifier id) { + return Optional.ofNullable(map.get(Objects.requireNonNull(id))).map(OptionIdentifierWithValue::value); + } + + @Override + public boolean contains(OptionName optionName) { + return optionNames.contains(Objects.requireNonNull(optionName)); + } + + @Override + public Set ids() { + return Collections.unmodifiableSet(map.keySet()); + } + + @Override + public DefaultOptions copyWithout(Iterable ids) { + return copy(StreamSupport.stream(ids.spliterator(), false), false); + } + + @Override + public DefaultOptions copyWith(Iterable ids) { + return copy(StreamSupport.stream(ids.spliterator(), false), true); + } + + DefaultOptions add(DefaultOptions other) { + return new DefaultOptions(new Snapshot(Stream.of(this, other).flatMap(v -> { + return v.map.values().stream(); + }).collect(toUnmodifiableMap(OptionIdentifierWithValue::id, x -> x, (first, _) -> { + return first; + })), Stream.of(this, other) + .map(DefaultOptions::optionNames) + .flatMap(Collection::stream) + .collect(toUnmodifiableSet()))); + } + + Set optionNames() { + return optionNames; + } + + Set withOptionIdentifierSet() { + return map.values().stream() + .map(OptionIdentifierWithValue::withId) + .collect(toUnmodifiableSet()); + } + + record Snapshot(Map map, Set optionNames) { + Snapshot { + Objects.requireNonNull(map); + Objects.requireNonNull(optionNames); + } + } + + record OptionIdentifierWithValue(WithOptionIdentifier withId, Object value) { + OptionIdentifierWithValue { + Objects.requireNonNull(withId); + Objects.requireNonNull(value); + } + + OptionIdentifier id() { + return withId.id(); + } + + OptionIdentifierWithValue copyWithValue(Object value) { + return new OptionIdentifierWithValue(withId, value); + } + } + + private DefaultOptions copy(Stream ids, boolean includes) { + var includeIds = ids.collect(toUnmodifiableSet()); + return new DefaultOptions(map.values().stream().filter(v -> { + return includeIds.contains(v.id()) == includes; + }).collect(toUnmodifiableMap(OptionIdentifierWithValue::withId, OptionIdentifierWithValue::value))); + } + + private static Stream optionNames(Stream options) { + return options.map(v -> { + Optional> spec; + switch (v) { + case Option option -> { + spec = Optional.of(option.spec()); + } + case OptionValue optionValue -> { + spec = optionValue.asOption().map(Option::spec); + } + default -> { + spec = Optional.empty(); + } + } + return spec; + }).filter(Optional::isPresent).map(Optional::get).map(OptionSpec::names).flatMap(Collection::stream); + } + + static final DefaultOptions EMPTY = new DefaultOptions(Map.of()); + + private final Map map; + private final Set optionNames; +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/HelpFormatter.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/HelpFormatter.java new file mode 100644 index 00000000000..1d8a5eefc79 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/HelpFormatter.java @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.jpackage.internal.cli; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; + + +/** + * Generic help formatter. + */ +final class HelpFormatter { + + private HelpFormatter(List optionGroups, OptionGroupFormatter formatter) { + this.optionGroups = Objects.requireNonNull(optionGroups); + this.formatter = Objects.requireNonNull(formatter); + } + + void format(Consumer sink) { + for (var group : optionGroups) { + formatter.format(group, sink); + } + } + + static Builder build() { + return new Builder(); + } + + + static final class Builder { + + private Builder() { + } + + HelpFormatter create() { + return new HelpFormatter(groups, validatedGroupFormatter()); + } + + Builder groups(Collection v) { + groups.addAll(v); + return this; + } + + Builder groups(OptionGroup... v) { + return groups(List.of(v)); + } + + Builder groupFormatter(OptionGroupFormatter v) { + groupFormatter = v; + return this; + } + + private OptionGroupFormatter validatedGroupFormatter() { + return Optional.ofNullable(groupFormatter).orElseGet(Builder::createConsoleFormatter); + } + + private static OptionGroupFormatter createConsoleFormatter() { + return new ConsoleOptionGroupFormatter(new ConsoleOptionFormatter(2, 10)); + } + + private final List groups = new ArrayList<>(); + private OptionGroupFormatter groupFormatter; + } + + + interface OptionFormatter { + + public default void format(OptionSpec optionSpec, Consumer sink) { + format(optionSpec.names().stream().map(OptionName::formatForCommandLine).collect(Collectors.joining(" ")), + optionSpec.valuePattern(), + optionSpec.description(), sink); + } + + void format(String optionNames, Optional valuePattern, String description, Consumer sink); + } + + interface OptionGroupFormatter { + + default void format(OptionGroup group, Consumer sink) { + formatHeader(group.name(), sink); + formatBody(group.options(), sink); + } + + void formatHeader(String gropName, Consumer sink); + + void formatBody(Iterable> optionSpecs, Consumer sink); + } + + + record ConsoleOptionFormatter(int nameOffset, int descriptionOffset) implements OptionFormatter { + + @Override + public void format(String optionNames, Optional valuePattern, String description, Consumer sink) { + sink.accept(" ".repeat(nameOffset)); + sink.accept(optionNames); + valuePattern.map(v -> " " + v).ifPresent(sink); + eol(sink); + final var descriptionOffsetStr = " ".repeat(descriptionOffset); + Stream.of(description.split("\\R")).map(line -> { + return descriptionOffsetStr + line; + }).forEach(line -> { + sink.accept(line); + eol(sink); + }); + } + } + + + record ConsoleOptionGroupFormatter(OptionFormatter optionFormatter) implements OptionGroupFormatter { + + ConsoleOptionGroupFormatter { + Objects.requireNonNull(optionFormatter); + } + + @Override + public void formatHeader(String groupName, Consumer sink) { + Objects.requireNonNull(groupName); + eol(sink); + sink.accept(groupName + ":"); + eol(sink); + } + + @Override + public void formatBody(Iterable> optionSpecs, Consumer sink) { + optionSpecs.forEach(optionSpec -> { + optionFormatter.format(optionSpec, sink); + }); + } + } + + + record OptionGroup(String name, List> options) { + + OptionGroup { + Objects.requireNonNull(name); + Objects.requireNonNull(options); + } + } + + + static Consumer eol(Consumer sink) { + sink.accept(System.lineSeparator()); + return sink; + } + + + private final List optionGroups; + private final OptionGroupFormatter formatter; +} diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBaseInstallerBundler.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/I18N.java similarity index 56% rename from src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBaseInstallerBundler.java rename to src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/I18N.java index 5c912728c32..63c2b88039f 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBaseInstallerBundler.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/I18N.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,31 +22,34 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ +package jdk.jpackage.internal.cli; -package jdk.jpackage.internal; - -import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE; - +import java.util.List; import java.util.Map; -import jdk.jpackage.internal.model.ConfigException; +import jdk.internal.util.OperatingSystem; +import jdk.jpackage.internal.util.MultiResourceBundle; +import jdk.jpackage.internal.util.StringBundle; -public abstract class MacBaseInstallerBundler extends AbstractBundler { +final class I18N { - public MacBaseInstallerBundler() { - appImageBundler = new MacAppBundler(); + private I18N() { } - protected void validateAppImageAndBundeler( - Map params) throws ConfigException { - if (PREDEFINED_APP_IMAGE.fetchFrom(params) == null) { - appImageBundler.validate(params); - } + static String format(String key, Object ... args) { + return BUNDLE.format(key, args); } - @Override - public String getBundleType() { - return "INSTALLER"; - } + private static final StringBundle BUNDLE; - private final Bundler appImageBundler; + static { + var prefix = "jdk.jpackage.internal.resources."; + BUNDLE = StringBundle.fromResourceBundle(MultiResourceBundle.create( + prefix + "MainResources", + Map.of( + OperatingSystem.LINUX, List.of(prefix + "LinuxResources"), + OperatingSystem.MACOS, List.of(prefix + "MacResources"), + OperatingSystem.WINDOWS, List.of(prefix + "WinResources") + ) + )); + } } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/JOptSimpleOptionsBuilder.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/JOptSimpleOptionsBuilder.java new file mode 100644 index 00000000000..57b92471e4a --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/JOptSimpleOptionsBuilder.java @@ -0,0 +1,817 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.jpackage.internal.cli; + +import static java.util.stream.Collectors.toUnmodifiableMap; +import static java.util.stream.Collectors.toUnmodifiableSet; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.function.UnaryOperator; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import jdk.internal.joptsimple.ArgumentAcceptingOptionSpec; +import jdk.internal.joptsimple.OptionParser; +import jdk.internal.joptsimple.OptionSet; +import jdk.jpackage.internal.cli.DefaultOptions.OptionIdentifierWithValue; +import jdk.jpackage.internal.cli.DefaultOptions.Snapshot; +import jdk.jpackage.internal.cli.OptionSpec.MergePolicy; +import jdk.jpackage.internal.util.Result; + + +/** + * Builds an instance of {@link Options} interface backed with joptsimple command + * line parser. + * + * Two types of command line argument processing are supported: + *
      + *
    1. Parse command line. Parsed data is stored as a map of strings. + *
    2. Convert strings to objects. Parsed data is stored as a map of objects. + *
    + */ +final class JOptSimpleOptionsBuilder { + + Function> create() { + return createJOptSimpleParser()::parse; + } + + JOptSimpleOptionsBuilder options(Collection v) { + v.stream().map(u -> { + switch (u) { + case Option o -> { + return o; + } + case OptionValue ov -> { + return ov.getOption(); + } + default -> { + throw new IllegalArgumentException(); + } + } + }).forEach(options::add); + return this; + } + + JOptSimpleOptionsBuilder options(WithOptionIdentifier... v) { + return options(List.of(v)); + } + + JOptSimpleOptionsBuilder optionSpecMapper(UnaryOperator> v) { + optionSpecMapper = v; + return this; + } + + JOptSimpleOptionsBuilder jOptSimpleParserErrorHandler(Function v) { + jOptSimpleParserErrorHandler = v; + return this; + } + + private JOptSimpleParser createJOptSimpleParser() { + return JOptSimpleParser.create(options, Optional.ofNullable(optionSpecMapper), + Optional.ofNullable(jOptSimpleParserErrorHandler)); + } + + + static final class ConvertedOptionsBuilder { + + private ConvertedOptionsBuilder(TypedOptions options) { + impl = Objects.requireNonNull(options); + } + + Options create() { + return impl; + } + + ConvertedOptionsBuilder copyWithExcludes(Collection v) { + return new ConvertedOptionsBuilder(impl.copyWithout(v)); + } + + List nonOptionArguments() { + return impl.nonOptionArguments(); + } + + List detectedOptions() { + return impl.detectedOptions(); + } + + private final TypedOptions impl; + } + + + static final class OptionsBuilder { + + private OptionsBuilder(UntypedOptions options) { + impl = Objects.requireNonNull(options); + } + + Result convertedOptions() { + return impl.toTypedOptions().map(ConvertedOptionsBuilder::new); + } + + Options create() { + return impl; + } + + OptionsBuilder copyWithExcludes(Collection v) { + return new OptionsBuilder(impl.copyWithout(v)); + } + + List nonOptionArguments() { + return impl.nonOptionArguments(); + } + + List detectedOptions() { + return impl.detectedOptions(); + } + + private final UntypedOptions impl; + } + + + enum JOptSimpleErrorType { + + // jdk.internal.joptsimple.UnrecognizedOptionException + UNRECOGNIZED_OPTION(() -> { + new OptionParser(false).parse("--foo"); + }), + + // jdk.internal.joptsimple.OptionMissingRequiredArgumentException + OPTION_MISSING_REQUIRED_ARGUMENT(() -> { + var parser = new OptionParser(false); + parser.accepts("foo").withRequiredArg(); + parser.parse("--foo"); + }), + ; + + JOptSimpleErrorType(Runnable initializer) { + try { + initializer.run(); + // Should never get to this point as the above line is expected to throw + // an exception of type `jdk.internal.joptsimple.OptionException`. + throw new AssertionError(); + } catch (jdk.internal.joptsimple.OptionException ex) { + type = ex.getClass(); + } + } + + private final Class type; + } + + + record JOptSimpleError(JOptSimpleErrorType type, OptionName optionName) { + + JOptSimpleError { + Objects.requireNonNull(type); + Objects.requireNonNull(optionName); + } + + static JOptSimpleError create(jdk.internal.joptsimple.OptionException ex) { + var optionName = OptionName.of(ex.options().getFirst()); + return Stream.of(JOptSimpleErrorType.values()).filter(v -> { + return v.type.isInstance(ex); + }).findFirst().map(v -> { + return new JOptSimpleError(v, optionName); + }).orElseThrow(); + } + } + + + private record JOptSimpleParser( + OptionParser parser, + Map> optionMap, + Optional> jOptSimpleParserErrorHandler) { + + private JOptSimpleParser { + Objects.requireNonNull(parser); + Objects.requireNonNull(optionMap); + Objects.requireNonNull(jOptSimpleParserErrorHandler); + } + + Result parse(String... args) { + return applyParser(parser, args).map(optionSet -> { + final OptionSet mergerOptionSet; + if (optionMap.values().stream().allMatch(spec -> spec.names().size() == 1)) { + // No specs with multiple names, merger not needed. + mergerOptionSet = optionSet; + } else { + final var parser2 = createOptionParser(); + final var optionSpecApplier = new OptionSpecApplier(); + for (final var spec : optionMap.values()) { + optionSpecApplier.applyToParser(parser2, spec); + } + + mergerOptionSet = parser2.parse(args); + } + return new OptionsBuilder(new UntypedOptions(optionSet, mergerOptionSet, optionMap)); + }); + } + + static JOptSimpleParser create(Iterable