Skip to content

Commit a022f56

Browse files
xinhaoyuancopybara-github
authored andcommitted
Windows support 1/x: centipede:util
This mainly add windows support for the on-demand mmap and make sure that file I/Os are in the binary mode to avoid the automatic CRLF conversion, which is undesired at least in the tests. Also add a Bazel platform for building under Windows and a GitHub action for testing under Windows. PiperOrigin-RevId: 959258384
1 parent 5ce9c5d commit a022f56

7 files changed

Lines changed: 194 additions & 24 deletions

File tree

.github/workflows/bazel_test_centipede.yml

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ jobs:
3030
# TODO(xinhaoyuan): Bump to 24.04 after https://github.com/llvm/llvm-project/issues/102443
3131
# is fixed.
3232
runs-on: ubuntu-22.04
33+
permissions:
34+
contents: read
3335
timeout-minutes: 60
3436
strategy:
3537
matrix:
@@ -39,13 +41,13 @@ jobs:
3941
run: |
4042
sudo sysctl -w kernel.core_pattern=""
4143
- name: Checkout repository
42-
uses: actions/checkout@v4
44+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
4345
- name: Install dependencies
4446
run: |
4547
sudo apt-get update && sudo apt-get install -yq \
4648
clang llvm libssl-dev
4749
- name: Restore latest cache
48-
uses: actions/cache/restore@v4
50+
uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0
4951
with:
5052
path: "~/.cache/bazel"
5153
key: bazel-centipede-cache-${{ matrix.config }}
@@ -84,19 +86,21 @@ jobs:
8486
bazel test --no//fuzztest:use_riegeli --test_output=errors --linkopt=-fsanitize=address --copt=-fsanitize=address --test_env=ASAN_OPTIONS=detect_leaks=0 --platform_suffix=asan --test_timeout=600 centipede/puzzles:all
8587
- name: Save new cache based on main
8688
if: github.ref == 'refs/heads/main'
87-
uses: actions/cache/save@v4
89+
uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0
8890
with:
8991
path: "~/.cache/bazel"
9092
key: bazel-centipede-cache-${{ matrix.config }}-${{ github.run_id }}
9193
run_tests_mac:
9294
name: Run Centipede tests (MacOS)
9395
runs-on: macos-15
96+
permissions:
97+
contents: read
9498
timeout-minutes: 60
9599
steps:
96100
- name: Checkout repository
97-
uses: actions/checkout@v4
101+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
98102
- name: Restore latest cache
99-
uses: actions/cache/restore@v4
103+
uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0
100104
with:
101105
path: "~/.cache/bazel"
102106
key: bazel-centipede-cache-mac
@@ -127,7 +131,37 @@ jobs:
127131
bazel --output_user_root="${HOME}/.cache/bazel" test --test_output=errors --no//fuzztest:use_riegeli --linkopt=-fsanitize=address --copt=-fsanitize=address --test_env=ASAN_OPTIONS=detect_leaks=0 --platform_suffix=asan --test_timeout=600 centipede/puzzles:all
128132
- name: Save new cache based on main
129133
if: github.ref == 'refs/heads/main'
130-
uses: actions/cache/save@v4
134+
uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0
131135
with:
132136
path: "~/.cache/bazel"
133137
key: bazel-centipede-cache-mac-${{ github.run_id }}
138+
run_tests_win:
139+
name: Run Centipede tests (Windows)
140+
runs-on: windows-latest
141+
permissions:
142+
contents: read
143+
timeout-minutes: 60
144+
steps:
145+
- name: Checkout repository
146+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
147+
- name: Restore latest cache
148+
uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0
149+
with:
150+
path: "~/.cache/bazel"
151+
key: bazel-centipede-cache-win-${{ matrix.config }}
152+
restore-keys: bazel-centipede-cache-win-${{ matrix.config }}-
153+
- name: Set environment variable
154+
run: echo "USE_BAZEL_VERSION=8.7.0" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
155+
- name: Run unit tests
156+
if: ${{ !cancelled() }}
157+
run: |
158+
<# Only supported libraries are tested here. #> `
159+
bazelisk test --disk_cache=~/.cache/bazel --local_test_jobs=1 --test_output=errors --no//fuzztest:use_riegeli `
160+
--extra_toolchains=@local_config_cc//:cc-toolchain-x64_windows-clang-cl `
161+
--extra_execution_platforms=//:x64_windows-clang-cl -- centipede:util_test
162+
- name: Save new cache based on main
163+
# if: github.ref == 'refs/heads/main'
164+
uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0
165+
with:
166+
path: "~/.cache/bazel"
167+
key: bazel-centipede-cache-win-${{ github.run_id }}

BUILD

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,12 @@
1313
# limitations under the License.
1414

