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