You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

324 lines
10 KiB

  1. /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
  2. file LICENSE.rst or https://cmake.org/licensing for details. */
  3. /* This code was originally taken from part of the Clang-Tidy LLVM project and
  4. * modified for use with CMake under the following original license: */
  5. //===--- HeaderGuard.cpp - clang-tidy
  6. //-------------------------------------===//
  7. //
  8. // Part of the LLVM Project, under the Apache License v2.0 with LLVM
  9. // Exceptions. See https://llvm.org/LICENSE.txt for license information.
  10. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  11. //
  12. //===----------------------------------------------------------------------===//
  13. #include "UsePragmaOnceCheck.h"
  14. #include <algorithm>
  15. #include <cassert>
  16. #include <clang/Frontend/CompilerInstance.h>
  17. #include <clang/Lex/PPCallbacks.h>
  18. #include <clang/Lex/Preprocessor.h>
  19. #include <clang/Tooling/Tooling.h>
  20. #include <llvm/Support/Path.h>
  21. namespace clang {
  22. namespace tidy {
  23. namespace cmake {
  24. /// canonicalize a path by removing ./ and ../ components.
  25. static std::string cleanPath(StringRef Path)
  26. {
  27. SmallString<256> Result = Path;
  28. llvm::sys::path::remove_dots(Result, true);
  29. return std::string(Result.str());
  30. }
  31. namespace {
  32. // This class is a workaround for the fact that PPCallbacks doesn't give us the
  33. // location of the hash for an #ifndef, #define, or #endif, so we have to find
  34. // it ourselves. We can't lex backwards, and attempting to turn on the
  35. // preprocessor's backtracking functionality wreaks havoc, so we have to
  36. // instantiate a second lexer and lex all the way from the beginning of the
  37. // file. Cache the results of this lexing so that we don't have to do it more
  38. // times than needed.
  39. //
  40. // TODO: Upstream a change to LLVM to give us the location of the hash in
  41. // PPCallbacks so we don't have to do this workaround.
  42. class DirectiveCache
  43. {
  44. public:
  45. DirectiveCache(Preprocessor* PP, FileID FID)
  46. : PP(PP)
  47. , FID(FID)
  48. {
  49. SourceManager& SM = this->PP->getSourceManager();
  50. OptionalFileEntryRef Entry = SM.getFileEntryRefForID(FID);
  51. assert(Entry && "Invalid FileID given");
  52. Lexer MyLexer(FID, SM.getMemoryBufferForFileOrFake(*Entry), SM,
  53. this->PP->getLangOpts());
  54. Token Tok;
  55. while (!MyLexer.LexFromRawLexer(Tok)) {
  56. if (Tok.getKind() == tok::hash) {
  57. assert(SM.getFileID(Tok.getLocation()) == this->FID &&
  58. "Token FileID does not match passed FileID");
  59. if (!this->HashLocs.empty()) {
  60. assert(SM.getFileOffset(this->HashLocs.back()) <
  61. SM.getFileOffset(Tok.getLocation()) &&
  62. "Tokens in file are not in order");
  63. }
  64. this->HashLocs.push_back(Tok.getLocation());
  65. }
  66. }
  67. }
  68. SourceRange createRangeForIfndef(SourceLocation IfndefMacroTokLoc)
  69. {
  70. // The #ifndef of an include guard is likely near the beginning of the
  71. // file, so search from the front.
  72. return SourceRange(this->findPreviousHashFromFront(IfndefMacroTokLoc),
  73. IfndefMacroTokLoc);
  74. }
  75. SourceRange createRangeForDefine(SourceLocation DefineMacroTokLoc)
  76. {
  77. // The #define of an include guard is likely near the beginning of the
  78. // file, so search from the front.
  79. return SourceRange(this->findPreviousHashFromFront(DefineMacroTokLoc),
  80. DefineMacroTokLoc);
  81. }
  82. SourceRange createRangeForEndif(SourceLocation EndifLoc)
  83. {
  84. // The #endif of an include guard is likely near the end of the file, so
  85. // search from the back.
  86. return SourceRange(this->findPreviousHashFromBack(EndifLoc), EndifLoc);
  87. }
  88. private:
  89. Preprocessor* PP;
  90. FileID FID;
  91. SmallVector<SourceLocation> HashLocs;
  92. SourceLocation findPreviousHashFromFront(SourceLocation Loc)
  93. {
  94. SourceManager& SM = this->PP->getSourceManager();
  95. Loc = SM.getExpansionLoc(Loc);
  96. assert(SM.getFileID(Loc) == this->FID &&
  97. "Loc FileID does not match our FileID");
  98. auto It = std::find_if(
  99. this->HashLocs.begin(), this->HashLocs.end(),
  100. [&SM, &Loc](SourceLocation const& OtherLoc) -> bool {
  101. return SM.getFileOffset(OtherLoc) >= SM.getFileOffset(Loc);
  102. });
  103. assert(It != this->HashLocs.begin() &&
  104. "No hash associated with passed Loc");
  105. return *--It;
  106. }
  107. SourceLocation findPreviousHashFromBack(SourceLocation Loc)
  108. {
  109. SourceManager& SM = this->PP->getSourceManager();
  110. Loc = SM.getExpansionLoc(Loc);
  111. assert(SM.getFileID(Loc) == this->FID &&
  112. "Loc FileID does not match our FileID");
  113. auto It =
  114. std::find_if(this->HashLocs.rbegin(), this->HashLocs.rend(),
  115. [&SM, &Loc](SourceLocation const& OtherLoc) -> bool {
  116. return SM.getFileOffset(OtherLoc) < SM.getFileOffset(Loc);
  117. });
  118. assert(It != this->HashLocs.rend() &&
  119. "No hash associated with passed Loc");
  120. return *It;
  121. }
  122. };
  123. class UsePragmaOncePPCallbacks : public PPCallbacks
  124. {
  125. public:
  126. UsePragmaOncePPCallbacks(Preprocessor* PP, UsePragmaOnceCheck* Check)
  127. : PP(PP)
  128. , Check(Check)
  129. {
  130. }
  131. void FileChanged(SourceLocation Loc, FileChangeReason Reason,
  132. SrcMgr::CharacteristicKind FileType,
  133. FileID PrevFID) override
  134. {
  135. // Record all files we enter. We'll need them to diagnose headers without
  136. // guards.
  137. SourceManager& SM = this->PP->getSourceManager();
  138. if (Reason == EnterFile && FileType == SrcMgr::C_User) {
  139. if (OptionalFileEntryRef FE =
  140. SM.getFileEntryRefForID(SM.getFileID(Loc))) {
  141. std::string FileName = cleanPath(FE->getName());
  142. this->Files.try_emplace(FileName, *FE);
  143. }
  144. }
  145. }
  146. void Ifndef(SourceLocation Loc, Token const& MacroNameTok,
  147. MacroDefinition const& MD) override
  148. {
  149. if (MD) {
  150. return;
  151. }
  152. // Record #ifndefs that succeeded. We also need the Location of the Name.
  153. this->Ifndefs[MacroNameTok.getIdentifierInfo()] =
  154. std::make_pair(Loc, MacroNameTok.getLocation());
  155. }
  156. void MacroDefined(Token const& MacroNameTok,
  157. MacroDirective const* MD) override
  158. {
  159. // Record all defined macros. We store the whole token to get info on the
  160. // name later.
  161. this->Macros.emplace_back(MacroNameTok, MD->getMacroInfo());
  162. }
  163. void Endif(SourceLocation Loc, SourceLocation IfLoc) override
  164. {
  165. // Record all #endif and the corresponding #ifs (including #ifndefs).
  166. this->EndIfs[IfLoc] = Loc;
  167. }
  168. void EndOfMainFile() override
  169. {
  170. // Now that we have all this information from the preprocessor, use it!
  171. SourceManager& SM = this->PP->getSourceManager();
  172. for (auto const& MacroEntry : this->Macros) {
  173. MacroInfo const* MI = MacroEntry.second;
  174. // We use clang's header guard detection. This has the advantage of also
  175. // emitting a warning for cases where a pseudo header guard is found but
  176. // preceded by something blocking the header guard optimization.
  177. if (!MI->isUsedForHeaderGuard()) {
  178. continue;
  179. }
  180. FileEntryRef FE =
  181. *SM.getFileEntryRefForID(SM.getFileID(MI->getDefinitionLoc()));
  182. std::string FileName = cleanPath(FE.getName());
  183. this->Files.erase(FileName);
  184. // Look up Locations for this guard.
  185. SourceLocation Ifndef =
  186. this->Ifndefs[MacroEntry.first.getIdentifierInfo()].second;
  187. SourceLocation Define = MacroEntry.first.getLocation();
  188. SourceLocation EndIf =
  189. this
  190. ->EndIfs[this->Ifndefs[MacroEntry.first.getIdentifierInfo()].first];
  191. std::vector<FixItHint> FixIts;
  192. HeaderSearch& HeaderInfo = this->PP->getHeaderSearchInfo();
  193. HeaderFileInfo& Info = HeaderInfo.getFileInfo(FE);
  194. DirectiveCache Cache(this->PP, SM.getFileID(MI->getDefinitionLoc()));
  195. SourceRange IfndefSrcRange = Cache.createRangeForIfndef(Ifndef);
  196. SourceRange DefineSrcRange = Cache.createRangeForDefine(Define);
  197. SourceRange EndifSrcRange = Cache.createRangeForEndif(EndIf);
  198. if (Info.isPragmaOnce) {
  199. FixIts.push_back(FixItHint::CreateRemoval(IfndefSrcRange));
  200. } else {
  201. FixIts.push_back(
  202. FixItHint::CreateReplacement(IfndefSrcRange, "#pragma once"));
  203. }
  204. FixIts.push_back(FixItHint::CreateRemoval(DefineSrcRange));
  205. FixIts.push_back(FixItHint::CreateRemoval(EndifSrcRange));
  206. this->Check->diag(IfndefSrcRange.getBegin(), "use #pragma once")
  207. << FixIts;
  208. }
  209. // Emit warnings for headers that are missing guards.
  210. checkGuardlessHeaders();
  211. clearAllState();
  212. }
  213. /// Looks for files that were visited but didn't have a header guard.
  214. /// Emits a warning with fixits suggesting adding one.
  215. void checkGuardlessHeaders()
  216. {
  217. // Look for header files that didn't have a header guard. Emit a warning
  218. // and fix-its to add the guard.
  219. // TODO: Insert the guard after top comments.
  220. for (auto const& FE : this->Files) {
  221. StringRef FileName = FE.getKey();
  222. if (!Check->shouldSuggestToAddPragmaOnce(FileName)) {
  223. continue;
  224. }
  225. SourceManager& SM = this->PP->getSourceManager();
  226. FileID FID = SM.translateFile(FE.getValue());
  227. SourceLocation StartLoc = SM.getLocForStartOfFile(FID);
  228. if (StartLoc.isInvalid()) {
  229. continue;
  230. }
  231. HeaderSearch& HeaderInfo = this->PP->getHeaderSearchInfo();
  232. HeaderFileInfo& Info = HeaderInfo.getFileInfo(FE.second);
  233. if (Info.isPragmaOnce) {
  234. continue;
  235. }
  236. this->Check->diag(StartLoc, "use #pragma once")
  237. << FixItHint::CreateInsertion(StartLoc, "#pragma once\n");
  238. }
  239. }
  240. private:
  241. void clearAllState()
  242. {
  243. this->Macros.clear();
  244. this->Files.clear();
  245. this->Ifndefs.clear();
  246. this->EndIfs.clear();
  247. }
  248. std::vector<std::pair<Token, MacroInfo const*>> Macros;
  249. llvm::StringMap<FileEntryRef> Files;
  250. std::map<IdentifierInfo const*, std::pair<SourceLocation, SourceLocation>>
  251. Ifndefs;
  252. std::map<SourceLocation, SourceLocation> EndIfs;
  253. Preprocessor* PP;
  254. UsePragmaOnceCheck* Check;
  255. };
  256. } // namespace
  257. void UsePragmaOnceCheck::storeOptions(ClangTidyOptions::OptionMap& Opts)
  258. {
  259. this->Options.store(Opts, "HeaderFileExtensions",
  260. RawStringHeaderFileExtensions);
  261. }
  262. void UsePragmaOnceCheck::registerPPCallbacks(SourceManager const& SM,
  263. Preprocessor* PP,
  264. Preprocessor* ModuleExpanderPP)
  265. {
  266. PP->addPPCallbacks(std::make_unique<UsePragmaOncePPCallbacks>(PP, this));
  267. }
  268. bool UsePragmaOnceCheck::shouldSuggestToAddPragmaOnce(StringRef FileName)
  269. {
  270. return utils::isFileExtension(FileName, this->HeaderFileExtensions);
  271. }
  272. } // namespace cmake
  273. } // namespace tidy
  274. } // namespace clang