clang-tools 10.0.0

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