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