diff --git a/src/java.base/share/native/libjli/args.c b/src/java.base/share/native/libjli/args.c index 54b31c8ecb0..547b279b7de 100644 --- a/src/java.base/share/native/libjli/args.c +++ b/src/java.base/share/native/libjli/args.c @@ -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 @@ -83,6 +83,9 @@ static jboolean relaunch = JNI_FALSE; * Prototypes for internal functions. */ static jboolean expand(JLI_List args, const char *str, const char *var_name); +#ifdef _WIN32 +static char * winGetEnv(const char * var_name); +#endif JNIEXPORT void JNICALL JLI_InitArgProcessing(jboolean hasJavaArgs, jboolean disableArgFile) { @@ -465,8 +468,6 @@ int isTerminalOpt(char *arg) { JNIEXPORT jboolean JNICALL JLI_AddArgsFromEnvVar(JLI_List args, const char *var_name) { - char *env = getenv(var_name); - if (firstAppArgIndex == 0) { // Not 'java', return return JNI_FALSE; @@ -476,12 +477,25 @@ JLI_AddArgsFromEnvVar(JLI_List args, const char *var_name) { return JNI_FALSE; } - if (NULL == env) { - return JNI_FALSE; +#ifdef _WIN32 + char *env = winGetEnv(var_name); +#else + char *env = getenv(var_name); +#endif + + jboolean ret = JNI_FALSE; + + if (NULL != env) { + JLI_ReportMessage(ARG_INFO_ENVVAR, var_name, env); + ret = expand(args, env, var_name); } - JLI_ReportMessage(ARG_INFO_ENVVAR, var_name, env); - return expand(args, env, var_name); +#ifdef _WIN32 + if (NULL != env) { + JLI_MemFree(env); + } +#endif + return ret; } /* @@ -583,6 +597,39 @@ static jboolean expand(JLI_List args, const char *str, const char *var_name) { return JNI_TRUE; } +#ifdef _WIN32 +/* + * getenv() without best-fit mapping. The return value is constructed by converting + * _wgetenv()'s return encoded in wide char to ANSI code page without best-fit map. + */ +static char * winGetEnv(const char * var_name) { + char * mbEnvVar = NULL; + + int wcCount = MultiByteToWideChar(CP_ACP, 0, var_name, -1, NULL, 0); + if (wcCount > 0) { + LPWSTR wcVarName = JLI_MemAlloc(wcCount * sizeof(wchar_t)); + if (MultiByteToWideChar(CP_ACP, 0, var_name, -1, wcVarName, wcCount) != 0) { + LPWSTR wcEnvVar = _wgetenv(wcVarName); + if (wcEnvVar != NULL) { + int mbSize = WideCharToMultiByte(CP_ACP, + WC_NO_BEST_FIT_CHARS | WC_COMPOSITECHECK | WC_DEFAULTCHAR, + wcEnvVar, -1, NULL, 0, NULL, NULL); + if (mbSize > 0) { + mbEnvVar = JLI_MemAlloc(mbSize); + if (WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS | WC_COMPOSITECHECK | WC_DEFAULTCHAR, + wcEnvVar, -1, mbEnvVar, mbSize, NULL, NULL) == 0) { + JLI_MemFree(mbEnvVar); + mbEnvVar = NULL; + } + } + } + } + JLI_MemFree(wcVarName); + } + return mbEnvVar; +} +#endif + #ifdef DEBUG_ARGFILE /* * Stand-alone sanity test, build with following command line diff --git a/src/java.base/windows/native/libjava/ProcessEnvironment_md.c b/src/java.base/windows/native/libjava/ProcessEnvironment_md.c index 5c552f449d8..a9b6ac29902 100644 --- a/src/java.base/windows/native/libjava/ProcessEnvironment_md.c +++ b/src/java.base/windows/native/libjava/ProcessEnvironment_md.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 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 @@ -28,46 +28,6 @@ #include "jni_util.h" #include -static jstring -environmentBlock9x(JNIEnv *env) -{ - int i; - jmethodID String_init_ID; - jbyteArray bytes; - jbyte *blockA; - jclass string_class; - - string_class = JNU_ClassString(env); - CHECK_NULL_RETURN(string_class, NULL); - - String_init_ID = - (*env)->GetMethodID(env, string_class, "", "([B)V"); - CHECK_NULL_RETURN(String_init_ID, NULL); - - blockA = (jbyte *) GetEnvironmentStringsA(); - if (blockA == NULL) { - /* Both GetEnvironmentStringsW and GetEnvironmentStringsA - * failed. Out of memory is our best guess. */ - JNU_ThrowOutOfMemoryError(env, "GetEnvironmentStrings failed"); - return NULL; - } - - /* Don't search for "\0\0", since an empty environment block may - legitimately consist of a single "\0". */ - for (i = 0; blockA[i];) - while (blockA[i++]) - ; - - if ((bytes = (*env)->NewByteArray(env, i)) == NULL) { - FreeEnvironmentStringsA(blockA); - return NULL; - } - (*env)->SetByteArrayRegion(env, bytes, 0, i, blockA); - FreeEnvironmentStringsA(blockA); - return (*env)->NewObject(env, string_class, - String_init_ID, bytes); -} - /* Returns a Windows style environment block, discarding final trailing NUL */ JNIEXPORT jstring JNICALL Java_java_lang_ProcessEnvironment_environmentBlock(JNIEnv *env, jclass klass) @@ -75,8 +35,11 @@ Java_java_lang_ProcessEnvironment_environmentBlock(JNIEnv *env, jclass klass) int i; jstring envblock; jchar *blockW = (jchar *) GetEnvironmentStringsW(); - if (blockW == NULL) - return environmentBlock9x(env); + if (blockW == NULL) { + /* Out of memory is our best guess. */ + JNU_ThrowOutOfMemoryError(env, "GetEnvironmentStrings failed"); + return NULL; + } /* Don't search for "\u0000\u0000", since an empty environment block may legitimately consist of a single "\u0000". */ diff --git a/test/jdk/tools/launcher/DisableBestFitMappingTest.java b/test/jdk/tools/launcher/DisableBestFitMappingTest.java index 6602aae60a9..e033b606bc8 100644 --- a/test/jdk/tools/launcher/DisableBestFitMappingTest.java +++ b/test/jdk/tools/launcher/DisableBestFitMappingTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,9 +23,10 @@ /* * @test - * @bug 8337506 - * @summary Verify Command Line arguments are not mapped with - * "best-fit" mappings on Windows + * @bug 8337506 8349254 + * @summary Verify command line arguments, including ones from + * "JDK_JAVA_OPTIONS" environment variables are not mapped + * with "best-fit" mappings on Windows * @requires (os.family == "windows") * @library /test/lib * @run junit DisableBestFitMappingTest @@ -34,9 +35,9 @@ import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.util.stream.Stream; + import jdk.test.lib.process.ProcessTools; -import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -49,10 +50,12 @@ public class DisableBestFitMappingTest { Charset.forName(System.getProperty("native.encoding")).newEncoder(); private static final String REPLACEMENT = NATIVE_ENC.charset().decode(ByteBuffer.wrap(NATIVE_ENC.replacement())).toString(); + private static final String TEST_ENV_VAR = "JDK_JAVA_OPTIONS"; + private static final String TEST_PROP_KEY = "testProp"; private static final int EXIT_SUCCESS = 0; private static final int EXIT_FAILURE = -1; - static Stream CMD_ARGS() { + static Stream TEST_ARGS() { return Stream.of( Arguments.of("aa\uff02 \uff02bb", "aa" + REPLACEMENT + " " + REPLACEMENT + "bb"), Arguments.of("aa\uff01bb", "aa" + REPLACEMENT + "bb"), @@ -61,23 +64,41 @@ public class DisableBestFitMappingTest { } @ParameterizedTest - @MethodSource("CMD_ARGS") - void testDisableBestFitMapping(String arg, String expected) throws Exception { + @MethodSource("TEST_ARGS") + void testCommandLineArgument(String arg, String expected) throws Exception { // Only execute if the arg cannot be encoded assumeFalse(NATIVE_ENC.canEncode(arg), "native.encoding (%s) can encode the argument '%s'. Test ignored." .formatted(NATIVE_ENC.charset(), arg)); - var result= ProcessTools.executeTestJava( - DisableBestFitMappingTest.class.getSimpleName(), arg, expected); + var result = ProcessTools.executeTestJava( + DisableBestFitMappingTest.class.getSimpleName(), expected, arg); result.asLines().forEach(System.out::println); assertEquals(EXIT_SUCCESS, result.getExitValue(), - "Disabling best-fit mapping failed"); + "Command line argument mapping failed"); + } + + @ParameterizedTest + @MethodSource("TEST_ARGS") + void testEnvironmentVariable(String propVal, String expected) throws Exception { + // Only execute if the arg from the environment variable cannot be encoded + assumeFalse(NATIVE_ENC.canEncode(propVal), + "native.encoding (%s) can encode the argument '%s'. Test ignored." + .formatted(NATIVE_ENC.charset(), propVal)); + + var pb = ProcessTools.createTestJavaProcessBuilder( + DisableBestFitMappingTest.class.getSimpleName(), expected); + pb.environment().put(TEST_ENV_VAR, "-D" + TEST_PROP_KEY + "=\"" + propVal + "\""); + var result = ProcessTools.executeProcess(pb); + result.asLines().forEach(System.out::println); + assertEquals(EXIT_SUCCESS, result.getExitValue(), + "Argument from JDK_JAVA_OPTIONS mapping failed"); } public static void main(String... args) { - System.out.println(args[0]); - System.out.println(args[1]); - System.exit(args[0].equals(args[1]) ? EXIT_SUCCESS : EXIT_FAILURE); + var expected = args[0]; + var actual = args.length > 1 ? args[1] : System.getProperty(TEST_PROP_KEY); + System.out.printf("expected: %s, actual: %s%n", expected, actual); + System.exit(expected.equals(actual) ? EXIT_SUCCESS : EXIT_FAILURE); } }