Unit + integration coverage for the session's work: incremental project graph/index, pcode encoder, unreachable/dead-branch, equiv/cross-TU, div-by-zero/sign-conversion/int-truncation, use-after-free, quality-precision, scan-router profile, source TU runner, detector plan, and the eval fixtures.
145 lines
6.1 KiB
C++
145 lines
6.1 KiB
C++
#include <catch2/catch_test_macros.hpp>
|
|
#include <set>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "fold/cli/detector_plan.hpp"
|
|
#include "fold/cli/scan_options.hpp"
|
|
#include "fold/cli/scan_runner.hpp"
|
|
|
|
namespace {
|
|
|
|
std::set<std::string> enabledNames(const std::vector<std::string>& requested) {
|
|
const auto plan = fold::cli::makeSourceDetectorPlan(requested);
|
|
const auto names = plan.enabledProfileNames();
|
|
return {names.begin(), names.end()};
|
|
}
|
|
|
|
bool contains(const std::set<std::string>& names, const char* name) {
|
|
return names.find(name) != names.end();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("source detector descriptors are complete and uniquely named", "[detector-plan]") {
|
|
using Category = fold::cli::SourceDetectorCategory;
|
|
std::set<std::string> names;
|
|
std::size_t default_count = 0;
|
|
std::size_t security_count = 0;
|
|
std::size_t quality_count = 0;
|
|
std::size_t redundancy_count = 0;
|
|
std::size_t composite_security_count = 0;
|
|
|
|
for (const auto& descriptor : fold::cli::sourceDetectorDescriptors()) {
|
|
REQUIRE(descriptor.name != nullptr);
|
|
REQUIRE(descriptor.enabled != nullptr);
|
|
REQUIRE(names.insert(descriptor.name).second);
|
|
default_count += descriptor.default_enabled ? 1U : 0U;
|
|
security_count += descriptor.category == Category::Security ? 1U : 0U;
|
|
quality_count += descriptor.category == Category::Quality ? 1U : 0U;
|
|
redundancy_count += descriptor.category == Category::Redundancy ? 1U : 0U;
|
|
composite_security_count += descriptor.composite_security_walk ? 1U : 0U;
|
|
}
|
|
|
|
REQUIRE(names.size() == fold::cli::sourceDetectorDescriptors().size);
|
|
REQUIRE(default_count == 5);
|
|
REQUIRE(security_count == 17);
|
|
REQUIRE(quality_count == 8);
|
|
REQUIRE(redundancy_count == 7);
|
|
// composite_security_walk is orthogonal to the Security category: a subset
|
|
// of Security detectors run in the shared symbolic walk.
|
|
REQUIRE(composite_security_count == 9);
|
|
REQUIRE(security_count + quality_count + redundancy_count ==
|
|
fold::cli::sourceDetectorDescriptors().size);
|
|
}
|
|
|
|
TEST_CASE("source detector plan preserves default and group selection", "[detector-plan]") {
|
|
const auto defaults = enabledNames({});
|
|
REQUIRE(defaults == std::set<std::string>{"always-condition", "class-dup", "clone", "dead",
|
|
"dead-statement", "equiv"});
|
|
|
|
const auto redundancy = enabledNames({"redundancy"});
|
|
REQUIRE(contains(redundancy, "clone"));
|
|
REQUIRE(contains(redundancy, "dead"));
|
|
REQUIRE(contains(redundancy, "equiv"));
|
|
REQUIRE(contains(redundancy, "class-dup"));
|
|
REQUIRE(contains(redundancy, "always-condition"));
|
|
REQUIRE(contains(redundancy, "unreachable"));
|
|
REQUIRE(contains(redundancy, "redundant-store"));
|
|
REQUIRE_FALSE(contains(redundancy, "div-by-zero"));
|
|
|
|
const auto security = enabledNames({"security"});
|
|
REQUIRE(contains(security, "div-by-zero"));
|
|
REQUIRE(contains(security, "null-deref"));
|
|
REQUIRE(contains(security, "int-overflow"));
|
|
REQUIRE(contains(security, "int-truncation"));
|
|
REQUIRE(contains(security, "sign-conversion"));
|
|
REQUIRE(contains(security, "oob"));
|
|
REQUIRE(contains(security, "taint-track"));
|
|
REQUIRE(contains(security, "sql-injection"));
|
|
REQUIRE(contains(security, "uninit-read"));
|
|
REQUIRE(contains(security, "memory-leak"));
|
|
// The security group is now the full Security category — including the
|
|
// standalone passes the old security_group flag omitted.
|
|
REQUIRE(contains(security, "double-free"));
|
|
REQUIRE(contains(security, "format-string"));
|
|
REQUIRE(contains(security, "use-after-free"));
|
|
REQUIRE(contains(security, "weak-crypto"));
|
|
REQUIRE(contains(security, "deprecated-banned"));
|
|
REQUIRE_FALSE(contains(security, "clone"));
|
|
|
|
const auto quality = enabledNames({"quality"});
|
|
REQUIRE(contains(quality, "dead-statement"));
|
|
REQUIRE(contains(quality, "complexity"));
|
|
REQUIRE(contains(quality, "unused-function"));
|
|
REQUIRE(contains(quality, "production-reachability"));
|
|
REQUIRE(contains(quality, "use-after-move"));
|
|
REQUIRE(contains(quality, "iterator-invalidation"));
|
|
REQUIRE_FALSE(contains(quality, "null-deref"));
|
|
REQUIRE_FALSE(contains(quality, "clone"));
|
|
|
|
const auto dead_code = enabledNames({"dead-code"});
|
|
REQUIRE(contains(dead_code, "dead"));
|
|
REQUIRE(contains(dead_code, "dead-statement"));
|
|
REQUIRE(contains(dead_code, "unused-function"));
|
|
REQUIRE(contains(dead_code, "production-reachability"));
|
|
REQUIRE(contains(dead_code, "always-condition"));
|
|
REQUIRE(contains(dead_code, "unreachable"));
|
|
REQUIRE_FALSE(contains(dead_code, "clone"));
|
|
}
|
|
|
|
TEST_CASE("source detector descriptors drive profiles and composite counts", "[detector-plan]") {
|
|
auto plan = fold::cli::makeSourceDetectorPlan({"div-by-zero", "double-free", "format-string"});
|
|
REQUIRE(plan.div_by_zero);
|
|
REQUIRE(plan.double_free);
|
|
REQUIRE(plan.format_string);
|
|
REQUIRE(plan.enabledSecurityDetectorCount() == 2);
|
|
|
|
const auto names = plan.enabledProfileNames();
|
|
REQUIRE(names == std::vector<std::string>{"div-by-zero", "format-string", "double-free"});
|
|
|
|
REQUIRE(fold::cli::detectorEnabled({"all"}, "future-detector"));
|
|
REQUIRE(fold::cli::detectorEnabled({"future-detector"}, "future-detector"));
|
|
REQUIRE_FALSE(fold::cli::detectorEnabled({}, "future-detector"));
|
|
}
|
|
|
|
TEST_CASE("language IR safety detector selection gates Rust and Swift symbolic passes",
|
|
"[detector-plan][language-ir]") {
|
|
auto div_only = fold::cli::requestedLanguageIrSafetyDetectors({"div-by-zero"});
|
|
REQUIRE(div_only.any());
|
|
REQUIRE_FALSE(div_only.null_deref);
|
|
REQUIRE(div_only.div_by_zero);
|
|
REQUIRE_FALSE(div_only.int_overflow);
|
|
REQUIRE_FALSE(div_only.oob);
|
|
|
|
auto overflow_only = fold::cli::requestedLanguageIrSafetyDetectors({"int-overflow"});
|
|
REQUIRE(overflow_only.any());
|
|
REQUIRE(overflow_only.int_overflow);
|
|
|
|
auto oob_alias = fold::cli::requestedLanguageIrSafetyDetectors({"oob-access"});
|
|
REQUIRE(oob_alias.any());
|
|
REQUIRE(oob_alias.oob);
|
|
|
|
auto unrelated = fold::cli::requestedLanguageIrSafetyDetectors({"clone"});
|
|
REQUIRE_FALSE(unrelated.any());
|
|
}
|