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.
123 lines
4.2 KiB
C++
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);
|
|
}
|