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"
23 SmallString<256> NewPath;
24 for (
auto I = llvm::sys::path::begin(Path), E = llvm::sys::path::end(Path);
30 NewPath.resize(llvm::sys::path::parent_path(NewPath).size());
32 if (!NewPath.empty() && !NewPath.endswith(
"/"))
43 explicit HeaderGuardPPCallbacks(Preprocessor *
PP, HeaderGuardCheck *
Check)
44 : PP(PP), Check(Check) {}
46 void FileChanged(SourceLocation
Loc, FileChangeReason Reason,
47 SrcMgr::CharacteristicKind FileType,
48 FileID PrevFID)
override {
51 SourceManager &
SM =
PP->getSourceManager();
52 if (Reason == EnterFile && FileType == SrcMgr::C_User) {
53 if (
const FileEntry *FE = SM.getFileEntryForID(SM.getFileID(Loc))) {
54 std::string FileName =
cleanPath(FE->getName());
60 void Ifndef(SourceLocation Loc,
const Token &MacroNameTok,
61 const MacroDefinition &MD)
override {
66 Ifndefs[MacroNameTok.getIdentifierInfo()] =
67 std::make_pair(Loc, MacroNameTok.getLocation());
70 void MacroDefined(
const Token &MacroNameTok,
71 const MacroDirective *MD)
override {
74 Macros.emplace_back(MacroNameTok, MD->getMacroInfo());
77 void Endif(SourceLocation Loc, SourceLocation IfLoc)
override {
82 void EndOfMainFile()
override {
84 SourceManager &SM =
PP->getSourceManager();
86 for (
const auto &MacroEntry :
Macros) {
87 const MacroInfo *MI = MacroEntry.second;
92 if (!MI->isUsedForHeaderGuard())
96 SM.getFileEntryForID(SM.getFileID(MI->getDefinitionLoc()));
97 std::string FileName =
cleanPath(FE->getName());
98 Files.erase(FileName);
101 if (!
Check->shouldFixHeaderGuard(FileName))
105 SourceLocation Ifndef =
106 Ifndefs[MacroEntry.first.getIdentifierInfo()].second;
107 SourceLocation Define = MacroEntry.first.getLocation();
108 SourceLocation EndIf =
113 StringRef CurHeaderGuard =
114 MacroEntry.first.getIdentifierInfo()->getName();
115 std::vector<FixItHint> FixIts;
116 std::string NewGuard = checkHeaderGuardDefinition(
117 Ifndef, Define, EndIf, FileName, CurHeaderGuard, FixIts);
121 checkEndifComment(FileName, EndIf, NewGuard, FixIts);
125 if (!FixIts.empty()) {
126 if (CurHeaderGuard != NewGuard) {
127 Check->diag(Ifndef,
"header guard does not follow preferred style")
130 Check->diag(EndIf,
"#endif for a header guard should reference the "
131 "guard macro in a comment")
138 checkGuardlessHeaders();
147 bool wouldFixEndifComment(StringRef FileName, SourceLocation EndIf,
148 StringRef HeaderGuard,
149 size_t *EndIfLenPtr =
nullptr) {
150 if (!EndIf.isValid())
152 const char *EndIfData =
PP->getSourceManager().getCharacterData(EndIf);
153 size_t EndIfLen = std::strcspn(EndIfData,
"\r\n");
155 *EndIfLenPtr = EndIfLen;
157 StringRef EndIfStr(EndIfData, EndIfLen);
158 EndIfStr = EndIfStr.substr(EndIfStr.find_first_not_of(
"#endif \t"));
161 size_t FindEscapedNewline = EndIfStr.find_last_not_of(
' ');
162 if (FindEscapedNewline != StringRef::npos &&
163 EndIfStr[FindEscapedNewline] ==
'\\')
166 if (!
Check->shouldSuggestEndifComment(FileName) &&
167 !(EndIfStr.startswith(
"//") ||
168 (EndIfStr.startswith(
"/*") && EndIfStr.endswith(
"*/"))))
171 return (EndIfStr !=
"// " + HeaderGuard.str()) &&
172 (EndIfStr !=
"/* " + HeaderGuard.str() +
" */");
178 std::string checkHeaderGuardDefinition(SourceLocation Ifndef,
179 SourceLocation Define,
180 SourceLocation EndIf,
182 StringRef CurHeaderGuard,
183 std::vector<FixItHint> &FixIts) {
184 std::string CPPVar =
Check->getHeaderGuard(FileName, CurHeaderGuard);
185 std::string CPPVarUnder = CPPVar +
'_';
189 if (Ifndef.isValid() && CurHeaderGuard != CPPVar &&
190 (CurHeaderGuard != CPPVarUnder ||
191 wouldFixEndifComment(FileName, EndIf, CurHeaderGuard))) {
192 FixIts.push_back(FixItHint::CreateReplacement(
193 CharSourceRange::getTokenRange(
194 Ifndef, Ifndef.getLocWithOffset(CurHeaderGuard.size())),
196 FixIts.push_back(FixItHint::CreateReplacement(
197 CharSourceRange::getTokenRange(
198 Define, Define.getLocWithOffset(CurHeaderGuard.size())),
202 return CurHeaderGuard;
207 void checkEndifComment(StringRef FileName, SourceLocation EndIf,
208 StringRef HeaderGuard,
209 std::vector<FixItHint> &FixIts) {
211 if (wouldFixEndifComment(FileName, EndIf, HeaderGuard, &EndIfLen)) {
212 FixIts.push_back(FixItHint::CreateReplacement(
213 CharSourceRange::getCharRange(EndIf,
214 EndIf.getLocWithOffset(EndIfLen)),
215 Check->formatEndIf(HeaderGuard)));
221 void checkGuardlessHeaders() {
225 for (
const auto &FE :
Files) {
226 StringRef FileName = FE.getKey();
227 if (!
Check->shouldSuggestToAddHeaderGuard(FileName))
230 SourceManager &SM =
PP->getSourceManager();
231 FileID FID = SM.translateFile(FE.getValue());
232 SourceLocation StartLoc = SM.getLocForStartOfFile(FID);
233 if (StartLoc.isInvalid())
236 std::string CPPVar =
Check->getHeaderGuard(FileName);
237 std::string CPPVarUnder = CPPVar +
'_';
242 bool SeenMacro =
false;
243 for (
const auto &MacroEntry : Macros) {
244 StringRef
Name = MacroEntry.first.getIdentifierInfo()->getName();
245 SourceLocation DefineLoc = MacroEntry.first.getLocation();
246 if ((Name == CPPVar || Name == CPPVarUnder) &&
247 SM.isWrittenInSameFile(StartLoc, DefineLoc)) {
250 "Header guard after code/includes. Consider moving it up.");
259 Check->diag(StartLoc,
"header is missing header guard")
260 << FixItHint::CreateInsertion(
261 StartLoc,
"#ifndef " + CPPVar +
"\n#define " + CPPVar +
"\n\n")
262 << FixItHint::CreateInsertion(
263 SM.getLocForEndOfFile(FID),
264 Check->shouldSuggestEndifComment(FileName)
265 ?
"\n#" +
Check->formatEndIf(CPPVar) +
"\n"
271 std::vector<std::pair<Token, const MacroInfo *>>
Macros;
272 llvm::StringMap<const FileEntry *>
Files;
273 std::map<const IdentifierInfo *, std::pair<SourceLocation, SourceLocation>>
275 std::map<SourceLocation, SourceLocation>
EndIfs;
283 Compiler.getPreprocessor().addPPCallbacks(
284 llvm::make_unique<HeaderGuardPPCallbacks>(&Compiler.getPreprocessor(),
289 return FileName.endswith(
".h");
295 return FileName.endswith(
".h");
299 return "endif // " + HeaderGuard.str();
SourceLocation Loc
'#' location in the include directive
std::vector< HeaderHandle > Path
static std::string cleanPath(StringRef Path)
canonicalize a path by removing ./ and ../ components.