https://github.com/kaladron updated https://github.com/llvm/llvm-project/pull/205839
>From 305cfad694a8fcf86692ec5170005286bcdbfa8f Mon Sep 17 00:00:00 2001 From: Jeff Bailey <[email protected]> Date: Thu, 25 Jun 2026 15:45:14 +0100 Subject: [PATCH] [clang-tidy] Fix llvm-header-guard for arbitrary worktree names The llvm-header-guard check failed to find the project root when working in git worktrees with names other than llvm-project or llvm. When `/llvm-project/` is not in the path, we now walk up the directory tree to find the project root by checking for a `.git` file/directory. The path is then canonicalized to start with `/llvm-project/` before calculating the header guard. Assisted-by: Automated tooling, human reviewed. --- .../clang-tidy/llvm/HeaderGuardCheck.cpp | 42 ++++++++- .../clang-tidy/llvm/HeaderGuardCheck.h | 3 + .../unittests/clang-tidy/LLVMModuleTest.cpp | 90 +++++++++++++++++++ 3 files changed, 134 insertions(+), 1 deletion(-) diff --git a/clang-tools-extra/clang-tidy/llvm/HeaderGuardCheck.cpp b/clang-tools-extra/clang-tidy/llvm/HeaderGuardCheck.cpp index ef8b6b1dfb8f7..f34ce4e90505f 100644 --- a/clang-tools-extra/clang-tidy/llvm/HeaderGuardCheck.cpp +++ b/clang-tools-extra/clang-tidy/llvm/HeaderGuardCheck.cpp @@ -8,6 +8,7 @@ #include "HeaderGuardCheck.h" #include "clang/Tooling/Tooling.h" +#include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" namespace clang::tidy::llvm_check { @@ -16,9 +17,48 @@ LLVMHeaderGuardCheck::LLVMHeaderGuardCheck(StringRef Name, ClangTidyContext *Context) : HeaderGuardCheck(Name, Context) {} +// Attempt to find the root of the LLVM project monorepo by walking up the +// directory tree from Filename and looking for a ".git" file/directory. +// This allows us to find the root even when working in git worktrees with +// arbitrary names. +std::string +LLVMHeaderGuardCheck::findLLVMProjectRoot(StringRef Filename) const { + SmallString<256> Path = Filename; + SmallString<256> Parent = llvm::sys::path::parent_path(Path); + while (!Parent.empty() && Parent != Path) { + Path = Parent; + + // Check for .git (file or directory) which indicates the root of the + // git repository or worktree. + SmallString<256> GitPath = Path; + llvm::sys::path::append(GitPath, ".git"); + if (llvm::sys::fs::exists(GitPath)) + return std::string(Path); + + Parent = llvm::sys::path::parent_path(Path); + } + return ""; +} + std::string LLVMHeaderGuardCheck::getHeaderGuard(StringRef Filename, StringRef OldGuard) { - std::string Guard = tooling::getAbsolutePath(Filename); + const std::string AbsolutePath = tooling::getAbsolutePath(Filename); + std::string Guard = AbsolutePath; + + // Check for "/llvm-project/" using a path with normalized slashes to ensure + // Windows paths (which use backslashes) are matched correctly. + const std::string CanonicalPath = + llvm::sys::path::convert_to_slash(AbsolutePath); + if (!StringRef(CanonicalPath).contains("/llvm-project/")) { + const std::string Root = findLLVMProjectRoot(AbsolutePath); + if (!Root.empty()) { + StringRef RelativePath = StringRef(AbsolutePath).substr(Root.size()); + if (!RelativePath.empty() && + llvm::sys::path::is_separator(RelativePath.front())) + RelativePath = RelativePath.drop_front(); + Guard = ("/llvm-project/" + RelativePath).str(); + } + } // When running under Windows, need to convert the path separators from // `\` to `/`. diff --git a/clang-tools-extra/clang-tidy/llvm/HeaderGuardCheck.h b/clang-tools-extra/clang-tidy/llvm/HeaderGuardCheck.h index cfc920bb85e23..20d8014710531 100644 --- a/clang-tools-extra/clang-tidy/llvm/HeaderGuardCheck.h +++ b/clang-tools-extra/clang-tidy/llvm/HeaderGuardCheck.h @@ -22,6 +22,9 @@ class LLVMHeaderGuardCheck : public utils::HeaderGuardCheck { bool shouldSuggestEndifComment(StringRef Filename) override { return false; } std::string getHeaderGuard(StringRef Filename, StringRef OldGuard) override; + +protected: + virtual std::string findLLVMProjectRoot(StringRef Filename) const; }; } // namespace clang::tidy::llvm_check diff --git a/clang-tools-extra/unittests/clang-tidy/LLVMModuleTest.cpp b/clang-tools-extra/unittests/clang-tidy/LLVMModuleTest.cpp index e1338c6ef1d5f..9638a116ec904 100644 --- a/clang-tools-extra/unittests/clang-tidy/LLVMModuleTest.cpp +++ b/clang-tools-extra/unittests/clang-tidy/LLVMModuleTest.cpp @@ -60,6 +60,24 @@ runHeaderGuardCheckWithEndif(StringRef Code, const Twine &Filename, std::optional<StringRef> ExpectedWarning) { return runCheck<WithEndifComment>(Code, Filename, std::move(ExpectedWarning)); } +static std::string GlobalMockRoot; + +struct MockLLVMHeaderGuardCheck : public LLVMHeaderGuardCheck { + MockLLVMHeaderGuardCheck(StringRef Name, ClangTidyContext *Context) + : LLVMHeaderGuardCheck(Name, Context) {} + std::string findLLVMProjectRoot(StringRef Filename) const override { + return GlobalMockRoot; + } +}; + +static std::string +runMockHeaderGuardCheck(StringRef Code, const Twine &Filename, + std::optional<StringRef> ExpectedWarning, + std::string MockRoot) { + GlobalMockRoot = MockRoot; + return runCheck<MockLLVMHeaderGuardCheck>(Code, Filename, + std::move(ExpectedWarning)); +} } // namespace TEST(LLVMHeaderGuardCheckTest, FixHeaderGuards) { @@ -297,6 +315,78 @@ TEST(LLVMHeaderGuardCheckTest, FixHeaderGuards) { "", "\\\\?\\C:\\llvm-project\\clang-tools-extra\\clangd\\foo.h", StringRef("header is missing header guard"))); #endif + + // Test path outside any git repo (real root detection should fail and not + // hang). + EXPECT_EQ("#ifndef NON_EXISTENT_DIRECTORY_123_X_H\n" + "#define NON_EXISTENT_DIRECTORY_123_X_H\n" + "\n" + "\n" + "#endif\n", + runHeaderGuardCheck("", "/non_existent_directory_123/x.h", + StringRef("header is missing header guard"))); + + // Test single-component absolute path (loop boundary, doesn't hang). + EXPECT_EQ("#ifndef X_H\n" + "#define X_H\n" + "\n" + "\n" + "#endif\n", + runHeaderGuardCheck("", "/x.h", + StringRef("header is missing header guard"))); +} + +TEST(LLVMHeaderGuardCheckTest, MockedProjectRoot) { + EXPECT_EQ("#ifndef LLVM_LIBC_SRC_STRING_STRLEN_H\n" + "#define LLVM_LIBC_SRC_STRING_STRLEN_H\n" + "\n" + "\n" + "#endif\n", + runMockHeaderGuardCheck( + "", "/my/mock/root/libc/src/string/strlen.h", + StringRef("header is missing header guard"), "/my/mock/root")); + + EXPECT_EQ( + "#ifndef SOME_OTHER_PATH_LIBC_SRC_STRING_STRLEN_H\n" + "#define SOME_OTHER_PATH_LIBC_SRC_STRING_STRLEN_H\n" + "\n" + "\n" + "#endif\n", + runMockHeaderGuardCheck("", "/some/other/path/libc/src/string/strlen.h", + StringRef("header is missing header guard"), "")); + + EXPECT_EQ("#ifndef LLVM_LIBC_SRC_STRING_STRLEN_H\n" + "#define LLVM_LIBC_SRC_STRING_STRLEN_H\n" + "\n" + "\n" + "#endif\n", + runMockHeaderGuardCheck( + "", "/path/to/llvm-project/libc/src/string/strlen.h", + StringRef("header is missing header guard"), + "/different/mock/root")); + + // Test when root has a trailing slash. + EXPECT_EQ("#ifndef LLVM_LIBC_SRC_STRING_STRLEN_H\n" + "#define LLVM_LIBC_SRC_STRING_STRLEN_H\n" + "\n" + "\n" + "#endif\n", + runMockHeaderGuardCheck( + "", "/my/mock/root/libc/src/string/strlen.h", + StringRef("header is missing header guard"), "/my/mock/root/")); + +#ifdef WIN32 + // Test Windows-style paths through the new code path (mocked root). + EXPECT_EQ("#ifndef LLVM_LIBC_SRC_STRING_STRLEN_H\n" + "#define LLVM_LIBC_SRC_STRING_STRLEN_H\n" + "\n" + "\n" + "#endif\n", + runMockHeaderGuardCheck( + "", "C:\\my\\mock\\root\\libc\\src\\string\\strlen.h", + StringRef("header is missing header guard"), + "C:\\my\\mock\\root")); +#endif } TEST(IncludeOrderCheck, GTestHeaders) { _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
