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