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
.str());
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 (const FileEntry
*FE
= SM
.getFileEntryForID(SM
.getFileID(Loc
))) {
39 std::string FileName
= cleanPath(FE
->getName());
45 void Ifndef(SourceLocation Loc
, const Token
&MacroNameTok
,
46 const MacroDefinition
&MD
) override
{
50 // Record #ifndefs that succeeded. We also need the Location of the Name.
51 Ifndefs
[MacroNameTok
.getIdentifierInfo()] =
52 std::make_pair(Loc
, MacroNameTok
.getLocation());
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
59 Macros
.emplace_back(MacroNameTok
, MD
->getMacroInfo());
62 void Endif(SourceLocation Loc
, SourceLocation IfLoc
) override
{
63 // Record all #endif and the corresponding #ifs (including #ifndefs).
67 void EndOfMainFile() override
{
68 // Now that we have all this information from the preprocessor, use it!
69 SourceManager
&SM
= PP
->getSourceManager();
71 for (const auto &MacroEntry
: Macros
) {
72 const MacroInfo
*MI
= MacroEntry
.second
;
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())
81 SM
.getFileEntryForID(SM
.getFileID(MI
->getDefinitionLoc()));
82 std::string FileName
= cleanPath(FE
->getName());
83 Files
.erase(FileName
);
85 // See if we should check and fix this header guard.
86 if (!Check
->shouldFixHeaderGuard(FileName
))
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
];
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
);
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
);
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")
115 Check
->diag(EndIf
, "#endif for a header guard should reference the "
116 "guard macro in a comment")
122 // Emit warnings for headers that are missing guards.
123 checkGuardlessHeaders();
127 bool wouldFixEndifComment(StringRef FileName
, SourceLocation EndIf
,
128 StringRef HeaderGuard
,
129 size_t *EndIfLenPtr
= nullptr) {
130 if (!EndIf
.isValid())
132 const char *EndIfData
= PP
->getSourceManager().getCharacterData(EndIf
);
133 size_t EndIfLen
= std::strcspn(EndIfData
, "\r\n");
135 *EndIfLenPtr
= EndIfLen
;
137 StringRef
EndIfStr(EndIfData
, EndIfLen
);
138 EndIfStr
= EndIfStr
.substr(EndIfStr
.find_first_not_of("#endif \t"));
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
] == '\\')
147 EndIfStr
.consume_front("//") ||
148 (EndIfStr
.consume_front("/*") && EndIfStr
.consume_back("*/"));
150 return Check
->shouldSuggestEndifComment(FileName
);
152 return EndIfStr
.trim() != HeaderGuard
;
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
158 std::string
checkHeaderGuardDefinition(SourceLocation Ifndef
,
159 SourceLocation Define
,
160 SourceLocation EndIf
,
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
+ '_';
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())),
177 FixIts
.push_back(FixItHint::CreateReplacement(
178 CharSourceRange::getTokenRange(
179 Define
, Define
.getLocWithOffset(CurHeaderGuard
.size())),
183 return std::string(CurHeaderGuard
);
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
) {
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
)));
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
))
211 SourceManager
&SM
= PP
->getSourceManager();
212 FileID FID
= SM
.translateFile(FE
.getValue());
213 SourceLocation StartLoc
= SM
.getLocForStartOfFile(FID
);
214 if (StartLoc
.isInvalid())
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
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");
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"
253 void clearAllState() {
260 std::vector
<std::pair
<Token
, const MacroInfo
*>> Macros
;
261 llvm::StringMap
<const FileEntry
*> Files
;
262 std::map
<const IdentifierInfo
*, std::pair
<SourceLocation
, SourceLocation
>>
264 std::map
<SourceLocation
, SourceLocation
> EndIfs
;
267 HeaderGuardCheck
*Check
;
271 void HeaderGuardCheck::storeOptions(ClangTidyOptions::OptionMap
&Opts
) {
272 Options
.store(Opts
, "HeaderFileExtensions", RawStringHeaderFileExtensions
);
275 void HeaderGuardCheck::registerPPCallbacks(const SourceManager
&SM
,
277 Preprocessor
*ModuleExpanderPP
) {
278 PP
->addPPCallbacks(std::make_unique
<HeaderGuardPPCallbacks
>(PP
, this));
281 std::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();
286 bool HeaderGuardCheck::shouldSuggestEndifComment(StringRef FileName
) {
287 return utils::isFileExtension(FileName
, HeaderFileExtensions
);
290 bool HeaderGuardCheck::shouldFixHeaderGuard(StringRef FileName
) { return true; }
292 bool HeaderGuardCheck::shouldSuggestToAddHeaderGuard(StringRef FileName
) {
293 return utils::isFileExtension(FileName
, HeaderFileExtensions
);
296 std::string
HeaderGuardCheck::formatEndIf(StringRef HeaderGuard
) {
297 return "endif // " + HeaderGuard
.str();
299 } // namespace clang::tidy::utils