658 lines
22 KiB
C++
658 lines
22 KiB
C++
#include <clang/AST/RecursiveASTVisitor.h>
|
|
#include <clang/Tooling/Tooling.h>
|
|
|
|
#include <catch2/catch_test_macros.hpp>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#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<clang::ASTUnit> parse(const std::string& code) {
|
|
return clang::tooling::buildASTFromCodeWithArgs(
|
|
code, {"-std=c++20", "-fcxx-exceptions", "-fexceptions"}, "input.cpp");
|
|
}
|
|
|
|
std::vector<fold::Finding> 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<fold::Finding> 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<FunctionFinder> {
|
|
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<fold::Finding> 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<fold::Finding> 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 <class T> T&& move(T& value) { return static_cast<T&&>(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<clang::FunctionDecl>(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<std::string>{"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 <class Derived> struct RecursiveASTVisitor {
|
|
void TraverseDecl(int*) { static_cast<Derived*>(this)->VisitFunctionDecl(nullptr); }
|
|
};
|
|
}
|
|
namespace {
|
|
struct Visitor : clang::RecursiveASTVisitor<Visitor> {
|
|
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 <class Deleter> 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 <class Container>
|
|
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());
|
|
}
|