summaryrefslogtreecommitdiff
path: root/Utilities
diff options
context:
space:
mode:
authorIgor-Mikhail-Valentin Glebov <iglebov@albany.edu>2022-11-16 14:30:20 -0500
committerKyle Edwards <kyle.edwards@kitware.com>2022-11-17 13:55:52 -0500
commitc9af6f2ff671163c8ed2c17daf43cd815eeef74f (patch)
treee9b9b9d308d8f62871a2afe23f27be096a5d7a02 /Utilities
parent68a4d97da718dac3227710100ba385a4b90c85c3 (diff)
downloadcmake-c9af6f2ff671163c8ed2c17daf43cd815eeef74f.tar.gz
clang-tidy module: add #pragma once check
Co-Authored-by: Kyle Edwards <kyle.edwards@kitware.com>
Diffstat (limited to 'Utilities')
-rw-r--r--Utilities/ClangTidyModule/CMakeLists.txt2
-rw-r--r--Utilities/ClangTidyModule/Module.cxx2
-rw-r--r--Utilities/ClangTidyModule/UsePragmaOnceCheck.cxx325
-rw-r--r--Utilities/ClangTidyModule/UsePragmaOnceCheck.h60
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