8349254: Disable "best-fit" mapping on Windows environment variables

Reviewed-by: jlu, jpai
This commit is contained in:
Naoto Sato 2025-02-11 17:20:50 +00:00
parent 32dc41c9f7
commit 642816538f
3 changed files with 95 additions and 64 deletions

View File

@ -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

View File

@ -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 <windows.h>
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, "<init>", "([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". */

View File

@ -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<Arguments> CMD_ARGS() {
static Stream<Arguments> 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);
}
}