fold/tests/unit/test_hash.cpp

99 lines
3.8 KiB
C++

#include <clang/AST/ASTContext.h>
#include <clang/AST/Decl.h>
#include <clang/AST/Stmt.h>
#include <clang/Frontend/ASTUnit.h>
#include <clang/Tooling/Tooling.h>
#include <catch2/catch_test_macros.hpp>
#include <memory>
#include <string>
#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<clang::FunctionDecl>(decl)) {
if (fd->getQualifiedNameAsString() == name && fd->hasBody()) {
return fd->getBody();
}
}
}
return nullptr;
}
std::unique_ptr<clang::ASTUnit> 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));
}