clang-tools 23.0.0git
HeaderGuard.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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"
11#include "clang/Frontend/CompilerInstance.h"
12#include "clang/Lex/PPCallbacks.h"
13#include "clang/Lex/Preprocessor.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 const SourceManager &SM = PP->getSourceManager();
37 if (Reason == EnterFile && FileType == SrcMgr::C_User) {
38 if (OptionalFileEntryRef FE =
39 SM.getFileEntryRefForID(SM.getFileID(Loc))) {
40 const 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()] = {Loc,
53 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 const 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 const 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 const auto &Locs = Ifndefs[MacroEntry.first.getIdentifierInfo()];
92 const SourceLocation Ifndef = Locs.second;
93 const SourceLocation Define = MacroEntry.first.getLocation();
94 const SourceLocation EndIf = EndIfs[Locs.first];
95
96 // If the macro Name is not equal to what we can compute, correct it in
97 // the #ifndef and #define.
98 const StringRef CurHeaderGuard =
99 MacroEntry.first.getIdentifierInfo()->getName();
100 std::vector<FixItHint> FixIts;
101 const std::string NewGuard = checkHeaderGuardDefinition(
102 Ifndef, Define, EndIf, FileName, CurHeaderGuard, FixIts);
103
104 // Now look at the #endif. We want a comment with the header guard. Fix it
105 // at the slightest deviation.
106 checkEndifComment(FileName, EndIf, NewGuard, FixIts);
107
108 // Bundle all fix-its into one warning. The message depends on whether we
109 // changed the header guard or not.
110 if (!FixIts.empty()) {
111 if (CurHeaderGuard != NewGuard) {
112 Check->diag(Ifndef, "header guard does not follow preferred style")
113 << FixIts;
114 } else {
115 Check->diag(EndIf, "#endif for a header guard should reference the "
116 "guard macro in a comment")
117 << FixIts;
118 }
119 }
120 }
121
122 // Emit warnings for headers that are missing guards.
123 checkGuardlessHeaders();
124 clearAllState();
125 }
126
127 bool wouldFixEndifComment(StringRef FileName, SourceLocation EndIf,
128 StringRef HeaderGuard,
129 size_t *EndIfLenPtr = nullptr) {
130 if (!EndIf.isValid())
131 return false;
132 const char *EndIfData = PP->getSourceManager().getCharacterData(EndIf);
133 const size_t EndIfLen = std::strcspn(EndIfData, "\r\n");
134 if (EndIfLenPtr)
135 *EndIfLenPtr = EndIfLen;
136
137 StringRef EndIfStr(EndIfData, EndIfLen);
138 EndIfStr = EndIfStr.substr(EndIfStr.find_first_not_of("#endif \t"));
139
140 // Give up if there's an escaped newline.
141 const size_t FindEscapedNewline = EndIfStr.find_last_not_of(' ');
142 if (FindEscapedNewline != StringRef::npos &&
143 EndIfStr[FindEscapedNewline] == '\\')
144 return false;
145
146 const bool IsLineComment =
147 EndIfStr.consume_front("//") ||
148 (EndIfStr.consume_front("/*") && EndIfStr.consume_back("*/"));
149 if (!IsLineComment)
150 return Check->shouldSuggestEndifComment(FileName);
151
152 return EndIfStr.trim() != HeaderGuard;
153 }
154
155 /// Look for header guards that don't match the preferred style. Emit
156 /// fix-its and return the suggested header guard (or the original if no
157 /// change was made.
158 std::string checkHeaderGuardDefinition(SourceLocation Ifndef,
159 SourceLocation Define,
160 SourceLocation EndIf,
161 StringRef FileName,
162 StringRef CurHeaderGuard,
163 std::vector<FixItHint> &FixIts) {
164 std::string CPPVar = Check->getHeaderGuard(FileName, CurHeaderGuard);
165 CPPVar = Check->sanitizeHeaderGuard(CPPVar);
166 const std::string CPPVarUnder = CPPVar + '_';
167
168 // Allow a trailing underscore if and only if we don't have to change the
169 // endif comment too.
170 if (Ifndef.isValid() && CurHeaderGuard != CPPVar &&
171 (CurHeaderGuard != CPPVarUnder ||
172 wouldFixEndifComment(FileName, EndIf, CurHeaderGuard))) {
173 FixIts.push_back(FixItHint::CreateReplacement(
174 CharSourceRange::getTokenRange(
175 Ifndef, Ifndef.getLocWithOffset(CurHeaderGuard.size())),
176 CPPVar));
177 FixIts.push_back(FixItHint::CreateReplacement(
178 CharSourceRange::getTokenRange(
179 Define, Define.getLocWithOffset(CurHeaderGuard.size())),
180 CPPVar));
181 return CPPVar;
182 }
183 return std::string(CurHeaderGuard);
184 }
185
186 /// Checks the comment after the #endif of a header guard and fixes it
187 /// if it doesn't match \c HeaderGuard.
188 void checkEndifComment(StringRef FileName, SourceLocation EndIf,
189 StringRef HeaderGuard,
190 std::vector<FixItHint> &FixIts) {
191 size_t EndIfLen = 0;
192 if (wouldFixEndifComment(FileName, EndIf, HeaderGuard, &EndIfLen)) {
193 FixIts.push_back(FixItHint::CreateReplacement(
194 CharSourceRange::getCharRange(EndIf,
195 EndIf.getLocWithOffset(EndIfLen)),
196 Check->formatEndIf(HeaderGuard)));
197 }
198 }
199
200 /// Looks for files that were visited but didn't have a header guard.
201 /// Emits a warning with fixits suggesting adding one.
202 void checkGuardlessHeaders() {
203 // Look for header files that didn't have a header guard. Emit a warning and
204 // fix-its to add the guard.
205 // TODO: Insert the guard after top comments.
206 for (const auto &FE : Files) {
207 const StringRef FileName = FE.getKey();
208 if (!Check->shouldSuggestToAddHeaderGuard(FileName))
209 continue;
210
211 const SourceManager &SM = PP->getSourceManager();
212 const FileID FID = SM.translateFile(FE.getValue());
213 const SourceLocation StartLoc = SM.getLocForStartOfFile(FID);
214 if (StartLoc.isInvalid())
215 continue;
216
217 std::string CPPVar = Check->getHeaderGuard(FileName);
218 CPPVar = Check->sanitizeHeaderGuard(CPPVar);
219 const std::string CPPVarUnder =
220 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 const StringRef Name = MacroEntry.first.getIdentifierInfo()->getName();
229 const 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,
245 (Twine("#ifndef ") + CPPVar + "\n#define " + CPPVar + "\n\n")
246 .str())
247 << FixItHint::CreateInsertion(
248 SM.getLocForEndOfFile(FID),
249 Check->shouldSuggestEndifComment(FileName)
250 ? "\n#" + Check->formatEndIf(CPPVar) + "\n"
251 : "\n#endif\n");
252 }
253 }
254
255private:
256 void clearAllState() {
257 Macros.clear();
258 Files.clear();
259 Ifndefs.clear();
260 EndIfs.clear();
261 }
262
263 std::vector<std::pair<Token, const MacroInfo *>> Macros;
264 llvm::StringMap<const FileEntry *> Files;
265 llvm::DenseMap<const IdentifierInfo *,
266 std::pair<SourceLocation, SourceLocation>>
267 Ifndefs;
268 llvm::DenseMap<SourceLocation, SourceLocation> EndIfs;
269
270 Preprocessor *PP;
271 HeaderGuardCheck *Check;
272};
273} // namespace
274
275void HeaderGuardCheck::registerPPCallbacks(const SourceManager &SM,
276 Preprocessor *PP,
277 Preprocessor *ModuleExpanderPP) {
278 PP->addPPCallbacks(std::make_unique<HeaderGuardPPCallbacks>(PP, this));
279}
280
281std::string HeaderGuardCheck::sanitizeHeaderGuard(StringRef Guard) {
282 // Only reserved identifiers are allowed to start with an '_'.
283 return Guard.ltrim('_').str();
284}
285
286bool HeaderGuardCheck::shouldSuggestEndifComment(StringRef FileName) {
287 return utils::isFileExtension(FileName, getHeaderFileExtensions());
288}
289
290bool HeaderGuardCheck::shouldFixHeaderGuard(StringRef FileName) { return true; }
291
292bool HeaderGuardCheck::shouldSuggestToAddHeaderGuard(StringRef FileName) {
293 return utils::isFileExtension(FileName, getHeaderFileExtensions());
294}
295
296std::string HeaderGuardCheck::formatEndIf(StringRef HeaderGuard) {
297 return "endif // " + HeaderGuard.str();
298}
299
300} // namespace clang::tidy::utils
std::string sanitizeHeaderGuard(StringRef Guard)
Ensure that the provided header guard is a non-reserved identifier.
virtual std::string formatEndIf(StringRef HeaderGuard)
Returns true if the check should suggest inserting a trailing comment / on the #endif of the header g...
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) override
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.