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