10#include "clang/Frontend/CompilerInstance.h"
11#include "clang/Lex/PPCallbacks.h"
12#include "clang/Lex/Preprocessor.h"
13#include "llvm/Support/Path.h"
19 SmallString<256> Result = Path;
20 llvm::sys::path::remove_dots(Result,
true);
21 return std::string(Result);
25class HeaderGuardPPCallbacks :
public PPCallbacks {
27 HeaderGuardPPCallbacks(Preprocessor *PP, HeaderGuardCheck *Check)
28 : PP(PP), Check(Check) {}
30 void FileChanged(SourceLocation Loc, FileChangeReason Reason,
31 SrcMgr::CharacteristicKind FileType,
32 FileID PrevFID)
override {
35 SourceManager &SM = PP->getSourceManager();
36 if (Reason == EnterFile && FileType == SrcMgr::C_User) {
37 if (OptionalFileEntryRef FE =
38 SM.getFileEntryRefForID(SM.getFileID(Loc))) {
39 std::string FileName =
cleanPath(FE->getName());
40 Files[FileName] = *FE;
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())
80 OptionalFileEntryRef FE =
81 SM.getFileEntryRefForID(SM.getFileID(MI->getDefinitionLoc()));
82 std::string FileName =
cleanPath(FE->getName());
83 Files.erase(FileName);
86 if (!Check->shouldFixHeaderGuard(FileName))
90 const auto &Locs = Ifndefs[MacroEntry.first.getIdentifierInfo()];
91 SourceLocation Ifndef = Locs.second;
92 SourceLocation Define = MacroEntry.first.getLocation();
93 SourceLocation EndIf = EndIfs[Locs.first];
97 StringRef CurHeaderGuard =
98 MacroEntry.first.getIdentifierInfo()->getName();
99 std::vector<FixItHint> FixIts;
100 std::string NewGuard = checkHeaderGuardDefinition(
101 Ifndef, Define, EndIf, FileName, CurHeaderGuard, FixIts);
105 checkEndifComment(FileName, EndIf, NewGuard, FixIts);
109 if (!FixIts.empty()) {
110 if (CurHeaderGuard != NewGuard) {
111 Check->diag(Ifndef,
"header guard does not follow preferred style")
114 Check->diag(EndIf,
"#endif for a header guard should reference the "
115 "guard macro in a comment")
122 checkGuardlessHeaders();
126 bool wouldFixEndifComment(StringRef FileName, SourceLocation EndIf,
127 StringRef HeaderGuard,
128 size_t *EndIfLenPtr =
nullptr) {
129 if (!EndIf.isValid())
131 const char *EndIfData = PP->getSourceManager().getCharacterData(EndIf);
132 size_t EndIfLen = std::strcspn(EndIfData,
"\r\n");
134 *EndIfLenPtr = EndIfLen;
136 StringRef EndIfStr(EndIfData, EndIfLen);
137 EndIfStr = EndIfStr.substr(EndIfStr.find_first_not_of(
"#endif \t"));
140 size_t FindEscapedNewline = EndIfStr.find_last_not_of(
' ');
141 if (FindEscapedNewline != StringRef::npos &&
142 EndIfStr[FindEscapedNewline] ==
'\\')
146 EndIfStr.consume_front(
"//") ||
147 (EndIfStr.consume_front(
"/*") && EndIfStr.consume_back(
"*/"));
149 return Check->shouldSuggestEndifComment(FileName);
151 return EndIfStr.trim() != HeaderGuard;
157 std::string checkHeaderGuardDefinition(SourceLocation Ifndef,
158 SourceLocation Define,
159 SourceLocation EndIf,
161 StringRef CurHeaderGuard,
162 std::vector<FixItHint> &FixIts) {
163 std::string CPPVar = Check->getHeaderGuard(FileName, CurHeaderGuard);
164 CPPVar = Check->sanitizeHeaderGuard(CPPVar);
165 std::string CPPVarUnder = CPPVar +
'_';
169 if (Ifndef.isValid() && CurHeaderGuard != CPPVar &&
170 (CurHeaderGuard != CPPVarUnder ||
171 wouldFixEndifComment(FileName, EndIf, CurHeaderGuard))) {
172 FixIts.push_back(FixItHint::CreateReplacement(
173 CharSourceRange::getTokenRange(
174 Ifndef, Ifndef.getLocWithOffset(CurHeaderGuard.size())),
176 FixIts.push_back(FixItHint::CreateReplacement(
177 CharSourceRange::getTokenRange(
178 Define, Define.getLocWithOffset(CurHeaderGuard.size())),
182 return std::string(CurHeaderGuard);
187 void checkEndifComment(StringRef FileName, SourceLocation EndIf,
188 StringRef HeaderGuard,
189 std::vector<FixItHint> &FixIts) {
191 if (wouldFixEndifComment(FileName, EndIf, HeaderGuard, &EndIfLen)) {
192 FixIts.push_back(FixItHint::CreateReplacement(
193 CharSourceRange::getCharRange(EndIf,
194 EndIf.getLocWithOffset(EndIfLen)),
195 Check->formatEndIf(HeaderGuard)));
201 void checkGuardlessHeaders() {
205 for (
const auto &FE : Files) {
206 StringRef FileName = FE.getKey();
207 if (!Check->shouldSuggestToAddHeaderGuard(FileName))
210 SourceManager &SM = PP->getSourceManager();
211 FileID FID = SM.translateFile(FE.getValue());
212 SourceLocation StartLoc = SM.getLocForStartOfFile(FID);
213 if (StartLoc.isInvalid())
216 std::string CPPVar = Check->getHeaderGuard(FileName);
217 CPPVar = Check->sanitizeHeaderGuard(CPPVar);
218 std::string CPPVarUnder = CPPVar +
'_';
224 bool SeenMacro =
false;
225 for (
const auto &MacroEntry : Macros) {
226 StringRef Name = MacroEntry.first.getIdentifierInfo()->getName();
227 SourceLocation DefineLoc = MacroEntry.first.getLocation();
228 if ((Name == CPPVar || Name == CPPVarUnder) &&
229 SM.isWrittenInSameFile(StartLoc, DefineLoc)) {
230 Check->diag(DefineLoc,
"code/includes outside of area guarded by "
231 "header guard; consider moving it");
240 Check->diag(StartLoc,
"header is missing header guard")
241 << FixItHint::CreateInsertion(
243 (Twine(
"#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;
274 Preprocessor *ModuleExpanderPP) {
275 PP->addPPCallbacks(std::make_unique<HeaderGuardPPCallbacks>(PP,
this));
280 return Guard.ltrim(
'_').str();
283bool HeaderGuardCheck::shouldSuggestEndifComment(StringRef FileName) {
287bool HeaderGuardCheck::shouldFixHeaderGuard(StringRef FileName) {
return true; }
289bool HeaderGuardCheck::shouldSuggestToAddHeaderGuard(StringRef FileName) {
294 return "endif // " + HeaderGuard.str();
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.