11 #include "clang/Frontend/CompilerInstance.h"
12 #include "clang/Lex/PPCallbacks.h"
13 #include "clang/Lex/Preprocessor.h"
14 #include "clang/Tooling/Tooling.h"
15 #include "llvm/Support/Path.h"
24 llvm::sys::path::remove_dots(Result,
true);
31 HeaderGuardPPCallbacks(Preprocessor *
PP, HeaderGuardCheck *
Check)
32 : PP(PP), Check(Check) {}
34 void FileChanged(SourceLocation
Loc, FileChangeReason Reason,
35 SrcMgr::CharacteristicKind FileType,
36 FileID PrevFID)
override {
39 SourceManager &
SM =
PP->getSourceManager();
40 if (Reason == EnterFile && FileType == SrcMgr::C_User) {
41 if (
const FileEntry *FE = SM.getFileEntryForID(SM.getFileID(Loc))) {
42 std::string FileName =
cleanPath(FE->getName());
48 void Ifndef(SourceLocation Loc,
const Token &MacroNameTok,
49 const MacroDefinition &MD)
override {
54 Ifndefs[MacroNameTok.getIdentifierInfo()] =
55 std::make_pair(Loc, MacroNameTok.getLocation());
58 void MacroDefined(
const Token &MacroNameTok,
59 const MacroDirective *MD)
override {
62 Macros.emplace_back(MacroNameTok, MD->getMacroInfo());
65 void Endif(SourceLocation Loc, SourceLocation IfLoc)
override {
70 void EndOfMainFile()
override {
72 SourceManager &SM =
PP->getSourceManager();
74 for (
const auto &MacroEntry :
Macros) {
75 const MacroInfo *MI = MacroEntry.second;
80 if (!MI->isUsedForHeaderGuard())
84 SM.getFileEntryForID(SM.getFileID(MI->getDefinitionLoc()));
85 std::string FileName =
cleanPath(FE->getName());
86 Files.erase(FileName);
89 if (!
Check->shouldFixHeaderGuard(FileName))
93 SourceLocation Ifndef =
94 Ifndefs[MacroEntry.first.getIdentifierInfo()].second;
95 SourceLocation Define = MacroEntry.first.getLocation();
96 SourceLocation EndIf =
101 StringRef CurHeaderGuard =
102 MacroEntry.first.getIdentifierInfo()->getName();
103 std::vector<FixItHint> FixIts;
104 std::string NewGuard = checkHeaderGuardDefinition(
105 Ifndef, Define, EndIf, FileName, CurHeaderGuard, FixIts);
109 checkEndifComment(FileName, EndIf, NewGuard, FixIts);
113 if (!FixIts.empty()) {
114 if (CurHeaderGuard != NewGuard) {
115 Check->diag(Ifndef,
"header guard does not follow preferred style")
118 Check->diag(EndIf,
"#endif for a header guard should reference the "
119 "guard macro in a comment")
126 checkGuardlessHeaders();
135 bool wouldFixEndifComment(StringRef FileName, SourceLocation EndIf,
136 StringRef HeaderGuard,
137 size_t *EndIfLenPtr =
nullptr) {
138 if (!EndIf.isValid())
140 const char *EndIfData =
PP->getSourceManager().getCharacterData(EndIf);
141 size_t EndIfLen = std::strcspn(EndIfData,
"\r\n");
143 *EndIfLenPtr = EndIfLen;
145 StringRef EndIfStr(EndIfData, EndIfLen);
146 EndIfStr = EndIfStr.substr(EndIfStr.find_first_not_of(
"#endif \t"));
149 size_t FindEscapedNewline = EndIfStr.find_last_not_of(
' ');
150 if (FindEscapedNewline != StringRef::npos &&
151 EndIfStr[FindEscapedNewline] ==
'\\')
154 if (!
Check->shouldSuggestEndifComment(FileName) &&
155 !(EndIfStr.startswith(
"//") ||
156 (EndIfStr.startswith(
"/*") && EndIfStr.endswith(
"*/"))))
159 return (EndIfStr !=
"// " + HeaderGuard.str()) &&
160 (EndIfStr !=
"/* " + HeaderGuard.str() +
" */");
166 std::string checkHeaderGuardDefinition(SourceLocation Ifndef,
167 SourceLocation Define,
168 SourceLocation EndIf,
170 StringRef CurHeaderGuard,
171 std::vector<FixItHint> &FixIts) {
172 std::string CPPVar =
Check->getHeaderGuard(FileName, CurHeaderGuard);
173 std::string CPPVarUnder = CPPVar +
'_';
177 if (Ifndef.isValid() && CurHeaderGuard != CPPVar &&
178 (CurHeaderGuard != CPPVarUnder ||
179 wouldFixEndifComment(FileName, EndIf, CurHeaderGuard))) {
180 FixIts.push_back(FixItHint::CreateReplacement(
181 CharSourceRange::getTokenRange(
182 Ifndef, Ifndef.getLocWithOffset(CurHeaderGuard.size())),
184 FixIts.push_back(FixItHint::CreateReplacement(
185 CharSourceRange::getTokenRange(
186 Define, Define.getLocWithOffset(CurHeaderGuard.size())),
190 return CurHeaderGuard;
195 void checkEndifComment(StringRef FileName, SourceLocation EndIf,
196 StringRef HeaderGuard,
197 std::vector<FixItHint> &FixIts) {
199 if (wouldFixEndifComment(FileName, EndIf, HeaderGuard, &EndIfLen)) {
200 FixIts.push_back(FixItHint::CreateReplacement(
201 CharSourceRange::getCharRange(EndIf,
202 EndIf.getLocWithOffset(EndIfLen)),
203 Check->formatEndIf(HeaderGuard)));
209 void checkGuardlessHeaders() {
213 for (
const auto &FE :
Files) {
214 StringRef FileName = FE.getKey();
215 if (!
Check->shouldSuggestToAddHeaderGuard(FileName))
218 SourceManager &SM =
PP->getSourceManager();
219 FileID FID = SM.translateFile(FE.getValue());
220 SourceLocation StartLoc = SM.getLocForStartOfFile(FID);
221 if (StartLoc.isInvalid())
224 std::string CPPVar =
Check->getHeaderGuard(FileName);
225 std::string CPPVarUnder = CPPVar +
'_';
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)) {
238 "Header guard after code/includes. Consider moving it up.");
247 Check->diag(StartLoc,
"header is missing header guard")
248 << FixItHint::CreateInsertion(
249 StartLoc,
"#ifndef " + CPPVar +
"\n#define " + CPPVar +
"\n\n")
250 << FixItHint::CreateInsertion(
251 SM.getLocForEndOfFile(FID),
252 Check->shouldSuggestEndifComment(FileName)
253 ?
"\n#" +
Check->formatEndIf(CPPVar) +
"\n"
259 std::vector<std::pair<Token, const MacroInfo *>>
Macros;
260 llvm::StringMap<const FileEntry *>
Files;
261 std::map<const IdentifierInfo *, std::pair<SourceLocation, SourceLocation>>
263 std::map<SourceLocation, SourceLocation>
EndIfs;
271 Compiler.getPreprocessor().addPPCallbacks(
272 llvm::make_unique<HeaderGuardPPCallbacks>(&Compiler.getPreprocessor(),
287 return "endif // " + HeaderGuard.str();
SourceLocation Loc
'#' location in the include directive
static std::string cleanPath(StringRef Path)
canonicalize a path by removing ./ and ../ components.
std::vector< HeaderHandle > Path
bool isHeaderFileExtension(StringRef FileName, const HeaderFileExtensionsSet &HeaderFileExtensions)
Decides whether a file has a header file extension.