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> Result =
Path;
24 llvm::sys::path::remove_dots(Result,
true);
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);
93 SourceLocation Ifndef =
94 Ifndefs[MacroEntry.first.getIdentifierInfo()].second;
95 SourceLocation Define = MacroEntry.first.getLocation();
96 SourceLocation EndIf =
97 EndIfs[Ifndefs[MacroEntry.first.getIdentifierInfo()].first];
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] ==
'\\')
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)),
209 void checkGuardlessHeaders() {
213 for (
const auto &FE : Files) {
214 StringRef FileName = FE.getKey();
218 SourceManager &SM = PP->getSourceManager();
219 FileID FID = SM.translateFile(FE.getValue());
220 SourceLocation StartLoc = SM.getLocForStartOfFile(FID);
221 if (StartLoc.isInvalid())
225 std::string CPPVarUnder = CPPVar +
'_';
231 bool SeenMacro =
false;
232 for (
const auto &MacroEntry : Macros) {
233 StringRef
Name = MacroEntry.first.getIdentifierInfo()->getName();
234 SourceLocation DefineLoc = MacroEntry.first.getLocation();
235 if ((Name == CPPVar || Name == CPPVarUnder) &&
236 SM.isWrittenInSameFile(StartLoc, DefineLoc)) {
237 Check->
diag(DefineLoc,
"code/includes outside of area guarded by " 238 "header guard; consider moving it");
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),
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.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.