blob: 63aa8b9bd43f7d7d665fd41e565970aced69cb75 [file] [log] [blame]
Laszlo Csomordcc80fb2019-08-09 05:05:21 -07001// Copyright 2019 The Bazel Authors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#ifndef WIN32_LEAN_AND_MEAN
16#define WIN32_LEAN_AND_MEAN
17#endif
18#include "src/main/native/windows/process.h"
19
20#include <wchar.h>
21#include <windows.h>
22
23#include <memory>
24#include <string>
25#include <vector>
26
27#include "gtest/gtest.h"
28#include "src/main/cpp/util/path.h"
29#include "src/main/native/windows/util.h"
30#include "tools/cpp/runfiles/runfiles.h"
31
32namespace {
33
34// Asserts argument escaping for subprocesses.
35//
36// For each pair in 'args', this method:
37// 1. asserts that WindowsEscapeArg(pair.first) == pair.second
38// 2. asserts that passing pair.second to a subprocess results in the subprocess
39// receiving pair.first
40//
41// The method performs the second assertion by running "printarg.exe" (a
42// data-dependency of this test) once for each argument.
43void AssertSubprocessReceivesArgsAsIntended(
44 const std::vector<std::pair<std::wstring, std::wstring> >& args) {
45 // Assert that the WindowsEscapeArg produces what we expect.
46 for (const auto& i : args) {
47 ASSERT_EQ(bazel::windows::WindowsEscapeArg(i.first), i.second);
48 }
49
50 // Create a Runfiles object.
51 std::string error;
52 std::unique_ptr<bazel::tools::cpp::runfiles::Runfiles> runfiles(
53 bazel::tools::cpp::runfiles::Runfiles::CreateForTest(&error));
54 ASSERT_NE(runfiles.get(), nullptr) << error;
55
56 // Look up the path of the printarg.exe utility.
57 std::string printarg =
58 runfiles->Rlocation("io_bazel/src/test/native/windows/printarg.exe");
59 ASSERT_NE(printarg, "");
60
61 // Convert printarg.exe's path to a wchar_t Windows path.
62 std::wstring wprintarg;
63 bool success =
64 blaze_util::AsAbsoluteWindowsPath(printarg, &wprintarg, &error);
65 ASSERT_TRUE(success) << error;
66
67 // SECURITY_ATTRIBUTES for inheritable HANDLEs.
68 SECURITY_ATTRIBUTES sa;
69 sa.nLength = sizeof(sa);
Vertexwahn26c7e102021-03-10 07:25:59 -080070 sa.lpSecurityDescriptor = nullptr;
Laszlo Csomordcc80fb2019-08-09 05:05:21 -070071 sa.bInheritHandle = TRUE;
72
73 // Open /dev/null that will be redirected into the subprocess' stdin.
74 bazel::windows::AutoHandle devnull(
75 CreateFileW(L"NUL", GENERIC_READ,
76 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, &sa,
Vertexwahn26c7e102021-03-10 07:25:59 -080077 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr));
Laszlo Csomordcc80fb2019-08-09 05:05:21 -070078 ASSERT_TRUE(devnull.IsValid());
79
80 // Create a pipe that the subprocess' stdout will be redirected to.
81 HANDLE pipe_read_h, pipe_write_h;
82 if (!CreatePipe(&pipe_read_h, &pipe_write_h, &sa, 0x10000)) {
83 DWORD err = GetLastError();
84 ASSERT_EQ(err, 0);
85 }
86 bazel::windows::AutoHandle pipe_read(pipe_read_h), pipe_write(pipe_write_h);
87
88 // Duplicate stderr, where the subprocess' stderr will be redirected to.
89 HANDLE stderr_h;
90 if (!DuplicateHandle(GetCurrentProcess(), GetStdHandle(STD_ERROR_HANDLE),
91 GetCurrentProcess(), &stderr_h, 0, TRUE,
92 DUPLICATE_SAME_ACCESS)) {
93 DWORD err = GetLastError();
94 ASSERT_EQ(err, 0);
95 }
96 bazel::windows::AutoHandle stderr_dup(stderr_h);
97
98 // Create the attribute object for the process creation. This object describes
99 // exactly which handles the subprocess shall inherit.
100 STARTUPINFOEXW startupInfo;
101 std::unique_ptr<bazel::windows::AutoAttributeList> attrs;
102 std::wstring werror;
103 ASSERT_TRUE(bazel::windows::AutoAttributeList::Create(
104 devnull, pipe_write, stderr_dup, &attrs, &werror));
105 attrs->InitStartupInfoExW(&startupInfo);
106
107 // MSDN says the maximum command line is 32767 characters, with a null
108 // terminator that is exactly 2^15 (= 0x8000).
109 static constexpr size_t kMaxCmdline = 0x8000;
110 wchar_t cmdline[kMaxCmdline];
111
112 // Copy printarg.exe's escaped path into the 'cmdline', and append a space.
113 // We will append arguments to this command line in the for-loop below.
114 wprintarg = bazel::windows::WindowsEscapeArg(wprintarg);
115 wcsncpy(cmdline, wprintarg.c_str(), wprintarg.size());
116 wchar_t* pcmdline = cmdline + wprintarg.size();
117 *pcmdline++ = L' ';
118
119 // Run a subprocess for each of the arguments and assert that the argument
120 // arrived to the subprocess as intended.
121 for (const auto& i : args) {
122 // We already asserted for every element that WindowsEscapeArg(i.first)
123 // produces the same output as i.second, so just use i.second instead of
124 // converting i.first again.
125 wcsncpy(pcmdline, i.second.c_str(), i.second.size());
126 pcmdline[i.second.size()] = 0;
127
128 // Run the subprocess.
129 PROCESS_INFORMATION processInfo;
130 BOOL ok = CreateProcessW(
Vertexwahn26c7e102021-03-10 07:25:59 -0800131 nullptr, cmdline, nullptr, nullptr, TRUE,
132 CREATE_UNICODE_ENVIRONMENT | EXTENDED_STARTUPINFO_PRESENT, nullptr,
133 nullptr, &startupInfo.StartupInfo, &processInfo);
Laszlo Csomordcc80fb2019-08-09 05:05:21 -0700134 if (!ok) {
135 DWORD err = GetLastError();
136 ASSERT_EQ(err, 0);
137 }
138 CloseHandle(processInfo.hThread);
139 bazel::windows::AutoHandle process(processInfo.hProcess);
140
141 // Wait for the subprocess to exit. Timeout is 5 seconds, which should be
142 // more than enough for the subprocess to finish.
143 ASSERT_EQ(WaitForSingleObject(process, 5000), WAIT_OBJECT_0);
144
145 // The subprocess printed its argv[1] (without a newline) to its stdout,
146 // which is redirected into the pipe.
147 // Let's write a null-terminator to the pipe to separate the output from the
148 // output of the subsequent subprocess. The null-terminator also yields
149 // null-terminated strings in the pipe, making it easy to read them out
150 // later.
151 DWORD dummy;
Vertexwahn26c7e102021-03-10 07:25:59 -0800152 ASSERT_TRUE(WriteFile(pipe_write, "\0", 1, &dummy, nullptr));
Laszlo Csomordcc80fb2019-08-09 05:05:21 -0700153 }
154
155 // Read the output of the subprocesses from the pipe. They are divided by
156 // null-terminators, so 'buf' will contain a sequence of null-terminated
157 // strings. We close the writing end so that ReadFile won't block until the
158 // desired amount of bytes is available.
159 DWORD total_output_len;
160 char buf[0x10000];
161 pipe_write = INVALID_HANDLE_VALUE;
Vertexwahn26c7e102021-03-10 07:25:59 -0800162 if (!ReadFile(pipe_read, buf, 0x10000, &total_output_len, nullptr)) {
Laszlo Csomordcc80fb2019-08-09 05:05:21 -0700163 DWORD err = GetLastError();
164 ASSERT_EQ(err, 0);
165 }
166
167 // Assert that the subprocesses produced exactly the *unescaped* arguments.
168 size_t start = 0;
169 for (const auto& arg : args) {
170 // Assert that there was enough data produced by the subprocesses.
171 ASSERT_LT(start, total_output_len);
172
173 // Find the output of the corresponding subprocess. Since all subprocesses
174 // printed into the same pipe and we added null-terminators between them,
175 // the output is already there, conveniently as a null-terminated string.
176 std::string actual_arg(buf + start);
177 start += actual_arg.size() + 1;
178
179 // 'args' contains wchar_t strings, but the subprocesses printed ASCII
180 // (char) strings. To compare, we convert arg.first to a char-string.
181 std::string expected_arg;
182 expected_arg.reserve(arg.first.size());
183 for (const auto& wc : arg.first) {
184 expected_arg.append(1, static_cast<char>(wc));
185 }
186
187 // Assert that the subprocess printed exactly the *unescaped* argument.
188 EXPECT_EQ(expected_arg, actual_arg);
189 }
190}
191
192TEST(ProcessTest, WindowsEscapeArgTest) {
193 AssertSubprocessReceivesArgsAsIntended({
194 {L"", L"\"\""},
195 {L" ", L"\" \""},
196 {L"\"", L"\"\\\"\""},
197 {L"\"\\", L"\"\\\"\\\\\""},
198 {L"\\", L"\\"},
199 {L"\\\"", L"\"\\\\\\\"\""},
200 {L"with space", L"\"with space\""},
201 {L"with^caret", L"with^caret"},
202 {L"space ^caret", L"\"space ^caret\""},
203 {L"caret^ space", L"\"caret^ space\""},
204 {L"with\"quote", L"\"with\\\"quote\""},
205 {L"with\\backslash", L"with\\backslash"},
206 {L"one\\ backslash and \\space", L"\"one\\ backslash and \\space\""},
207 {L"two\\\\backslashes", L"two\\\\backslashes"},
208 {L"two\\\\ backslashes \\\\and space",
209 L"\"two\\\\ backslashes \\\\and space\""},
210 {L"one\\\"x", L"\"one\\\\\\\"x\""},
211 {L"two\\\\\"x", L"\"two\\\\\\\\\\\"x\""},
212 {L"a \\ b", L"\"a \\ b\""},
213 {L"a \\\" b", L"\"a \\\\\\\" b\""},
214 {L"A", L"A"},
215 {L"\"a\"", L"\"\\\"a\\\"\""},
216 {L"B C", L"\"B C\""},
217 {L"\"b c\"", L"\"\\\"b c\\\"\""},
218 {L"D\"E", L"\"D\\\"E\""},
219 {L"\"d\"e\"", L"\"\\\"d\\\"e\\\"\""},
220 {L"C:\\F G", L"\"C:\\F G\""},
221 {L"\"C:\\f g\"", L"\"\\\"C:\\f g\\\"\""},
222 {L"C:\\H\"I", L"\"C:\\H\\\"I\""},
223 {L"\"C:\\h\"i\"", L"\"\\\"C:\\h\\\"i\\\"\""},
224 {L"C:\\J\\\"K", L"\"C:\\J\\\\\\\"K\""},
225 {L"\"C:\\j\\\"k\"", L"\"\\\"C:\\j\\\\\\\"k\\\"\""},
226 {L"C:\\L M ", L"\"C:\\L M \""},
227 {L"\"C:\\l m \"", L"\"\\\"C:\\l m \\\"\""},
228 {L"C:\\N O\\", L"\"C:\\N O\\\\\""},
229 {L"\"C:\\n o\\\"", L"\"\\\"C:\\n o\\\\\\\"\""},
230 {L"C:\\P Q\\ ", L"\"C:\\P Q\\ \""},
231 {L"\"C:\\p q\\ \"", L"\"\\\"C:\\p q\\ \\\"\""},
232 {L"C:\\R\\S\\", L"C:\\R\\S\\"},
233 {L"C:\\R x\\S\\", L"\"C:\\R x\\S\\\\\""},
234 {L"\"C:\\r\\s\\\"", L"\"\\\"C:\\r\\s\\\\\\\"\""},
235 {L"\"C:\\r x\\s\\\"", L"\"\\\"C:\\r x\\s\\\\\\\"\""},
236 {L"C:\\T U\\W\\", L"\"C:\\T U\\W\\\\\""},
237 {L"\"C:\\t u\\w\\\"", L"\"\\\"C:\\t u\\w\\\\\\\"\""},
238 });
239}
240
241} // namespace