clang-tools 17.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.str());
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 (const FileEntry *FE = SM.getFileEntryForID(SM.getFileID(Loc))) {
39 std::string FileName = cleanPath(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(Loc, 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(MacroNameTok, 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 const FileEntry *FE =
81 SM.getFileEntryForID(SM.getFileID(MI->getDefinitionLoc()));
82 std::string FileName = cleanPath(FE->getName());
83 Files.erase(FileName);
84
85 // See if we should check and fix this header guard.
86 if (!Check->shouldFixHeaderGuard(FileName))
87 continue;
88
89 // Look up Locations for this guard.
90 SourceLocation Ifndef =
91 Ifndefs[MacroEntry.first.getIdentifierInfo()].second;
92 SourceLocation Define = MacroEntry.first.getLocation();
93 SourceLocation EndIf =
94 EndIfs[Ifndefs[MacroEntry.first.getIdentifierInfo()].first];
95
96 // If the macro Name is not equal to what we can compute, correct it in
97 // the #ifndef and #define.
98 StringRef CurHeaderGuard =
99 MacroEntry.first.getIdentifierInfo()->getName();
100 std::vector<FixItHint> FixIts;
101 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 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 size_t FindEscapedNewline = EndIfStr.find_last_not_of(' ');
142 if (FindEscapedNewline != StringRef::npos &&
143 EndIfStr[FindEscapedNewline] == '\\')
144 return false;
145
146 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 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;
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 StringRef FileName = FE.getKey();
208 if (!Check->shouldSuggestToAddHeaderGuard(FileName))
209 continue;
210
211 SourceManager &SM = PP->getSourceManager();
212 FileID FID = SM.translateFile(FE.getValue());
213 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 std::string CPPVarUnder = CPPVar + '_'; // Allow a trailing underscore.
220 // If there's a macro with a name that follows the header guard convention
221 // but was not recognized by the preprocessor as a header guard there must
222 // be code outside of the guarded area. Emit a plain warning without
223 // fix-its.
224 // FIXME: Can we move it into the right spot?
225 bool SeenMacro = false;
226 for (const auto &MacroEntry : Macros) {
227 StringRef Name = MacroEntry.first.getIdentifierInfo()->getName();
228 SourceLocation DefineLoc = MacroEntry.first.getLocation();
229 if ((Name == CPPVar || Name == CPPVarUnder) &&
230 SM.isWrittenInSameFile(StartLoc, DefineLoc)) {
231 Check->diag(DefineLoc, "code/includes outside of area guarded by "
232 "header guard; consider moving it");
233 SeenMacro = true;
234 break;
235 }
236 }
237
238 if (SeenMacro)
239 continue;
240
241 Check->diag(StartLoc, "header is missing header guard")
242 << FixItHint::CreateInsertion(
243 StartLoc, "#ifndef " + CPPVar + "\n#define " + CPPVar + "\n\n")
244 << FixItHint::CreateInsertion(
245 SM.getLocForEndOfFile(FID),
246 Check->shouldSuggestEndifComment(FileName)
247 ? "\n#" + Check->formatEndIf(CPPVar) + "\n"
248 : "\n#endif\n");
249 }
250 }
251
252private:
253 void clearAllState() {
254 Macros.clear();
255 Files.clear();
256 Ifndefs.clear();
257 EndIfs.clear();
258 }
259
260 std::vector<std::pair<Token, const MacroInfo *>> Macros;
261 llvm::StringMap<const FileEntry *> Files;
262 std::map<const IdentifierInfo *, std::pair<SourceLocation, SourceLocation>>
263 Ifndefs;
264 std::map<SourceLocation, SourceLocation> EndIfs;
265
266 Preprocessor *PP;
267 HeaderGuardCheck *Check;
268};
269} // namespace
270
272 Options.store(Opts, "HeaderFileExtensions", RawStringHeaderFileExtensions);
273}
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.drop_while([](char C) { return C == '_'; }).str();
284}
285
287 return utils::isFileExtension(FileName, HeaderFileExtensions);
288}
289
290bool HeaderGuardCheck::shouldFixHeaderGuard(StringRef FileName) { return true; }
291
293 return utils::isFileExtension(FileName, HeaderFileExtensions);
294}
295
296std::string HeaderGuardCheck::formatEndIf(StringRef HeaderGuard) {
297 return "endif // " + HeaderGuard.str();
298}
299} // namespace clang::tidy::utils
const Criteria C
StringRef FileName
SourceLocation Loc
Token Name
std::vector< HeaderHandle > Path
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
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 ...
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
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
llvm::StringMap< ClangTidyValue > OptionMap