Adds fold::symbolic::runUninitAnalysisOnTU(ctx) returning aggregate
counts of functions with local read-before-write uses, derived from
clang's UninitVariablesAnalysis. Scope reminder pinned by the 4
unit tests: this flags LOCAL variable uninit reads, NOT parameter
nullability. The original matrix C5 framing ("null-deref FP triage
pre-pass") was wrong-hypothesis — the analyzer treats parameters as
always-initialised, so it cannot drive null-deref triage.
CFG build options need AddInitializers + setAllAlwaysAdd before the
per-fn AnalysisDeclContext sees decl initializers; without that the
analyzer silently reports zero uses.
Scan-pipeline integration deferred — needs a per-TU accumulator
threaded through the priming consumer.
Refs docs/library-expansion-matrix.md row C5.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
83 lines
2.8 KiB
C++
83 lines
2.8 KiB
C++
// Measurement-only first cut for matrix row C5
|
|
// (`clang::UninitializedValues` pre-pass). The original framing —
|
|
// "triage null-deref FPs via uninit pre-pass" — was a wrong-hypothesis:
|
|
// clang's analyzer flags LOCAL read-before-write, not parameter
|
|
// nullability. This test pins what the analyzer *does* report so the
|
|
// fold-side wrapper stays honest about its scope. Future sessions can
|
|
// decide whether to graduate the signal into a standalone detector.
|
|
|
|
#include <clang/Frontend/ASTUnit.h>
|
|
#include <clang/Tooling/Tooling.h>
|
|
|
|
#include <catch2/catch_test_macros.hpp>
|
|
#include <memory>
|
|
#include <string>
|
|
|
|
#include "fold/symbolic/uninit_pre_pass.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("uninit pre-pass: function with no uninit reads scores 0", "[uninit-pre-pass][clean]") {
|
|
auto unit = parse(R"(
|
|
int safe(int x) {
|
|
int y = x + 1;
|
|
return y;
|
|
}
|
|
)");
|
|
REQUIRE(unit);
|
|
auto stats = fold::symbolic::runUninitAnalysisOnTU(unit->getASTContext());
|
|
REQUIRE(stats.total_functions >= 1);
|
|
REQUIRE(stats.functions_with_uninit_reads == 0);
|
|
REQUIRE(stats.total_uninit_reads == 0);
|
|
}
|
|
|
|
TEST_CASE("uninit pre-pass: read-before-write local is flagged",
|
|
"[uninit-pre-pass][always-uninit]") {
|
|
auto unit = parse(R"(
|
|
int oops() {
|
|
int y;
|
|
return y + 1;
|
|
}
|
|
)");
|
|
REQUIRE(unit);
|
|
auto stats = fold::symbolic::runUninitAnalysisOnTU(unit->getASTContext());
|
|
REQUIRE(stats.total_functions >= 1);
|
|
REQUIRE(stats.functions_with_uninit_reads >= 1);
|
|
REQUIRE(stats.total_uninit_reads >= 1);
|
|
}
|
|
|
|
TEST_CASE("uninit pre-pass: branch-conditional uninit is flagged",
|
|
"[uninit-pre-pass][sometimes-uninit]") {
|
|
auto unit = parse(R"(
|
|
int maybe(int cond) {
|
|
int y;
|
|
if (cond) y = 7;
|
|
return y;
|
|
}
|
|
)");
|
|
REQUIRE(unit);
|
|
auto stats = fold::symbolic::runUninitAnalysisOnTU(unit->getASTContext());
|
|
REQUIRE(stats.functions_with_uninit_reads >= 1);
|
|
}
|
|
|
|
TEST_CASE("uninit pre-pass: parameters are never flagged uninit (scope check)",
|
|
"[uninit-pre-pass][scope]") {
|
|
// The original C5 hypothesis was "null-deref FP triage via uninit
|
|
// pre-pass." This test pins the reason that hypothesis was wrong:
|
|
// clang's analyzer treats function parameters as always-initialized.
|
|
// A param can still be nullable, but the uninit analysis won't say so.
|
|
auto unit = parse(R"(
|
|
int param_only(int* p) {
|
|
return *p;
|
|
}
|
|
)");
|
|
REQUIRE(unit);
|
|
auto stats = fold::symbolic::runUninitAnalysisOnTU(unit->getASTContext());
|
|
REQUIRE(stats.functions_with_uninit_reads == 0);
|
|
}
|