Enumeration: replace the hand-rolled top-level `tu->decls()` walks (TU
security prefilter + every interprocedural pre-pass/cross-TU primer in
div-by-zero/int-overflow/null-deref/oob) with a shared RAV-based
`fold::detect::forEachFunctionWithBody`, so functions nested in namespaces
and classes are analyzed. Fixes 0-findings on namespaced C++ (dbench CTF).
TU prefilter: extracted to fold/cli/tu_security_prefilter; flags unsigned
-/* wraps so int-overflow candidates in size arithmetic are not skipped.
Timeout robustness: cooperative per-TU walk deadline (WalkOptions) checked
in the walk/unrollLoop/CFG-block checkpoints and the BatchSafetyObserver
flush loop, plus a non-progress unroll fixpoint — slow TUs degrade with
partial findings instead of being SIGALRM-quarantined.
OOB precheck: treat `*(p+i)` and indirect callees as candidates.
null-deref drift fix: remove the unsound `priorFunctionTextProvesNonNull`
source-text heuristic (added in 331a68467) that treated a textual `p!=0`
before a deref as proof of non-null, silently suppressing real null-derefs
(CWE-476 F1 0.485 -> 0.583, recall 0.379 -> 0.517).
Tests: test_function_enumeration, test_tu_security_prefilter, namespaced
cross-TU int-overflow regression. Full suite green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
80 lines
2.7 KiB
C++
80 lines
2.7 KiB
C++
#include <clang/AST/Decl.h>
|
|
#include <clang/Frontend/ASTUnit.h>
|
|
#include <clang/Tooling/Tooling.h>
|
|
|
|
#include <catch2/catch_test_macros.hpp>
|
|
#include <memory>
|
|
#include <set>
|
|
#include <string>
|
|
|
|
#include "fold/detect/function_enumeration.hpp"
|
|
|
|
namespace {
|
|
|
|
std::unique_ptr<clang::ASTUnit> parse(const std::string& code) {
|
|
return clang::tooling::buildASTFromCodeWithArgs(code, {"-std=c++20"}, "input.cpp");
|
|
}
|
|
|
|
// Collects qualified names of every function definition the helper visits.
|
|
std::multiset<std::string> visitedNames(const std::string& code) {
|
|
auto unit = parse(code);
|
|
REQUIRE(unit);
|
|
std::multiset<std::string> names;
|
|
fold::detect::forEachFunctionWithBody(unit->getASTContext(), [&](clang::FunctionDecl* fd) {
|
|
names.insert(fd->getQualifiedNameAsString());
|
|
});
|
|
return names;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("forEachFunctionWithBody: visits free, namespaced, and class-method defs",
|
|
"[enumeration]") {
|
|
const std::string code = R"cpp(
|
|
void freeFn() {}
|
|
namespace ns {
|
|
void nsFree() {}
|
|
struct B {
|
|
void inlineMethod() {} // in-class inline, inside namespace
|
|
void outOfLine();
|
|
};
|
|
void B::outOfLine() {} // out-of-line def, inside namespace
|
|
}
|
|
)cpp";
|
|
auto names = visitedNames(code);
|
|
REQUIRE(names.count("freeFn") == 1);
|
|
REQUIRE(names.count("ns::nsFree") == 1);
|
|
REQUIRE(names.count("ns::B::inlineMethod") == 1);
|
|
REQUIRE(names.count("ns::B::outOfLine") == 1);
|
|
}
|
|
|
|
TEST_CASE("forEachFunctionWithBody: visits instantiated function templates", "[enumeration]") {
|
|
// A template is only a definition once instantiated; the call site forces
|
|
// instantiation. The helper should see at least the primary pattern.
|
|
const std::string code = R"cpp(
|
|
namespace ns {
|
|
template <typename T> T addOne(T x) { return x + 1; }
|
|
int use() { return addOne<int>(41); }
|
|
}
|
|
)cpp";
|
|
auto names = visitedNames(code);
|
|
REQUIRE(names.count("ns::addOne") >= 1);
|
|
REQUIRE(names.count("ns::use") == 1);
|
|
}
|
|
|
|
TEST_CASE("forEachFunctionWithBody: does not double-count a decl+def pair", "[enumeration]") {
|
|
const std::string code = R"cpp(
|
|
int declared(); // forward declaration
|
|
int declared() { return 0; } // definition
|
|
)cpp";
|
|
auto names = visitedNames(code);
|
|
REQUIRE(names.count("declared") == 1);
|
|
}
|
|
|
|
TEST_CASE("forEachFunctionWithBody: skips pure declarations with no body", "[enumeration]") {
|
|
const std::string code = R"cpp(
|
|
namespace ns { void onlyDeclared(); }
|
|
)cpp";
|
|
auto names = visitedNames(code);
|
|
REQUIRE(names.count("ns::onlyDeclared") == 0);
|
|
}
|