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.
95 lines
3 KiB
C++
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());
|
|
}
|