#include #include #include #include #include #include "fold/detect/use_after_free.hpp" #include "fold/report/finding.hpp" #include "fold/symbolic/budget.hpp" namespace { std::unique_ptr parse(const std::string& code) { return clang::tooling::buildASTFromCodeWithArgs(code, {"-std=c++20"}, "input.cpp"); } } // namespace TEST_CASE("use-after-free: simple free then deref is flagged", "[use-after-free][free]") { auto unit = parse(R"( extern "C" void free(void*); int f(int* p) { free(p); return *p; } )"); REQUIRE(unit); auto findings = fold::detectUseAfterFreeInContext(unit->getASTContext()); REQUIRE(findings.size() == 1); REQUIRE(findings[0].kind == fold::FindingKind::UseAfterFree); } TEST_CASE("use-after-free: reassignment refreshes pointer", "[use-after-free][reassign]") { auto unit = parse(R"( extern "C" void free(void*); int f(int* p, int* q) { free(p); p = q; return *p; } )"); REQUIRE(unit); auto findings = fold::detectUseAfterFreeInContext(unit->getASTContext()); REQUIRE(findings.empty()); } TEST_CASE("use-after-free: free null constant does not poison", "[use-after-free][null]") { auto unit = parse(R"( extern "C" void free(void*); int f(int* p) { free((void*)0); return *p; } )"); REQUIRE(unit); auto findings = fold::detectUseAfterFreeInContext(unit->getASTContext()); REQUIRE(findings.empty()); } TEST_CASE("use-after-free: realloc assignment refreshes same pointer", "[use-after-free][realloc]") { auto unit = parse(R"( extern "C" void free(void*); extern "C" void* realloc(void*, unsigned long); int f(int* p) { free(p); p = (int*)realloc(p, 16); return *p; } )"); REQUIRE(unit); auto findings = fold::detectUseAfterFreeInContext(unit->getASTContext()); REQUIRE(findings.empty()); } TEST_CASE("use-after-free: alias is poisoned with original", "[use-after-free][alias]") { auto unit = parse(R"( extern "C" void free(void*); int f(int* p) { int* q = p; free(p); return *q; } )"); REQUIRE(unit); auto findings = fold::detectUseAfterFreeInContext(unit->getASTContext()); REQUIRE(findings.size() == 1); } TEST_CASE("use-after-free: delete then member/deref use is flagged", "[use-after-free][delete]") { auto unit = parse(R"( struct S { int x; }; int f(S* p) { delete p; return p->x; } )"); REQUIRE(unit); auto findings = fold::detectUseAfterFreeInContext(unit->getASTContext()); REQUIRE(findings.size() == 1); } TEST_CASE("use-after-free: delete then plain deref into non-pointer var is flagged", "[use-after-free][delete]") { auto unit = parse(R"( int f(int* p) { delete p; volatile int r = *p; return r; } )"); REQUIRE(unit); auto findings = fold::detectUseAfterFreeInContext(unit->getASTContext()); REQUIRE(findings.size() == 1); REQUIRE(findings[0].kind == fold::FindingKind::UseAfterFree); } TEST_CASE("use-after-free: plain deref before delete is not flagged", "[use-after-free][delete]") { auto unit = parse(R"( int f(int* p) { volatile int r = *p; delete p; return r; } )"); REQUIRE(unit); auto findings = fold::detectUseAfterFreeInContext(unit->getASTContext()); REQUIRE(findings.empty()); } TEST_CASE("use-after-free: conditional use inside freed branch is flagged", "[use-after-free][branch]") { auto unit = parse(R"( extern "C" void free(void*); int f(int* p, int c) { if (c) { free(p); return *p; } return 0; } )"); REQUIRE(unit); auto findings = fold::detectUseAfterFreeInContext(unit->getASTContext()); REQUIRE(findings.size() == 1); } TEST_CASE("use-after-free: functions without free or delete skip symbolic walking", "[use-after-free][precheck]") { auto unit = parse("int f(int value) { return value + 1; }"); REQUIRE(unit); fold::SolverBudget budget; fold::SolverStats stats; auto findings = fold::detectUseAfterFreeInContext(unit->getASTContext(), budget, stats); REQUIRE(findings.empty()); REQUIRE(stats.functions_prechecked_safe == 1); }