#include #include #include #include "fold/fuzz/harness_emitter.hpp" #include "fold/fuzz/witness_recorder.hpp" using fold::fuzz::emitHarness; using fold::fuzz::HarnessArtifacts; using fold::fuzz::makeSafeName; using fold::fuzz::ParamWitness; using fold::fuzz::PathWitness; using fold::fuzz::PointerShape; using fold::fuzz::WitnessKind; namespace { PathWitness twoParamIntegerWitness() { PathWitness w; w.function = "ns::target"; w.cpp_return_type = "void"; w.finding_id = "null-deref:test.c:10:1"; ParamWitness x; x.param_index = 0; x.name = "x"; x.cpp_type = "int"; x.kind = WitnessKind::Integer; x.bit_width = 32; x.is_signed = true; ParamWitness y; y.param_index = 1; y.name = "y"; y.cpp_type = "unsigned char"; y.kind = WitnessKind::Integer; y.bit_width = 8; y.is_signed = false; w.params = {x, y}; return w; } PathWitness pointerWitness() { PathWitness w; w.function = "raw_target"; w.cpp_return_type = "void"; w.finding_id = "null-deref:test.c:42:1"; ParamWitness p; p.param_index = 0; p.name = "p"; p.cpp_type = "char*"; p.kind = WitnessKind::Pointer; p.pointer.shape = PointerShape::NullableBuffer; p.pointer.buffer_bytes = 16; w.params = {p}; return w; } } // namespace TEST_CASE("makeSafeName sanitizes :: and template chars", "[fuzz][harness][safe_name]") { REQUIRE(makeSafeName("ns::target") == "ns_target"); REQUIRE(makeSafeName("ns::Vec::push_back") == "ns_Vec_int_push_back"); REQUIRE(makeSafeName("f") == "f"); REQUIRE(makeSafeName("a::") == "a"); } TEST_CASE("makeSafeName strips anonymous namespace markers", "[fuzz][harness][safe_name]") { REQUIRE(makeSafeName("fold::(anonymous namespace)::helper") == "fold_helper"); REQUIRE(makeSafeName("(anonymous namespace)::fn") == "fn"); REQUIRE(makeSafeName("a::(anonymous namespace)::b::(anonymous namespace)::c") == "a_b_c"); } TEST_CASE("emitHarness rejects witnesses with all unsupported params", "[fuzz][harness]") { fold::fuzz::PathWitness w; w.function = "fn_with_ast_types"; w.finding_id = "test:file:1:1"; fold::fuzz::ParamWitness p1; p1.kind = fold::fuzz::WitnessKind::Unsupported; p1.cpp_type = "clang::BinaryOperator*"; w.params.push_back(p1); fold::fuzz::ParamWitness p2; p2.kind = fold::fuzz::WitnessKind::Unsupported; p2.cpp_type = "clang::ASTContext&"; w.params.push_back(p2); auto art = emitHarness({w}); REQUIRE(!art.error.empty()); REQUIRE(art.error.find("unsupported") != std::string::npos); } TEST_CASE("emitHarness rejects empty witness list", "[fuzz][harness]") { auto art = emitHarness({}); REQUIRE(!art.error.empty()); } TEST_CASE("emitHarness rejects witnesses spanning multiple functions", "[fuzz][harness]") { PathWitness a = twoParamIntegerWitness(); PathWitness b = twoParamIntegerWitness(); b.function = "other::name"; auto art = emitHarness({a, b}); REQUIRE(art.error == "witnesses span multiple functions"); } TEST_CASE("emitHarness computes total_input_bytes from slot sizes", "[fuzz][harness]") { auto art = emitHarness({twoParamIntegerWitness()}); REQUIRE(art.error.empty()); REQUIRE(art.total_input_bytes == 5); // 4 (int32) + 1 (uint8) REQUIRE(art.safe_name == "ns_target"); REQUIRE_FALSE(art.partial); } TEST_CASE("emitHarness writes nested-namespace forward declaration", "[fuzz][harness]") { auto art = emitHarness({twoParamIntegerWitness()}); REQUIRE(art.harness_source.find("namespace ns {") != std::string::npos); REQUIRE(art.harness_source.find("void target(int, unsigned char);") != std::string::npos); } TEST_CASE("emitHarness emits LLVMFuzzerTestOneInput with size-gate", "[fuzz][harness]") { auto art = emitHarness({twoParamIntegerWitness()}); REQUIRE(art.harness_source.find("extern \"C\" int LLVMFuzzerTestOneInput") != std::string::npos); REQUIRE(art.harness_source.find("if (size < 5u) return 0;") != std::string::npos); } TEST_CASE("emitHarness packs integer parameters via memcpy at correct offsets", "[fuzz][harness]") { auto art = emitHarness({twoParamIntegerWitness()}); REQUIRE(art.harness_source.find("std::memcpy(&arg0, data + 0, 4);") != std::string::npos); REQUIRE(art.harness_source.find("std::memcpy(&arg1, data + 4, 1);") != std::string::npos); REQUIRE(art.harness_source.find("ns::target(arg0, arg1);") != std::string::npos); } TEST_CASE("emitHarness heap-allocates and frees buffers for pointer params", "[fuzz][harness][pointer]") { auto art = emitHarness({pointerWitness()}); REQUIRE(art.error.empty()); REQUIRE(art.total_input_bytes == 16); REQUIRE(art.harness_source.find("new unsigned char[16]") != std::string::npos); REQUIRE(art.harness_source.find("reinterpret_cast") != std::string::npos); REQUIRE(art.harness_source.find("delete[] arg0_buf;") != std::string::npos); } TEST_CASE("emitHarness null-pointer shape passes nullptr", "[fuzz][harness][pointer]") { PathWitness w = pointerWitness(); w.params[0].pointer.shape = PointerShape::ConcreteNull; w.params[0].pointer.buffer_bytes = 0; auto art = emitHarness({w}); REQUIRE(art.total_input_bytes == 0); REQUIRE(art.harness_source.find("char* arg0 = nullptr;") != std::string::npos); REQUIRE(art.harness_source.find("new unsigned char") == std::string::npos); } TEST_CASE("emitHarness schema_json includes per-parameter byte offsets", "[fuzz][harness][schema]") { auto art = emitHarness({twoParamIntegerWitness()}); REQUIRE(art.schema_json.find("\"function\": \"ns::target\"") != std::string::npos); REQUIRE(art.schema_json.find("\"total_bytes\": 5") != std::string::npos); REQUIRE(art.schema_json.find("\"byte_offset\": 0") != std::string::npos); REQUIRE(art.schema_json.find("\"byte_offset\": 4") != std::string::npos); REQUIRE(art.schema_json.find("\"kind\": \"integer\"") != std::string::npos); } TEST_CASE("emitHarness schema_json records partial flag", "[fuzz][harness][schema]") { PathWitness w = twoParamIntegerWitness(); w.partial = true; auto art = emitHarness({w}); REQUIRE(art.schema_json.find("\"partial\": true") != std::string::npos); REQUIRE(art.partial); } TEST_CASE("emitHarness schema_json records pointer shape", "[fuzz][harness][schema][pointer]") { auto art = emitHarness({pointerWitness()}); REQUIRE(art.schema_json.find("\"kind\": \"pointer\"") != std::string::npos); REQUIRE(art.schema_json.find("\"pointer_shape\": \"nullable_buffer\"") != std::string::npos); } TEST_CASE("emitHarness wraps the forward decl in extern \"C\" for C-linkage targets", "[fuzz][harness][c_linkage]") { PathWitness w = twoParamIntegerWitness(); w.function = "leaky"; // simple C-style name w.is_c_linkage = true; auto art = emitHarness({w}); REQUIRE(art.error.empty()); // extern "C" wrapper, no namespace blocks REQUIRE(art.harness_source.find("extern \"C\" {") != std::string::npos); REQUIRE(art.harness_source.find("namespace ") == std::string::npos); REQUIRE(art.harness_source.find("void leaky(int, unsigned char);") != std::string::npos); } TEST_CASE("emitHarness honors c_linkage in schema_json", "[fuzz][harness][schema][c_linkage]") { PathWitness w = twoParamIntegerWitness(); w.is_c_linkage = true; auto art = emitHarness({w}); REQUIRE(art.schema_json.find("\"c_linkage\": true") != std::string::npos); } TEST_CASE("emitHarness defaults c_linkage to false (C++ path)", "[fuzz][harness][schema][c_linkage]") { auto art = emitHarness({twoParamIntegerWitness()}); REQUIRE(art.schema_json.find("\"c_linkage\": false") != std::string::npos); // C++ path still emits the namespace blocks REQUIRE(art.harness_source.find("namespace ns {") != std::string::npos); } TEST_CASE("emitBinaryHarness generates valid dlopen C code", "[fuzz][harness][binary]") { auto harness = fold::fuzz::emitBinaryHarness("/tmp/test.dylib", "target_fn", 3); REQUIRE(harness.find("dlopen") != std::string::npos); REQUIRE(harness.find("dlsym") != std::string::npos); REQUIRE(harness.find("target_fn") != std::string::npos); REQUIRE(harness.find("arg0") != std::string::npos); REQUIRE(harness.find("arg1") != std::string::npos); REQUIRE(harness.find("arg2") != std::string::npos); REQUIRE(harness.find("LLVMFuzzerTestOneInput") != std::string::npos); REQUIRE(harness.find("__attribute__((constructor))") != std::string::npos); } TEST_CASE("emitBinaryHarness zero-param produces valid skeleton", "[fuzz][harness][binary]") { auto harness = fold::fuzz::emitBinaryHarness("/tmp/lib.dylib", "init", 0); REQUIRE(harness.find("dlopen") != std::string::npos); }