1 //===--- HeaderGuard.cpp - clang-tidy -------------------------------------===//
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
7 //===----------------------------------------------------------------------===//
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"
16 namespace clang::tidy::utils
{
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(Result
, true);
22 return std::string(Result
);
26 class HeaderGuardPPCallbacks
: public PPCallbacks
{
28 HeaderGuardPPCallbacks(Preprocessor
*PP
, HeaderGuardCheck
*Check
)
29 : PP(PP
), Check(Check
) {}
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
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
;
46 void Ifndef(SourceLocation Loc
, const Token
&MacroNameTok
,
47 const MacroDefinition
&MD
) override
{
51 // Record #ifndefs that succeeded. We also need the Location of the Name.
52 Ifndefs
[MacroNameTok
.getIdentifierInfo()] =
53 std::make_pair(Loc
, MacroNameTok
.getLocation());
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
60 Macros
.emplace_back(MacroNameTok
, MD
->getMacroInfo());
63 void Endif(SourceLocation Loc
, SourceLocation IfLoc
) override
{
64 // Record all #endif and the corresponding #ifs (including #ifndefs).
68 void EndOfMainFile() override
{
69 // Now that we have all this information from the preprocessor, use it!
70 SourceManager
&SM
= PP
->getSourceManager();
72 for (const auto &MacroEntry
: Macros
) {
73 const MacroInfo
*MI
= MacroEntry
.second
;
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())
81 OptionalFileEntryRef FE
=
82 SM
.getFileEntryRefForID(SM
.getFileID(MI
->getDefinitionLoc()));
83 std::string FileName
= cleanPath(FE
->getName());
84 Files
.erase(FileName
);
86 // See if we should check and fix this header guard.
87 if (!Check
->shouldFixHeaderGuard(FileName
))
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
];
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
);
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
);
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")
116 Check
->diag(EndIf
, "#endif for a header guard should reference the "
117 "guard macro in a comment")
123 // Emit warnings for headers that are missing guards.
124 checkGuardlessHeaders();
128 bool wouldFixEndifComment(StringRef FileName
, SourceLocation EndIf
,
129 StringRef HeaderGuard
,
130 size_t *EndIfLenPtr
= nullptr) {
131 if (!EndIf
.isValid())
133 const char *EndIfData
= PP
->getSourceManager().getCharacterData(EndIf
);
134 size_t EndIfLen
= std::strcspn(EndIfData
, "\r\n");
136 *EndIfLenPtr
= EndIfLen
;
138 StringRef
EndIfStr(EndIfData
, EndIfLen
);
139 EndIfStr
= EndIfStr
.substr(EndIfStr
.find_first_not_of("#endif \t"));
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
] == '\\')
148 EndIfStr
.consume_front("//") ||
149 (EndIfStr
.consume_front("/*") && EndIfStr
.consume_back("*/"));
151 return Check
->shouldSuggestEndifComment(FileName
);
153 return EndIfStr
.trim() != HeaderGuard
;
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
159 std::string
checkHeaderGuardDefinition(SourceLocation Ifndef
,
160 SourceLocation Define
,
161 SourceLocation EndIf
,
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
+ '_';
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())),
178 FixIts
.push_back(FixItHint::CreateReplacement(
179 CharSourceRange::getTokenRange(
180 Define
, Define
.getLocWithOffset(CurHeaderGuard
.size())),
184 return std::string(CurHeaderGuard
);
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
) {
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
)));
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
))
212 SourceManager
&SM
= PP
->getSourceManager();
213 FileID FID
= SM
.translateFile(FE
.getValue());
214 SourceLocation StartLoc
= SM
.getLocForStartOfFile(FID
);
215 if (StartLoc
.isInvalid())
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
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");
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"
254 void clearAllState() {
261 std::vector
<std::pair
<Token
, const MacroInfo
*>> Macros
;
262 llvm::StringMap
<const FileEntry
*> Files
;
263 std::map
<const IdentifierInfo
*, std::pair
<SourceLocation
, SourceLocation
>>
265 std::map
<SourceLocation
, SourceLocation
> EndIfs
;
268 HeaderGuardCheck
*Check
;
272 void HeaderGuardCheck::registerPPCallbacks(const SourceManager
&SM
,
274 Preprocessor
*ModuleExpanderPP
) {
275 PP
->addPPCallbacks(std::make_unique
<HeaderGuardPPCallbacks
>(PP
, this));
278 std::string
HeaderGuardCheck::sanitizeHeaderGuard(StringRef Guard
) {
279 // Only reserved identifiers are allowed to start with an '_'.
280 return Guard
.ltrim('_').str();
283 bool HeaderGuardCheck::shouldSuggestEndifComment(StringRef FileName
) {
284 return utils::isFileExtension(FileName
, HeaderFileExtensions
);
287 bool HeaderGuardCheck::shouldFixHeaderGuard(StringRef FileName
) { return true; }
289 bool HeaderGuardCheck::shouldSuggestToAddHeaderGuard(StringRef FileName
) {
290 return utils::isFileExtension(FileName
, HeaderFileExtensions
);
293 std::string
HeaderGuardCheck::formatEndIf(StringRef HeaderGuard
) {
294 return "endif // " + HeaderGuard
.str();
296 } // namespace clang::tidy::utils