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"
20 SmallString<256> Result =
Path;
21 llvm::sys::path::remove_dots(Result,
true);
22 return std::string(Result.str());
26class 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 {
36 SourceManager &SM = PP->getSourceManager();
37 if (Reason == EnterFile && FileType == SrcMgr::C_User) {
38 if (
const FileEntry *FE = SM.getFileEntryForID(SM.getFileID(
Loc))) {
45 void Ifndef(SourceLocation
Loc,
const Token &MacroNameTok,
46 const MacroDefinition &MD)
override {
51 Ifndefs[MacroNameTok.getIdentifierInfo()] =
52 std::make_pair(
Loc, MacroNameTok.getLocation());
55 void MacroDefined(
const Token &MacroNameTok,
56 const MacroDirective *MD)
override {
59 Macros.emplace_back(MacroNameTok, MD->getMacroInfo());
62 void Endif(SourceLocation
Loc, SourceLocation IfLoc)
override {
67 void EndOfMainFile()
override {
69 SourceManager &SM = PP->getSourceManager();
71 for (
const auto &MacroEntry : Macros) {
72 const MacroInfo *MI = MacroEntry.second;
77 if (!MI->isUsedForHeaderGuard())
81 SM.getFileEntryForID(SM.getFileID(MI->getDefinitionLoc()));
86 if (!Check->shouldFixHeaderGuard(
FileName))
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];
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);
106 checkEndifComment(
FileName, EndIf, NewGuard, FixIts);
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")
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"));
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;
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 +
'_';
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);
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)));
202 void checkGuardlessHeaders() {
206 for (
const auto &FE : Files) {
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 +
'_';
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;
272 Options.
store(Opts,
"HeaderFileExtensions", RawStringHeaderFileExtensions);
277 Preprocessor *ModuleExpanderPP) {
278 PP->addPPCallbacks(std::make_unique<HeaderGuardPPCallbacks>(PP,
this));
283 return Guard.drop_while([](
char C) {
return C ==
'_'; }).str();
297 return "endif // " + HeaderGuard.str();
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.
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.
llvm::StringMap< ClangTidyValue > OptionMap