#include #include #include #include #include #include #include #include #include "fold/ir/hash.hpp" #include "fold/ir/normalize.hpp" namespace { // Find the body of the named function in the given AST unit. Returns nullptr // if no such definition is found. const clang::Stmt* bodyOf(clang::ASTUnit& unit, const std::string& name) { auto& ctx = unit.getASTContext(); for (auto* decl : ctx.getTranslationUnitDecl()->decls()) { if (auto* fd = clang::dyn_cast(decl)) { if (fd->getQualifiedNameAsString() == name && fd->hasBody()) { return fd->getBody(); } } } return nullptr; } std::unique_ptr parse(const std::string& code) { return clang::tooling::buildASTFromCodeWithArgs(code, {"-std=c++20"}, "input.cpp"); } } // namespace TEST_CASE("fnv1a64 is deterministic and distinguishes inputs", "[hash]") { REQUIRE(fold::fnv1a64("hello") == fold::fnv1a64("hello")); REQUIRE(fold::fnv1a64("hello") != fold::fnv1a64("world")); REQUIRE(fold::fnv1a64("") != 0); // FNV offset basis is nonzero } TEST_CASE("mix64 is order-sensitive", "[hash]") { REQUIRE(fold::mix64(1, 2) != fold::mix64(2, 1)); } TEST_CASE("exact body hash matches for identical functions", "[hash][exact]") { auto unit = parse("int sum_a(int a, int b){ int x = a + b; return x; }" "int sum_b(int a, int b){ int x = a + b; return x; }"); REQUIRE(unit); const clang::Stmt* ba = bodyOf(*unit, "sum_a"); const clang::Stmt* bb = bodyOf(*unit, "sum_b"); REQUIRE(ba); REQUIRE(bb); REQUIRE(fold::hashStmt(ba, unit->getASTContext(), fold::HashMode::Exact) == fold::hashStmt(bb, unit->getASTContext(), fold::HashMode::Exact)); } TEST_CASE("rename-insensitive hash ignores identifier renames", "[hash][rename]") { auto unit = parse("int prod_a(int a, int b){ int t = a * b; return t; }" "int prod_b(int x, int y){ int r = x * y; return r; }"); REQUIRE(unit); const clang::Stmt* ba = bodyOf(*unit, "prod_a"); const clang::Stmt* bb = bodyOf(*unit, "prod_b"); REQUIRE(ba); REQUIRE(bb); auto& ctx = unit->getASTContext(); REQUIRE(fold::hashStmt(ba, ctx, fold::HashMode::Exact) != fold::hashStmt(bb, ctx, fold::HashMode::Exact)); REQUIRE(fold::hashStmt(ba, ctx, fold::HashMode::RenameInsensitive) == fold::hashStmt(bb, ctx, fold::HashMode::RenameInsensitive)); } TEST_CASE("literal-insensitive hash ignores integer-literal changes", "[hash][literal]") { auto unit = parse("int s2(int a){ int r = a << 2; return r; }" "int s5(int a){ int r = a << 5; return r; }"); REQUIRE(unit); const clang::Stmt* ba = bodyOf(*unit, "s2"); const clang::Stmt* bb = bodyOf(*unit, "s5"); REQUIRE(ba); REQUIRE(bb); auto& ctx = unit->getASTContext(); REQUIRE(fold::hashStmt(ba, ctx, fold::HashMode::RenameInsensitive) != fold::hashStmt(bb, ctx, fold::HashMode::RenameInsensitive)); REQUIRE(fold::hashStmt(ba, ctx, fold::HashMode::LiteralInsensitive) == fold::hashStmt(bb, ctx, fold::HashMode::LiteralInsensitive)); } TEST_CASE("structurally different functions do not collide", "[hash][diff]") { auto unit = parse("int mx(int a, int b){ if(a > b) return a; return b; }" "int ab(int a){ if(a < 0) return -a; return a; }"); REQUIRE(unit); const clang::Stmt* ba = bodyOf(*unit, "mx"); const clang::Stmt* bb = bodyOf(*unit, "ab"); REQUIRE(ba); REQUIRE(bb); auto& ctx = unit->getASTContext(); REQUIRE(fold::hashStmt(ba, ctx, fold::HashMode::LiteralInsensitive) != fold::hashStmt(bb, ctx, fold::HashMode::LiteralInsensitive)); }