165 lines
7.2 KiB
C++
165 lines
7.2 KiB
C++
// Pin the provenance contract on CrossTUCache. Phase 2 of the
|
|
// cross-TU cache persistence design: insertFromTU(tu_path, ...)
|
|
// records which TU contributed each entry so the per-TU save path
|
|
// can write exactly those entries (and only those) to each TU's
|
|
// on-disk snapshot. The existing flat `insert(name, props)` API
|
|
// remains for backward compatibility but does not track provenance.
|
|
|
|
#include <algorithm>
|
|
#include <catch2/catch_test_macros.hpp>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "fold/detect/cross_tu_cache.hpp"
|
|
#include "fold/detect/cross_tu_cache_io.hpp"
|
|
|
|
namespace {
|
|
|
|
fold::detect::CalleeProperties propsWith(std::initializer_list<unsigned> derefed,
|
|
bool returns_null = false) {
|
|
fold::detect::CalleeProperties p;
|
|
p.derefed_params.insert(derefed.begin(), derefed.end());
|
|
p.returns_null = returns_null;
|
|
return p;
|
|
}
|
|
|
|
bool hasEntry(const std::vector<fold::detect::CrossTUCacheEntry>& vec, const std::string& name) {
|
|
return std::any_of(vec.begin(), vec.end(), [&](const auto& e) { return e.name == name; });
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("CrossTUCache provenance: insertFromTU populates the flat lookup",
|
|
"[cross-tu-cache][provenance][insert]") {
|
|
using namespace fold::detect;
|
|
CrossTUCache::instance().clear();
|
|
CrossTUCache::instance().insertFromTU("/abs/a.c", "foo", propsWith({0, 1}));
|
|
const auto* p = CrossTUCache::instance().lookup("foo");
|
|
REQUIRE(p != nullptr);
|
|
REQUIRE(p->derefed_params.count(0) == 1);
|
|
REQUIRE(p->derefed_params.count(1) == 1);
|
|
CrossTUCache::instance().clear();
|
|
}
|
|
|
|
TEST_CASE("CrossTUCache provenance: entriesForTU returns only this TU's contributions",
|
|
"[cross-tu-cache][provenance][per-tu]") {
|
|
using namespace fold::detect;
|
|
CrossTUCache::instance().clear();
|
|
CrossTUCache::instance().insertFromTU("/abs/a.c", "alpha", propsWith({0}));
|
|
CrossTUCache::instance().insertFromTU("/abs/a.c", "beta", propsWith({1}));
|
|
CrossTUCache::instance().insertFromTU("/abs/b.c", "gamma", propsWith({2}));
|
|
|
|
auto a_entries = CrossTUCache::instance().entriesForTU("/abs/a.c");
|
|
auto b_entries = CrossTUCache::instance().entriesForTU("/abs/b.c");
|
|
REQUIRE(a_entries.size() == 2);
|
|
REQUIRE(hasEntry(a_entries, "alpha"));
|
|
REQUIRE(hasEntry(a_entries, "beta"));
|
|
REQUIRE(b_entries.size() == 1);
|
|
REQUIRE(hasEntry(b_entries, "gamma"));
|
|
CrossTUCache::instance().clear();
|
|
}
|
|
|
|
TEST_CASE("CrossTUCache provenance: same name from two TUs records in both provenance lists",
|
|
"[cross-tu-cache][provenance][collision]") {
|
|
using namespace fold::detect;
|
|
CrossTUCache::instance().clear();
|
|
// Static fn `helper` defined separately in TU-A and TU-B: collision
|
|
// at root qualified name is real in curl. Both TUs' caches must list
|
|
// it so re-priming either TU restores its own contribution.
|
|
CrossTUCache::instance().insertFromTU("/abs/a.c", "helper", propsWith({0}));
|
|
CrossTUCache::instance().insertFromTU("/abs/b.c", "helper", propsWith({1}, true));
|
|
|
|
auto a = CrossTUCache::instance().entriesForTU("/abs/a.c");
|
|
auto b = CrossTUCache::instance().entriesForTU("/abs/b.c");
|
|
REQUIRE(a.size() == 1);
|
|
REQUIRE(a[0].name == "helper");
|
|
REQUIRE(a[0].props.derefed_params == std::set<unsigned>{0});
|
|
REQUIRE_FALSE(a[0].props.returns_null);
|
|
REQUIRE(b.size() == 1);
|
|
REQUIRE(b[0].name == "helper");
|
|
REQUIRE(b[0].props.derefed_params == std::set<unsigned>{1});
|
|
REQUIRE(b[0].props.returns_null);
|
|
CrossTUCache::instance().clear();
|
|
}
|
|
|
|
TEST_CASE("CrossTUCache provenance: flat lookup merges across TUs",
|
|
"[cross-tu-cache][provenance][merge]") {
|
|
using namespace fold::detect;
|
|
// Even though provenance is tracked per-TU, the flat lookup() still
|
|
// returns the merged view across TUs — detectors querying a callee's
|
|
// properties see the union of what's been observed everywhere.
|
|
CrossTUCache::instance().clear();
|
|
CrossTUCache::instance().insertFromTU("/abs/a.c", "helper", propsWith({0}));
|
|
CrossTUCache::instance().insertFromTU("/abs/b.c", "helper", propsWith({1}, true));
|
|
const auto* p = CrossTUCache::instance().lookup("helper");
|
|
REQUIRE(p != nullptr);
|
|
REQUIRE(p->derefed_params == std::set<unsigned>{0, 1});
|
|
REQUIRE(p->returns_null);
|
|
CrossTUCache::instance().clear();
|
|
}
|
|
|
|
TEST_CASE("CrossTUCache provenance: same TU + same name calls merge within the TU",
|
|
"[cross-tu-cache][provenance][dedupe]") {
|
|
using namespace fold::detect;
|
|
CrossTUCache::instance().clear();
|
|
CrossTUCache::instance().insertFromTU("/abs/a.c", "foo", propsWith({0}));
|
|
CrossTUCache::instance().insertFromTU("/abs/a.c", "foo", propsWith({1}));
|
|
auto a = CrossTUCache::instance().entriesForTU("/abs/a.c");
|
|
REQUIRE(a.size() == 1);
|
|
REQUIRE(a[0].name == "foo");
|
|
REQUIRE(a[0].props.derefed_params == std::set<unsigned>{0, 1});
|
|
CrossTUCache::instance().clear();
|
|
}
|
|
|
|
TEST_CASE("CrossTUCache provenance: trackedTUs returns the contributing-TU set",
|
|
"[cross-tu-cache][provenance][tracked]") {
|
|
using namespace fold::detect;
|
|
CrossTUCache::instance().clear();
|
|
CrossTUCache::instance().insertFromTU("/abs/a.c", "x", {});
|
|
CrossTUCache::instance().insertFromTU("/abs/b.c", "y", {});
|
|
CrossTUCache::instance().insertFromTU("/abs/a.c", "z", {}); // dup TU
|
|
auto tus = CrossTUCache::instance().trackedTUs();
|
|
std::sort(tus.begin(), tus.end());
|
|
REQUIRE(tus == std::vector<std::string>{"/abs/a.c", "/abs/b.c"});
|
|
CrossTUCache::instance().clear();
|
|
}
|
|
|
|
TEST_CASE("CrossTUCache provenance: clear wipes both flat lookup and provenance",
|
|
"[cross-tu-cache][provenance][clear]") {
|
|
using namespace fold::detect;
|
|
CrossTUCache::instance().insertFromTU("/abs/a.c", "foo", propsWith({0}));
|
|
CrossTUCache::instance().clear();
|
|
REQUIRE(CrossTUCache::instance().lookup("foo") == nullptr);
|
|
REQUIRE(CrossTUCache::instance().entriesForTU("/abs/a.c").empty());
|
|
REQUIRE(CrossTUCache::instance().trackedTUs().empty());
|
|
}
|
|
|
|
TEST_CASE("CrossTUCache provenance: entriesForTU on unknown TU returns empty",
|
|
"[cross-tu-cache][provenance][miss]") {
|
|
using namespace fold::detect;
|
|
CrossTUCache::instance().clear();
|
|
auto entries = CrossTUCache::instance().entriesForTU("/abs/never-primed.c");
|
|
REQUIRE(entries.empty());
|
|
}
|
|
|
|
TEST_CASE("CrossTUCache provenance: legacy insert() does not register TU provenance",
|
|
"[cross-tu-cache][provenance][legacy]") {
|
|
using namespace fold::detect;
|
|
CrossTUCache::instance().clear();
|
|
CrossTUCache::instance().insert("legacy_fn", propsWith({0}));
|
|
// Flat lookup works.
|
|
REQUIRE(CrossTUCache::instance().lookup("legacy_fn") != nullptr);
|
|
// But no TU is tracked.
|
|
REQUIRE(CrossTUCache::instance().trackedTUs().empty());
|
|
CrossTUCache::instance().clear();
|
|
}
|
|
|
|
TEST_CASE("CrossTUCache provenance: insertFromTU ignores empty tu_path or empty name",
|
|
"[cross-tu-cache][provenance][validation]") {
|
|
using namespace fold::detect;
|
|
CrossTUCache::instance().clear();
|
|
CrossTUCache::instance().insertFromTU("", "foo", propsWith({0}));
|
|
CrossTUCache::instance().insertFromTU("/abs/a.c", "", propsWith({0}));
|
|
REQUIRE(CrossTUCache::instance().lookup("foo") == nullptr);
|
|
REQUIRE(CrossTUCache::instance().trackedTUs().empty());
|
|
}
|