#include #include #include #include #include "fold/fuzz/boundary_seeds.hpp" #include "fold/fuzz/witness_recorder.hpp" using fold::fuzz::deriveBoundarySeeds; using fold::fuzz::ParamWitness; using fold::fuzz::PathWitness; using fold::fuzz::PointerShape; using fold::fuzz::WitnessKind; namespace { PathWitness withSingleInt(std::uint32_t bit_width, bool is_signed) { PathWitness w; w.function = "f"; w.finding_id = "test:1"; ParamWitness p; p.cpp_type = is_signed ? "int" : "unsigned"; p.kind = WitnessKind::Integer; p.bit_width = bit_width; p.is_signed = is_signed; p.bytes = std::vector{std::byte{0x42}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}}; w.params = {p}; return w; } std::uint64_t leAt(const std::vector& bytes, std::size_t off, std::size_t n) { std::uint64_t out = 0; for (std::size_t i = 0; i < n && off + i < bytes.size(); ++i) out |= static_cast(std::to_integer(bytes[off + i])) << (8 * i); return out; } } // namespace TEST_CASE("deriveBoundarySeeds: signed int produces 5 derivatives (0, 1, -1, MAX, MIN)", "[fuzz][boundary]") { auto seeds = deriveBoundarySeeds(withSingleInt(32, /*is_signed=*/true)); REQUIRE(seeds.size() == 5); // Spot-check each value at offset 0 (the only param). std::vector seen; seen.reserve(seeds.size()); for (const auto& s : seeds) { REQUIRE(s.params.size() == 1); REQUIRE(s.params[0].bytes.size() == 4); seen.push_back(leAt(s.params[0].bytes, 0, 4)); } REQUIRE(std::find(seen.begin(), seen.end(), 0U) != seen.end()); REQUIRE(std::find(seen.begin(), seen.end(), 1U) != seen.end()); REQUIRE(std::find(seen.begin(), seen.end(), 0xFFFFFFFFU) != seen.end()); // -1 REQUIRE(std::find(seen.begin(), seen.end(), 0x7FFFFFFFU) != seen.end()); // INT_MAX REQUIRE(std::find(seen.begin(), seen.end(), 0x80000000U) != seen.end()); // INT_MIN } TEST_CASE("deriveBoundarySeeds: unsigned int produces 3 derivatives (0, 1, MAX) — no -1/MIN", "[fuzz][boundary]") { auto seeds = deriveBoundarySeeds(withSingleInt(32, /*is_signed=*/false)); REQUIRE(seeds.size() == 3); std::vector seen; seen.reserve(seeds.size()); for (const auto& s : seeds) seen.push_back(leAt(s.params[0].bytes, 0, 4)); REQUIRE(std::find(seen.begin(), seen.end(), 0U) != seen.end()); REQUIRE(std::find(seen.begin(), seen.end(), 1U) != seen.end()); REQUIRE(std::find(seen.begin(), seen.end(), 0xFFFFFFFFU) != seen.end()); // UINT_MAX } TEST_CASE("deriveBoundarySeeds: 8-bit signed gives the right INT_MIN/INT_MAX", "[fuzz][boundary]") { auto seeds = deriveBoundarySeeds(withSingleInt(8, true)); REQUIRE(seeds.size() == 5); std::vector seen; for (const auto& s : seeds) seen.push_back(leAt(s.params[0].bytes, 0, 1)); REQUIRE(std::find(seen.begin(), seen.end(), 0U) != seen.end()); REQUIRE(std::find(seen.begin(), seen.end(), 1U) != seen.end()); REQUIRE(std::find(seen.begin(), seen.end(), 0xFFU) != seen.end()); // -1 in u8 REQUIRE(std::find(seen.begin(), seen.end(), 0x7FU) != seen.end()); // INT8_MAX REQUIRE(std::find(seen.begin(), seen.end(), 0x80U) != seen.end()); // INT8_MIN } TEST_CASE("deriveBoundarySeeds: skips Pointer and Unsupported params", "[fuzz][boundary]") { PathWitness w; w.function = "f"; w.finding_id = "test:1"; ParamWitness ptr; ptr.cpp_type = "char*"; ptr.kind = WitnessKind::Pointer; ptr.pointer.shape = PointerShape::NullableBuffer; ptr.pointer.buffer_bytes = 64; ParamWitness unsup; unsup.cpp_type = "struct S"; unsup.kind = WitnessKind::Unsupported; w.params = {ptr, unsup}; REQUIRE(deriveBoundarySeeds(w).empty()); } TEST_CASE("deriveBoundarySeeds: 2-param witness perturbs each int independently", "[fuzz][boundary]") { PathWitness w; w.function = "f"; w.finding_id = "test:1"; ParamWitness a; a.cpp_type = "int"; a.kind = WitnessKind::Integer; a.bit_width = 32; a.is_signed = true; a.bytes = std::vector(4, std::byte{0xAA}); ParamWitness b; b.cpp_type = "unsigned char"; b.kind = WitnessKind::Integer; b.bit_width = 8; b.is_signed = false; b.bytes = std::vector{std::byte{0xBB}}; w.params = {a, b}; auto seeds = deriveBoundarySeeds(w); // 5 perturbations of `a` (each leaves b untouched) + 3 of `b` // (each leaves a untouched) = 8 total. REQUIRE(seeds.size() == 5 + 3); // When `a` is perturbed, `b` should still be 0xBB. Spot-check // one such seed. bool found_a_perturbation_with_b_intact = false; for (const auto& s : seeds) { const std::uint64_t a_val = leAt(s.params[0].bytes, 0, 4); const std::uint64_t b_val = leAt(s.params[1].bytes, 0, 1); if (a_val == 0 && b_val == 0xBBU) { found_a_perturbation_with_b_intact = true; break; } } REQUIRE(found_a_perturbation_with_b_intact); } TEST_CASE("deriveBoundarySeeds: bool param yields 2 derivatives (0, 1)", "[fuzz][boundary]") { PathWitness w; w.function = "f"; w.finding_id = "test:1"; ParamWitness p; p.cpp_type = "bool"; p.kind = WitnessKind::Bool; p.bit_width = 8; p.bytes = std::vector{std::byte{1}}; w.params = {p}; auto seeds = deriveBoundarySeeds(w); REQUIRE(seeds.size() == 2); REQUIRE(static_cast(seeds[0].params[0].bytes[0]) == 0); REQUIRE(static_cast(seeds[1].params[0].bytes[0]) == 1); } TEST_CASE("deriveBoundarySeeds marks derivatives as partial", "[fuzz][boundary]") { PathWitness w = withSingleInt(32, true); w.partial = false; auto seeds = deriveBoundarySeeds(w); REQUIRE(!seeds.empty()); for (const auto& s : seeds) REQUIRE(s.partial); // Original untouched. REQUIRE_FALSE(w.partial); }