clang-tools 3.8.0

- Main Page
- Namespaces
- Classes
- Files

- File List
- File Members

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>>
274 Ifndefs;
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
288 bool HeaderGuardCheck::shouldSuggestEndifComment(StringRef FileName) {
289 return FileName.endswith(".h");
290 }
291
292 bool HeaderGuardCheck::shouldFixHeaderGuard(StringRef FileName) { return true; }
293
294 bool HeaderGuardCheck::shouldSuggestToAddHeaderGuard(StringRef FileName) {
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
Loc
SourceLocation Loc
'#' location in the include directive
Definition: IncludeOrderCheck.cpp:34
clang::tidy::HeaderGuardCheck::formatEndIf
virtual std::string formatEndIf(StringRef HeaderGuard)
Returns a replacement for endif line with a comment mentioning HeaderGuard.
Definition: HeaderGuard.cpp:298
Name
StringHandle Name
Definition: PreprocessorTracker.cpp:525
clang::tidy::HeaderGuardCheck::shouldSuggestToAddHeaderGuard
virtual bool shouldSuggestToAddHeaderGuard(StringRef Filename)
Returns true if the checker should add a header guard to the file if it has none. ...
Definition: HeaderGuard.cpp:294
Ifndefs
std::map< const IdentifierInfo *, std::pair< SourceLocation, SourceLocation > > Ifndefs
Definition: HeaderGuard.cpp:274
Path
std::vector< HeaderHandle > Path
Definition: PreprocessorTracker.cpp:538
SM
SourceManager & SM
Definition: IncludeOrderCheck.cpp:44
Macros
std::vector< std::pair< Token, const MacroInfo * > > Macros
Definition: HeaderGuard.cpp:271
clang::tidy::HeaderGuardCheck::shouldSuggestEndifComment
virtual bool shouldSuggestEndifComment(StringRef Filename)
Returns true if the checker should suggest inserting a trailing comment on the #endif of the header g...
Definition: HeaderGuard.cpp:288
clang::tidy::HeaderGuardCheck::registerPPCallbacks
void registerPPCallbacks(CompilerInstance &Compiler) override
Override this to register PPCallbacks with Compiler.
Definition: HeaderGuard.cpp:282
Check
HeaderGuardCheck * Check
Definition: HeaderGuard.cpp:278
clang::tidy::HeaderGuardCheck::shouldFixHeaderGuard
virtual bool shouldFixHeaderGuard(StringRef Filename)
Returns true if the checker should suggest changing an existing header guard to the string returned b...
Definition: HeaderGuard.cpp:292
PPCallbacks
clang::tidy::cleanPath
static std::string cleanPath(StringRef Path)
canonicalize a path by removing ./ and ../ components.
Definition: HeaderGuard.cpp:22
Files
llvm::StringMap< const FileEntry * > Files
Definition: HeaderGuard.cpp:272
HeaderGuard.h
PP
Preprocessor * PP
Definition: HeaderGuard.cpp:277
EndIfs
std::map< SourceLocation, SourceLocation > EndIfs
Definition: HeaderGuard.cpp:275