#include #include #include #include #include "fold/cli/detector_plan.hpp" #include "fold/cli/scan_options.hpp" #include "fold/cli/scan_runner.hpp" namespace { std::set enabledNames(const std::vector& requested) { const auto plan = fold::cli::makeSourceDetectorPlan(requested); const auto names = plan.enabledProfileNames(); return {names.begin(), names.end()}; } bool contains(const std::set& 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 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{"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{"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()); }