Skip to content

Commit f81d522

Browse files
committed
Properly quote arguments passed via argument files
NativeImage passes some arguments via the @file feature of the JDK launcher. When some arguments, separated by new lines, contain whitespace *and* aren't properly quoted, the JDK launcher code fails to parse the argument file and the native image generator class is never launched. Shell-quoting arguments before writing them to an @argument file avoids this issue. However, this is further complicated by the fact that on Windows, argument files may contain paths including '\'. Yet, '\' is also an escape character in argument files. Therefore, windows path entries in argument files need to be properly escaped as they they are also going to be quoted. Closes #3769
1 parent 9009fa4 commit f81d522

File tree

1 file changed

+27
-8
lines changed

1 file changed

+27
-8
lines changed

substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import java.util.Map;
5151
import java.util.Optional;
5252
import java.util.Properties;
53+
import java.util.StringJoiner;
5354
import java.util.function.BiConsumer;
5455
import java.util.function.BiFunction;
5556
import java.util.function.Consumer;
@@ -996,7 +997,7 @@ private int completeImageBuild() {
996997
if (config.useJavaModules()) {
997998
config.getBuilderModulePath().forEach(this::addImageBuilderModulePath);
998999
String upgradeModulePath = config.getBuilderUpgradeModulePath().stream()
999-
.map(p -> canonicalize(p).toString())
1000+
.map(p -> shellPath(canonicalize(p)))
10001001
.collect(Collectors.joining(File.pathSeparator));
10011002
if (!upgradeModulePath.isEmpty()) {
10021003
addImageBuilderJavaArgs(Arrays.asList("--upgrade-module-path", upgradeModulePath));
@@ -1005,7 +1006,7 @@ private int completeImageBuild() {
10051006
config.getBuilderJVMCIClasspath().forEach((Consumer<? super Path>) this::addImageBuilderClasspath);
10061007
if (!config.getBuilderJVMCIClasspathAppend().isEmpty()) {
10071008
String builderJavaArg = config.getBuilderJVMCIClasspathAppend()
1008-
.stream().map(path -> canonicalize(path).toString())
1009+
.stream().map(path -> shellPath(canonicalize(path)))
10091010
.collect(Collectors.joining(File.pathSeparator, "-Djvmci.class.path.append=", ""));
10101011
addImageBuilderJavaArgs(builderJavaArg);
10111012
}
@@ -1019,7 +1020,7 @@ private int completeImageBuild() {
10191020

10201021
String clibrariesPath = (targetPlatform != null) ? targetPlatform : platform;
10211022
String clibrariesBuilderArg = config.getBuilderCLibrariesPaths().stream()
1022-
.map(path -> canonicalize(path.resolve(clibrariesPath)).toString())
1023+
.map(path -> shellPath(canonicalize(path.resolve(clibrariesPath))))
10231024
.collect(Collectors.joining(",", oHCLibraryPath, ""));
10241025
addPlainImageBuilderArg(clibrariesBuilderArg);
10251026

@@ -1235,7 +1236,7 @@ private List<String> getAgentArguments() {
12351236
if (!agentOptions.isEmpty()) {
12361237
args.add("-agentlib:native-image-diagnostics-agent=" + agentOptions);
12371238
}
1238-
args.add("-javaagent:" + config.getAgentJAR().toAbsolutePath() + (agentOptions.isEmpty() ? "" : "=" + agentOptions));
1239+
args.add("-javaagent:" + shellPath(config.getAgentJAR()) + (agentOptions.isEmpty() ? "" : "=" + agentOptions));
12391240
return args;
12401241
}
12411242

@@ -1301,7 +1302,14 @@ protected static List<String> createImageBuilderArgs(ArrayList<String> imageArgs
13011302
protected static String createVMInvocationArgumentFile(List<String> arguments) {
13021303
try {
13031304
Path argsFile = Files.createTempFile("vminvocation", ".args");
1304-
String joinedOptions = String.join("\n", arguments);
1305+
StringJoiner joiner = new StringJoiner("\n");
1306+
for (String arg : arguments) {
1307+
// Options in @argfile need to be properly quoted as
1308+
// this relies on the JDK's @argfile parsing when the
1309+
// native image generator is being launched.
1310+
joiner.add(SubstrateUtil.quoteShellArg(arg));
1311+
}
1312+
String joinedOptions = joiner.toString();
13051313
Files.write(argsFile, joinedOptions.getBytes());
13061314
argsFile.toFile().deleteOnExit();
13071315
return "@" + argsFile;
@@ -1327,14 +1335,14 @@ protected int buildImage(List<String> javaArgs, LinkedHashSet<Path> bcp, LinkedH
13271335
List<String> arguments = new ArrayList<>();
13281336
arguments.addAll(javaArgs);
13291337
if (!bcp.isEmpty()) {
1330-
arguments.add(bcp.stream().map(Path::toString).collect(Collectors.joining(File.pathSeparator, "-Xbootclasspath/a:", "")));
1338+
arguments.add(bcp.stream().map(NativeImage::shellPath).collect(Collectors.joining(File.pathSeparator, "-Xbootclasspath/a:", "")));
13311339
}
13321340

13331341
if (!cp.isEmpty()) {
1334-
arguments.addAll(Arrays.asList("-cp", cp.stream().map(Path::toString).collect(Collectors.joining(File.pathSeparator))));
1342+
arguments.addAll(Arrays.asList("-cp", cp.stream().map(NativeImage::shellPath).collect(Collectors.joining(File.pathSeparator))));
13351343
}
13361344
if (!mp.isEmpty()) {
1337-
List<String> strings = Arrays.asList("--module-path", mp.stream().map(Path::toString).collect(Collectors.joining(File.pathSeparator)));
1345+
List<String> strings = Arrays.asList("--module-path", mp.stream().map(NativeImage::shellPath).collect(Collectors.joining(File.pathSeparator)));
13381346
arguments.addAll(strings);
13391347
}
13401348

@@ -1399,6 +1407,17 @@ protected int buildImage(List<String> javaArgs, LinkedHashSet<Path> bcp, LinkedH
13991407
return exitStatus;
14001408
}
14011409

1410+
// Since path variables get used in @argumenet files and '\' is an unsafe shell
1411+
// character, be sure to escape backslashes in paths so they can get interpreted correctly
1412+
// in @argument files when they are quoted. I.e. 'C:\foo\bar' => 'C:\\foo\\bar'
1413+
private static String shellPath(Path path) {
1414+
String retval = path.toString();
1415+
if (OS.getCurrent() == OS.WINDOWS) {
1416+
return retval.replace("\\", "\\\\");
1417+
}
1418+
return retval;
1419+
}
1420+
14021421
private static final Function<BuildConfiguration, NativeImage> defaultNativeImageProvider = config -> new NativeImage(config);
14031422

14041423
public static void main(String[] args) {

0 commit comments

Comments
 (0)