Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions tcmalloc/internal/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -1331,6 +1331,7 @@ cc_test(
deps = [
":cpu_utils",
":sysinfo",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/strings:string_view",
"@com_google_fuzztest//fuzztest",
"@com_google_fuzztest//fuzztest:fuzztest_gtest_main",
Expand Down
74 changes: 74 additions & 0 deletions tcmalloc/internal/sysinfo_fuzz.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,14 @@
#include <cstddef>
#include <cstring>
#include <optional>
#include <string>
#include <vector>

#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "fuzztest/fuzztest.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "absl/strings/string_view.h"
#include "tcmalloc/internal/cpu_utils.h"
#include "tcmalloc/internal/sysinfo.h"
Expand All @@ -44,5 +50,73 @@ void ParseInput(absl::string_view s) {

FUZZ_TEST(SysinfoTest, ParseInput);

void FuzzCpuSetRoundTrip(const std::vector<int>& cpus_to_set,
const std::vector<size_t>& read_sizes) {
CpuSet reference;
reference.Zero();

// Serialize the reference set into a cpulist-style string.
for (int cpu : cpus_to_set) {
if (cpu >= 0 && cpu < kMaxCpus) {
reference.Set(cpu);
}
}

std::vector<std::string> components;
for (int cpu = 0; cpu < kMaxCpus; cpu++) {
if (!reference.IsSet(cpu)) continue;

const int start = cpu;
int next = cpu + 1;
while (next < kMaxCpus && reference.IsSet(next)) {
cpu = next;
next = cpu + 1;
}

if (cpu == start) {
components.push_back(absl::StrCat(cpu));
} else {
components.push_back(absl::StrCat(start, "-", cpu));
}
}
const std::string serialized = absl::StrJoin(components, ",");

absl::string_view remaining(serialized);
// Now parse that string using our ParseCpulist function, randomizing the
// amount of data we provide to it from each read.
size_t read_sizes_idx = 0;

const std::optional<CpuSet> parsed =
ParseCpulist([&](char* const buf, const size_t count) -> ssize_t {
// Calculate how much data we have left to provide.
const size_t max = std::min(count, remaining.size());

// If none, we have no choice but to provide nothing.
if (max == 0) return 0;

// If we do have data, return a randomly sized subset of it to stress
// the logic around reading partial values.
size_t copy;
if (read_sizes_idx < read_sizes.size()) {
copy = (read_sizes[read_sizes_idx] % max) + 1;
read_sizes_idx++;
} else {
copy = max;
}

memcpy(buf, remaining.data(), copy);
remaining.remove_prefix(copy);
return copy;
});

// We ought to have parsed the same set of CPUs that we serialized.
ASSERT_THAT(parsed, testing::Ne(std::nullopt));
EXPECT_TRUE(CPU_EQUAL_S(kCpuSetBytes, parsed->data(), reference.data()));
}

FUZZ_TEST(SysinfoTest, FuzzCpuSetRoundTrip)
.WithDomains(fuzztest::VectorOf(fuzztest::InRange<int>(0, kMaxCpus - 1)),
fuzztest::VectorOf(fuzztest::Arbitrary<size_t>()));

} // namespace
} // namespace tcmalloc::tcmalloc_internal
62 changes: 0 additions & 62 deletions tcmalloc/internal/sysinfo_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -99,68 +99,6 @@ TEST(ParseCpulistTest, NotInBounds) {
ASSERT_THAT(parsed, testing::Eq(std::nullopt));
}

// Ensure that we can parse randomized cpulists correctly.
TEST(ParseCpulistTest, Random) {
absl::BitGen gen;

static constexpr int kIterations = 100;
for (int i = 0; i < kIterations; i++) {
CpuSet reference;
reference.Zero();

// Set a random number of CPUs within the reference set.
const double density = absl::Uniform(gen, 0.0, 1.0);
for (int cpu = 0; cpu < kMaxCpus; cpu++) {
if (absl::Bernoulli(gen, density)) {
reference.Set(cpu);
}
}

// Serialize the reference set into a cpulist-style string.
std::vector<std::string> components;
for (int cpu = 0; cpu < kMaxCpus; cpu++) {
if (!reference.IsSet(cpu)) continue;

const int start = cpu;
int next = cpu + 1;
while (next < kMaxCpus && reference.IsSet(next)) {
cpu = next;
next = cpu + 1;
}

if (cpu == start) {
components.push_back(absl::StrCat(cpu));
} else {
components.push_back(absl::StrCat(start, "-", cpu));
}
}
const std::string serialized = absl::StrJoin(components, ",");

// Now parse that string using our ParseCpulist function, randomizing the
// amount of data we provide to it from each read.
absl::string_view remaining(serialized);
const absl::optional<CpuSet> parsed =
ParseCpulist([&](char* const buf, const size_t count) -> ssize_t {
// Calculate how much data we have left to provide.
const size_t max = std::min(count, remaining.size());

// If none, we have no choice but to provide nothing.
if (max == 0) return 0;

// If we do have data, return a randomly sized subset of it to stress
// the logic around reading partial values.
const size_t copy = absl::Uniform(gen, static_cast<size_t>(1), max);
memcpy(buf, remaining.data(), copy);
remaining.remove_prefix(copy);
return copy;
});

// We ought to have parsed the same set of CPUs that we serialized.
ASSERT_THAT(parsed, testing::Ne(std::nullopt));
EXPECT_TRUE(CPU_EQUAL_S(kCpuSetBytes, parsed->data(), reference.data()));
}
}

TEST(NumCPUs, NoCache) {
const int result = []() {
AllocationGuard guard;
Expand Down
Loading