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 
276 bool HeaderGuardCheck::shouldSuggestEndifComment(StringRef FileName) {
277   return utils::isHeaderFileExtension(FileName, HeaderFileExtensions);
278 }
279 
280 bool HeaderGuardCheck::shouldFixHeaderGuard(StringRef FileName) { return true; }
281 
282 bool HeaderGuardCheck::shouldSuggestToAddHeaderGuard(StringRef FileName) {
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
293