clang-tools 19.0.0git
HeaderGuard.cpp
Go to the documentation of this file.
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
16namespace clang::tidy::utils {
17
18/// canonicalize a path by removing ./ and ../ components.
19static std::string cleanPath(StringRef Path) {
20 SmallString<256> Result = Path;
21 llvm::sys::path::remove_dots(Result, true);
22 return std::string(Result);
23}
24
25namespace {
26class HeaderGuardPPCallbacks : public PPCallbacks {
27public:
28 HeaderGuardPPCallbacks(Preprocessor *PP, HeaderGuardCheck *Check)
29 : PP(PP), Check(Check) {}
30
31 void FileChanged(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(SM.getFileID(Loc))) {
40 std::string FileName = cleanPath(FE->getName());
41 Files[FileName] = *FE;
42 }
43 }
44 }
45
46 void Ifndef(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(Loc, MacroNameTok.getLocation());
54 }
55
56 void MacroDefined(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(MacroNameTok, MD->getMacroInfo());
61 }
62
63 void Endif(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(SM.getFileID(MI->getDefinitionLoc()));
83 std::string FileName = cleanPath(FE->getName());
84 Files.erase(FileName);
85
86 // See if we should check and fix this header guard.
87 if (!Check->shouldFixHeaderGuard(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 CurHeaderGuard =
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, 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(Ifndef, "header guard does not follow preferred style")
114 << FixIts;
115 } else {
116 Check->diag(EndIf, "#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 wouldFixEndifComment(StringRef FileName, SourceLocation EndIf,
129 StringRef HeaderGuard,
130 size_t *EndIfLenPtr = nullptr) {
131 if (!EndIf.isValid())
132 return false;
133 const char *EndIfData = PP->getSourceManager().getCharacterData(EndIf);
134 size_t EndIfLen = std::strcspn(EndIfData, "\r\n");
135 if (EndIfLenPtr)
136 *EndIfLenPtr = EndIfLen;
137
138 StringRef EndIfStr(EndIfData, EndIfLen);
139 EndIfStr = EndIfStr.substr(EndIfStr.find_first_not_of("#endif \t"));
140
141 // Give up if there's an escaped newline.
142 size_t FindEscapedNewline = EndIfStr.find_last_not_of(' ');
143 if (FindEscapedNewline != StringRef::npos &&
144 EndIfStr[FindEscapedNewline] == '\\')
145 return false;
146
147 bool IsLineComment =
148 EndIfStr.consume_front("//") ||
149 (EndIfStr.consume_front("/*") && EndIfStr.consume_back("*/"));
150 if (!IsLineComment)
151 return Check->shouldSuggestEndifComment(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 checkHeaderGuardDefinition(SourceLocation Ifndef,
160 SourceLocation Define,
161 SourceLocation EndIf,
162 StringRef FileName,
163 StringRef CurHeaderGuard,
164 std::vector<FixItHint> &FixIts) {
165 std::string CPPVar = Check->getHeaderGuard(FileName, CurHeaderGuard);
166 CPPVar = Check->sanitizeHeaderGuard(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, CurHeaderGuard))) {
174 FixIts.push_back(FixItHint::CreateReplacement(
175 CharSourceRange::getTokenRange(
176 Ifndef, Ifndef.getLocWithOffset(CurHeaderGuard.size())),
177 CPPVar));
178 FixIts.push_back(FixItHint::CreateReplacement(
179 CharSourceRange::getTokenRange(
180 Define, Define.getLocWithOffset(CurHeaderGuard.size())),
181 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 checkEndifComment(StringRef FileName, SourceLocation EndIf,
190 StringRef HeaderGuard,
191 std::vector<FixItHint> &FixIts) {
192 size_t EndIfLen = 0;
193 if (wouldFixEndifComment(FileName, EndIf, HeaderGuard, &EndIfLen)) {
194 FixIts.push_back(FixItHint::CreateReplacement(
195 CharSourceRange::getCharRange(EndIf,
196 EndIf.getLocWithOffset(EndIfLen)),
197 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 checkGuardlessHeaders() {
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))
210 continue;
211
212 SourceManager &SM = PP->getSourceManager();
213 FileID FID = SM.translateFile(FE.getValue());
214 SourceLocation StartLoc = SM.getLocForStartOfFile(FID);
215 if (StartLoc.isInvalid())
216 continue;
217
218 std::string CPPVar = Check->getHeaderGuard(FileName);
219 CPPVar = Check->sanitizeHeaderGuard(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(StartLoc, DefineLoc)) {
232 Check->diag(DefineLoc, "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(StartLoc, "header is missing header guard")
243 << FixItHint::CreateInsertion(
244 StartLoc, "#ifndef " + CPPVar + "\n#define " + CPPVar + "\n\n")
245 << FixItHint::CreateInsertion(
246 SM.getLocForEndOfFile(FID),
247 Check->shouldSuggestEndifComment(FileName)
248 ? "\n#" + Check->formatEndIf(CPPVar) + "\n"
249 : "\n#endif\n");
250 }
251 }
252
253private:
254 void clearAllState() {
255 Macros.clear();
256 Files.clear();
257 Ifndefs.clear();
258 EndIfs.clear();
259 }
260
261 std::vector<std::pair<Token, const MacroInfo *>> Macros;
262 llvm::StringMap<const FileEntry *> Files;
263 std::map<const IdentifierInfo *, std::pair<SourceLocation, SourceLocation>>
264 Ifndefs;
265 std::map<SourceLocation, SourceLocation> EndIfs;
266
267 Preprocessor *PP;
268 HeaderGuardCheck *Check;
269};
270} // namespace
271
272void HeaderGuardCheck::registerPPCallbacks(const SourceManager &SM,
273 Preprocessor *PP,
274 Preprocessor *ModuleExpanderPP) {
275 PP->addPPCallbacks(std::make_unique<HeaderGuardPPCallbacks>(PP, this));
276}
277
278std::string HeaderGuardCheck::sanitizeHeaderGuard(StringRef Guard) {
279 // Only reserved identifiers are allowed to start with an '_'.
280 return Guard.ltrim('_').str();
281}
282
284 return utils::isFileExtension(FileName, HeaderFileExtensions);
285}
286
287bool HeaderGuardCheck::shouldFixHeaderGuard(StringRef FileName) { return true; }
288
290 return utils::isFileExtension(FileName, HeaderFileExtensions);
291}
292
293std::string HeaderGuardCheck::formatEndIf(StringRef HeaderGuard) {
294 return "endif // " + HeaderGuard.str();
295}
296} // namespace clang::tidy::utils
llvm::SmallString< 256U > Name
StringRef FileName
SourceLocation Loc
std::vector< HeaderHandle > Path
std::string sanitizeHeaderGuard(StringRef Guard)
Ensure that the provided header guard is a non-reserved identifier.
virtual bool shouldSuggestToAddHeaderGuard(StringRef Filename)
Returns true if the check should add a header guard to the file if it has none.
virtual bool shouldFixHeaderGuard(StringRef Filename)
Returns true if the check should suggest changing an existing header guard to the string returned by ...
virtual bool shouldSuggestEndifComment(StringRef Filename)
Returns true if the check should suggest inserting a trailing comment on the #endif of the header gua...
virtual std::string formatEndIf(StringRef HeaderGuard)
Returns a replacement for the #endif line with a comment mentioning HeaderGuard.
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) override
Override this to register PPCallbacks in the preprocessor.
bool isFileExtension(StringRef FileName, const FileExtensionsSet &FileExtensions)
Decides whether a file has one of the specified file extensions.
static std::string cleanPath(StringRef Path)
canonicalize a path by removing ./ and ../ components.
Definition: HeaderGuard.cpp:19