#include #include #include #include #include #include #include "fold/detect/redundant_store.hpp" #include "fold/detect/uninit_read.hpp" #include "fold/detect/unused_function.hpp" #include "fold/detect/use_after_move.hpp" #include "fold/frontend/statement_count.hpp" #include "fold/report/finding.hpp" #include "fold/symbolic/budget.hpp" #include "fold/symbolic/theory.hpp" namespace { std::unique_ptr parse(const std::string& code) { return clang::tooling::buildASTFromCodeWithArgs( code, {"-std=c++20", "-fcxx-exceptions", "-fexceptions"}, "input.cpp"); } std::vector useAfterMove(const std::string& code, fold::SolverStats* stats = nullptr) { auto unit = parse(code); REQUIRE(unit); fold::SolverBudget budget; fold::SolverStats local_stats; auto findings = fold::detectUseAfterMoveInContext(unit->getASTContext(), budget, local_stats); if (stats) *stats = local_stats; return findings; } std::vector uninitReads(const std::string& code) { auto unit = parse(code); REQUIRE(unit); fold::SolverBudget budget; fold::SolverStats stats; return fold::detectUninitReadsInContext(unit->getASTContext(), budget, stats); } class FunctionFinder final : public clang::RecursiveASTVisitor { public: explicit FunctionFinder(std::string name) : name_(std::move(name)) {} bool VisitFunctionDecl(clang::FunctionDecl* function) { if (function && function->hasBody() && function->getNameAsString() == name_) function_ = function; return true; } clang::FunctionDecl* function() const { return function_; } private: std::string name_; clang::FunctionDecl* function_ = nullptr; }; clang::FunctionDecl* findFunction(clang::ASTContext& context, const std::string& name) { FunctionFinder finder(name); finder.TraverseDecl(context.getTranslationUnitDecl()); return finder.function(); } std::vector redundantStores(const std::string& code, fold::SolverStats* stats = nullptr) { auto unit = parse(code); REQUIRE(unit); fold::SolverBudget budget; fold::SolverStats local_stats; auto findings = fold::detectRedundantStoresInContext(unit->getASTContext(), budget, local_stats); if (stats) *stats = local_stats; return findings; } std::vector unusedFunctions(const std::string& code) { auto unit = parse(code); REQUIRE(unit); return fold::detectUnusedFunctionsInContext(unit->getASTContext()); } constexpr const char* move_support = R"( namespace std { template T&& move(T& value) { return static_cast(value); } } struct Token { Token(); Token(Token&&); Token& operator=(Token&&); int value() const; }; )"; } // namespace TEST_CASE("clone sizing counts executable statements instead of expression AST nodes", "[quality-precision][clone]") { auto unit = parse(R"( int accessor(int value) { return value + value * 2; } int workflow(int value) { int first = value + 1; int second = first * 2; if (second > 10) return second; return first; } )"); REQUIRE(unit); int accessor_count = -1; int workflow_count = -1; for (const clang::Decl* decl : unit->getASTContext().getTranslationUnitDecl()->decls()) { const auto* function = clang::dyn_cast(decl); if (!function || !function->hasBody()) continue; if (function->getName() == "accessor") accessor_count = fold::countExecutableStatements(function->getBody()); if (function->getName() == "workflow") workflow_count = fold::countExecutableStatements(function->getBody()); } CHECK(accessor_count == 1); CHECK(workflow_count == 5); } TEST_CASE("use-after-move: the move expression is not its own use", "[quality-precision][use-after-move]") { auto findings = useAfterMove(std::string(move_support) + R"( int clean() { Token source; Token destination(std::move(source)); return destination.value(); } )"); REQUIRE(findings.empty()); } TEST_CASE("use-after-move: std::move-free functions are prechecked safe", "[quality-precision][use-after-move][prefilter]") { // A function with no std::move call can never populate the moved-from // set, so the walker is skipped entirely and the precheck counter rises. fold::SolverStats stats; auto findings = useAfterMove(std::string(move_support) + R"( int no_move(int x) { int y = x + 1; if (y > 0) return y; return -y; } )", &stats); REQUIRE(findings.empty()); CHECK(stats.functions_prechecked_safe >= 1); } TEST_CASE("use-after-move: a later read is SAT-confirmed", "[quality-precision][use-after-move]") { fold::SolverStats stats; auto findings = useAfterMove(std::string(move_support) + R"( int broken() { Token source; Token destination(std::move(source)); return source.value() + destination.value(); } )", &stats); REQUIRE(findings.size() == 1); CHECK(findings.front().kind == fold::FindingKind::UseAfterMove); CHECK(findings.front().confidence == fold::Confidence::Proved); CHECK(stats.queries == 1); CHECK(stats.sat == 1); } TEST_CASE("use-after-move: overloaded assignment reinitializes the moved object", "[quality-precision][use-after-move][lifetime]") { auto findings = useAfterMove(std::string(move_support) + R"( Token refresh(Token&&); int clean_assignment() { Token source; source = refresh(std::move(source)); return source.value(); } )"); REQUIRE(findings.empty()); } TEST_CASE("use-after-move: unsupported try control flow suppresses prefix candidates", "[quality-precision][use-after-move]") { fold::SolverStats stats; auto findings = useAfterMove(std::string(move_support) + R"( int uncertain() { Token source; Token destination(std::move(source)); try { return source.value() + destination.value(); } catch (...) { return 0; } } )", &stats); REQUIRE(findings.empty()); CHECK(stats.bailed_functions == 1); } TEST_CASE("redundant-store: condition reads keep scalar initializers live", "[quality-precision][redundant-store]") { auto findings = redundantStores(R"( int live(int input) { int value = input; if (value > 0) return 1; return 0; } )"); REQUIRE(findings.empty()); } TEST_CASE("redundant-store: store-free function is prechecked safe", "[quality-precision][redundant-store][prefilter]") { // A function with no assignment to a scalar local can never create a // pending store, so neither checkWrites nor drainPending can emit; the // walker is skipped and the precheck counter rises. fold::SolverStats stats; auto findings = redundantStores(R"( struct Box { int get() const; }; int read_only(const Box& box) { return box.get() + 1; } )", &stats); REQUIRE(findings.empty()); CHECK(stats.functions_prechecked_safe >= 1); } TEST_CASE("redundant-store: a straight-line overwritten scalar is retained", "[quality-precision][redundant-store]") { auto findings = redundantStores(R"( int dead() { int value = 1; value = 2; return value; } )"); REQUIRE(findings.size() == 1); CHECK(findings.front().kind == fold::FindingKind::RedundantStore); CHECK(findings.front().confidence == fold::Confidence::Heuristic); REQUIRE(findings.front().evidence.quality.has_value()); CHECK(findings.front().evidence.quality->classification == "redundant-store"); CHECK(findings.front().evidence.quality->incomplete); CHECK(findings.front().evidence.quality->uncertainty_reasons == std::vector{"bounded-ast-dataflow"}); } TEST_CASE("redundant-store identity survives unrelated preceding statements", "[quality-precision][redundant-store][identity]") { const auto baseline = redundantStores(R"( int dead() { int value = 1; value = 2; return value; } )"); const auto transformed = redundantStores(R"( int dead() { int unrelated = 0; int value = 1; value = 2; return value + unrelated; } )"); REQUIRE(baseline.size() == 1); REQUIRE(transformed.size() == 1); REQUIRE(baseline.front().id == transformed.front().id); } TEST_CASE("redundant-store: class lifetime and RAII are not modeled as dead stores", "[quality-precision][redundant-store]") { auto findings = redundantStores(R"( struct Guard { Guard(); ~Guard(); }; void guarded() { Guard lock; } )"); REQUIRE(findings.empty()); } TEST_CASE("redundant-store: unsupported range-for suppresses prefix observations", "[quality-precision][redundant-store]") { fold::SolverStats stats; auto findings = redundantStores(R"( int sum(const int (&values)[3]) { int total = 0; for (int value : values) total += value; return total; } )", &stats); REQUIRE(findings.empty()); CHECK(stats.bailed_functions == 1); } TEST_CASE("uninit-read precheck requires a potentially uninitialized local", "[quality-precision][uninit-read]") { auto unit = parse(R"cpp( int initialized_only(int input) { int local = input; return local; } int uninitialized_read() { int local; return local; } int nested_uninitialized() { int outer = 0; { int local; return local; } } )cpp"); REQUIRE(unit); auto& context = unit->getASTContext(); CHECK_FALSE(fold::uninit_read_detail::functionMayReadUninitializedLocal( findFunction(context, "initialized_only"))); CHECK(fold::uninit_read_detail::functionMayReadUninitializedLocal( findFunction(context, "uninitialized_read"))); CHECK(fold::uninit_read_detail::functionMayReadUninitializedLocal( findFunction(context, "nested_uninitialized"))); } TEST_CASE("uninit-read: modeled C APIs initialize opaque output objects", "[quality-precision][uninit-read][api-init]") { auto findings = uninitReads(R"( using Actions = void*; extern "C" int posix_spawn_file_actions_init(Actions*); extern "C" int posix_spawn_file_actions_addclose(Actions*, int); int configure() { Actions actions; if (posix_spawn_file_actions_init(&actions) != 0) return -1; return posix_spawn_file_actions_addclose(&actions, 3); } )"); REQUIRE(findings.empty()); } TEST_CASE("uninit-read: unknown pointer APIs do not imply initialization", "[quality-precision][uninit-read][api-init]") { auto findings = uninitReads(R"( extern "C" void observe(int*); int inspect() { int value; observe(&value); return value; } )"); REQUIRE_FALSE(findings.empty()); } TEST_CASE("unused-function: implicit destructor invocation counts as lifetime use", "[quality-precision][unused-function][lifetime]") { auto findings = unusedFunctions(R"( namespace { struct Guard { Guard() {} ~Guard() {} }; } int use() { Guard guard; return 0; } )"); REQUIRE(findings.empty()); } TEST_CASE("unused-function: CRTP traversal callbacks are template-invoked", "[quality-precision][unused-function][crtp]") { auto findings = unusedFunctions(R"( namespace clang { template struct RecursiveASTVisitor { void TraverseDecl(int*) { static_cast(this)->VisitFunctionDecl(nullptr); } }; } namespace { struct Visitor : clang::RecursiveASTVisitor { bool VisitFunctionDecl(int*) { return true; } bool TraverseWhileStmt(int*) { return true; } int unusedHelper() { return 7; } }; } int use() { Visitor visitor; visitor.TraverseDecl(nullptr); return 0; } )"); REQUIRE(findings.size() == 1); CHECK(findings.front().locations.front().function.find("unusedHelper") != std::string::npos); } TEST_CASE("unused-function: standard-library callback operators are template-invoked", "[quality-precision][unused-function][callback]") { auto findings = unusedFunctions(R"( namespace { struct Finalizer { void operator()(int*) const {} }; struct Key { bool operator==(const Key&) const = default; }; struct Hash { unsigned long operator()(const Key&) const { return 0; } }; } template void consume(Deleter deleter) { deleter(nullptr); } int use() { consume(Finalizer{}); return 0; } )"); REQUIRE(findings.empty()); } TEST_CASE("unused-function: dependent template lookup counts helper candidates as called", "[quality-precision][unused-function]") { auto findings = unusedFunctions(R"( static int joinAnd(int value) { return value + 1; } template int appendListClause(const Container& values) { return joinAnd(values.front()); } int use() { struct Values { int front() const { return 1; } } values; return appendListClause(values); } )"); REQUIRE(findings.empty()); } TEST_CASE("unused-function: constructor expressions count as calls", "[quality-precision][unused-function]") { auto findings = unusedFunctions(R"( namespace { class DisjointSet { public: explicit DisjointSet(int size) : size_(size) {} int size() const { return size_; } private: int size_; }; } int use() { DisjointSet semanticComponents(3); DisjointSet reciprocalComponents(4); return semanticComponents.size() + reciprocalComponents.size(); } )"); REQUIRE(findings.empty()); } TEST_CASE("unused-function identity survives harmless preceding insertion", "[quality-precision][unused-function][identity]") { const auto baseline = unusedFunctions(R"( static int unusedHelper(int value) { return value + 1; } int use(int value) { return value; } )"); const auto transformed = unusedFunctions(R"( int unrelated() { return 0; } static int unusedHelper(int value) { return value + 1; } int use(int value) { return value; } )"); REQUIRE(baseline.size() == 1); REQUIRE(transformed.size() == 1); REQUIRE(baseline.front().id == transformed.front().id); } TEST_CASE("unused-function: genuinely unused internal function remains reported", "[quality-precision][unused-function]") { auto findings = unusedFunctions(R"( static int unusedHelper(int value) { return value + 1; } int use(int value) { return value; } )"); REQUIRE(findings.size() == 1); CHECK(findings.front().kind == fold::FindingKind::UnusedFunction); } TEST_CASE("uninit-read: known output-param API initializes the local", "[quality-precision][uninit-read][api-init]") { // CFNumberGetValue(..., &size) writes the third argument; the modeled // output-param contract must treat the local as initialized after the call. auto findings = uninitReads(R"( extern "C" int CFNumberGetValue(const void*, unsigned long, void*); int f() { int x; if (CFNumberGetValue(0, 0, &x)) { return x; } return 0; } )"); REQUIRE(findings.empty()); } TEST_CASE("uninit-read: CFNumberGetValue drive-info success shape initializes output", "[quality-precision][uninit-read][api-init][chromium-shape]") { auto findings = uninitReads(R"( extern "C" int CFNumberGetValue(const void*, unsigned long, void*); using Boolean = int; long long read_size() { long long size; Boolean success = CFNumberGetValue(nullptr, 0, &size); if (success) { return size; } return 0; } )"); REQUIRE(findings.empty()); } TEST_CASE("uninit-read: CFNumberGetValue failure path does not read output", "[quality-precision][uninit-read][api-init][chromium-shape]") { auto findings = uninitReads(R"( extern "C" int CFNumberGetValue(const void*, unsigned long, void*); long long read_size() { long long size; if (!CFNumberGetValue(nullptr, 0, &size)) { return 0; } return size; } )"); REQUIRE(findings.empty()); } TEST_CASE("uninit-read: CFNumberGetValue comparison success guard initializes output", "[quality-precision][uninit-read][api-init][chromium-shape]") { auto findings = uninitReads(R"( extern "C" int CFNumberGetValue(const void*, unsigned long, void*); long long read_size() { long long size; int success = CFNumberGetValue(nullptr, 0, &size); if (success != 0) { return size; } return 0; } )"); REQUIRE(findings.empty()); } TEST_CASE("uninit-read: CFNumberGetValue comparison failure path avoids output read", "[quality-precision][uninit-read][api-init][chromium-shape]") { auto findings = uninitReads(R"( extern "C" int CFNumberGetValue(const void*, unsigned long, void*); long long read_size() { long long size; int success = CFNumberGetValue(nullptr, 0, &size); if (success == 0) { return 0; } return size; } )"); REQUIRE(findings.empty()); } TEST_CASE("uninit-read: CFNumberGetValue success short-circuit guard initializes output", "[quality-precision][uninit-read][api-init][chromium-shape]") { auto findings = uninitReads(R"( extern "C" int CFNumberGetValue(const void*, unsigned long, void*); extern "C" int ready(); long long read_size() { long long size; int success = CFNumberGetValue(nullptr, 0, &size); if (success && ready()) { return size; } return 0; } )"); REQUIRE(findings.empty()); } TEST_CASE("uninit-read: CFNumberGetValue success OR guard does not prove output", "[quality-precision][uninit-read][api-init][chromium-shape]") { auto findings = uninitReads(R"( extern "C" int CFNumberGetValue(const void*, unsigned long, void*); extern "C" int ready(); long long read_size() { long long size; int success = CFNumberGetValue(nullptr, 0, &size); if (success || ready()) { return size; } return 0; } )"); REQUIRE_FALSE(findings.empty()); } TEST_CASE("uninit-read: CFNumberGetValue failure short-circuit path remains uncertain", "[quality-precision][uninit-read][api-init][chromium-shape]") { auto findings = uninitReads(R"( extern "C" int CFNumberGetValue(const void*, unsigned long, void*); extern "C" int ready(); long long read_size() { long long size; int success = CFNumberGetValue(nullptr, 0, &size); if (!success && ready()) { return 0; } return size; } )"); REQUIRE_FALSE(findings.empty()); } TEST_CASE("uninit-read: negated OR false branch proves CFNumberGetValue success", "[quality-precision][uninit-read][api-init][chromium-shape]") { auto findings = uninitReads(R"( extern "C" int CFNumberGetValue(const void*, unsigned long, void*); extern "C" int ready(); long long read_size() { long long size; int success = CFNumberGetValue(nullptr, 0, &size); if (!success || ready()) { return 0; } return size; } )"); REQUIRE(findings.empty()); } TEST_CASE("uninit-read: negated AND true branch does not prove CFNumber success", "[quality-precision][uninit-read][api-init][chromium-shape]") { auto findings = uninitReads(R"( extern "C" int CFNumberGetValue(const void*, unsigned long, void*); extern "C" int ready(); long long read_size() { long long size; int success = CFNumberGetValue(nullptr, 0, &size); if (!(success && ready())) { return size; } return 0; } )"); REQUIRE_FALSE(findings.empty()); } TEST_CASE("uninit-read: ignored CFNumberGetValue result leaves output uncertain", "[quality-precision][uninit-read][api-init][chromium-shape]") { auto findings = uninitReads(R"( extern "C" int CFNumberGetValue(const void*, unsigned long, void*); long long read_size() { long long size; CFNumberGetValue(nullptr, 0, &size); return size; } )"); REQUIRE_FALSE(findings.empty()); } TEST_CASE("uninit-read: unmodeled pointer APIs still flag uninit locals", "[quality-precision][uninit-read][api-init]") { // Design contract: unknown pointer APIs do NOT imply initialization. auto findings = uninitReads(R"( extern "C" void observe(int*); int g() { int y; observe(&y); return y; } )"); REQUIRE_FALSE(findings.empty()); }