1515
exports_files(["MODULE.bazel"])
16+
17+
platform(
18+
name = "x64_windows-clang-cl",
19+
constraint_values = [
20+
"@platforms//cpu:x86_64",
21+
"@platforms//os:windows",
22+
"@bazel_tools//tools/cpp:clang-cl",
23+
],
24+
)

MODULE.bazel

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ bazel_dep(
2323
name = "rules_cc",
2424
version = "0.2.17",
2525
)
26+
27+
cc_configure = use_extension("@rules_cc//cc:extensions.bzl", "cc_configure_extension")
28+
use_repo(cc_configure, "local_config_cc")
29+
2630
bazel_dep(
2731
name = "rules_shell",
2832
version = "0.6.1",

centipede/BUILD

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,12 +211,18 @@ cc_library(
211211
)
212212

213213
# Various utilities.
214+
cc_library(
215+
name = "windows_includes",
216+
hdrs = ["windows_includes.h"],
217+
)
218+
214219
cc_library(
215220
name = "util",
216221
srcs = ["util.cc"],
217222
hdrs = ["util.h"],
218223
deps = [
219224
":feature",
225+
":windows_includes",
220226
"@abseil-cpp//absl/base:core_headers",
221227
"@abseil-cpp//absl/base:nullability",
222228
"@abseil-cpp//absl/strings",
@@ -1385,7 +1391,12 @@ cc_test(
13851391
cc_test(
13861392
name = "util_test",
13871393
srcs = ["util_test.cc"],
1388-
copts = ["-fno-signed-char"],
1394+
copts = select({
1395+
"@platforms//os:windows": [
1396+
"/J", # Make unsigned char the default
1397+
],
1398+
"//conditions:default": ["-fno-signed-char"],
1399+
}),
13891400
deps = [
13901401
":feature",
13911402
":thread_pool",

centipede/util.cc

Lines changed: 73 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,12 @@
1717

1818
#include "./centipede/util.h"
1919

20+
#if defined(_WIN32)
21+
#include "./centipede/windows_includes.h"
22+
#else
2023
#include <sys/mman.h>
2124
#include <unistd.h>
25+
#endif
2226

2327
#include <algorithm>
2428
#include <cctype>
@@ -47,6 +51,7 @@
4751
#include "absl/base/const_init.h"
4852
#include "absl/base/nullability.h"
4953
#include "absl/base/thread_annotations.h"
54+
#include "absl/strings/match.h"
5055
#include "absl/strings/str_format.h"
5156
#include "absl/strings/str_replace.h"
5257
#include "absl/strings/str_split.h"
@@ -63,8 +68,13 @@ namespace fuzztest::internal {
6368

6469
size_t GetRandomSeed(size_t seed) {
6570
if (seed != 0) return seed;
71+
#if defined(_WIN32)
72+
return time(nullptr) + GetCurrentProcessId() +
73+
std::hash<std::thread::id>{}(std::this_thread::get_id());
74+
#else
6675
return time(nullptr) + getpid() +
6776
std::hash<std::thread::id>{}(std::this_thread::get_id());
77+
#endif
6878
}
6979

7080
std::string AsPrintableString(ByteSpan data, size_t max_len) {
@@ -83,7 +93,7 @@ std::string AsPrintableString(ByteSpan data, size_t max_len) {
8393

8494
template <typename Container>
8595
void ReadFromLocalFile(std::string_view file_path, Container &data) {
86-
std::ifstream f(std::string{file_path});
96+
std::ifstream f(std::string{file_path}, std::ios::in | std::ios::binary);
8797
if (!f) return;
8898
f.seekg(0, std::ios_base::end);
8999
auto size = f.tellg();
@@ -112,12 +122,13 @@ void ReadFromLocalFile(std::string_view file_path,
112122
}
113123

114124
void ClearLocalFileContents(std::string_view file_path) {
115-
std::ofstream f(std::string{file_path}, std::ios::out | std::ios::trunc);
125+
std::ofstream f(std::string{file_path},
126+
std::ios::out | std::ios::trunc | std::ios::binary);
116127
FUZZTEST_CHECK(f) << "Failed to clear the file: " << file_path;
117128
}
118129

119130
void WriteToLocalFile(std::string_view file_path, ByteSpan data) {
120-
std::ofstream f(std::string{file_path});
131+
std::ofstream f(std::string{file_path}, std::ios::out | std::ios::binary);
121132
FUZZTEST_CHECK(f) << "Failed to open local file: " << file_path;
122133
f.write(reinterpret_cast<const char *>(data.data()),
123134
static_cast<int64_t>(data.size()));
@@ -136,13 +147,15 @@ void WriteToLocalFile(std::string_view file_path, const FeatureVec &data) {
136147

137148
void WriteToLocalHashedFileInDir(std::string_view dir_path, ByteSpan data) {
138149
if (dir_path.empty()) return;
139-
std::string file_path = std::filesystem::path(dir_path).append(Hash(data));
150+
std::string file_path =
151+
std::filesystem::path(dir_path).append(Hash(data)).string();
140152
WriteToLocalFile(file_path, data);
141153
}
142154

143155
void WriteToRemoteHashedFileInDir(std::string_view dir_path, ByteSpan data) {
144156
if (dir_path.empty()) return;
145-
std::string file_path = std::filesystem::path(dir_path).append(Hash(data));
157+
std::string file_path =
158+
std::filesystem::path(dir_path).append(Hash(data)).string();
146159
FUZZTEST_CHECK_OK(
147160
RemoteFileSetContents(file_path, std::string(data.begin(), data.end())));
148161
}
@@ -155,17 +168,24 @@ std::string HashOfFileContents(std::string_view file_path) {
155168
}
156169

157170
std::string ProcessAndThreadUniqueID(std::string_view prefix) {
158-
// operator << is the only way to serialize std::this_thread::get_id().
159171
std::ostringstream oss;
172+
#if defined(_WIN32)
173+
oss << prefix << GetCurrentProcessId() << "-" << GetCurrentThreadId();
174+
#else
175+
// operator << is the only way to serialize std::this_thread::get_id().
160176
oss << prefix << getpid() << "-" << std::this_thread::get_id();
177+
#endif
161178
return oss.str();
162179
}
163180

164181
std::string TemporaryLocalDirPath() {
165182
const char *TMPDIR = getenv("TMPDIR");
183+
if (!TMPDIR) TMPDIR = getenv("TEMP");
184+
if (!TMPDIR) TMPDIR = getenv("TMP");
166185
std::string tmp = TMPDIR ? TMPDIR : "/tmp";
167-
return std::filesystem::path(tmp).append(
168-
ProcessAndThreadUniqueID("centipede-"));
186+
return std::filesystem::path(tmp)
187+
.append(ProcessAndThreadUniqueID("centipede-"))
188+
.string();
169189
}
170190

171191
// We need to maintain a global set of dirs that CreateLocalDirRemovedAtExit()
@@ -189,13 +209,15 @@ static void RemoveDirsAtExit() {
189209

190210
void CreateLocalDirRemovedAtExit(std::string_view path) {
191211
// Safeguard against removing dirs not created by TemporaryLocalDirPath().
192-
FUZZTEST_CHECK_NE(path.find("/centipede-"), std::string::npos);
212+
FUZZTEST_CHECK(absl::StrContains(path, "/centipede-") ||
213+
absl::StrContains(path, "\\centipede-"));
193214
// Create the dir.
194215
std::error_code error;
195-
std::filesystem::remove_all(path, error);
196-
FUZZTEST_LOG_IF(ERROR, error)
197-
<< "Unable to clean up existing dir " << path << ": " << error.message();
198-
std::filesystem::create_directories(path);
216+
std::filesystem::path p(path);
217+
if (std::filesystem::exists(p, error)) {
218+
std::filesystem::remove_all(p, error);
219+
}
220+
std::filesystem::create_directories(p, error);
199221
// Add to dirs_to_delete_at_exit.
200222
absl::MutexLock lock(dirs_to_delete_at_exit_mutex);
201223
if (!dirs_to_delete_at_exit) {
@@ -206,7 +228,7 @@ void CreateLocalDirRemovedAtExit(std::string_view path) {
206228
}
207229

208230
ScopedFile::ScopedFile(std::string_view dir_path, std::string_view name)
209-
: my_path_(std::filesystem::path(dir_path) / name) {}
231+
: my_path_((std::filesystem::path(dir_path) / name).string()) {}
210232

211233
ScopedFile::~ScopedFile() {
212234
std::error_code error;
@@ -359,16 +381,53 @@ std::vector<size_t> RandomWeightedSubset(absl::Span<const uint64_t> set,
359381
return res;
360382
}
361383

384+
#if defined(_WIN32)
385+
static LONG CALLBACK
386+
AutoCommitPageFaultHandler(PEXCEPTION_POINTERS ExceptionInfo) {
387+
if (ExceptionInfo->ExceptionRecord->ExceptionCode ==
388+
EXCEPTION_ACCESS_VIOLATION) {
389+
ULONG_PTR fault_addr =
390+
ExceptionInfo->ExceptionRecord->ExceptionInformation[1];
391+
if (VirtualAlloc(reinterpret_cast<void*>(fault_addr), 1, MEM_COMMIT,
392+
PAGE_READWRITE) != nullptr) {
393+
return EXCEPTION_CONTINUE_EXECUTION;
394+
}
395+
}
396+
return EXCEPTION_CONTINUE_SEARCH;
397+
}
398+
#endif
399+
362400
uint8_t *MmapNoReserve(size_t size) {
401+
#if defined(_WIN32)
402+
// Set up page fault handler to commit page on demand.
403+
static bool installed_veh = []() {
404+
// Must use `First=0` as it could otherwise conflict with e.g. sanitizers.
405+
AddVectoredExceptionHandler(/*First=*/0, AutoCommitPageFaultHandler);
406+
return true;
407+
}();
408+
(void)installed_veh;
409+
// MEM_RESERVE has different semantics and does not contradict with
410+
// MAP_NORESERVE for mmap.
411+
auto result = VirtualAlloc(nullptr, size, MEM_RESERVE, PAGE_READWRITE);
412+
FUZZTEST_CHECK(result != nullptr)
413+
<< "VirtualAlloc failed for size " << size << " err=" << GetLastError();
414+
return reinterpret_cast<uint8_t*>(result);
415+
#else
363416
auto result = mmap(0, size, PROT_READ | PROT_WRITE,
364417
MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0);
365418
FUZZTEST_CHECK(result != MAP_FAILED);
366419
return reinterpret_cast<uint8_t *>(result);
420+
#endif
367421
}
368422

369423
void Munmap(uint8_t *ptr, size_t size) {
424+
#if defined(_WIN32)
425+
BOOL result = VirtualFree(ptr, 0, MEM_RELEASE);
426+
FUZZTEST_CHECK(result != 0);
427+
#else
370428
auto result = munmap(ptr, size);
371429
FUZZTEST_CHECK_EQ(result, 0);
430+
#endif
372431
}
373432

374433
int PollTimeoutMs(absl::Duration timeout) {

centipede/util_test.cc

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@
3232
#include "./common/hash.h"
3333
#include "./common/logging.h"
3434

35+
#if defined(_WIN32)
36+
#define setenv(n, v, _r) _putenv_s(n, v)
37+
#endif
38+
3539
namespace fuzztest::internal {
3640

3741
TEST(UtilTest, AsString) {
@@ -116,7 +120,8 @@ TEST(UtilTest, TemporaryLocalDirPath) {
116120
auto temp_dir = TemporaryLocalDirPath();
117121
// Create dir, create a file there, write to file, read from it, remove dir.
118122
std::filesystem::create_directories(temp_dir);
119-
std::string temp_file_path = std::filesystem::path(temp_dir).append("blah");
123+
std::string temp_file_path =
124+
std::filesystem::path(temp_dir).append("blah").string();
120125
ByteArray written_data{1, 2, 3};
121126
WriteToLocalFile(temp_file_path, written_data);
122127
ByteArray read_data;
@@ -162,8 +167,8 @@ TEST(UtilTest, CreateLocalDirRemovedAtExit) {
162167
EXPECT_TRUE(std::filesystem::exists(tmpdir));
163168
setenv("CENTIPEDE_UTIL_TEST_TEMP_DIR", tmpdir.c_str(), 1);
164169
// Create two subdirs via CreateLocalDirRemovedAtExit.
165-
std::string subdir1 = std::filesystem::path(tmpdir).append("1");
166-
std::string subdir2 = std::filesystem::path(tmpdir).append("2");
170+
std::string subdir1 = std::filesystem::path(tmpdir).append("1").string();
171+
std::string subdir2 = std::filesystem::path(tmpdir).append("2").string();
167172
CreateLocalDirRemovedAtExit(subdir1);
168173
CreateLocalDirRemovedAtExit(subdir2);
169174
EXPECT_TRUE(std::filesystem::exists(subdir1));
@@ -293,6 +298,15 @@ TEST(UtilTest, RemoveSubset) {
293298
testing::ElementsAre(std::vector<int>{1}, std::vector<int>{3}));
294299
}
295300

301+
TEST(UtilTest, MmapTest) {
302+
static constexpr size_t kBufSize = 1 << 30; // 1 GiB
303+
auto* buf = MmapNoReserve(kBufSize);
304+
ASSERT_NE(buf, nullptr);
305+
EXPECT_EQ(buf[1234], 0);
306+
EXPECT_EQ(buf[567890], 0);
307+
Munmap(buf, kBufSize);
308+
}
309+
296310
TEST(UtilTest, PollTimeoutMsWorks) {
297311
EXPECT_GT(PollTimeoutMs(absl::ZeroDuration()), 0);
298312
EXPECT_GT(PollTimeoutMs(-absl::InfiniteDuration()), 0);

0 commit comments

Comments
 (0)