ARM NEON reductions (vaddvq_*/vminvq_*/vmaxvq_*) expand to GNU statement
expressions `({ ...; __ret = builtin(x); __ret; })`. The uninit-read walker
flat-traversed the statement expression, flagging the result read `__ret`
before noticing the assignment that writes it inside the same expression.
Track variables assigned within a StmtExpr (TraverseStmtExpr) so the result
read is not a false uninit-read.
Verified: simeon neon.cpp range_neon 2->0, statement-expression repro 1->0,
genuine uninit local still fires; full suite 137/137.
141 lines
4.8 KiB
C++
141 lines
4.8 KiB
C++
// Matrix row C5-wiring: graduate the UninitializedValues pre-pass
|
|
// from a measurement scaffold into a real detector that emits
|
|
// `uninit-local-read` findings. C5's library function was already
|
|
// shipped (see test_uninit_pre_pass.cpp); this test pins the
|
|
// scan-pipeline-facing entry point.
|
|
|
|
#include <clang/Frontend/ASTUnit.h>
|
|
#include <clang/Tooling/Tooling.h>
|
|
|
|
#include <catch2/catch_test_macros.hpp>
|
|
#include <memory>
|
|
#include <string>
|
|
|
|
#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("uninit-local-read: int x; int y = x; emits a finding", "[uninit-local-read][positive]") {
|
|
auto unit = parse(R"(
|
|
int leak() {
|
|
int x;
|
|
int y = x;
|
|
return y;
|
|
}
|
|
)");
|
|
REQUIRE(unit);
|
|
auto findings = fold::detectUninitLocalReadInContext(unit->getASTContext());
|
|
REQUIRE(findings.size() >= 1);
|
|
REQUIRE(findings[0].kind == fold::FindingKind::UninitLocalRead);
|
|
}
|
|
|
|
TEST_CASE("uninit-local-read: int y = x where x is initialised does not fire",
|
|
"[uninit-local-read][negative]") {
|
|
auto unit = parse(R"(
|
|
int safe(int x) {
|
|
int y = x;
|
|
return y;
|
|
}
|
|
)");
|
|
REQUIRE(unit);
|
|
auto findings = fold::detectUninitLocalReadInContext(unit->getASTContext());
|
|
REQUIRE(findings.empty());
|
|
}
|
|
|
|
TEST_CASE("uninit-local-read: parameter reads are NOT flagged",
|
|
"[uninit-local-read][param-scope]") {
|
|
// C5's calibrated scope: clang's analyzer flags only LOCAL
|
|
// read-before-write. Parameters are always treated as initialised
|
|
// (they're not — but encoding nullability requires a different
|
|
// analyzer). Pin the scope.
|
|
auto unit = parse(R"(
|
|
int reads_param(int p) {
|
|
return p;
|
|
}
|
|
)");
|
|
REQUIRE(unit);
|
|
auto findings = fold::detectUninitLocalReadInContext(unit->getASTContext());
|
|
REQUIRE(findings.empty());
|
|
}
|
|
|
|
TEST_CASE("uninit-local-read: finding location points at the read, not the decl",
|
|
"[uninit-local-read][location]") {
|
|
auto unit = parse(R"(
|
|
int leak() {
|
|
int x;
|
|
return x;
|
|
}
|
|
)");
|
|
REQUIRE(unit);
|
|
auto findings = fold::detectUninitLocalReadInContext(unit->getASTContext());
|
|
REQUIRE(findings.size() >= 1);
|
|
REQUIRE_FALSE(findings[0].locations.empty());
|
|
// The `return x;` line is 4 in the parsed snippet (line 1 is the
|
|
// opening blank-after-R"(); int leak() is line 2; int x; is line
|
|
// 3; return x; is line 4). The decl-only location would be line 3.
|
|
REQUIRE(findings[0].locations[0].line == 4);
|
|
}
|
|
|
|
TEST_CASE("uninit-local-read: self-init `int x = x` is flagged", "[uninit-local-read][self-init]") {
|
|
auto unit = parse(R"(
|
|
int self_init() {
|
|
int x = x;
|
|
return x;
|
|
}
|
|
)");
|
|
REQUIRE(unit);
|
|
auto findings = fold::detectUninitLocalReadInContext(unit->getASTContext());
|
|
REQUIRE(findings.size() >= 1);
|
|
}
|
|
|
|
TEST_CASE("uninit-local-read: strtol endptr write counts as initialisation",
|
|
"[uninit-local-read][strtol]") {
|
|
auto unit = parse(R"(
|
|
extern "C" long strtol(const char*, char**, int);
|
|
int parse_tail() {
|
|
char input[] = "7";
|
|
char* end;
|
|
(void)strtol(input, &end, 10);
|
|
return *end == '\0';
|
|
}
|
|
)");
|
|
REQUIRE(unit);
|
|
auto findings = fold::detectUninitLocalReadInContext(unit->getASTContext());
|
|
REQUIRE(findings.empty());
|
|
}
|
|
|
|
TEST_CASE("uninit-local-read: statement expression result read is not flagged",
|
|
"[uninit-local-read][stmt-expr]") {
|
|
// ARM NEON intrinsics (vaddvq_*/vminvq_*/vmaxvq_*) expand to GNU statement
|
|
// expressions whose result reads an internal `__ret` written by a preceding
|
|
// assignment inside the same expression. A flat traversal would flag the
|
|
// result read; the walker must honor the intra-expression write ordering.
|
|
auto unit = parse(R"(
|
|
extern "C" float g(void*);
|
|
float f(void* acc) {
|
|
float sum = ({ void* __a = acc; float __ret; __ret = g(__a); __ret; });
|
|
return sum;
|
|
}
|
|
)");
|
|
REQUIRE(unit);
|
|
auto findings = fold::detectUninitLocalReadInContext(unit->getASTContext());
|
|
REQUIRE(findings.empty());
|
|
}
|
|
|
|
TEST_CASE("uninit-local-read: genuinely uninitialized local still fires",
|
|
"[uninit-local-read][stmt-expr][negative]") {
|
|
auto unit = parse(R"(
|
|
int f() { int x; return x; }
|
|
)");
|
|
REQUIRE(unit);
|
|
auto findings = fold::detectUninitLocalReadInContext(unit->getASTContext());
|
|
REQUIRE(findings.size() >= 1);
|
|
REQUIRE(findings[0].kind == fold::FindingKind::UninitLocalRead);
|
|
}
|