diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsJavaMainWrapperArgsSupport.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsJavaMainWrapperArgsSupport.java new file mode 100644 index 000000000000..a26c3255ef06 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsJavaMainWrapperArgsSupport.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2025, 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.oracle.svm.core.windows; + +import static com.oracle.svm.core.windows.headers.StringAPISet.CP_ACP; +import static com.oracle.svm.core.windows.headers.StringAPISet.MultiByteToWideChar; + +import org.graalvm.nativeimage.c.type.CCharPointer; + +import com.oracle.svm.core.JavaMainWrapper; +import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; +import com.oracle.svm.core.handles.PrimitiveArrayView; +import com.oracle.svm.core.log.StringBuilderLog; +import com.oracle.svm.core.util.UnsignedUtils; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.core.windows.headers.WinBase; +import com.oracle.svm.core.windows.headers.WindowsLibC; + +import jdk.graal.compiler.word.Word; + +@AutomaticallyRegisteredImageSingleton +class WindowsJavaMainWrapperArgsSupport extends JavaMainWrapper.ArgsSupport { + @Override + protected String toJavaArg(CCharPointer rawArg) { + /* + * On Windows, wide-character strings are UTF-16LE, matching Java char[]. So we convert ANSI + * bytes directly into a Java char[] (excluding the trailing NUL) and construct the String. + */ + int rawLen = UnsignedUtils.safeToInt(WindowsLibC.strlen(rawArg)); // excludes trailing NUL + if (rawLen == 0) { + return ""; // MultiByteToWideChar would fail here + } + int wcLen = checkResult(MultiByteToWideChar(CP_ACP(), 0, rawArg, rawLen, Word.nullPointer(), 0)); + char[] wcArg = new char[wcLen]; + try (var wcBuf = PrimitiveArrayView.createForReadingAndWriting(wcArg)) { + checkResult(MultiByteToWideChar(CP_ACP(), 0, rawArg, rawLen, wcBuf.addressOfArrayElement(0), wcLen)); + } + return new String(wcArg); + } + + private static int checkResult(int result) { + if (result == 0) { + var log = new StringBuilderLog(); + log.string("MultiByteToWideChar failed with error ").hex(WinBase.GetLastError()); + throw VMError.shouldNotReachHere(log.getResult()); + } + return result; + } +} diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/headers/StringAPISet.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/headers/StringAPISet.java new file mode 100644 index 000000000000..fe42efb6590a --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/headers/StringAPISet.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2025, 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.oracle.svm.core.windows.headers; + +import org.graalvm.nativeimage.c.CContext; +import org.graalvm.nativeimage.c.constant.CConstant; +import org.graalvm.nativeimage.c.function.CFunction; +import org.graalvm.nativeimage.c.type.CCharPointer; + +import com.oracle.svm.core.windows.headers.WindowsLibC.WCharPointer; + +// Checkstyle: stop + +/** + * Definitions for Windows stringapiset.h + */ +@CContext(WindowsDirectives.class) +public class StringAPISet { + + /** The system-wide Windows ANSI code page. */ + @CConstant + public static native int CP_ACP(); + + /** Maps a character string to a UTF-16 (wide character) string. */ + @CFunction(transition = CFunction.Transition.NO_TRANSITION) + public static native int MultiByteToWideChar(int CodePage, int dwFlags, CCharPointer lpMultiByteStr, int cbMultiByte, WCharPointer lpWideCharStr, int cchWideChar); +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMainWrapper.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMainWrapper.java index 3c112cefd8c4..92ec643131c7 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMainWrapper.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMainWrapper.java @@ -65,6 +65,7 @@ import com.oracle.svm.core.c.function.CEntryPointOptions.NoEpilogue; import com.oracle.svm.core.c.function.CEntryPointOptions.NoPrologue; import com.oracle.svm.core.c.function.CEntryPointSetup; +import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; import com.oracle.svm.core.graal.snippets.CEntryPointSnippets; import com.oracle.svm.core.jdk.InternalVMMethod; import com.oracle.svm.core.jdk.RuntimeSupport; @@ -150,7 +151,7 @@ public String getJavaCommand() { public List getInputArguments() { CEntryPointCreateIsolateParameters args = MAIN_ISOLATE_PARAMETERS.get(); if (args.getArgv().isNonNull() && args.getArgc() > 0) { - String[] unmodifiedArgs = SubstrateUtil.convertCToJavaArgs(args.getArgc(), args.getArgv()); + String[] unmodifiedArgs = ArgsSupport.convertCToJavaArgs(args.getArgc(), args.getArgv()); List inputArgs = new ArrayList<>(Arrays.asList(unmodifiedArgs)); if (mainArgs != null) { @@ -479,4 +480,34 @@ static void enter(Isolate isolate) { } } } + + /** Support for platform-specific conversion of the command line to Java main arguments. */ + @AutomaticallyRegisteredImageSingleton(ArgsSupport.class) + public static class ArgsSupport { + private static ArgsSupport singleton() { + return ImageSingletons.lookup(ArgsSupport.class); + } + + /** + * Convert C-style to Java-style command line arguments. The first C-style argument, which + * is always the executable file name, is ignored. + * + * @param argc the number of arguments in the {@code argv} array. + * @param argv a C {@code char**}. + * + * @return the command line argument strings in a Java string array. + */ + public static String[] convertCToJavaArgs(int argc, CCharPointerPointer argv) { + String[] args = new String[argc - 1]; + for (int i = 1; i < argc; ++i) { + args[i - 1] = singleton().toJavaArg(argv.read(i)); + } + return args; + } + + /** Converts a single argv element to a Java string. */ + protected String toJavaArg(CCharPointer rawArg) { + return CTypeConversion.toJavaString(rawArg); + } + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateUtil.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateUtil.java index 0bf41cf52df7..99b410d40176 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateUtil.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateUtil.java @@ -40,8 +40,6 @@ import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.c.type.CCharPointer; -import org.graalvm.nativeimage.c.type.CCharPointerPointer; -import org.graalvm.nativeimage.c.type.CTypeConversion; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; @@ -152,23 +150,6 @@ public static FileDescriptor getFileDescriptor(FileOutputStream out) { return SubstrateUtil.cast(out, Target_java_io_FileOutputStream.class).fd; } - /** - * Convert C-style to Java-style command line arguments. The first C-style argument, which is - * always the executable file name, is ignored. - * - * @param argc the number of arguments in the {@code argv} array. - * @param argv a C {@code char**}. - * - * @return the command line argument strings in a Java string array. - */ - public static String[] convertCToJavaArgs(int argc, CCharPointerPointer argv) { - String[] args = new String[argc - 1]; - for (int i = 1; i < argc; ++i) { - args[i - 1] = CTypeConversion.toJavaString(argv.read(i)); - } - return args; - } - /** * Returns the length of a C {@code char*} string. */ diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/CEntryPointSnippets.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/CEntryPointSnippets.java index 620163532d2a..bdd8e7309db0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/CEntryPointSnippets.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/CEntryPointSnippets.java @@ -35,6 +35,7 @@ import java.util.Map; +import com.oracle.svm.core.JavaMainWrapper; import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Isolate; @@ -60,7 +61,6 @@ import com.oracle.svm.core.RuntimeAssertionsSupport; import com.oracle.svm.core.SubstrateDiagnostics; import com.oracle.svm.core.SubstrateOptions; -import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.UnmanagedMemoryUtil; import com.oracle.svm.core.c.CGlobalData; @@ -452,7 +452,7 @@ private static int initializeIsolateInterruptibly1(CEntryPointCreateIsolateParam exitWhenArgumentParsingFails = parameters.getExitWhenArgumentParsingFails(); } - String[] args = SubstrateUtil.convertCToJavaArgs(parameters.getArgc(), parameters.getArgv()); + String[] args = JavaMainWrapper.ArgsSupport.convertCToJavaArgs(parameters.getArgc(), parameters.getArgv()); try { args = RuntimeOptionParser.parseAndConsumeAllOptions(args, ignoreUnrecognized); } catch (IllegalArgumentException e) {