From f3bc3a10ebe1f48f2fc8129a05dd8d2e864cb0e2 Mon Sep 17 00:00:00 2001 From: Laszlo Csomor Date: Tue, 29 Jan 2019 09:37:27 +0100 Subject: [PATCH] Windows: fix java_binary.jvm_flags escaping Bazel on Windows now correctly forwards java_binary.jvm_flags to the binary. Bazel Bash-tokenizes java_binary.jvm_flags. On Linux/macOS, this is done by embedding the flags into the Java stub script, which is a Bash script, so Bash itself does the tokenization. On Windows, java_binary is launched by a native C++ binary. Therefore nothing could Bash-tokenize the flags until now. From now on, Bazel itself Bash-tokenizes the flags before embedding them in the launcher. The launcher then escapes these flags for CreateProcessW. This PR also adds a new, CreateProcessW-aware escaping method called CreateProcessEscapeArg, to the launcher. The pre-existing GetEscapedArgument escaped only with Bash semantics in mind. This method is now renamed to BashEscapeArg, and used only for the Bash launcher. For the Python and Java launcher, the new CreateProcessEscapeArg method is used. Fixes https://github.com/bazelbuild/bazel/issues/7072 --- .../java/com/google/devtools/build/lib/BUILD | 1 + .../bazel/rules/java/BazelJavaSemantics.java | 31 +++++- .../build/lib/rules/java/JavaSemantics.java | 4 +- src/test/py/bazel/launcher_test.py | 104 +++++++++++++++++- src/tools/launcher/bash_launcher.cc | 9 +- src/tools/launcher/java_launcher.cc | 5 +- src/tools/launcher/python_launcher.cc | 2 +- src/tools/launcher/util/launcher_util.cc | 86 ++++++++++++++- src/tools/launcher/util/launcher_util.h | 9 +- src/tools/launcher/util/launcher_util_test.cc | 80 +++++++++++--- 10 files changed, 287 insertions(+), 44 deletions(-) diff --git a/src/main/java/com/google/devtools/build/lib/BUILD b/src/main/java/com/google/devtools/build/lib/BUILD index d676f94a16c83e..9953896ff96b33 100644 --- a/src/main/java/com/google/devtools/build/lib/BUILD +++ b/src/main/java/com/google/devtools/build/lib/BUILD @@ -751,6 +751,7 @@ java_library( "//src/main/java/com/google/devtools/build/lib/rules/java:java-rules", "//src/main/java/com/google/devtools/build/lib/rules/objc", "//src/main/java/com/google/devtools/build/lib/rules/platform", + "//src/main/java/com/google/devtools/build/lib/shell", "//src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec", "//src/main/java/com/google/devtools/build/lib/skylarkbuildapi/android", "//src/main/java/com/google/devtools/build/lib/skylarkbuildapi/apple", diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaSemantics.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaSemantics.java index 0058d60a627deb..57590252886f9f 100644 --- a/src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaSemantics.java +++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaSemantics.java @@ -43,6 +43,7 @@ import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; import com.google.devtools.build.lib.collect.nestedset.Order; import com.google.devtools.build.lib.packages.BuildType; +import com.google.devtools.build.lib.packages.RuleClass.ConfiguredTargetFactory.RuleErrorException; import com.google.devtools.build.lib.packages.TargetUtils; import com.google.devtools.build.lib.rules.cpp.CcInfo; import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration; @@ -66,6 +67,7 @@ import com.google.devtools.build.lib.rules.java.JavaToolchainProvider; import com.google.devtools.build.lib.rules.java.JavaUtil; import com.google.devtools.build.lib.rules.java.proto.GeneratedExtensionRegistryProvider; +import com.google.devtools.build.lib.shell.ShellUtils; import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec; import com.google.devtools.build.lib.syntax.Type; import com.google.devtools.build.lib.util.OS; @@ -265,7 +267,7 @@ public Artifact createStubAction( List jvmFlags, Artifact executable, String javaStartClass, - String javaExecutable) { + String javaExecutable) throws RuleErrorException { return createStubAction( ruleContext, javaCommon, @@ -288,7 +290,7 @@ public Artifact createStubAction( String coverageStartClass, NestedSetBuilder filesBuilder, String javaExecutable, - boolean createCoverageMetadataJar) { + boolean createCoverageMetadataJar) throws RuleErrorException { Preconditions.checkState(ruleContext.getConfiguration().hasFragment(JavaConfiguration.class)); Preconditions.checkNotNull(jvmFlags); @@ -399,12 +401,25 @@ public Artifact createStubAction( arguments.add(Substitution.ofSpaceSeparatedList("%jvm_flags%", jvmFlagsList)); if (OS.getCurrent() == OS.WINDOWS) { + // Bash-tokenize the JVM flags. On Linux/macOS tokenization is achieved by embedding the + // 'jvmFlags' into the java_binary stub script (which is a Bash script) and letting Bash + // tokenize them. + List tokenizedJvmFlags = new ArrayList<>(jvmFlagsList.size()); + for (String s : jvmFlags) { + try { + ShellUtils.tokenize(tokenizedJvmFlags, s); + } catch (ShellUtils.TokenizationException e) { + ruleContext.attributeError( + "jvm_flags", "Could not Bash-tokenize \"" + s + "\": " + e.getMessage()); + throw new RuleErrorException(); + } + } return createWindowsExeLauncher( ruleContext, javaExecutable, classpath, javaStartClass, - jvmFlagsList, + tokenizedJvmFlags, executable); } @@ -418,7 +433,7 @@ private static Artifact createWindowsExeLauncher( String javaExecutable, NestedSet classpath, String javaStartClass, - ImmutableList jvmFlags, + List bashTokenizedJvmFlags, Artifact javaLauncher) { LaunchInfo launchInfo = @@ -440,7 +455,13 @@ private static Artifact createWindowsExeLauncher( "classpath", ";", Iterables.transform(classpath, Artifact.ROOT_RELATIVE_PATH_STRING)) - .addJoinedValues("jvm_flags", " ", jvmFlags) + // TODO(laszlocsomor): joining flags on \t means no flag may contain this character, + // otherwise the launcher would split it into two flags. This solution is good enough + // because flags typically don't contain tabs, but we can't rule out the possibility + // that they do. Let's find a better way to embed the JVM flags to the launcher, e.g. + // by using one jvm_flags entry per flag instead of joining all flags as one jvm_flags + // value. + .addJoinedValues("jvm_flags", "\t", bashTokenizedJvmFlags) .build(); LauncherFileWriteAction.createAndRegister(ruleContext, javaLauncher, launchInfo); diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaSemantics.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaSemantics.java index d40f9b0df41675..b2dbc6d3d5e11b 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaSemantics.java +++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaSemantics.java @@ -304,7 +304,7 @@ Artifact createStubAction( Artifact executable, String javaStartClass, String javaExecutable) - throws InterruptedException; + throws InterruptedException, RuleErrorException; /** * Same as {@link #createStubAction(RuleContext, JavaCommon, List, Artifact, String, String)}. @@ -326,7 +326,7 @@ public Artifact createStubAction( NestedSetBuilder filesBuilder, String javaExecutable, boolean createCoverageMetadataJar) - throws InterruptedException; + throws InterruptedException, RuleErrorException; /** * Returns true if {@code createStubAction} considers {@code javaExecutable} as a substitution. diff --git a/src/test/py/bazel/launcher_test.py b/src/test/py/bazel/launcher_test.py index dfab59f76bd7a8..cd2f46bdfd0c15 100644 --- a/src/test/py/bazel/launcher_test.py +++ b/src/test/py/bazel/launcher_test.py @@ -200,9 +200,10 @@ def _buildAndCheckArgumentPassing(self, package, target_name): bin_suffix)))) arguments = ['a', 'a b', '"b"', 'C:\\a\\b\\', '"C:\\a b\\c\\"'] + parenthesized_arguments = ['(%s)' % a for a in arguments] exit_code, stdout, stderr = self.RunProgram([bin1] + arguments) self.AssertExitCode(exit_code, 0, stderr) - self.assertEqual(stdout, arguments) + self.assertEqual(stdout, parenthesized_arguments) def testJavaBinaryLauncher(self): self.ScratchFile('WORKSPACE') @@ -248,7 +249,7 @@ def testJavaBinaryArgumentPassing(self): 'public class Main {', ' public static void main(String[] args) {' ' for (String arg : args) {', - ' System.out.println(arg);', + ' System.out.printf("(%s)%n", arg);', ' }' ' }', '}', @@ -316,7 +317,7 @@ def testShBinaryArgumentPassing(self): 'N=${#args[@]}', '# Echo each argument', 'for (( i=0;i<$N;i++)); do', - ' echo ${args[${i}]}', + ' echo "(${args[${i}]})"', 'done', ]) os.chmod(foo_sh, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) @@ -389,7 +390,7 @@ def testPyBinaryArgumentPassing(self): self.ScratchFile('foo/bin.py', [ 'import sys', 'for arg in sys.argv[1:]:', - ' print(arg)', + ' print("(%s)" % arg)', ]) self._buildAndCheckArgumentPassing('foo', 'bin') @@ -409,7 +410,7 @@ def testWindowsJavaExeLauncher(self): ]) self.ScratchFile('foo/Main.java', [ 'public class Main {', - ' public static void main(String[] args) {' + ' public static void main(String[] args) {', ' System.out.println("helloworld");', ' }', '}', @@ -502,7 +503,7 @@ def testWindowsJavaExeLauncher(self): self.AssertExitCode(exit_code, 0, stderr) self.assertIn('local_jdk/bin/java.exe', ''.join(stdout)) - my_tmp_dir = self.ScratchDir('my/temp/dir') + my_tmp_dir = self.ScratchDir('my/temp/dir').replace('\\', '/') exit_code, stdout, stderr = self.RunProgram( [binary, print_cmd], env_add={'TEST_TMPDIR': my_tmp_dir}) self.AssertExitCode(exit_code, 0, stderr) @@ -651,6 +652,97 @@ def AssertRunfilesManifestContains(self, manifest, entry): return self.fail('Runfiles manifest "%s" did not contain "%s"' % (manifest, entry)) + def testJvmFlagsFromBuildFile(self): + self.ScratchFile('WORKSPACE') + self.ScratchFile('BUILD', [r""" +java_binary( + name = "foo", + srcs = ["Main.java"], + main_class = "Main", + jvm_flags = [ + '-Darg0=A', + '-Darg1="A"', + '-Darg2=\'"a"\'', + '-Darg3=\'B\'', + '-Darg4="\'b\'"', + '-Darg5="\\"b\\""', + '-Darg6=\'D E\'', + '-Darg7=\'"d e"\'', + '-Darg8=\'F"G\'', + '-Darg9=\'"F"G"\'', + '-Darg10=\'C:\\H I\'', + '-Darg11=\'"C:\\h i"\'', + '-Darg12=\'C:\\J"K\'', + '-Darg13=\'"C:\\j"k"\'', + '-Darg14=\'C:\\L\\"M\'', + '-Darg15=\'"C:\\l\\"m"\'', + '-Darg16=\'C:\\N O \'', + '-Darg17=\'"C:\\n o "\'', + '-Darg18=\'C:\\P Q\\\'', + '-Darg19=\'"C:\\p q\\"\'', + '-Darg20=\'C:\\R S\\ \'', + '-Darg21=\'"C:\\r s\\ "\'', + '-Darg22=\'C:\\T\\U\\\'', + '-Darg23=\'"C:\\t\\u\\"\'', + '-Darg24=\'C:\\V W\\X\\\'', + '-Darg25=\'"C:\\v w\\x\\"\'', + ], +)"""]) + self.ScratchFile('Main.java', [""" +public class Main { + public static void main(String[] args) { + for (int i = 0; ; ++i) { + String value = System.getProperty("arg" + i); + if (value == null) { + break; + } else { + System.out.printf("arg%d=(%s)%n", i, value); + } + } + } +}"""]) + exit_code, stdout, stderr = self.RunBazel(['info', 'bazel-bin']) + self.AssertExitCode(exit_code, 0, stderr) + bazel_bin = stdout[0] + + exit_code, _, stderr = self.RunBazel(['build', '//:foo']) + self.AssertExitCode(exit_code, 0, stderr) + + if self.IsWindows(): + foo_path = os.path.abspath(os.path.join(bazel_bin, 'foo.exe')) + else: + foo_path = os.path.abspath(os.path.join(bazel_bin, 'foo')) + exit_code, stdout, stderr = self.RunProgram([foo_path]) + self.AssertExitCode(exit_code, 0, stderr) + self.assertListEqual([ + "arg0=(A)", + "arg1=(A)", + "arg2=(\"a\")", + "arg3=(B)", + "arg4=('b')", + "arg5=(\"b\")", + "arg6=(D E)", + "arg7=(\"d e\")", + "arg8=(F\"G)", + "arg9=(\"F\"G\")", + "arg10=(C:\\H I)", + "arg11=(\"C:\\h i\")", + "arg12=(C:\\J\"K)", + "arg13=(\"C:\\j\"k\")", + "arg14=(C:\\L\\\"M)", + "arg15=(\"C:\\l\\\"m\")", + "arg16=(C:\\N O )", + "arg17=(\"C:\\n o \")", + "arg18=(C:\\P Q\\)", + "arg19=(\"C:\\p q\\\")", + "arg20=(C:\\R S\\ )", + "arg21=(\"C:\\r s\\ \")", + "arg22=(C:\\T\\U\\)", + "arg23=(\"C:\\t\\u\\\")", + "arg24=(C:\\V W\\X\\)", + "arg25=(\"C:\\v w\\x\\\")"], stdout) + + if __name__ == '__main__': unittest.main() diff --git a/src/tools/launcher/bash_launcher.cc b/src/tools/launcher/bash_launcher.cc index 3115c813f00a92..0020d5f92d55d3 100644 --- a/src/tools/launcher/bash_launcher.cc +++ b/src/tools/launcher/bash_launcher.cc @@ -45,18 +45,15 @@ ExitCode BashBinaryLauncher::Launch() { vector origin_args = this->GetCommandlineArguments(); wostringstream bash_command; wstring bash_main_file = GetBinaryPathWithoutExtension(origin_args[0]); - bash_command << GetEscapedArgument(bash_main_file, - /*escape_backslash = */ true); + bash_command << BashEscapeArg(bash_main_file); for (int i = 1; i < origin_args.size(); i++) { bash_command << L' '; - bash_command << GetEscapedArgument(origin_args[i], - /*escape_backslash = */ true); + bash_command << BashEscapeArg(origin_args[i]); } vector args; args.push_back(L"-c"); - args.push_back( - GetEscapedArgument(bash_command.str(), /*escape_backslash = */ true)); + args.push_back(BashEscapeArg(bash_command.str())); return this->LaunchProcess(bash_binary, args); } diff --git a/src/tools/launcher/java_launcher.cc b/src/tools/launcher/java_launcher.cc index c7915e05603a20..df8e2a2a81aad4 100644 --- a/src/tools/launcher/java_launcher.cc +++ b/src/tools/launcher/java_launcher.cc @@ -359,7 +359,7 @@ ExitCode JavaBinaryLauncher::Launch() { jvm_flags.push_back(flag); } wstringstream jvm_flags_launch_info_ss(this->GetLaunchInfoByKey(JVM_FLAGS)); - while (getline(jvm_flags_launch_info_ss, flag, L' ')) { + while (getline(jvm_flags_launch_info_ss, flag, L'\t')) { jvm_flags.push_back(flag); } @@ -411,8 +411,7 @@ ExitCode JavaBinaryLauncher::Launch() { vector escaped_arguments; // Quote the arguments if having spaces for (const auto& arg : arguments) { - escaped_arguments.push_back( - GetEscapedArgument(arg, /*escape_backslash = */ false)); + escaped_arguments.push_back(CreateProcessEscapeArg(arg)); } ExitCode exit_code = this->LaunchProcess(java_bin, escaped_arguments); diff --git a/src/tools/launcher/python_launcher.cc b/src/tools/launcher/python_launcher.cc index 81d836c35ed261..6e0c153fa4af7e 100644 --- a/src/tools/launcher/python_launcher.cc +++ b/src/tools/launcher/python_launcher.cc @@ -49,7 +49,7 @@ ExitCode PythonBinaryLauncher::Launch() { // Escape arguments that has spaces for (int i = 1; i < args.size(); i++) { - args[i] = GetEscapedArgument(args[i], /*escape_backslash = */ false); + args[i] = CreateProcessEscapeArg(args[i]); } return this->LaunchProcess(python_binary, args); diff --git a/src/tools/launcher/util/launcher_util.cc b/src/tools/launcher/util/launcher_util.cc index f1dc7eb895c20b..4b509c49b282f7 100644 --- a/src/tools/launcher/util/launcher_util.cc +++ b/src/tools/launcher/util/launcher_util.cc @@ -128,7 +128,7 @@ wstring GetBinaryPathWithExtension(const wstring& binary) { return GetBinaryPathWithoutExtension(binary) + L".exe"; } -wstring GetEscapedArgument(const wstring& argument, bool escape_backslash) { +wstring BashEscapeArg(const wstring& argument) { wstring escaped_arg; // escaped_arg will be at least this long escaped_arg.reserve(argument.size()); @@ -150,8 +150,7 @@ wstring GetEscapedArgument(const wstring& argument, bool escape_backslash) { break; case L'\\': - // Escape back slashes if escape_backslash is true - escaped_arg += (escape_backslash ? L"\\\\" : L"\\"); + escaped_arg += L"\\\\"; break; default: @@ -165,6 +164,87 @@ wstring GetEscapedArgument(const wstring& argument, bool escape_backslash) { return escaped_arg; } +std::wstring CreateProcessEscapeArg(const std::wstring& s) { + if (s.empty()) { + return L"\"\""; + } else { + bool needs_escaping = false; + for (const auto& c : s) { + if (c == ' ' || c == '"') { + needs_escaping = true; + break; + } + } + if (!needs_escaping) { + return s; + } + } + + std::wostringstream result; + result << L'"'; + int start = 0; + for (int i = 0; i < s.size(); ++i) { + char c = s[i]; + if (c == '"' || c == '\\') { + // Copy the segment since the last special character. + if (start >= 0) { + result << s.substr(start, i - start); + start = -1; + } + + // Handle the current special character. + if (c == '"') { + // This is a quote character. Escape it with a single backslash. + result << L"\\\""; + } else { + // This is a backslash (or the first one in a run of backslashes). + // Whether we escape it depends on whether the run ends with a quote. + int run_len = 1; + int j = i + 1; + while (j < s.size() && s[j] == '\\') { + run_len++; + j++; + } + if (j == s.size()) { + // The run of backslashes goes to the end. + // We have to escape every backslash with another backslash. + for (int k = 0; k < run_len * 2; ++k) { + result << L'\\'; + } + break; + } else if (j < s.size() && s[j] == '"') { + // The run of backslashes is terminated by a quote. + // We have to escape every backslash with another backslash, and + // escape the quote with one backslash. + for (int k = 0; k < run_len * 2; ++k) { + result << L'\\'; + } + result << L"\\\""; + i += run_len; // 'i' is also increased in the loop iteration step + } else { + // No quote found. Each backslash counts for itself, they must not be + // escaped. + for (int k = 0; k < run_len; ++k) { + result << L'\\'; + } + i += run_len - 1; // 'i' is also increased in the loop iteration step + } + } + } else { + // This is not a special character. Start the segment if necessary. + if (start < 0) { + start = i; + } + } + } + // Save final segment after the last special character. + if (start != -1) { + result << s.substr(start); + } + result << L'"'; + return result.str(); +} + // An environment variable has a maximum size limit of 32,767 characters // https://msdn.microsoft.com/en-us/library/ms683188.aspx static const int BUFFER_SIZE = 32767; diff --git a/src/tools/launcher/util/launcher_util.h b/src/tools/launcher/util/launcher_util.h index 7cd0cf21533202..e84b027244a864 100644 --- a/src/tools/launcher/util/launcher_util.h +++ b/src/tools/launcher/util/launcher_util.h @@ -44,10 +44,11 @@ std::wstring GetBinaryPathWithExtension(const std::wstring& binary); // Escape a command line argument. // // If the argument has space, then we quote it. -// Escape " to \" -// Escape \ to \\ if escape_backslash is true -std::wstring GetEscapedArgument(const std::wstring& argument, - bool escape_backslash); +// Escape " to \" . +// Escape \ to \\ . +std::wstring BashEscapeArg(const std::wstring& argument); + +std::wstring CreateProcessEscapeArg(const std::wstring& arg); // Convert a path to an absolute Windows path with \\?\ prefix. // This method will print an error and exit if it cannot convert the path. diff --git a/src/tools/launcher/util/launcher_util_test.cc b/src/tools/launcher/util/launcher_util_test.cc index bedcdb7b130ac3..e0cb0a1c23c5bb 100644 --- a/src/tools/launcher/util/launcher_util_test.cc +++ b/src/tools/launcher/util/launcher_util_test.cc @@ -74,22 +74,74 @@ TEST_F(LaunchUtilTest, GetBinaryPathWithExtensionTest) { ASSERT_EQ(L"foo.sh.exe", GetBinaryPathWithExtension(L"foo.sh")); } -TEST_F(LaunchUtilTest, GetEscapedArgumentTest) { - ASSERT_EQ(L"\"\"", GetEscapedArgument(L"", true)); - ASSERT_EQ(L"foo", GetEscapedArgument(L"foo", true)); - ASSERT_EQ(L"\"foo bar\"", GetEscapedArgument(L"foo bar", true)); - ASSERT_EQ(L"\"\\\"foo bar\\\"\"", GetEscapedArgument(L"\"foo bar\"", true)); - ASSERT_EQ(L"foo\\\\bar", GetEscapedArgument(L"foo\\bar", true)); - ASSERT_EQ(L"foo\\\"bar", GetEscapedArgument(L"foo\"bar", true)); - ASSERT_EQ(L"C:\\\\foo\\\\bar\\\\", - GetEscapedArgument(L"C:\\foo\\bar\\", true)); +TEST_F(LaunchUtilTest, BashEscapedArgTest) { + ASSERT_EQ(L"\"\"", BashEscapeArg(L"")); + ASSERT_EQ(L"foo", BashEscapeArg(L"foo")); + ASSERT_EQ(L"\"foo bar\"", BashEscapeArg(L"foo bar")); + ASSERT_EQ(L"\"\\\"foo bar\\\"\"", BashEscapeArg(L"\"foo bar\"")); + ASSERT_EQ(L"foo\\\\bar", BashEscapeArg(L"foo\\bar")); + ASSERT_EQ(L"foo\\\"bar", BashEscapeArg(L"foo\"bar")); + ASSERT_EQ(L"C:\\\\foo\\\\bar\\\\", BashEscapeArg(L"C:\\foo\\bar\\")); ASSERT_EQ(L"\"C:\\\\foo foo\\\\bar\\\\\"", - GetEscapedArgument(L"C:\\foo foo\\bar\\", true)); + BashEscapeArg(L"C:\\foo foo\\bar\\")); +} - ASSERT_EQ(L"foo\\bar", GetEscapedArgument(L"foo\\bar", false)); - ASSERT_EQ(L"C:\\foo\\bar\\", GetEscapedArgument(L"C:\\foo\\bar\\", false)); - ASSERT_EQ(L"\"C:\\foo foo\\bar\\\"", - GetEscapedArgument(L"C:\\foo foo\\bar\\", false)); +TEST_F(LaunchUtilTest, CreateProcessEscapeArgTest) { + ASSERT_EQ(L"\"\"", CreateProcessEscapeArg(L"")); + ASSERT_EQ(L"\"with\\\"quote\"", CreateProcessEscapeArg(L"with\"quote")); + ASSERT_EQ(L"one\\backslash", CreateProcessEscapeArg(L"one\\backslash")); + ASSERT_EQ(L"\"one\\ backslash and \\space\"", + CreateProcessEscapeArg(L"one\\ backslash and \\space")); + ASSERT_EQ(L"two\\\\backslashes", + CreateProcessEscapeArg(L"two\\\\backslashes")); + ASSERT_EQ(L"\"two\\\\ backslashes \\\\and space\"", + CreateProcessEscapeArg(L"two\\\\ backslashes \\\\and space")); + ASSERT_EQ(L"\"one\\\\\\\"x\"", CreateProcessEscapeArg(L"one\\\"x")); + ASSERT_EQ(L"\"two\\\\\\\\\\\"x\"", CreateProcessEscapeArg(L"two\\\\\"x")); + ASSERT_EQ(L"\"a \\ b\"", CreateProcessEscapeArg(L"a \\ b")); + ASSERT_EQ(L"\"a \\\\\\\" b\"", CreateProcessEscapeArg(L"a \\\" b")); + + ASSERT_EQ(L"A", CreateProcessEscapeArg(L"A")); + ASSERT_EQ(L"\"\\\"a\\\"\"", CreateProcessEscapeArg(L"\"a\"")); + + ASSERT_EQ(L"\"B C\"", CreateProcessEscapeArg(L"B C")); + ASSERT_EQ(L"\"\\\"b c\\\"\"", CreateProcessEscapeArg(L"\"b c\"")); + + ASSERT_EQ(L"\"D\\\"E\"", CreateProcessEscapeArg(L"D\"E")); + ASSERT_EQ(L"\"\\\"d\\\"e\\\"\"", CreateProcessEscapeArg(L"\"d\"e\"")); + + ASSERT_EQ(L"\"C:\\F G\"", CreateProcessEscapeArg(L"C:\\F G")); + ASSERT_EQ(L"\"\\\"C:\\f g\\\"\"", CreateProcessEscapeArg(L"\"C:\\f g\"")); + + ASSERT_EQ(L"\"C:\\H\\\"I\"", CreateProcessEscapeArg(L"C:\\H\"I")); + ASSERT_EQ(L"\"\\\"C:\\h\\\"i\\\"\"", CreateProcessEscapeArg(L"\"C:\\h\"i\"")); + + ASSERT_EQ(L"\"C:\\J\\\\\\\"K\"", CreateProcessEscapeArg(L"C:\\J\\\"K")); + ASSERT_EQ(L"\"\\\"C:\\j\\\\\\\"k\\\"\"", + CreateProcessEscapeArg(L"\"C:\\j\\\"k\"")); + + ASSERT_EQ(L"\"C:\\L M \"", CreateProcessEscapeArg(L"C:\\L M ")); + ASSERT_EQ(L"\"\\\"C:\\l m \\\"\"", + CreateProcessEscapeArg(L"\"C:\\l m \"")); + + ASSERT_EQ(L"\"C:\\N O\\\\\"", CreateProcessEscapeArg(L"C:\\N O\\")); + ASSERT_EQ(L"\"\\\"C:\\n o\\\\\\\"\"", + CreateProcessEscapeArg(L"\"C:\\n o\\\"")); + + ASSERT_EQ(L"\"C:\\P Q\\ \"", CreateProcessEscapeArg(L"C:\\P Q\\ ")); + ASSERT_EQ(L"\"\\\"C:\\p q\\ \\\"\"", + CreateProcessEscapeArg(L"\"C:\\p q\\ \"")); + + ASSERT_EQ(L"C:\\R\\S\\", CreateProcessEscapeArg(L"C:\\R\\S\\")); + ASSERT_EQ(L"\"C:\\R x\\S\\\\\"", CreateProcessEscapeArg(L"C:\\R x\\S\\")); + ASSERT_EQ(L"\"\\\"C:\\r\\s\\\\\\\"\"", + CreateProcessEscapeArg(L"\"C:\\r\\s\\\"")); + ASSERT_EQ(L"\"\\\"C:\\r x\\s\\\\\\\"\"", + CreateProcessEscapeArg(L"\"C:\\r x\\s\\\"")); + + ASSERT_EQ(L"\"C:\\T U\\W\\\\\"", CreateProcessEscapeArg(L"C:\\T U\\W\\")); + ASSERT_EQ(L"\"\\\"C:\\t u\\w\\\\\\\"\"", + CreateProcessEscapeArg(L"\"C:\\t u\\w\\\"")); } TEST_F(LaunchUtilTest, DoesFilePathExistTest) {