fold/tests/unit/test_use_after_free.cpp
trvon fd4a68dfff
test: add coverage for project graph, pcode encoder, and detector behaviors
Unit + integration coverage for the session's work: incremental project
graph/index, pcode encoder, unreachable/dead-branch, equiv/cross-TU,
div-by-zero/sign-conversion/int-truncation, use-after-free, quality-precision,
scan-router profile, source TU runner, detector plan, and the eval fixtures.
2026-08-16 16:46:07 -06:00

123 lines
4.2 KiB
C++

#include <clang/Frontend/ASTUnit.h>
#include <clang/Tooling/Tooling.h>
#include <catch2/catch_test_macros.hpp>
#include <memory>
#include <string>
#include "fold/detect/use_after_free.hpp"
#include "fold/report/finding.hpp"
#include "fold/symbolic/budget.hpp"
namespace {
std::unique_ptr<clang::ASTUnit> 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);
}