HeaderGuard.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. //===--- HeaderGuard.cpp - clang-tidy -------------------------------------===//
  2. //
  3. // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
  4. // See https://llvm.org/LICENSE.txt for license information.
  5. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  6. //
  7. //===----------------------------------------------------------------------===//
  8. #include "HeaderGuard.h"
  9. #include "clang/Frontend/CompilerInstance.h"
  10. #include "clang/Lex/PPCallbacks.h"
  11. #include "clang/Lex/Preprocessor.h"
  12. #include "clang/Tooling/Tooling.h"
  13. #include "llvm/Support/Path.h"
  14. namespace clang::tidy::utils {
  15. /// canonicalize a path by removing ./ and ../ components.
  16. static std::string cleanPath(StringRef Path) {
  17. SmallString<256> Result = Path;
  18. llvm::sys::path::remove_dots(Result, true);
  19. return std::string(Result.str());
  20. }
  21. namespace {
  22. class HeaderGuardPPCallbacks : public PPCallbacks {
  23. public:
  24. HeaderGuardPPCallbacks(Preprocessor *PP, HeaderGuardCheck *Check)
  25. : PP(PP), Check(Check) {}
  26. void FileChanged(SourceLocation Loc, FileChangeReason Reason,
  27. SrcMgr::CharacteristicKind FileType,
  28. FileID PrevFID) override {
  29. // Record all files we enter. We'll need them to diagnose headers without
  30. // guards.
  31. SourceManager &SM = PP->getSourceManager();
  32. if (Reason == EnterFile && FileType == SrcMgr::C_User) {
  33. if (const FileEntry *FE = SM.getFileEntryForID(SM.getFileID(Loc))) {
  34. std::string FileName = cleanPath(FE->getName());
  35. Files[FileName] = FE;
  36. }
  37. }
  38. }
  39. void Ifndef(SourceLocation Loc, const Token &MacroNameTok,
  40. const MacroDefinition &MD) override {
  41. if (MD)
  42. return;
  43. // Record #ifndefs that succeeded. We also need the Location of the Name.
  44. Ifndefs[MacroNameTok.getIdentifierInfo()] =
  45. std::make_pair(Loc, MacroNameTok.getLocation());
  46. }
  47. void MacroDefined(const Token &MacroNameTok,
  48. const MacroDirective *MD) override {
  49. // Record all defined macros. We store the whole token to get info on the
  50. // name later.
  51. Macros.emplace_back(MacroNameTok, MD->getMacroInfo());
  52. }
  53. void Endif(SourceLocation Loc, SourceLocation IfLoc) override {
  54. // Record all #endif and the corresponding #ifs (including #ifndefs).
  55. EndIfs[IfLoc] = Loc;
  56. }
  57. void EndOfMainFile() override {
  58. // Now that we have all this information from the preprocessor, use it!
  59. SourceManager &SM = PP->getSourceManager();
  60. for (const auto &MacroEntry : Macros) {
  61. const MacroInfo *MI = MacroEntry.second;
  62. // We use clang's header guard detection. This has the advantage of also
  63. // emitting a warning for cases where a pseudo header guard is found but
  64. // preceded by something blocking the header guard optimization.
  65. if (!MI->isUsedForHeaderGuard())
  66. continue;
  67. const FileEntry *FE =
  68. SM.getFileEntryForID(SM.getFileID(MI->getDefinitionLoc()));
  69. std::string FileName = cleanPath(FE->getName());
  70. Files.erase(FileName);
  71. // See if we should check and fix this header guard.
  72. if (!Check->shouldFixHeaderGuard(FileName))
  73. continue;
  74. // Look up Locations for this guard.
  75. SourceLocation Ifndef =
  76. Ifndefs[MacroEntry.first.getIdentifierInfo()].second;
  77. SourceLocation Define = MacroEntry.first.getLocation();
  78. SourceLocation EndIf =
  79. EndIfs[Ifndefs[MacroEntry.first.getIdentifierInfo()].first];
  80. // If the macro Name is not equal to what we can compute, correct it in
  81. // the #ifndef and #define.
  82. StringRef CurHeaderGuard =
  83. MacroEntry.first.getIdentifierInfo()->getName();
  84. std::vector<FixItHint> FixIts;
  85. std::string NewGuard = checkHeaderGuardDefinition(
  86. Ifndef, Define, EndIf, FileName, CurHeaderGuard, FixIts);
  87. // Now look at the #endif. We want a comment with the header guard. Fix it
  88. // at the slightest deviation.
  89. checkEndifComment(FileName, EndIf, NewGuard, FixIts);
  90. // Bundle all fix-its into one warning. The message depends on whether we
  91. // changed the header guard or not.
  92. if (!FixIts.empty()) {
  93. if (CurHeaderGuard != NewGuard) {
  94. Check->diag(Ifndef, "header guard does not follow preferred style")
  95. << FixIts;
  96. } else {
  97. Check->diag(EndIf, "#endif for a header guard should reference the "
  98. "guard macro in a comment")
  99. << FixIts;
  100. }
  101. }
  102. }
  103. // Emit warnings for headers that are missing guards.
  104. checkGuardlessHeaders();
  105. clearAllState();
  106. }
  107. bool wouldFixEndifComment(StringRef FileName, SourceLocation EndIf,
  108. StringRef HeaderGuard,
  109. size_t *EndIfLenPtr = nullptr) {
  110. if (!EndIf.isValid())
  111. return false;
  112. const char *EndIfData = PP->getSourceManager().getCharacterData(EndIf);
  113. size_t EndIfLen = std::strcspn(EndIfData, "\r\n");
  114. if (EndIfLenPtr)
  115. *EndIfLenPtr = EndIfLen;
  116. StringRef EndIfStr(EndIfData, EndIfLen);
  117. EndIfStr = EndIfStr.substr(EndIfStr.find_first_not_of("#endif \t"));
  118. // Give up if there's an escaped newline.
  119. size_t FindEscapedNewline = EndIfStr.find_last_not_of(' ');
  120. if (FindEscapedNewline != StringRef::npos &&
  121. EndIfStr[FindEscapedNewline] == '\\')
  122. return false;
  123. bool IsLineComment =
  124. EndIfStr.consume_front("//") ||
  125. (EndIfStr.consume_front("/*") && EndIfStr.consume_back("*/"));
  126. if (!IsLineComment)
  127. return Check->shouldSuggestEndifComment(FileName);
  128. return EndIfStr.trim() != HeaderGuard;
  129. }
  130. /// Look for header guards that don't match the preferred style. Emit
  131. /// fix-its and return the suggested header guard (or the original if no
  132. /// change was made.
  133. std::string checkHeaderGuardDefinition(SourceLocation Ifndef,
  134. SourceLocation Define,
  135. SourceLocation EndIf,
  136. StringRef FileName,
  137. StringRef CurHeaderGuard,
  138. std::vector<FixItHint> &FixIts) {
  139. std::string CPPVar = Check->getHeaderGuard(FileName, CurHeaderGuard);
  140. CPPVar = Check->sanitizeHeaderGuard(CPPVar);
  141. std::string CPPVarUnder = CPPVar + '_';
  142. // Allow a trailing underscore if and only if we don't have to change the
  143. // endif comment too.
  144. if (Ifndef.isValid() && CurHeaderGuard != CPPVar &&
  145. (CurHeaderGuard != CPPVarUnder ||
  146. wouldFixEndifComment(FileName, EndIf, CurHeaderGuard))) {
  147. FixIts.push_back(FixItHint::CreateReplacement(
  148. CharSourceRange::getTokenRange(
  149. Ifndef, Ifndef.getLocWithOffset(CurHeaderGuard.size())),
  150. CPPVar));
  151. FixIts.push_back(FixItHint::CreateReplacement(
  152. CharSourceRange::getTokenRange(
  153. Define, Define.getLocWithOffset(CurHeaderGuard.size())),
  154. CPPVar));
  155. return CPPVar;
  156. }
  157. return std::string(CurHeaderGuard);
  158. }
  159. /// Checks the comment after the #endif of a header guard and fixes it
  160. /// if it doesn't match \c HeaderGuard.
  161. void checkEndifComment(StringRef FileName, SourceLocation EndIf,
  162. StringRef HeaderGuard,
  163. std::vector<FixItHint> &FixIts) {
  164. size_t EndIfLen;
  165. if (wouldFixEndifComment(FileName, EndIf, HeaderGuard, &EndIfLen)) {
  166. FixIts.push_back(FixItHint::CreateReplacement(
  167. CharSourceRange::getCharRange(EndIf,
  168. EndIf.getLocWithOffset(EndIfLen)),
  169. Check->formatEndIf(HeaderGuard)));
  170. }
  171. }
  172. /// Looks for files that were visited but didn't have a header guard.
  173. /// Emits a warning with fixits suggesting adding one.
  174. void checkGuardlessHeaders() {
  175. // Look for header files that didn't have a header guard. Emit a warning and
  176. // fix-its to add the guard.
  177. // TODO: Insert the guard after top comments.
  178. for (const auto &FE : Files) {
  179. StringRef FileName = FE.getKey();
  180. if (!Check->shouldSuggestToAddHeaderGuard(FileName))
  181. continue;
  182. SourceManager &SM = PP->getSourceManager();
  183. FileID FID = SM.translateFile(FE.getValue());
  184. SourceLocation StartLoc = SM.getLocForStartOfFile(FID);
  185. if (StartLoc.isInvalid())
  186. continue;
  187. std::string CPPVar = Check->getHeaderGuard(FileName);
  188. CPPVar = Check->sanitizeHeaderGuard(CPPVar);
  189. std::string CPPVarUnder = CPPVar + '_'; // Allow a trailing underscore.
  190. // If there's a macro with a name that follows the header guard convention
  191. // but was not recognized by the preprocessor as a header guard there must
  192. // be code outside of the guarded area. Emit a plain warning without
  193. // fix-its.
  194. // FIXME: Can we move it into the right spot?
  195. bool SeenMacro = false;
  196. for (const auto &MacroEntry : Macros) {
  197. StringRef Name = MacroEntry.first.getIdentifierInfo()->getName();
  198. SourceLocation DefineLoc = MacroEntry.first.getLocation();
  199. if ((Name == CPPVar || Name == CPPVarUnder) &&
  200. SM.isWrittenInSameFile(StartLoc, DefineLoc)) {
  201. Check->diag(DefineLoc, "code/includes outside of area guarded by "
  202. "header guard; consider moving it");
  203. SeenMacro = true;
  204. break;
  205. }
  206. }
  207. if (SeenMacro)
  208. continue;
  209. Check->diag(StartLoc, "header is missing header guard")
  210. << FixItHint::CreateInsertion(
  211. StartLoc, "#ifndef " + CPPVar + "\n#define " + CPPVar + "\n\n")
  212. << FixItHint::CreateInsertion(
  213. SM.getLocForEndOfFile(FID),
  214. Check->shouldSuggestEndifComment(FileName)
  215. ? "\n#" + Check->formatEndIf(CPPVar) + "\n"
  216. : "\n#endif\n");
  217. }
  218. }
  219. private:
  220. void clearAllState() {
  221. Macros.clear();
  222. Files.clear();
  223. Ifndefs.clear();
  224. EndIfs.clear();
  225. }
  226. std::vector<std::pair<Token, const MacroInfo *>> Macros;
  227. llvm::StringMap<const FileEntry *> Files;
  228. std::map<const IdentifierInfo *, std::pair<SourceLocation, SourceLocation>>
  229. Ifndefs;
  230. std::map<SourceLocation, SourceLocation> EndIfs;
  231. Preprocessor *PP;
  232. HeaderGuardCheck *Check;
  233. };
  234. } // namespace
  235. void HeaderGuardCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
  236. Options.store(Opts, "HeaderFileExtensions", RawStringHeaderFileExtensions);
  237. }
  238. void HeaderGuardCheck::registerPPCallbacks(const SourceManager &SM,
  239. Preprocessor *PP,
  240. Preprocessor *ModuleExpanderPP) {
  241. PP->addPPCallbacks(std::make_unique<HeaderGuardPPCallbacks>(PP, this));
  242. }
  243. std::string HeaderGuardCheck::sanitizeHeaderGuard(StringRef Guard) {
  244. // Only reserved identifiers are allowed to start with an '_'.
  245. return Guard.drop_while([](char C) { return C == '_'; }).str();
  246. }
  247. bool HeaderGuardCheck::shouldSuggestEndifComment(StringRef FileName) {
  248. return utils::isFileExtension(FileName, HeaderFileExtensions);
  249. }
  250. bool HeaderGuardCheck::shouldFixHeaderGuard(StringRef FileName) { return true; }
  251. bool HeaderGuardCheck::shouldSuggestToAddHeaderGuard(StringRef FileName) {
  252. return utils::isFileExtension(FileName, HeaderFileExtensions);
  253. }
  254. std::string HeaderGuardCheck::formatEndIf(StringRef HeaderGuard) {
  255. return "endif // " + HeaderGuard.str();
  256. }
  257. } // namespace clang::tidy::utils