fold/tests/unit/test_sign_conversion.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

60 lines
2.2 KiB
C++

#include <clang/Frontend/ASTUnit.h>
#include <clang/Tooling/Tooling.h>
#include <catch2/catch_test_macros.hpp>
#include <memory>
#include <string>
#include <vector>
#include "fold/detect/sign_conversion.hpp"
#include "fold/report/finding.hpp"
#include "fold/symbolic/budget.hpp"
namespace {
struct TestResult {
std::vector<fold::Finding> findings;
fold::SolverStats stats;
};
TestResult run(const std::string& code) {
auto unit = clang::tooling::buildASTFromCodeWithArgs(code, {"-std=c++20"}, "input.cpp");
REQUIRE(unit);
fold::SolverBudget b;
fold::SolverStats s;
auto findings = fold::detectSignConversionInContext(unit->getASTContext(), b, s);
return {std::move(findings), s};
}
} // namespace
TEST_CASE("sign-conversion: signed to unsigned param conversion is latent",
"[sign][signed-to-unsigned]") {
auto [findings, stats] = run("unsigned f(int x){ unsigned y = x; return y; }");
REQUIRE(findings.size() == 1);
REQUIRE(findings[0].kind == fold::FindingKind::SignConversion);
// Conversion of a function parameter is caller-precondition-dependent
// (mirrors div-by-zero / int-overflow latent contract): the callee alone
// cannot prove a negative value is reachable.
REQUIRE(findings[0].evidence.latent);
REQUIRE(findings[0].confidence == fold::Confidence::Latent);
REQUIRE(stats.queries == 1);
REQUIRE(stats.sat == 1);
}
TEST_CASE("sign-conversion: unsigned to signed is flagged", "[sign][unsigned-to-signed]") {
auto [findings, stats] = run("int f(unsigned x){ int y = x; return y; }");
REQUIRE(findings.size() == 1);
REQUIRE(findings[0].kind == fold::FindingKind::SignConversion);
REQUIRE(stats.queries == 1);
REQUIRE(stats.sat == 1);
}
TEST_CASE("sign-conversion: havoc-backed SAT result is heuristic", "[sign][havoc][precision]") {
auto [findings, stats] = run("extern void mutate(int&); unsigned f(){ int x = 0; mutate(x); "
"unsigned y = x; return y; }");
REQUIRE(findings.size() == 1);
REQUIRE(findings[0].confidence == fold::Confidence::Heuristic);
REQUIRE(findings[0].evidence.proof_mode == "over-approximated");
REQUIRE(stats.sat == 1);
}