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
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
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:276
Loc
SourceLocation Loc
&#39;#&#39; location in the include directive
Definition: IncludeOrderCheck.cpp:36
Name
StringHandle Name
Definition: PreprocessorTracker.cpp:525
clang::tidy::utils::HeaderGuardCheck::getHeaderGuard
virtual std::string getHeaderGuard(StringRef Filename, StringRef OldGuard=StringRef())=0
Gets the canonical header guard for a file.
clang::tidy::utils::HeaderGuardCheck
Finds and fixes header guards.
Definition: HeaderGuard.h:27
clang::tidy::utils::cleanPath
static std::string cleanPath(StringRef Path)
canonicalize a path by removing ./ and ../ components.
Definition: HeaderGuard.cpp:22
Path
std::vector< HeaderHandle > Path
Definition: PreprocessorTracker.cpp:538
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:286
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:280
PPCallbacks
clang
Definition: AndroidTidyModule.cpp:28
HeaderGuard.h
clang::tidy::utils::HeaderGuardCheck::registerPPCallbacks
void registerPPCallbacks(CompilerInstance &Compiler) override
Override this to register PPCallbacks with Compiler.
Definition: HeaderGuard.cpp:270
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:282
clang::tidy::utils::isHeaderFileExtension
bool isHeaderFileExtension(StringRef FileName, const HeaderFileExtensionsSet &HeaderFileExtensions)
Decides whether a file has a header file extension.
Definition: HeaderFileExtensionsUtils.cpp:60
clang::tidy::ClangTidyCheck::diag
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check&#39;s name.
Definition: ClangTidy.cpp:416