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