99 lines
3.8 KiB
C++
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));
|
|
}
|