Skip to content

Commit ec1a22a

Browse files
committed
use widePath and _w variants on Windows for the file APIs
1 parent cbac2d9 commit ec1a22a

File tree

5 files changed

+424
-34
lines changed

5 files changed

+424
-34
lines changed

lib/Basic/PlatformUtility.cpp

Lines changed: 61 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,10 @@ using namespace llbuild::basic;
3838

3939
bool sys::chdir(const char *fileName) {
4040
#if defined(_WIN32)
41-
llvm::SmallVector<llvm::UTF16, 20> wFileName;
42-
llvm::convertUTF8ToUTF16String(fileName, wFileName);
43-
return SetCurrentDirectoryW((LPCWSTR)wFileName.data());
41+
llvm::SmallVector<wchar_t, MAX_PATH> wFileName;
42+
if (llvm::sys::path::widenPath(fileName, wFileName))
43+
return false;
44+
return ::_wchdir(wFileName.data()) == 0;
4445
#else
4546
return ::chdir(fileName) == 0;
4647
#endif
@@ -63,10 +64,13 @@ time_t filetimeToTime_t(FILETIME ft) {
6364

6465
int sys::lstat(const char *fileName, sys::StatStruct *buf) {
6566
#if defined(_WIN32)
66-
llvm::SmallVector<llvm::UTF16, 20> wfilename;
67-
llvm::convertUTF8ToUTF16String(fileName, wfilename);
67+
llvm::SmallVector<wchar_t, MAX_PATH> wfilename;
68+
if (llvm::sys::path::widenPath(fileName, wfilename)) {
69+
errno = EINVAL;
70+
return -1;
71+
}
6872
HANDLE h = CreateFileW(
69-
/*lpFileName=*/(LPCWSTR)wfilename.data(),
73+
/*lpFileName=*/wfilename.data(),
7074
/*dwDesiredAccess=*/0,
7175
/*dwShareMode=*/FILE_SHARE_READ,
7276
/*lpSecurityAttributes=*/NULL,
@@ -123,7 +127,10 @@ int sys::lstat(const char *fileName, sys::StatStruct *buf) {
123127

124128
bool sys::mkdir(const char* fileName) {
125129
#if defined(_WIN32)
126-
return _mkdir(fileName) == 0;
130+
llvm::SmallVector<wchar_t, MAX_PATH> wfilename;
131+
if (llvm::sys::path::widenPath(fileName, wfilename))
132+
return false;
133+
return ::_wmkdir(wfilename.data()) == 0;
127134
#else
128135
return ::mkdir(fileName, S_IRWXU | S_IRWXG | S_IRWXO) == 0;
129136
#endif
@@ -164,15 +171,25 @@ int sys::read(int fileHandle, void *destinationBuffer,
164171

165172
int sys::rmdir(const char *path) {
166173
#if defined(_WIN32)
167-
return ::_rmdir(path);
174+
llvm::SmallVector<wchar_t, MAX_PATH> wpath;
175+
if (llvm::sys::path::widenPath(path, wpath)) {
176+
errno = EINVAL;
177+
return -1;
178+
}
179+
return ::_wrmdir(wpath.data());
168180
#else
169181
return ::rmdir(path);
170182
#endif
171183
}
172184

173185
int sys::stat(const char *fileName, StatStruct *buf) {
174186
#if defined(_WIN32)
175-
return ::_stat(fileName, buf);
187+
llvm::SmallVector<wchar_t, MAX_PATH> wfilename;
188+
if (llvm::sys::path::widenPath(fileName, wfilename)) {
189+
errno = EINVAL;
190+
return -1;
191+
}
192+
return ::_wstat(wfilename.data(), buf);
176193
#else
177194
return ::stat(fileName, buf);
178195
#endif
@@ -181,19 +198,21 @@ int sys::stat(const char *fileName, StatStruct *buf) {
181198
// Create a symlink named linkPath which contains the string pointsTo
182199
int sys::symlink(const char *pointsTo, const char *linkPath) {
183200
#if defined(_WIN32)
184-
llvm::SmallVector<llvm::UTF16, 20> wPointsTo;
185-
llvm::convertUTF8ToUTF16String(pointsTo, wPointsTo);
186-
llvm::SmallVector<llvm::UTF16, 20> wLinkPath;
187-
llvm::convertUTF8ToUTF16String(linkPath, wLinkPath);
188-
DWORD attributes = GetFileAttributesW((LPCWSTR)wPointsTo.data());
201+
llvm::SmallVector<wchar_t, MAX_PATH> wPointsTo;
202+
if (llvm::sys::path::widenPath(pointsTo, wPointsTo))
203+
return -1;
204+
llvm::SmallVector<wchar_t, MAX_PATH> wLinkPath;
205+
if (llvm::sys::path::widenPath(linkPath, wLinkPath))
206+
return -1;
207+
DWORD attributes = GetFileAttributesW(wPointsTo.data());
189208
DWORD directoryFlag = (attributes != INVALID_FILE_ATTRIBUTES &&
190209
attributes & FILE_ATTRIBUTE_DIRECTORY)
191210
? SYMBOLIC_LINK_FLAG_DIRECTORY
192211
: 0;
193212
// Note that CreateSymbolicLinkW takes its arguments in reverse order
194213
// compared to symlink/_symlink
195214
return !::CreateSymbolicLinkW(
196-
(LPCWSTR)wLinkPath.data(), (LPCWSTR)wPointsTo.data(),
215+
wLinkPath.data(), wPointsTo.data(),
197216
SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE | directoryFlag);
198217
#else
199218
return ::symlink(pointsTo, linkPath);
@@ -202,7 +221,12 @@ int sys::symlink(const char *pointsTo, const char *linkPath) {
202221

203222
int sys::unlink(const char *fileName) {
204223
#if defined(_WIN32)
205-
return ::_unlink(fileName);
224+
llvm::SmallVector<wchar_t, MAX_PATH> wfilename;
225+
if (llvm::sys::path::widenPath(fileName, wfilename)) {
226+
errno = EINVAL;
227+
return -1;
228+
}
229+
return ::_wunlink(wfilename.data());
206230
#else
207231
return ::unlink(fileName);
208232
#endif
@@ -263,14 +287,15 @@ int sys::raiseOpenFileLimit(llbuild_rlim_t limit) {
263287
sys::MATCH_RESULT sys::filenameMatch(const std::string& pattern,
264288
const std::string& filename) {
265289
#if defined(_WIN32)
266-
llvm::SmallVector<llvm::UTF16, 20> wpattern;
267-
llvm::SmallVector<llvm::UTF16, 20> wfilename;
290+
llvm::SmallVector<wchar_t, MAX_PATH> wpattern;
291+
llvm::SmallVector<wchar_t, MAX_PATH> wfilename;
268292

269-
llvm::convertUTF8ToUTF16String(pattern, wpattern);
270-
llvm::convertUTF8ToUTF16String(filename, wfilename);
293+
if (llvm::sys::path::widenPath(pattern, wpattern) ||
294+
llvm::sys::path::widenPath(filename, wfilename))
295+
return sys::MATCH_ERROR;
271296

272297
bool result =
273-
PathMatchSpecW((LPCWSTR)wfilename.data(), (LPCWSTR)wpattern.data());
298+
PathMatchSpecW(wfilename.data(), wpattern.data());
274299
return result ? sys::MATCH : sys::NO_MATCH;
275300
#else
276301
int result = fnmatch(pattern.c_str(), filename.c_str(), 0);
@@ -342,9 +367,17 @@ std::string sys::makeTmpDir() {
342367
#if defined(_WIN32)
343368
char path[MAX_PATH];
344369
tmpnam_s(path, MAX_PATH);
345-
llvm::SmallVector<llvm::UTF16, 20> wPath;
346-
llvm::convertUTF8ToUTF16String(path, wPath);
347-
CreateDirectoryW((LPCWSTR)wPath.data(), NULL);
370+
llvm::SmallVector<wchar_t, MAX_PATH> wPath;
371+
if (llvm::sys::path::widenPath(path, wPath))
372+
return std::string();
373+
if (!CreateDirectoryW(wPath.data(), NULL)) {
374+
DWORD error = GetLastError();
375+
if (error != ERROR_ALREADY_EXISTS) {
376+
fprintf(stderr, "Failed to create temporary directory '%s': error code %lu\n",
377+
path, (unsigned long)error);
378+
return std::string();
379+
}
380+
}
348381
return std::string(path);
349382
#else
350383
if (const char *tmpDir = std::getenv("TMPDIR")) {
@@ -371,15 +404,10 @@ std::string sys::getPathSeparators() {
371404

372405
sys::ModuleTraits<>::Handle sys::OpenLibrary(const char *path) {
373406
#if defined(_WIN32)
374-
int cchLength =
375-
MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, path, strlen(path),
376-
nullptr, 0);
377-
std::u16string buffer(cchLength + 1, 0);
378-
MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, path, strlen(path),
379-
const_cast<LPWSTR>(reinterpret_cast<LPCWSTR>(buffer.data())),
380-
buffer.size());
381-
382-
return LoadLibraryW(reinterpret_cast<LPCWSTR>(buffer.data()));
407+
llvm::SmallVector<wchar_t, MAX_PATH> wPath;
408+
if (llvm::sys::path::widenPath(path, wPath))
409+
return nullptr;
410+
return LoadLibraryW(wPath.data());
383411
#else
384412
return dlopen(path, RTLD_LAZY);
385413
#endif

src/llbuild3/LocalExecutor.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,9 +246,19 @@ std::string formatWindowsCommandString(std::vector<std::string> args) {
246246

247247
std::error_code checkExecutable(const std::filesystem::path& path) {
248248

249+
#if defined(_WIN32)
250+
llvm::SmallVector<wchar_t, 128> wpath;
251+
if (llvm::sys::path::widenPath(path.c_str(), wpath)) {
252+
return std::make_error_code(std::errc::invalid_argument);
253+
}
254+
if (::_waccess(path.c_str(), R_OK | X_OK) == -1) {
255+
return std::error_code(errno, std::generic_category());
256+
}
257+
#else
249258
if (::access(path.c_str(), R_OK | X_OK) == -1) {
250259
return std::error_code(errno, std::generic_category());
251260
}
261+
#endif
252262

253263
// Don't say that directories are executable.
254264
#if defined(_WIN32)
@@ -258,7 +268,7 @@ std::error_code checkExecutable(const std::filesystem::path& path) {
258268
#endif
259269

260270
#if defined(_WIN32)
261-
if (0 != ::_stat(path.c_str(), &buf)) {
271+
if (0 != ::_wstat(wpath.data(), &buf)) {
262272
#else
263273
if (0 != ::stat(path.c_str(), &buf)) {
264274
#endif

unittests/Basic/CMakeLists.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,18 @@ if(NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
1616
target_link_libraries(BasicTests PRIVATE
1717
curses)
1818
endif()
19+
20+
# Add widenPath performance benchmark (Windows only)
21+
if(${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
22+
add_executable(widenPathBenchmark
23+
widenPathBenchmark.cpp
24+
)
25+
26+
target_link_libraries(widenPathBenchmark PRIVATE
27+
llbuildBasic
28+
llvmSupport)
29+
30+
# Set output directory to make it easy to find
31+
set_target_properties(widenPathBenchmark PROPERTIES
32+
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
33+
endif()

unittests/Basic/FileSystemTest.cpp

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424

2525
#include "gtest/gtest.h"
2626

27+
#include <chrono>
28+
#include <vector>
29+
2730
using namespace llbuild;
2831
using namespace llbuild::basic;
2932

@@ -222,4 +225,132 @@ TEST(ChecksumOnlyFileSystem, basic) {
222225
EXPECT_FALSE(ec);
223226
}
224227

228+
#if defined(_WIN32)
229+
TEST(FileSystemTest, widenPathPerformance) {
230+
// Performance test for widenPath function on Windows
231+
using namespace std::chrono;
232+
233+
// Test data: various path types that exercise different code paths
234+
std::vector<std::string> testPaths = {
235+
// Short absolute paths (should hit early return optimization)
236+
"C:\\Windows\\System32\\kernel32.dll",
237+
"C:\\Program Files\\test.exe",
238+
"D:\\temp\\file.txt",
239+
240+
// Relative paths (require current directory lookup)
241+
"..\\..\\test.txt",
242+
"src\\main.cpp",
243+
"build\\debug\\output.exe",
244+
245+
// Long paths that require \\?\ prefix
246+
"C:\\very\\long\\path\\that\\exceeds\\the\\normal\\MAX_PATH\\limit\\and\\requires\\special\\handling\\with\\the\\long\\path\\prefix\\to\\work\\correctly\\on\\windows\\systems\\file.txt",
247+
248+
// Paths with . and .. components (require canonicalization)
249+
"C:\\Windows\\..\\Program Files\\..\\Windows\\System32\\kernel32.dll",
250+
"..\\src\\..\\build\\..\\src\\main.cpp",
251+
252+
// UNC paths
253+
"\\\\server\\share\\file.txt",
254+
255+
// Already prefixed paths
256+
"\\\\?\\C:\\already\\prefixed\\path.txt"
257+
};
258+
259+
const int iterations = 1000;
260+
261+
// Warm up
262+
for (const auto& path : testPaths) {
263+
llvm::SmallVector<wchar_t, 260> result;
264+
llvm::sys::path::widenPath(path, result);
265+
}
266+
267+
// Measure performance
268+
auto start = high_resolution_clock::now();
269+
270+
for (int i = 0; i < iterations; ++i) {
271+
for (const auto& path : testPaths) {
272+
llvm::SmallVector<wchar_t, 260> result;
273+
std::error_code ec = llvm::sys::path::widenPath(path, result);
274+
EXPECT_FALSE(ec) << "Failed to widen path: " << path;
275+
}
276+
}
277+
278+
auto end = high_resolution_clock::now();
279+
auto duration = duration_cast<microseconds>(end - start);
280+
281+
double avgTimePerCall = static_cast<double>(duration.count()) / (iterations * testPaths.size());
282+
283+
// Print performance results
284+
std::cout << "\nwidenPath Performance Results:\n";
285+
std::cout << "Total iterations: " << iterations << "\n";
286+
std::cout << "Test paths: " << testPaths.size() << "\n";
287+
std::cout << "Total calls: " << (iterations * testPaths.size()) << "\n";
288+
std::cout << "Total time: " << duration.count() << " microseconds\n";
289+
std::cout << "Average time per call: " << avgTimePerCall << " microseconds\n";
290+
291+
// Performance expectations (these are reasonable targets)
292+
EXPECT_LT(avgTimePerCall, 50.0) << "widenPath is taking too long on average";
293+
294+
// Test correctness of results
295+
for (const auto& path : testPaths) {
296+
llvm::SmallVector<wchar_t, 260> result;
297+
std::error_code ec = llvm::sys::path::widenPath(path, result);
298+
EXPECT_FALSE(ec) << "widenPath failed for: " << path;
299+
EXPECT_GT(result.size(), 0) << "widenPath returned empty result for: " << path;
300+
}
301+
}
302+
303+
TEST(FileSystemTest, widenPathCorrectnessAndOptimizations) {
304+
// Test that optimizations don't break correctness
305+
306+
// Test early return for short absolute paths
307+
{
308+
std::string shortAbsPath = "C:\\Windows\\System32\\test.exe";
309+
llvm::SmallVector<wchar_t, 260> result;
310+
std::error_code ec = llvm::sys::path::widenPath(shortAbsPath, result);
311+
EXPECT_FALSE(ec);
312+
EXPECT_GT(result.size(), 0);
313+
314+
// Convert back to check correctness
315+
llvm::SmallVector<char, 260> backConverted;
316+
llvm::sys::windows::UTF16ToUTF8(result.data(), result.size(), backConverted);
317+
std::string converted(backConverted.data(), backConverted.size());
318+
EXPECT_EQ(converted, shortAbsPath);
319+
}
320+
321+
// Test relative path handling
322+
{
323+
std::string relPath = "..\\test.txt";
324+
llvm::SmallVector<wchar_t, 260> result;
325+
std::error_code ec = llvm::sys::path::widenPath(relPath, result);
326+
EXPECT_FALSE(ec);
327+
EXPECT_GT(result.size(), 0);
328+
}
329+
330+
// Test long path with \\?\ prefix
331+
{
332+
std::string longPath = "C:\\very\\long\\path\\that\\exceeds\\the\\normal\\MAX_PATH\\limit\\and\\requires\\special\\handling\\with\\the\\long\\path\\prefix\\to\\work\\correctly\\on\\windows\\systems\\and\\should\\trigger\\the\\long\\path\\support\\mechanism\\file.txt";
333+
llvm::SmallVector<wchar_t, 1024> result;
334+
std::error_code ec = llvm::sys::path::widenPath(longPath, result);
335+
EXPECT_FALSE(ec);
336+
EXPECT_GT(result.size(), 0);
337+
338+
// Should have \\?\ prefix for long paths
339+
if (result.size() >= 4) {
340+
std::wstring prefix(result.data(), 4);
341+
EXPECT_EQ(prefix, L"\\\\?\\") << "Long path should have \\\\?\\ prefix";
342+
}
343+
}
344+
345+
// Test path canonicalization
346+
{
347+
std::string pathWithDots = "C:\\Windows\\..\\Program Files\\..\\Windows\\System32\\kernel32.dll";
348+
llvm::SmallVector<wchar_t, 1024> result;
349+
std::error_code ec = llvm::sys::path::widenPath(pathWithDots, result);
350+
EXPECT_FALSE(ec);
351+
EXPECT_GT(result.size(), 0);
352+
}
353+
}
354+
#endif // _WIN32
355+
225356
}

0 commit comments

Comments
 (0)