Index: clang-tools-extra/clang-tidy/ClangTidyModule.h =================================================================== --- clang-tools-extra/clang-tidy/ClangTidyModule.h +++ clang-tools-extra/clang-tidy/ClangTidyModule.h @@ -10,6 +10,7 @@ #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYMODULE_H #include "ClangTidyOptions.h" +#include "utils/HeaderGuardStyle.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" #include @@ -89,6 +90,11 @@ /// belonging to this module. virtual void addCheckFactories(ClangTidyCheckFactories &CheckFactories) = 0; + /// Implement this function in order to register all \c StyleFactories + /// belonging to this module. + virtual void addHeaderGuardStyleFactories( + utils::HeaderGuardStyleFactories &StyleFactories){}; + /// Gets default options for checks defined in this module. virtual ClangTidyOptions getModuleOptions(); }; Index: clang-tools-extra/clang-tidy/llvm/CMakeLists.txt =================================================================== --- clang-tools-extra/clang-tidy/llvm/CMakeLists.txt +++ clang-tools-extra/clang-tidy/llvm/CMakeLists.txt @@ -5,6 +5,7 @@ add_clang_library(clangTidyLLVMModule HeaderGuardCheck.cpp + HeaderGuardStyle.cpp IncludeOrderCheck.cpp LLVMTidyModule.cpp PreferIsaOrDynCastInConditionalsCheck.cpp Index: clang-tools-extra/clang-tidy/llvm/HeaderGuardCheck.h =================================================================== --- clang-tools-extra/clang-tidy/llvm/HeaderGuardCheck.h +++ clang-tools-extra/clang-tidy/llvm/HeaderGuardCheck.h @@ -9,7 +9,7 @@ #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_HEADERGUARDCHECK_H #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_HEADERGUARDCHECK_H -#include "../utils/HeaderGuard.h" +#include "../readability/HeaderGuardCheck.h" namespace clang::tidy::llvm_check { @@ -23,12 +23,13 @@ /// /// For extension-less header files, using an empty string or leaving an /// empty string between ";" if there are other filename extensions. -class LLVMHeaderGuardCheck : public utils::HeaderGuardCheck { +class LLVMHeaderGuardCheck : public readability::HeaderGuardCheck { public: LLVMHeaderGuardCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; - bool shouldSuggestEndifComment(StringRef Filename) override { return false; } - std::string getHeaderGuard(StringRef Filename, StringRef OldGuard) override; +protected: + std::unique_ptr createHeaderGuardStyle() override; }; } // namespace clang::tidy::llvm_check Index: clang-tools-extra/clang-tidy/llvm/HeaderGuardCheck.cpp =================================================================== --- clang-tools-extra/clang-tidy/llvm/HeaderGuardCheck.cpp +++ clang-tools-extra/clang-tidy/llvm/HeaderGuardCheck.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "HeaderGuardCheck.h" +#include "HeaderGuardStyle.h" #include "clang/Tooling/Tooling.h" #include "llvm/Support/Path.h" @@ -16,52 +17,13 @@ ClangTidyContext *Context) : HeaderGuardCheck(Name, Context) {} -std::string LLVMHeaderGuardCheck::getHeaderGuard(StringRef Filename, - StringRef OldGuard) { - std::string Guard = tooling::getAbsolutePath(Filename); - - // When running under Windows, need to convert the path separators from - // `\` to `/`. - Guard = llvm::sys::path::convert_to_slash(Guard); - - // Sanitize the path. There are some rules for compatibility with the historic - // style in include/llvm and include/clang which we want to preserve. - - // We don't want _INCLUDE_ in our guards. - size_t PosInclude = Guard.rfind("include/"); - if (PosInclude != StringRef::npos) - Guard = Guard.substr(PosInclude + std::strlen("include/")); - - // For clang we drop the _TOOLS_. - size_t PosToolsClang = Guard.rfind("tools/clang/"); - if (PosToolsClang != StringRef::npos) - Guard = Guard.substr(PosToolsClang + std::strlen("tools/")); - - // Unlike LLVM svn, LLVM git monorepo is named llvm-project, so we replace - // "/llvm-project/" with the canonical "/llvm/". - const static StringRef LLVMProject = "/llvm-project/"; - size_t PosLLVMProject = Guard.rfind(std::string(LLVMProject)); - if (PosLLVMProject != StringRef::npos) - Guard = Guard.replace(PosLLVMProject, LLVMProject.size(), "/llvm/"); - - // The remainder is LLVM_FULL_PATH_TO_HEADER_H - size_t PosLLVM = Guard.rfind("llvm/"); - if (PosLLVM != StringRef::npos) - Guard = Guard.substr(PosLLVM); - - std::replace(Guard.begin(), Guard.end(), '/', '_'); - std::replace(Guard.begin(), Guard.end(), '.', '_'); - std::replace(Guard.begin(), Guard.end(), '-', '_'); - - // The prevalent style in clang is LLVM_CLANG_FOO_BAR_H - if (StringRef(Guard).startswith("clang")) - Guard = "LLVM_" + Guard; - - // The prevalent style in flang is FORTRAN_FOO_BAR_H - if (StringRef(Guard).startswith("flang")) - Guard = "FORTRAN" + Guard.substr(sizeof("flang") - 1); +void LLVMHeaderGuardCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "HeaderFileExtensions", RawStringHeaderFileExtensions); +} - return StringRef(Guard).upper(); +std::unique_ptr +LLVMHeaderGuardCheck::createHeaderGuardStyle() { + return std::make_unique(this); } } // namespace clang::tidy::llvm_check Index: clang-tools-extra/clang-tidy/llvm/HeaderGuardStyle.h =================================================================== --- /dev/null +++ clang-tools-extra/clang-tidy/llvm/HeaderGuardStyle.h @@ -0,0 +1,36 @@ +//===--- HeaderGuardStyle.h - clang-tidy ------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_HEADERGUARDSTYLE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_HEADERGUARDSTYLE_H + +#include "../utils/MacroHeaderGuardStyle.h" + +namespace clang::tidy::llvm_check { + +/// Finds and fixes header guards that do not adhere to LLVM style. +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/llvm/header-guard.html +/// The check supports these options: +/// - `HeaderFileExtensions`: a semicolon-separated list of filename +/// extensions of header files (The filename extension should not contain +/// "." prefix). ";h;hh;hpp;hxx" by default. +/// +/// For extension-less header files, using an empty string or leaving an +/// empty string between ";" if there are other filename extensions. +class LLVMHeaderGuardStyle : public utils::MacroHeaderGuardStyle { +public: + LLVMHeaderGuardStyle(readability::HeaderGuardCheck *Check); + + bool shouldSuggestEndifComment(StringRef Filename) override { return false; } + std::string getHeaderGuard(StringRef Filename, StringRef OldGuard) override; +}; + +} // namespace clang::tidy::llvm_check + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_HEADERGUARDSTYLE_H Index: clang-tools-extra/clang-tidy/llvm/HeaderGuardStyle.cpp =================================================================== --- /dev/null +++ clang-tools-extra/clang-tidy/llvm/HeaderGuardStyle.cpp @@ -0,0 +1,66 @@ +//===--- HeaderGuardStyle.cpp - clang-tidy --------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "HeaderGuardStyle.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/Support/Path.h" + +namespace clang::tidy::llvm_check { + +LLVMHeaderGuardStyle::LLVMHeaderGuardStyle(readability::HeaderGuardCheck *Check) + : MacroHeaderGuardStyle(Check) {} + +std::string LLVMHeaderGuardStyle::getHeaderGuard(StringRef Filename, + StringRef OldGuard) { + std::string Guard = tooling::getAbsolutePath(Filename); + + // When running under Windows, need to convert the path separators from + // `\` to `/`. + Guard = llvm::sys::path::convert_to_slash(Guard); + + // Sanitize the path. There are some rules for compatibility with the historic + // style in include/llvm and include/clang which we want to preserve. + + // We don't want _INCLUDE_ in our guards. + size_t PosInclude = Guard.rfind("include/"); + if (PosInclude != StringRef::npos) + Guard = Guard.substr(PosInclude + std::strlen("include/")); + + // For clang we drop the _TOOLS_. + size_t PosToolsClang = Guard.rfind("tools/clang/"); + if (PosToolsClang != StringRef::npos) + Guard = Guard.substr(PosToolsClang + std::strlen("tools/")); + + // Unlike LLVM svn, LLVM git monorepo is named llvm-project, so we replace + // "/llvm-project/" with the canonical "/llvm/". + const static StringRef LLVMProject = "/llvm-project/"; + size_t PosLLVMProject = Guard.rfind(std::string(LLVMProject)); + if (PosLLVMProject != StringRef::npos) + Guard = Guard.replace(PosLLVMProject, LLVMProject.size(), "/llvm/"); + + // The remainder is LLVM_FULL_PATH_TO_HEADER_H + size_t PosLLVM = Guard.rfind("llvm/"); + if (PosLLVM != StringRef::npos) + Guard = Guard.substr(PosLLVM); + + std::replace(Guard.begin(), Guard.end(), '/', '_'); + std::replace(Guard.begin(), Guard.end(), '.', '_'); + std::replace(Guard.begin(), Guard.end(), '-', '_'); + + // The prevalent style in clang is LLVM_CLANG_FOO_BAR_H + if (StringRef(Guard).startswith("clang")) + Guard = "LLVM_" + Guard; + + // The prevalent style in flang is FORTRAN_FOO_BAR_H + if (StringRef(Guard).startswith("flang")) + Guard = "FORTRAN" + Guard.substr(sizeof("flang") - 1); + + return StringRef(Guard).upper(); +} + +} // namespace clang::tidy::llvm_check Index: clang-tools-extra/clang-tidy/llvm/LLVMTidyModule.cpp =================================================================== --- clang-tools-extra/clang-tidy/llvm/LLVMTidyModule.cpp +++ clang-tools-extra/clang-tidy/llvm/LLVMTidyModule.cpp @@ -13,6 +13,7 @@ #include "../readability/NamespaceCommentCheck.h" #include "../readability/QualifiedAutoCheck.h" #include "HeaderGuardCheck.h" +#include "HeaderGuardStyle.h" #include "IncludeOrderCheck.h" #include "PreferIsaOrDynCastInConditionalsCheck.h" #include "PreferRegisterOverUnsignedCheck.h" @@ -39,6 +40,11 @@ CheckFactories.registerCheck("llvm-twine-local"); } + void addHeaderGuardStyleFactories( + utils::HeaderGuardStyleFactories &StyleFactories) override { + StyleFactories.registerStyle("llvm"); + } + ClangTidyOptions getModuleOptions() override { ClangTidyOptions Options; Options.CheckOptions["llvm-qualified-auto.AddConstToQualified"] = "false"; Index: clang-tools-extra/clang-tidy/readability/CMakeLists.txt =================================================================== --- clang-tools-extra/clang-tidy/readability/CMakeLists.txt +++ clang-tools-extra/clang-tidy/readability/CMakeLists.txt @@ -16,6 +16,7 @@ ElseAfterReturnCheck.cpp FunctionCognitiveComplexityCheck.cpp FunctionSizeCheck.cpp + HeaderGuardCheck.cpp IdentifierLengthCheck.cpp IdentifierNamingCheck.cpp ImplicitBoolConversionCheck.cpp Index: clang-tools-extra/clang-tidy/readability/HeaderGuardCheck.h =================================================================== --- /dev/null +++ clang-tools-extra/clang-tidy/readability/HeaderGuardCheck.h @@ -0,0 +1,51 @@ +//===--- HeaderGuardCheck.h - clang-tidy ------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_HEADERGUARDCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_HEADERGUARDCHECK_H + +#include "../ClangTidyCheck.h" +#include "../utils/FileExtensionsUtils.h" +#include "../utils/HeaderGuardStyle.h" + +namespace clang::tidy::readability { + +/// Finds and fixes header guards. +/// The check supports these options: +/// - `Style`: the name of a header guard style to use. The only available +/// option is "llvm". "llvm" by default. +class HeaderGuardCheck : public ClangTidyCheck { +public: + HeaderGuardCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + RawStringHeaderFileExtensions(Options.getLocalOrGlobal( + "HeaderFileExtensions", utils::defaultHeaderFileExtensions())), + StyleName(Options.getLocalOrGlobal("Style", "llvm")) { + utils::parseFileExtensions(RawStringHeaderFileExtensions, + HeaderFileExtensions, + utils::defaultFileExtensionDelimiters()); + } + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, + Preprocessor *ModuleExpanderPP) override; + utils::FileExtensionsSet &getHeaderFileExtensions() { + return HeaderFileExtensions; + } + +protected: + virtual std::unique_ptr createHeaderGuardStyle(); + + std::string RawStringHeaderFileExtensions; + utils::FileExtensionsSet HeaderFileExtensions; + std::string StyleName; + std::unique_ptr Style; +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_HEADERGUARDCHECK_H Index: clang-tools-extra/clang-tidy/readability/HeaderGuardCheck.cpp =================================================================== --- /dev/null +++ clang-tools-extra/clang-tidy/readability/HeaderGuardCheck.cpp @@ -0,0 +1,182 @@ +//===--- HeaderGuardCheck.cpp - clang-tidy --------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "HeaderGuardCheck.h" +#include "../ClangTidyModuleRegistry.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/PPCallbacks.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/Support/Path.h" + +namespace clang::tidy::readability { + +/// canonicalize a path by removing ./ and ../ components. +static std::string cleanPath(StringRef Path) { + SmallString<256> Result = Path; + llvm::sys::path::remove_dots(Result, true); + return std::string(Result.str()); +} + +namespace { +class HeaderGuardCheckPPCallbacks : public PPCallbacks { +public: + HeaderGuardCheckPPCallbacks(Preprocessor *PP, HeaderGuardCheck *Check, + utils::HeaderGuardStyle *Style) + : PP(PP), Check(Check), Style(Style) {} + + void FileChanged(SourceLocation Loc, FileChangeReason Reason, + SrcMgr::CharacteristicKind FileType, + FileID PrevFID) override { + // Record all files we enter. We'll need them to diagnose headers without + // guards. + SourceManager &SM = PP->getSourceManager(); + if (Reason == EnterFile && FileType == SrcMgr::C_User) { + if (const FileEntry *FE = SM.getFileEntryForID(SM.getFileID(Loc))) { + std::string FileName = cleanPath(FE->getName()); + Files[FileName] = FE; + } + } + } + + void Ifndef(SourceLocation HashLoc, SourceLocation Loc, + const Token &MacroNameTok, const MacroDefinition &MD) override { + if (MD) + return; + + // Record #ifndefs that succeeded. We also need the Location of the Name. + Ifndefs[MacroNameTok.getIdentifierInfo()] = + std::make_tuple(HashLoc, Loc, MacroNameTok.getLocation()); + } + + void MacroDefined(SourceLocation HashLoc, const Token &MacroNameTok, + const MacroDirective *MD) override { + // Record all defined macros. We store the whole token to get info on the + // name later. + Macros.emplace_back(HashLoc, MacroNameTok, MD->getMacroInfo()); + } + + void Endif(SourceLocation HashLoc, SourceLocation Loc, + SourceLocation IfLoc) override { + // Record all #endif and the corresponding #ifs (including #ifndefs). + EndIfs[IfLoc] = std::make_pair(HashLoc, Loc); + } + + void EndOfMainFile() override { + // Now that we have all this information from the preprocessor, use it! + SourceManager &SM = PP->getSourceManager(); + + for (const auto &MacroEntry : Macros) { + const MacroInfo *MI = std::get<2>(MacroEntry); + + // We use clang's header guard detection. This has the advantage of also + // emitting a warning for cases where a pseudo header guard is found but + // preceded by something blocking the header guard optimization. + if (!MI->isUsedForHeaderGuard()) + continue; + + const FileEntry *FE = + SM.getFileEntryForID(SM.getFileID(MI->getDefinitionLoc())); + std::string FileName = cleanPath(FE->getName()); + Files.erase(FileName); + + // See if we should check and fix this header guard. + if (!Style->shouldFixIfHeaderGuard(FileName)) + continue; + + // Look up Locations for this guard. + std::tuple Ifndef = + Ifndefs[std::get<1>(MacroEntry).getIdentifierInfo()]; + std::pair EndIf = + EndIfs[std::get<1>(Ifndef)]; + + Style->onHeaderGuard(PP, FileName, FE, std::get<0>(Ifndef), + std::get<1>(Ifndef), std::get<2>(Ifndef), + std::get<0>(MacroEntry), std::get<1>(MacroEntry), + EndIf.first, EndIf.second); + } + + // Emit warnings for headers that are missing guards. + checkGuardlessHeaders(); + clearAllState(); + } + + /// Looks for files that were visited but didn't have a header guard. + /// Emits a warning with fixits suggesting adding one. + void checkGuardlessHeaders() { + // Look for header files that didn't have a header guard. Emit a warning and + // fix-its to add the guard. + // TODO: Insert the guard after top comments. + for (const auto &FE : Files) { + StringRef FileName = FE.getKey(); + if (!Style->shouldFixIfNoHeaderGuard(FileName)) + continue; + + SourceManager &SM = PP->getSourceManager(); + FileID FID = SM.translateFile(FE.getValue()); + SourceLocation StartLoc = SM.getLocForStartOfFile(FID); + if (StartLoc.isInvalid()) + continue; + + Style->onGuardlessHeader(PP, FileName, FE.getValue(), StartLoc, Macros); + } + } + +private: + void clearAllState() { + Macros.clear(); + Files.clear(); + Ifndefs.clear(); + EndIfs.clear(); + } + + std::vector> Macros; + llvm::StringMap Files; + std::map> + Ifndefs; + std::map> EndIfs; + + Preprocessor *PP; + HeaderGuardCheck *Check; + utils::HeaderGuardStyle *Style; +}; +} // namespace + +void HeaderGuardCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "Style", StyleName); +} + +void HeaderGuardCheck::registerPPCallbacks(const SourceManager &SM, + Preprocessor *PP, + Preprocessor *ModuleExpanderPP) { + Style = createHeaderGuardStyle(); + if (Style) + PP->addPPCallbacks( + std::make_unique(PP, this, Style.get())); +} + +std::unique_ptr +HeaderGuardCheck::createHeaderGuardStyle() { + utils::HeaderGuardStyleFactories StyleFactories; + for (ClangTidyModuleRegistry::entry E : ClangTidyModuleRegistry::entries()) { + E.instantiate()->addHeaderGuardStyleFactories(StyleFactories); + } + + auto It = StyleFactories.find(StyleName); + if (It == StyleFactories.end()) { + std::string MsgStr; + llvm::raw_string_ostream Msg(MsgStr); + Msg << "no such header guard style: '" << StyleName << "'"; + configurationDiag(Msg.str(), DiagnosticIDs::Error); + return nullptr; + } + + return It->second(this); +} +} // namespace clang::tidy::readability Index: clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp =================================================================== --- clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp +++ clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp @@ -21,6 +21,7 @@ #include "ElseAfterReturnCheck.h" #include "FunctionCognitiveComplexityCheck.h" #include "FunctionSizeCheck.h" +#include "HeaderGuardCheck.h" #include "IdentifierLengthCheck.h" #include "IdentifierNamingCheck.h" #include "ImplicitBoolConversionCheck.h" @@ -82,6 +83,7 @@ "readability-function-cognitive-complexity"); CheckFactories.registerCheck( "readability-function-size"); + CheckFactories.registerCheck("readability-header-guard"); CheckFactories.registerCheck( "readability-identifier-length"); CheckFactories.registerCheck( Index: clang-tools-extra/clang-tidy/utils/CMakeLists.txt =================================================================== --- clang-tools-extra/clang-tidy/utils/CMakeLists.txt +++ clang-tools-extra/clang-tidy/utils/CMakeLists.txt @@ -11,10 +11,11 @@ ExprSequence.cpp FileExtensionsUtils.cpp FixItHintUtils.cpp - HeaderGuard.cpp + HeaderGuardStyle.cpp IncludeInserter.cpp IncludeSorter.cpp LexerUtils.cpp + MacroHeaderGuardStyle.cpp NamespaceAliaser.cpp OptionsUtils.cpp RenamerClangTidyCheck.cpp Index: clang-tools-extra/clang-tidy/utils/HeaderGuard.h =================================================================== --- clang-tools-extra/clang-tidy/utils/HeaderGuard.h +++ /dev/null @@ -1,66 +0,0 @@ -//===--- HeaderGuard.h - clang-tidy -----------------------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_HEADERGUARD_H -#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_HEADERGUARD_H - -#include "../ClangTidyCheck.h" -#include "../utils/FileExtensionsUtils.h" - -namespace clang::tidy::utils { - -/// Finds and fixes header guards. -/// The check supports these options: -/// - `HeaderFileExtensions`: a semicolon-separated list of filename -/// extensions of header files (The filename extension should not contain -/// "." prefix). ";h;hh;hpp;hxx" by default. -/// -/// For extension-less header files, using an empty string or leaving an -/// empty string between ";" if there are other filename extensions. -class HeaderGuardCheck : public ClangTidyCheck { -public: - HeaderGuardCheck(StringRef Name, ClangTidyContext *Context) - : ClangTidyCheck(Name, Context), - RawStringHeaderFileExtensions(Options.getLocalOrGlobal( - "HeaderFileExtensions", utils::defaultHeaderFileExtensions())) { - utils::parseFileExtensions(RawStringHeaderFileExtensions, - HeaderFileExtensions, - utils::defaultFileExtensionDelimiters()); - } - void storeOptions(ClangTidyOptions::OptionMap &Opts) override; - void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, - Preprocessor *ModuleExpanderPP) override; - - /// Ensure that the provided header guard is a non-reserved identifier. - std::string sanitizeHeaderGuard(StringRef Guard); - - /// Returns ``true`` if the check should suggest inserting a trailing comment - /// on the ``#endif`` of the header guard. It will use the same name as - /// returned by ``HeaderGuardCheck::getHeaderGuard``. - virtual bool shouldSuggestEndifComment(StringRef Filename); - /// Returns ``true`` if the check should suggest changing an existing header - /// guard to the string returned by ``HeaderGuardCheck::getHeaderGuard``. - virtual bool shouldFixHeaderGuard(StringRef Filename); - /// Returns ``true`` if the check should add a header guard to the file - /// if it has none. - virtual bool shouldSuggestToAddHeaderGuard(StringRef Filename); - /// Returns a replacement for the ``#endif`` line with a comment mentioning - /// \p HeaderGuard. The replacement should start with ``endif``. - virtual std::string formatEndIf(StringRef HeaderGuard); - /// Gets the canonical header guard for a file. - virtual std::string getHeaderGuard(StringRef Filename, - StringRef OldGuard = StringRef()) = 0; - -private: - std::string RawStringHeaderFileExtensions; - utils::FileExtensionsSet HeaderFileExtensions; -}; - -} // namespace clang::tidy::utils - -#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_HEADERGUARD_H Index: clang-tools-extra/clang-tidy/utils/HeaderGuard.cpp =================================================================== --- clang-tools-extra/clang-tidy/utils/HeaderGuard.cpp +++ /dev/null @@ -1,300 +0,0 @@ -//===--- HeaderGuard.cpp - clang-tidy -------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include "HeaderGuard.h" -#include "clang/Frontend/CompilerInstance.h" -#include "clang/Lex/PPCallbacks.h" -#include "clang/Lex/Preprocessor.h" -#include "clang/Tooling/Tooling.h" -#include "llvm/Support/Path.h" - -namespace clang::tidy::utils { - -/// canonicalize a path by removing ./ and ../ components. -static std::string cleanPath(StringRef Path) { - SmallString<256> Result = Path; - llvm::sys::path::remove_dots(Result, true); - return std::string(Result.str()); -} - -namespace { -class HeaderGuardPPCallbacks : public PPCallbacks { -public: - HeaderGuardPPCallbacks(Preprocessor *PP, HeaderGuardCheck *Check) - : PP(PP), Check(Check) {} - - void FileChanged(SourceLocation Loc, FileChangeReason Reason, - SrcMgr::CharacteristicKind FileType, - FileID PrevFID) override { - // Record all files we enter. We'll need them to diagnose headers without - // guards. - SourceManager &SM = PP->getSourceManager(); - if (Reason == EnterFile && FileType == SrcMgr::C_User) { - if (const FileEntry *FE = SM.getFileEntryForID(SM.getFileID(Loc))) { - std::string FileName = cleanPath(FE->getName()); - Files[FileName] = FE; - } - } - } - - void Ifndef(SourceLocation HashLoc, SourceLocation Loc, - const Token &MacroNameTok, const MacroDefinition &MD) override { - if (MD) - return; - - // Record #ifndefs that succeeded. We also need the Location of the Name. - Ifndefs[MacroNameTok.getIdentifierInfo()] = - std::make_pair(Loc, MacroNameTok.getLocation()); - } - - void MacroDefined(SourceLocation HashLoc, const Token &MacroNameTok, - const MacroDirective *MD) override { - // Record all defined macros. We store the whole token to get info on the - // name later. - Macros.emplace_back(MacroNameTok, MD->getMacroInfo()); - } - - void Endif(SourceLocation HashLoc, SourceLocation Loc, - SourceLocation IfLoc) override { - // Record all #endif and the corresponding #ifs (including #ifndefs). - EndIfs[IfLoc] = Loc; - } - - void EndOfMainFile() override { - // Now that we have all this information from the preprocessor, use it! - SourceManager &SM = PP->getSourceManager(); - - for (const auto &MacroEntry : Macros) { - const MacroInfo *MI = MacroEntry.second; - - // We use clang's header guard detection. This has the advantage of also - // emitting a warning for cases where a pseudo header guard is found but - // preceded by something blocking the header guard optimization. - if (!MI->isUsedForHeaderGuard()) - continue; - - const FileEntry *FE = - SM.getFileEntryForID(SM.getFileID(MI->getDefinitionLoc())); - std::string FileName = cleanPath(FE->getName()); - Files.erase(FileName); - - // See if we should check and fix this header guard. - if (!Check->shouldFixHeaderGuard(FileName)) - continue; - - // Look up Locations for this guard. - SourceLocation Ifndef = - Ifndefs[MacroEntry.first.getIdentifierInfo()].second; - SourceLocation Define = MacroEntry.first.getLocation(); - SourceLocation EndIf = - EndIfs[Ifndefs[MacroEntry.first.getIdentifierInfo()].first]; - - // If the macro Name is not equal to what we can compute, correct it in - // the #ifndef and #define. - StringRef CurHeaderGuard = - MacroEntry.first.getIdentifierInfo()->getName(); - std::vector FixIts; - std::string NewGuard = checkHeaderGuardDefinition( - Ifndef, Define, EndIf, FileName, CurHeaderGuard, FixIts); - - // Now look at the #endif. We want a comment with the header guard. Fix it - // at the slightest deviation. - checkEndifComment(FileName, EndIf, NewGuard, FixIts); - - // Bundle all fix-its into one warning. The message depends on whether we - // changed the header guard or not. - if (!FixIts.empty()) { - if (CurHeaderGuard != NewGuard) { - Check->diag(Ifndef, "header guard does not follow preferred style") - << FixIts; - } else { - Check->diag(EndIf, "#endif for a header guard should reference the " - "guard macro in a comment") - << FixIts; - } - } - } - - // Emit warnings for headers that are missing guards. - checkGuardlessHeaders(); - clearAllState(); - } - - bool wouldFixEndifComment(StringRef FileName, SourceLocation EndIf, - StringRef HeaderGuard, - size_t *EndIfLenPtr = nullptr) { - if (!EndIf.isValid()) - return false; - const char *EndIfData = PP->getSourceManager().getCharacterData(EndIf); - size_t EndIfLen = std::strcspn(EndIfData, "\r\n"); - if (EndIfLenPtr) - *EndIfLenPtr = EndIfLen; - - StringRef EndIfStr(EndIfData, EndIfLen); - EndIfStr = EndIfStr.substr(EndIfStr.find_first_not_of("#endif \t")); - - // Give up if there's an escaped newline. - size_t FindEscapedNewline = EndIfStr.find_last_not_of(' '); - if (FindEscapedNewline != StringRef::npos && - EndIfStr[FindEscapedNewline] == '\\') - return false; - - bool IsLineComment = - EndIfStr.consume_front("//") || - (EndIfStr.consume_front("/*") && EndIfStr.consume_back("*/")); - if (!IsLineComment) - return Check->shouldSuggestEndifComment(FileName); - - return EndIfStr.trim() != HeaderGuard; - } - - /// Look for header guards that don't match the preferred style. Emit - /// fix-its and return the suggested header guard (or the original if no - /// change was made. - std::string checkHeaderGuardDefinition(SourceLocation Ifndef, - SourceLocation Define, - SourceLocation EndIf, - StringRef FileName, - StringRef CurHeaderGuard, - std::vector &FixIts) { - std::string CPPVar = Check->getHeaderGuard(FileName, CurHeaderGuard); - CPPVar = Check->sanitizeHeaderGuard(CPPVar); - std::string CPPVarUnder = CPPVar + '_'; - - // Allow a trailing underscore if and only if we don't have to change the - // endif comment too. - if (Ifndef.isValid() && CurHeaderGuard != CPPVar && - (CurHeaderGuard != CPPVarUnder || - wouldFixEndifComment(FileName, EndIf, CurHeaderGuard))) { - FixIts.push_back(FixItHint::CreateReplacement( - CharSourceRange::getTokenRange( - Ifndef, Ifndef.getLocWithOffset(CurHeaderGuard.size())), - CPPVar)); - FixIts.push_back(FixItHint::CreateReplacement( - CharSourceRange::getTokenRange( - Define, Define.getLocWithOffset(CurHeaderGuard.size())), - CPPVar)); - return CPPVar; - } - return std::string(CurHeaderGuard); - } - - /// Checks the comment after the #endif of a header guard and fixes it - /// if it doesn't match \c HeaderGuard. - void checkEndifComment(StringRef FileName, SourceLocation EndIf, - StringRef HeaderGuard, - std::vector &FixIts) { - size_t EndIfLen; - if (wouldFixEndifComment(FileName, EndIf, HeaderGuard, &EndIfLen)) { - FixIts.push_back(FixItHint::CreateReplacement( - CharSourceRange::getCharRange(EndIf, - EndIf.getLocWithOffset(EndIfLen)), - Check->formatEndIf(HeaderGuard))); - } - } - - /// Looks for files that were visited but didn't have a header guard. - /// Emits a warning with fixits suggesting adding one. - void checkGuardlessHeaders() { - // Look for header files that didn't have a header guard. Emit a warning and - // fix-its to add the guard. - // TODO: Insert the guard after top comments. - for (const auto &FE : Files) { - StringRef FileName = FE.getKey(); - if (!Check->shouldSuggestToAddHeaderGuard(FileName)) - continue; - - SourceManager &SM = PP->getSourceManager(); - FileID FID = SM.translateFile(FE.getValue()); - SourceLocation StartLoc = SM.getLocForStartOfFile(FID); - if (StartLoc.isInvalid()) - continue; - - std::string CPPVar = Check->getHeaderGuard(FileName); - CPPVar = Check->sanitizeHeaderGuard(CPPVar); - std::string CPPVarUnder = CPPVar + '_'; // Allow a trailing underscore. - // If there's a macro with a name that follows the header guard convention - // but was not recognized by the preprocessor as a header guard there must - // be code outside of the guarded area. Emit a plain warning without - // fix-its. - // FIXME: Can we move it into the right spot? - bool SeenMacro = false; - for (const auto &MacroEntry : Macros) { - StringRef Name = MacroEntry.first.getIdentifierInfo()->getName(); - SourceLocation DefineLoc = MacroEntry.first.getLocation(); - if ((Name == CPPVar || Name == CPPVarUnder) && - SM.isWrittenInSameFile(StartLoc, DefineLoc)) { - Check->diag(DefineLoc, "code/includes outside of area guarded by " - "header guard; consider moving it"); - SeenMacro = true; - break; - } - } - - if (SeenMacro) - continue; - - Check->diag(StartLoc, "header is missing header guard") - << FixItHint::CreateInsertion( - StartLoc, "#ifndef " + CPPVar + "\n#define " + CPPVar + "\n\n") - << FixItHint::CreateInsertion( - SM.getLocForEndOfFile(FID), - Check->shouldSuggestEndifComment(FileName) - ? "\n#" + Check->formatEndIf(CPPVar) + "\n" - : "\n#endif\n"); - } - } - -private: - void clearAllState() { - Macros.clear(); - Files.clear(); - Ifndefs.clear(); - EndIfs.clear(); - } - - std::vector> Macros; - llvm::StringMap Files; - std::map> - Ifndefs; - std::map EndIfs; - - Preprocessor *PP; - HeaderGuardCheck *Check; -}; -} // namespace - -void HeaderGuardCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { - Options.store(Opts, "HeaderFileExtensions", RawStringHeaderFileExtensions); -} - -void HeaderGuardCheck::registerPPCallbacks(const SourceManager &SM, - Preprocessor *PP, - Preprocessor *ModuleExpanderPP) { - PP->addPPCallbacks(std::make_unique(PP, this)); -} - -std::string HeaderGuardCheck::sanitizeHeaderGuard(StringRef Guard) { - // Only reserved identifiers are allowed to start with an '_'. - return Guard.drop_while([](char C) { return C == '_'; }).str(); -} - -bool HeaderGuardCheck::shouldSuggestEndifComment(StringRef FileName) { - return utils::isFileExtension(FileName, HeaderFileExtensions); -} - -bool HeaderGuardCheck::shouldFixHeaderGuard(StringRef FileName) { return true; } - -bool HeaderGuardCheck::shouldSuggestToAddHeaderGuard(StringRef FileName) { - return utils::isFileExtension(FileName, HeaderFileExtensions); -} - -std::string HeaderGuardCheck::formatEndIf(StringRef HeaderGuard) { - return "endif // " + HeaderGuard.str(); -} -} // namespace clang::tidy::utils Index: clang-tools-extra/clang-tidy/utils/HeaderGuardStyle.h =================================================================== --- /dev/null +++ clang-tools-extra/clang-tidy/utils/HeaderGuardStyle.h @@ -0,0 +1,130 @@ +//===--- HeaderGuardStyle.h - clang-tidy ------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_HEADERGUARDSTYLE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_HEADERGUARDSTYLE_H + +#include "clang/Lex/Preprocessor.h" +#include "llvm/ADT/StringMap.h" + +namespace clang { +class MacroInfo; + +namespace tidy { +namespace readability { +class HeaderGuardCheck; +} // namespace readability + +namespace utils { +class HeaderGuardStyle { +public: + HeaderGuardStyle(readability::HeaderGuardCheck *Check) : Check(Check) {} + virtual ~HeaderGuardStyle() = default; + + /// Returns ``true`` if the check should suggest fixing a header file if it + /// has an existing header guard. + virtual bool shouldFixIfHeaderGuard(StringRef Filename); + + /// Returns ``true`` if the check should suggest fixing a header file if it + /// does not have an existing header guard. + virtual bool shouldFixIfNoHeaderGuard(StringRef Filename); + + /// @brief Called when a header guard is detected. + /// @param PP Preprocessor. + /// @param FileName Name of the file. + /// @param FE FileEntry. + /// @param IfndefHash Location of '#' in '#ifndef'. + /// @param Ifndef Location of 'ifndef' in '#ifndef'. + /// @param IfndefToken Location of macro token in '#ifndef'. + /// @param DefineHash Location of '#' in '#define'. + /// @param Define Location of macro token in '#define'. + /// @param EndIfHash Location of '#' in '#endif'. + /// @param EndIf Location of 'endif' in '#endif'. + virtual void onHeaderGuard(Preprocessor *PP, StringRef FileName, + const FileEntry *FE, SourceLocation IfndefHash, + SourceLocation Ifndef, SourceLocation IfndefToken, + SourceLocation DefineHash, const Token &Define, + SourceLocation EndIfHash, + SourceLocation EndIf) = 0; + + /// @brief Called when a header with no header guard is detected. + /// @param PP Preprocessor. + /// @param FileName Name of the file. + /// @param FE FileEntry. + /// @param StartLoc Location of the start of the file. + /// @param Macros List of macros in this file. Contains location of '#', macro + /// token, and macro info. + virtual void onGuardlessHeader( + Preprocessor *PP, StringRef FileName, const FileEntry *FE, + SourceLocation StartLoc, + const std::vector> + &Macros) = 0; + +protected: + readability::HeaderGuardCheck *Check; +}; + +/// A collection of \c HeaderGuardStyle factories. +/// +/// All clang-tidy modules register their header guard style factories with an +/// instance of this object. +class HeaderGuardStyleFactories { +public: + using StyleFactory = std::function( + readability::HeaderGuardCheck *Check)>; + + /// Registers style \p Factory with name \p Name. + /// + /// For all style that have constructors that take a HeaderGuardCheck*, use \c + /// registerStyle. + void registerStyleFactory(llvm::StringRef Name, StyleFactory Factory); + + /// Registers the \c StyleType with the name \p Name. + /// + /// This method should be used for all \c HeaderGuardStyles whose constructors + /// take one \c HeaderGuardCheck * parameter. + /// + /// For example, if you have a header guard style like: + /// \code + /// class MyStyle : public HeaderGuardStyle { + /// bool shouldFixIfHeaderGuard(StringRef Filename) override { + /// .. + /// } + /// }; + /// \endcode + /// you can register it with: + /// \code + /// class MyModule : public ClangTidyModule { + /// void addHeaderGuardStyleFactories(HeaderGuardStyleFactories &Factories) + /// override { + /// Factories.registerStyle("myproject-my-style"); + /// } + /// }; + /// \endcode + template void registerStyle(llvm::StringRef StyleName) { + registerStyleFactory(StyleName, [](readability::HeaderGuardCheck *Check) { + return std::make_unique(Check); + }); + } + + typedef llvm::StringMap FactoryMap; + FactoryMap::const_iterator begin() const { return Factories.begin(); } + FactoryMap::const_iterator end() const { return Factories.end(); } + FactoryMap::const_iterator find(StringRef Name) const { + return Factories.find(Name); + } + bool empty() const { return Factories.empty(); } + +private: + FactoryMap Factories; +}; +} // namespace utils +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_HEADERGUARDSTYLE_H Index: clang-tools-extra/clang-tidy/utils/HeaderGuardStyle.cpp =================================================================== --- /dev/null +++ clang-tools-extra/clang-tidy/utils/HeaderGuardStyle.cpp @@ -0,0 +1,26 @@ +//===--- HeaderGuardStyle.cpp - clang-tidy ----------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "HeaderGuardStyle.h" +#include "../readability/HeaderGuardCheck.h" +#include "../utils/FileExtensionsUtils.h" + +namespace clang::tidy::utils { +bool HeaderGuardStyle::shouldFixIfHeaderGuard(StringRef Filename) { + return true; +} + +bool HeaderGuardStyle::shouldFixIfNoHeaderGuard(StringRef FileName) { + return utils::isFileExtension(FileName, Check->getHeaderFileExtensions()); +} + +void HeaderGuardStyleFactories::registerStyleFactory(StringRef Name, + StyleFactory Factory) { + Factories.insert_or_assign(Name, std::move(Factory)); +} +} // namespace clang::tidy::utils Index: clang-tools-extra/clang-tidy/utils/MacroHeaderGuardStyle.h =================================================================== --- /dev/null +++ clang-tools-extra/clang-tidy/utils/MacroHeaderGuardStyle.h @@ -0,0 +1,60 @@ +//===--- MacroHeaderGuardStyle.h - clang-tidy -------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_MACROHEADERGUARDSTYLE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_MACROHEADERGUARDSTYLE_H + +#include "HeaderGuardStyle.h" + +namespace clang::tidy::utils { +class MacroHeaderGuardStyle : public HeaderGuardStyle { +public: + MacroHeaderGuardStyle(readability::HeaderGuardCheck *Check) + : HeaderGuardStyle(Check) {} + + /// Ensure that the provided header guard is a non-reserved identifier. + std::string sanitizeHeaderGuard(StringRef Guard); + + /// Returns ``true`` if the check should suggest inserting a trailing comment + /// on the ``#endif`` of the header guard. It will use the same name as + /// returned by ``HeaderGuardCheck::getHeaderGuard``. + virtual bool shouldSuggestEndifComment(StringRef Filename); + /// Returns a replacement for the ``#endif`` line with a comment mentioning + /// \p HeaderGuard. The replacement should start with ``endif``. + virtual std::string formatEndIf(StringRef HeaderGuard); + /// Gets the canonical header guard for a file. + virtual std::string getHeaderGuard(StringRef Filename, + StringRef OldGuard = StringRef()) = 0; + + void onHeaderGuard(Preprocessor *PP, StringRef FileName, const FileEntry *FE, + SourceLocation IfndefHash, SourceLocation Ifndef, + SourceLocation IfndefToken, SourceLocation DefineHash, + const Token &Define, SourceLocation EndIfHash, + SourceLocation EndIf) override; + void onGuardlessHeader( + Preprocessor *PP, StringRef FileName, const FileEntry *FE, + SourceLocation StartLoc, + const std::vector> + &Macros) override; + +private: + bool wouldFixEndifComment(Preprocessor *PP, StringRef FileName, + SourceLocation EndIf, StringRef HeaderGuard, + size_t *EndIfLenPtr = nullptr); + std::string + checkHeaderGuardDefinition(Preprocessor *PP, SourceLocation Ifndef, + SourceLocation Define, SourceLocation EndIf, + StringRef FileName, StringRef CurHeaderGuard, + std::vector &FixIts); + void checkEndifComment(Preprocessor *PP, StringRef FileName, + SourceLocation EndIf, StringRef HeaderGuard, + std::vector &FixIts); +}; +} // namespace clang::tidy::utils + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_MACROHEADERGUARDSTYLE_H Index: clang-tools-extra/clang-tidy/utils/MacroHeaderGuardStyle.cpp =================================================================== --- /dev/null +++ clang-tools-extra/clang-tidy/utils/MacroHeaderGuardStyle.cpp @@ -0,0 +1,172 @@ +//===--- MacroHeaderGuardStyle.cpp - clang-tidy -----------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "MacroHeaderGuardStyle.h" +#include "../readability/HeaderGuardCheck.h" +#include "../utils/FileExtensionsUtils.h" +#include "clang/Lex/Preprocessor.h" + +namespace clang::tidy::utils { + +void MacroHeaderGuardStyle::onHeaderGuard( + Preprocessor *PP, StringRef FileName, const FileEntry *FE, + SourceLocation IfndefHash, SourceLocation Ifndef, + SourceLocation IfndefToken, SourceLocation DefineHash, const Token &Define, + SourceLocation EndIfHash, SourceLocation EndIf) { + // If the macro Name is not equal to what we can compute, correct it in + // the #ifndef and #define. + StringRef CurHeaderGuard = Define.getIdentifierInfo()->getName(); + std::vector FixIts; + std::string NewGuard = + checkHeaderGuardDefinition(PP, IfndefToken, Define.getLocation(), EndIf, + FileName, CurHeaderGuard, FixIts); + + // Now look at the #endif. We want a comment with the header guard. Fix it + // at the slightest deviation. + checkEndifComment(PP, FileName, EndIf, NewGuard, FixIts); + + // Bundle all fix-its into one warning. The message depends on whether we + // changed the header guard or not. + if (!FixIts.empty()) { + if (CurHeaderGuard != NewGuard) { + Check->diag(Ifndef, "header guard does not follow preferred style") + << FixIts; + } else { + Check->diag(EndIf, "#endif for a header guard should reference the " + "guard macro in a comment") + << FixIts; + } + } +} + +bool MacroHeaderGuardStyle::wouldFixEndifComment(Preprocessor *PP, + StringRef FileName, + SourceLocation EndIf, + StringRef HeaderGuard, + size_t *EndIfLenPtr) { + if (!EndIf.isValid()) + return false; + const char *EndIfData = PP->getSourceManager().getCharacterData(EndIf); + size_t EndIfLen = std::strcspn(EndIfData, "\r\n"); + if (EndIfLenPtr) + *EndIfLenPtr = EndIfLen; + + StringRef EndIfStr(EndIfData, EndIfLen); + EndIfStr = EndIfStr.substr(EndIfStr.find_first_not_of("#endif \t")); + + // Give up if there's an escaped newline. + size_t FindEscapedNewline = EndIfStr.find_last_not_of(' '); + if (FindEscapedNewline != StringRef::npos && + EndIfStr[FindEscapedNewline] == '\\') + return false; + + bool IsLineComment = + EndIfStr.consume_front("//") || + (EndIfStr.consume_front("/*") && EndIfStr.consume_back("*/")); + if (!IsLineComment) + return shouldSuggestEndifComment(FileName); + + return EndIfStr.trim() != HeaderGuard; +} + +/// Look for header guards that don't match the preferred style. Emit +/// fix-its and return the suggested header guard (or the original if no +/// change was made. +std::string MacroHeaderGuardStyle::checkHeaderGuardDefinition( + Preprocessor *PP, SourceLocation Ifndef, SourceLocation Define, + SourceLocation EndIf, StringRef FileName, StringRef CurHeaderGuard, + std::vector &FixIts) { + std::string CPPVar = getHeaderGuard(FileName, CurHeaderGuard); + CPPVar = sanitizeHeaderGuard(CPPVar); + std::string CPPVarUnder = CPPVar + '_'; + + // Allow a trailing underscore if and only if we don't have to change the + // endif comment too. + if (Ifndef.isValid() && CurHeaderGuard != CPPVar && + (CurHeaderGuard != CPPVarUnder || + wouldFixEndifComment(PP, FileName, EndIf, CurHeaderGuard))) { + FixIts.push_back(FixItHint::CreateReplacement( + CharSourceRange::getTokenRange( + Ifndef, Ifndef.getLocWithOffset(CurHeaderGuard.size())), + CPPVar)); + FixIts.push_back(FixItHint::CreateReplacement( + CharSourceRange::getTokenRange( + Define, Define.getLocWithOffset(CurHeaderGuard.size())), + CPPVar)); + return CPPVar; + } + return std::string(CurHeaderGuard); +} + +/// Checks the comment after the #endif of a header guard and fixes it +/// if it doesn't match \c HeaderGuard. +void MacroHeaderGuardStyle::checkEndifComment(Preprocessor *PP, + StringRef FileName, + SourceLocation EndIf, + StringRef HeaderGuard, + std::vector &FixIts) { + size_t EndIfLen; + if (wouldFixEndifComment(PP, FileName, EndIf, HeaderGuard, &EndIfLen)) { + FixIts.push_back(FixItHint::CreateReplacement( + CharSourceRange::getCharRange(EndIf, EndIf.getLocWithOffset(EndIfLen)), + formatEndIf(HeaderGuard))); + } +} + +void MacroHeaderGuardStyle::onGuardlessHeader( + Preprocessor *PP, StringRef FileName, const FileEntry *FE, + SourceLocation StartLoc, + const std::vector> + &Macros) { + SourceManager &SM = PP->getSourceManager(); + std::string CPPVar = getHeaderGuard(FileName); + CPPVar = sanitizeHeaderGuard(CPPVar); + std::string CPPVarUnder = CPPVar + '_'; // Allow a trailing underscore. + // If there's a macro with a name that follows the header guard convention + // but was not recognized by the preprocessor as a header guard there must + // be code outside of the guarded area. Emit a plain warning without + // fix-its. + // FIXME: Can we move it into the right spot? + bool SeenMacro = false; + for (const auto &MacroEntry : Macros) { + StringRef Name = std::get<1>(MacroEntry).getIdentifierInfo()->getName(); + SourceLocation DefineLoc = std::get<1>(MacroEntry).getLocation(); + if ((Name == CPPVar || Name == CPPVarUnder) && + SM.isWrittenInSameFile(StartLoc, DefineLoc)) { + Check->diag(DefineLoc, "code/includes outside of area guarded by " + "header guard; consider moving it"); + SeenMacro = true; + break; + } + } + + if (SeenMacro) + return; + + Check->diag(StartLoc, "header is missing header guard") + << FixItHint::CreateInsertion( + StartLoc, "#ifndef " + CPPVar + "\n#define " + CPPVar + "\n\n") + << FixItHint::CreateInsertion(SM.getLocForEndOfFile(SM.translateFile(FE)), + shouldSuggestEndifComment(FileName) + ? "\n#" + formatEndIf(CPPVar) + "\n" + : "\n#endif\n"); +} + +std::string MacroHeaderGuardStyle::sanitizeHeaderGuard(StringRef Guard) { + // Only reserved identifiers are allowed to start with an '_'. + return Guard.drop_while([](char C) { return C == '_'; }).str(); +} + +bool MacroHeaderGuardStyle::shouldSuggestEndifComment(StringRef FileName) { + return utils::isFileExtension(FileName, Check->getHeaderFileExtensions()); +} + +std::string MacroHeaderGuardStyle::formatEndIf(StringRef HeaderGuard) { + return "endif // " + HeaderGuard.str(); +} +} // namespace clang::tidy::utils Index: clang-tools-extra/docs/ReleaseNotes.rst =================================================================== --- clang-tools-extra/docs/ReleaseNotes.rst +++ clang-tools-extra/docs/ReleaseNotes.rst @@ -110,6 +110,11 @@ Warns when lambda specify a capture default and capture ``this``. +- New :doc:`readability-header-guard + ` check. + + Finds header guards and corrects them if necessary. + New check aliases ^^^^^^^^^^^^^^^^^ @@ -124,6 +129,11 @@ Changes in existing checks ^^^^^^^^^^^^^^^^^^^^^^^^^^ +- Deprecated :doc:`llvm-header-guard + ` check. Use + :doc:`readability-header-guard + ` with the ``llvm`` style instead. + Removed checks ^^^^^^^^^^^^^^ Index: clang-tools-extra/docs/clang-tidy/checks/list.rst =================================================================== --- clang-tools-extra/docs/clang-tidy/checks/list.rst +++ clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -339,6 +339,7 @@ `readability-else-after-return `_, "Yes" `readability-function-cognitive-complexity `_, `readability-function-size `_, + `readability-header-guard `_, `readability-identifier-length `_, `readability-identifier-naming `_, "Yes" `readability-implicit-bool-conversion `_, "Yes" Index: clang-tools-extra/docs/clang-tidy/checks/llvm/header-guard.rst =================================================================== --- clang-tools-extra/docs/clang-tidy/checks/llvm/header-guard.rst +++ clang-tools-extra/docs/clang-tidy/checks/llvm/header-guard.rst @@ -5,6 +5,10 @@ Finds and fixes header guards that do not adhere to LLVM style. +Note: this check is deprecated, it will be removed in :program:`clang-tidy` +version 19. Please use the check `readability-header-guard`. with the ``llvm`` +style. + Options ------- Index: clang-tools-extra/docs/clang-tidy/checks/readability/header-guard.rst =================================================================== --- /dev/null +++ clang-tools-extra/docs/clang-tidy/checks/readability/header-guard.rst @@ -0,0 +1,26 @@ +.. title:: clang-tidy - readability-header-guard + +readability-header-guard +======================== + +Finds and fixes header guards that do not adhere to a specified style. + +Options +------- + +.. option:: HeaderFileExtensions + + A comma-separated list of filename extensions of header files (the filename + extensions should not include "." prefix). Default is "h,hh,hpp,hxx". + For header files without an extension, use an empty string (if there are no + other desired extensions) or leave an empty element in the list. E.g., + "h,hh,hpp,hxx," (note the trailing comma). + +.. option:: Style + + The name of a header guard style to select. The default is "llvm". Available + options are: + + ``llvm`` + + Use the LLVM header guard style. Index: clang-tools-extra/unittests/clang-tidy/LLVMModuleTest.cpp =================================================================== --- clang-tools-extra/unittests/clang-tidy/LLVMModuleTest.cpp +++ clang-tools-extra/unittests/clang-tidy/LLVMModuleTest.cpp @@ -3,6 +3,7 @@ #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/StringRef.h" #include "llvm/HeaderGuardCheck.h" +#include "llvm/HeaderGuardStyle.h" #include "llvm/IncludeOrderCheck.h" #include "gtest/gtest.h" #include @@ -49,10 +50,18 @@ } namespace { +struct WithEndifCommentStyle : public LLVMHeaderGuardStyle { + WithEndifCommentStyle(readability::HeaderGuardCheck *Check) + : LLVMHeaderGuardStyle(Check) {} + bool shouldSuggestEndifComment(StringRef Filename) override { return true; } +}; + struct WithEndifComment : public LLVMHeaderGuardCheck { WithEndifComment(StringRef Name, ClangTidyContext *Context) : LLVMHeaderGuardCheck(Name, Context) {} - bool shouldSuggestEndifComment(StringRef Filename) override { return true; } + std::unique_ptr createHeaderGuardStyle() override { + return std::make_unique(this); + } }; static std::string