fold/tests/unit/test_fix_its.cpp
trvon a791230552
feat(report): C9 FixIts + unified-diff --fix-output
Adds the FixIt collector + per-file unified-diff patch writer
behind a new `fold scan --fix-output <path>` flag. Patch-only --
no in-place rewrites -- so a bad suggestion can't silently
overwrite source.

Consumers (only when the transform is provably sound):
- UninitLocalRead: scalar integer locals only.
  `int x;` -> `int x = 0;`. Non-scalar types deferred.
- DeprecatedApi: regex extracts `[Uu]se foo()` from the
  deprecation message; silent skip when unparseable rather
  than hallucinating a rewrite.

Patches include 3 lines of context above + below each fix so
`git apply --check` accepts them. End-to-end verified against a
synthetic fixture.
2026-05-25 16:40:43 -04:00

95 lines
3 KiB
C++

// Matrix row C9: detector-side FixIt emission for UninitLocalRead +
// DeprecatedApi.
//
// Pin:
// 1. UninitLocalRead on `int x;` emits exactly one FixIt with
// replacement ` = 0` inserted just before the `;`.
// 2. UninitLocalRead on non-scalar locals (struct, array) emits
// no FixIt — fold doesn't yet know the right zero-init form.
// 3. DeprecatedApi with message containing `Use foo() instead`
// emits a FixIt replacing the call expression with `foo()`.
// 4. DeprecatedApi without a parseable replacement emits no
// FixIt (silent skip, not a hallucinated rewrite).
#include <clang/Frontend/ASTUnit.h>
#include <clang/Tooling/Tooling.h>
#include <catch2/catch_test_macros.hpp>
#include <memory>
#include <string>
#include "fold/detect/deprecated_api.hpp"
#include "fold/detect/uninit_local_read.hpp"
#include "fold/report/finding.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("c9 uninit-local-read: emits zero-init FixIt for int scalar",
"[c9][uninit-local-read][int]") {
auto unit = parse(R"(
int leak() {
int x;
return x;
}
)");
REQUIRE(unit);
auto findings = fold::detectUninitLocalReadInContext(unit->getASTContext());
REQUIRE(findings.size() >= 1);
bool found_fix = false;
for (const auto& f : findings) {
for (const auto& fix : f.fix_its) {
if (fix.replacement == " = 0") {
found_fix = true;
break;
}
}
}
REQUIRE(found_fix);
}
TEST_CASE("c9 uninit-local-read: no FixIt for non-scalar locals",
"[c9][uninit-local-read][non-scalar]") {
auto unit = parse(R"(
struct S { int a; int b; };
int leak() {
struct S s;
return s.a;
}
)");
REQUIRE(unit);
auto findings = fold::detectUninitLocalReadInContext(unit->getASTContext());
for (const auto& f : findings)
REQUIRE(f.fix_its.empty());
}
TEST_CASE("c9 deprecated-api: parses `Use foo() instead` from message",
"[c9][deprecated-api][replace]") {
auto unit = parse(R"(
[[deprecated("Use foo() instead")]] int legacy();
int foo();
int caller() { return legacy(); }
)");
REQUIRE(unit);
auto findings = fold::detectDeprecatedApiInContext(unit->getASTContext());
REQUIRE(findings.size() == 1);
REQUIRE(findings[0].fix_its.size() == 1);
REQUIRE(findings[0].fix_its[0].replacement == "foo()");
}
TEST_CASE("c9 deprecated-api: unparseable message yields no FixIt",
"[c9][deprecated-api][no-replace]") {
auto unit = parse(R"(
[[deprecated("this is bad")]] int legacy();
int caller() { return legacy(); }
)");
REQUIRE(unit);
auto findings = fold::detectDeprecatedApiInContext(unit->getASTContext());
REQUIRE(findings.size() == 1);
REQUIRE(findings[0].fix_its.empty());
}