diff options
Diffstat (limited to 'Utilities/ClangTidyModule')
-rw-r--r-- | Utilities/ClangTidyModule/CMakeLists.txt | 2 | ||||
-rw-r--r-- | Utilities/ClangTidyModule/Module.cxx | 2 | ||||
-rw-r--r-- | Utilities/ClangTidyModule/UsePragmaOnceCheck.cxx | 325 | ||||
-rw-r--r-- | Utilities/ClangTidyModule/UsePragmaOnceCheck.h | 60 |
4 files changed, 389 insertions, 0 deletions
diff --git a/Utilities/ClangTidyModule/CMakeLists.txt b/Utilities/ClangTidyModule/CMakeLists.txt index c51f43a6a6..6fc54b1e74 100644 --- a/Utilities/ClangTidyModule/CMakeLists.txt +++ b/Utilities/ClangTidyModule/CMakeLists.txt @@ -22,6 +22,8 @@ add_library(cmake-clang-tidy-module MODULE UseCmstrlenCheck.h UseCmsysFstreamCheck.cxx UseCmsysFstreamCheck.h + UsePragmaOnceCheck.cxx + UsePragmaOnceCheck.h ) target_include_directories(cmake-clang-tidy-module PRIVATE ${CLANG_INCLUDE_DIRS}) target_link_libraries(cmake-clang-tidy-module PRIVATE clang-tidy) diff --git a/Utilities/ClangTidyModule/Module.cxx b/Utilities/ClangTidyModule/Module.cxx index 7ef8e7dabe..c747cee0fb 100644 --- a/Utilities/ClangTidyModule/Module.cxx +++ b/Utilities/ClangTidyModule/Module.cxx @@ -7,6 +7,7 @@ #include "UseBespokeEnumClassCheck.h" #include "UseCmstrlenCheck.h" #include "UseCmsysFstreamCheck.h" +#include "UsePragmaOnceCheck.h" namespace clang { namespace tidy { @@ -23,6 +24,7 @@ public: "cmake-use-bespoke-enum-class"); CheckFactories.registerCheck<OstringstreamUseCmstrcatCheck>( "cmake-ostringstream-use-cmstrcat"); + CheckFactories.registerCheck<UsePragmaOnceCheck>("cmake-use-pragma-once"); } }; diff --git a/Utilities/ClangTidyModule/UsePragmaOnceCheck.cxx b/Utilities/ClangTidyModule/UsePragmaOnceCheck.cxx new file mode 100644 index 0000000000..7a42798a6d --- /dev/null +++ b/Utilities/ClangTidyModule/UsePragmaOnceCheck.cxx @@ -0,0 +1,325 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ + +/* This code was originally taken from part of the Clang-Tidy LLVM project and + * modified for use with CMake under the following original license: */ + +//===--- 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 "UsePragmaOnceCheck.h" + +#include <algorithm> +#include <cassert> + +#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 { +namespace tidy { +namespace cmake { + +/// 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 { +// This class is a workaround for the fact that PPCallbacks doesn't give us the +// location of the hash for an #ifndef, #define, or #endif, so we have to find +// it ourselves. We can't lex backwards, and attempting to turn on the +// preprocessor's backtracking functionality wreaks havoc, so we have to +// instantiate a second lexer and lex all the way from the beginning of the +// file. Cache the results of this lexing so that we don't have to do it more +// times than needed. +// +// TODO: Upstream a change to LLVM to give us the location of the hash in +// PPCallbacks so we don't have to do this workaround. +class DirectiveCache +{ +public: + DirectiveCache(Preprocessor* PP, FileID FID) + : PP(PP) + , FID(FID) + { + SourceManager& SM = this->PP->getSourceManager(); + const FileEntry* Entry = SM.getFileEntryForID(FID); + assert(Entry && "Invalid FileID given"); + + Lexer MyLexer(FID, SM.getMemoryBufferForFileOrFake(Entry), SM, + this->PP->getLangOpts()); + Token Tok; + + while (!MyLexer.LexFromRawLexer(Tok)) { + if (Tok.getKind() == tok::hash) { + assert(SM.getFileID(Tok.getLocation()) == this->FID && + "Token FileID does not match passed FileID"); + if (!this->HashLocs.empty()) { + assert(SM.getFileOffset(this->HashLocs.back()) < + SM.getFileOffset(Tok.getLocation()) && + "Tokens in file are not in order"); + } + + this->HashLocs.push_back(Tok.getLocation()); + } + } + } + + SourceRange createRangeForIfndef(SourceLocation IfndefMacroTokLoc) + { + // The #ifndef of an include guard is likely near the beginning of the + // file, so search from the front. + return SourceRange(this->findPreviousHashFromFront(IfndefMacroTokLoc), + IfndefMacroTokLoc); + } + + SourceRange createRangeForDefine(SourceLocation DefineMacroTokLoc) + { + // The #define of an include guard is likely near the beginning of the + // file, so search from the front. + return SourceRange(this->findPreviousHashFromFront(DefineMacroTokLoc), + DefineMacroTokLoc); + } + + SourceRange createRangeForEndif(SourceLocation EndifLoc) + { + // The #endif of an include guard is likely near the end of the file, so + // search from the back. + return SourceRange(this->findPreviousHashFromBack(EndifLoc), EndifLoc); + } + +private: + Preprocessor* PP; + FileID FID; + SmallVector<SourceLocation> HashLocs; + + SourceLocation findPreviousHashFromFront(SourceLocation Loc) + { + SourceManager& SM = this->PP->getSourceManager(); + Loc = SM.getExpansionLoc(Loc); + assert(SM.getFileID(Loc) == this->FID && + "Loc FileID does not match our FileID"); + + auto It = std::find_if( + this->HashLocs.begin(), this->HashLocs.end(), + [&SM, &Loc](const SourceLocation& OtherLoc) -> bool { + return SM.getFileOffset(OtherLoc) >= SM.getFileOffset(Loc); + }); + assert(It != this->HashLocs.begin() && + "No hash associated with passed Loc"); + return *--It; + } + + SourceLocation findPreviousHashFromBack(SourceLocation Loc) + { + SourceManager& SM = this->PP->getSourceManager(); + Loc = SM.getExpansionLoc(Loc); + assert(SM.getFileID(Loc) == this->FID && + "Loc FileID does not match our FileID"); + + auto It = + std::find_if(this->HashLocs.rbegin(), this->HashLocs.rend(), + [&SM, &Loc](const SourceLocation& OtherLoc) -> bool { + return SM.getFileOffset(OtherLoc) < SM.getFileOffset(Loc); + }); + assert(It != this->HashLocs.rend() && + "No hash associated with passed Loc"); + return *It; + } +}; + +class UsePragmaOncePPCallbacks : public PPCallbacks +{ +public: + UsePragmaOncePPCallbacks(Preprocessor* PP, UsePragmaOnceCheck* 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 = this->PP->getSourceManager(); + if (Reason == EnterFile && FileType == SrcMgr::C_User) { + if (const FileEntry* FE = SM.getFileEntryForID(SM.getFileID(Loc))) { + std::string FileName = cleanPath(FE->getName()); + this->Files[FileName] = FE; + } + } + } + + void Ifndef(SourceLocation Loc, const Token& MacroNameTok, + const MacroDefinition& MD) override + { + if (MD) { + return; + } + + // Record #ifndefs that succeeded. We also need the Location of the Name. + this->Ifndefs[MacroNameTok.getIdentifierInfo()] = + std::make_pair(Loc, MacroNameTok.getLocation()); + } + + void MacroDefined(const Token& MacroNameTok, + const MacroDirective* MD) override + { + // Record all defined macros. We store the whole token to get info on the + // name later. + this->Macros.emplace_back(MacroNameTok, MD->getMacroInfo()); + } + + void Endif(SourceLocation Loc, SourceLocation IfLoc) override + { + // Record all #endif and the corresponding #ifs (including #ifndefs). + this->EndIfs[IfLoc] = Loc; + } + + void EndOfMainFile() override + { + // Now that we have all this information from the preprocessor, use it! + SourceManager& SM = this->PP->getSourceManager(); + + for (const auto& MacroEntry : this->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()); + this->Files.erase(FileName); + + // Look up Locations for this guard. + SourceLocation Ifndef = + this->Ifndefs[MacroEntry.first.getIdentifierInfo()].second; + SourceLocation Define = MacroEntry.first.getLocation(); + SourceLocation EndIf = + this + ->EndIfs[this->Ifndefs[MacroEntry.first.getIdentifierInfo()].first]; + + StringRef CurHeaderGuard = + MacroEntry.first.getIdentifierInfo()->getName(); + std::vector<FixItHint> FixIts; + + HeaderSearch& HeaderInfo = this->PP->getHeaderSearchInfo(); + + HeaderFileInfo& Info = HeaderInfo.getFileInfo(FE); + + DirectiveCache Cache(this->PP, SM.getFileID(MI->getDefinitionLoc())); + SourceRange IfndefSrcRange = Cache.createRangeForIfndef(Ifndef); + SourceRange DefineSrcRange = Cache.createRangeForDefine(Define); + SourceRange EndifSrcRange = Cache.createRangeForEndif(EndIf); + + if (Info.isPragmaOnce) { + FixIts.push_back(FixItHint::CreateRemoval(IfndefSrcRange)); + } else { + FixIts.push_back( + FixItHint::CreateReplacement(IfndefSrcRange, "#pragma once")); + } + + FixIts.push_back(FixItHint::CreateRemoval(DefineSrcRange)); + FixIts.push_back(FixItHint::CreateRemoval(EndifSrcRange)); + + this->Check->diag(IfndefSrcRange.getBegin(), "use #pragma once") + << FixIts; + } + + // 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 : this->Files) { + StringRef FileName = FE.getKey(); + if (!Check->shouldSuggestToAddPragmaOnce(FileName)) { + continue; + } + + SourceManager& SM = this->PP->getSourceManager(); + FileID FID = SM.translateFile(FE.getValue()); + SourceLocation StartLoc = SM.getLocForStartOfFile(FID); + if (StartLoc.isInvalid()) { + continue; + } + + HeaderSearch& HeaderInfo = this->PP->getHeaderSearchInfo(); + + HeaderFileInfo& Info = HeaderInfo.getFileInfo(FE.second); + if (Info.isPragmaOnce) { + continue; + } + + this->Check->diag(StartLoc, "use #pragma once") + << FixItHint::CreateInsertion(StartLoc, "#pragma once\n"); + } + } + +private: + void clearAllState() + { + this->Macros.clear(); + this->Files.clear(); + this->Ifndefs.clear(); + this->EndIfs.clear(); + } + + std::vector<std::pair<Token, const MacroInfo*>> Macros; + llvm::StringMap<const FileEntry*> Files; + std::map<const IdentifierInfo*, std::pair<SourceLocation, SourceLocation>> + Ifndefs; + std::map<SourceLocation, SourceLocation> EndIfs; + + Preprocessor* PP; + UsePragmaOnceCheck* Check; +}; +} // namespace + +void UsePragmaOnceCheck::storeOptions(ClangTidyOptions::OptionMap& Opts) +{ + this->Options.store(Opts, "HeaderFileExtensions", + RawStringHeaderFileExtensions); +} + +void UsePragmaOnceCheck::registerPPCallbacks(const SourceManager& SM, + Preprocessor* PP, + Preprocessor* ModuleExpanderPP) +{ + PP->addPPCallbacks(std::make_unique<UsePragmaOncePPCallbacks>(PP, this)); +} + +bool UsePragmaOnceCheck::shouldSuggestToAddPragmaOnce(StringRef FileName) +{ + return utils::isFileExtension(FileName, this->HeaderFileExtensions); +} + +} // namespace cmake +} // namespace tidy +} // namespace clang diff --git a/Utilities/ClangTidyModule/UsePragmaOnceCheck.h b/Utilities/ClangTidyModule/UsePragmaOnceCheck.h new file mode 100644 index 0000000000..08c2099a9f --- /dev/null +++ b/Utilities/ClangTidyModule/UsePragmaOnceCheck.h @@ -0,0 +1,60 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ + +/* This code was originally taken from part of the Clang-Tidy LLVM project and + * modified for use with CMake under the following original license: */ + +//===--- 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 +// +//===----------------------------------------------------------------------===// + +#pragma once + +#include <clang-tidy/ClangTidyCheck.h> +#include <clang-tidy/utils/FileExtensionsUtils.h> + +namespace clang { +namespace tidy { +namespace cmake { + +/// Finds and replaces header guards with pragma once. +/// 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 UsePragmaOnceCheck : public ClangTidyCheck +{ +public: + UsePragmaOnceCheck(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; + + /// Returns ``true`` if the check should add pragma once to the file + /// if it has none. + virtual bool shouldSuggestToAddPragmaOnce(StringRef Filename); + +private: + std::string RawStringHeaderFileExtensions; + utils::FileExtensionsSet HeaderFileExtensions; +}; + +} // namespace cmake +} // namespace tidy +} // namespace clang |