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);
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 (OptionalFileEntryRef FE =
39 SM.getFileEntryRefForID(SM.getFileID(
Loc))) {
46 void Ifndef(SourceLocation
Loc,
const Token &MacroNameTok,
47 const MacroDefinition &MD)
override {
52 Ifndefs[MacroNameTok.getIdentifierInfo()] =
53 std::make_pair(
Loc, MacroNameTok.getLocation());
56 void MacroDefined(
const Token &MacroNameTok,
57 const MacroDirective *MD)
override {
60 Macros.emplace_back(MacroNameTok, MD->getMacroInfo());
63 void Endif(SourceLocation
Loc, SourceLocation IfLoc)
override {
68 void EndOfMainFile()
override {
70 SourceManager &SM = PP->getSourceManager();
72 for (
const auto &MacroEntry : Macros) {
73 const MacroInfo *MI = MacroEntry.second;
78 if (!MI->isUsedForHeaderGuard())
81 OptionalFileEntryRef FE =
82 SM.getFileEntryRefForID(SM.getFileID(MI->getDefinitionLoc()));
87 if (!Check->shouldFixHeaderGuard(
FileName))
91 SourceLocation Ifndef =
92 Ifndefs[MacroEntry.first.getIdentifierInfo()].second;
93 SourceLocation Define = MacroEntry.first.getLocation();
94 SourceLocation EndIf =
95 EndIfs[Ifndefs[MacroEntry.first.getIdentifierInfo()].first];
99 StringRef CurHeaderGuard =
100 MacroEntry.first.getIdentifierInfo()->getName();
101 std::vector<FixItHint> FixIts;
102 std::string NewGuard = checkHeaderGuardDefinition(
103 Ifndef, Define, EndIf,
FileName, CurHeaderGuard, FixIts);
107 checkEndifComment(
FileName, EndIf, NewGuard, FixIts);
111 if (!FixIts.empty()) {
112 if (CurHeaderGuard != NewGuard) {
113 Check->diag(Ifndef,
"header guard does not follow preferred style")
116 Check->diag(EndIf,
"#endif for a header guard should reference the "
117 "guard macro in a comment")
124 checkGuardlessHeaders();
128 bool wouldFixEndifComment(StringRef
FileName, SourceLocation EndIf,
129 StringRef HeaderGuard,
130 size_t *EndIfLenPtr =
nullptr) {
131 if (!EndIf.isValid())
133 const char *EndIfData = PP->getSourceManager().getCharacterData(EndIf);
134 size_t EndIfLen = std::strcspn(EndIfData,
"\r\n");
136 *EndIfLenPtr = EndIfLen;
138 StringRef EndIfStr(EndIfData, EndIfLen);
139 EndIfStr = EndIfStr.substr(EndIfStr.find_first_not_of(
"#endif \t"));
142 size_t FindEscapedNewline = EndIfStr.find_last_not_of(
' ');
143 if (FindEscapedNewline != StringRef::npos &&
144 EndIfStr[FindEscapedNewline] ==
'\\')
148 EndIfStr.consume_front(
"//") ||
149 (EndIfStr.consume_front(
"/*") && EndIfStr.consume_back(
"*/"));
151 return Check->shouldSuggestEndifComment(
FileName);
153 return EndIfStr.trim() != HeaderGuard;
159 std::string checkHeaderGuardDefinition(SourceLocation Ifndef,
160 SourceLocation Define,
161 SourceLocation EndIf,
163 StringRef CurHeaderGuard,
164 std::vector<FixItHint> &FixIts) {
165 std::string CPPVar = Check->getHeaderGuard(
FileName, CurHeaderGuard);
166 CPPVar = Check->sanitizeHeaderGuard(CPPVar);
167 std::string CPPVarUnder = CPPVar +
'_';
171 if (Ifndef.isValid() && CurHeaderGuard != CPPVar &&
172 (CurHeaderGuard != CPPVarUnder ||
173 wouldFixEndifComment(
FileName, EndIf, CurHeaderGuard))) {
174 FixIts.push_back(FixItHint::CreateReplacement(
175 CharSourceRange::getTokenRange(
176 Ifndef, Ifndef.getLocWithOffset(CurHeaderGuard.size())),
178 FixIts.push_back(FixItHint::CreateReplacement(
179 CharSourceRange::getTokenRange(
180 Define, Define.getLocWithOffset(CurHeaderGuard.size())),
184 return std::string(CurHeaderGuard);
189 void checkEndifComment(StringRef
FileName, SourceLocation EndIf,
190 StringRef HeaderGuard,
191 std::vector<FixItHint> &FixIts) {
193 if (wouldFixEndifComment(
FileName, EndIf, HeaderGuard, &EndIfLen)) {
194 FixIts.push_back(FixItHint::CreateReplacement(
195 CharSourceRange::getCharRange(EndIf,
196 EndIf.getLocWithOffset(EndIfLen)),
197 Check->formatEndIf(HeaderGuard)));
203 void checkGuardlessHeaders() {
207 for (
const auto &FE : Files) {
209 if (!Check->shouldSuggestToAddHeaderGuard(
FileName))
212 SourceManager &SM = PP->getSourceManager();
213 FileID FID = SM.translateFile(FE.getValue());
214 SourceLocation StartLoc = SM.getLocForStartOfFile(FID);
215 if (StartLoc.isInvalid())
218 std::string CPPVar = Check->getHeaderGuard(
FileName);
219 CPPVar = Check->sanitizeHeaderGuard(CPPVar);
220 std::string CPPVarUnder = CPPVar +
'_';
226 bool SeenMacro =
false;
227 for (
const auto &MacroEntry : Macros) {
228 StringRef
Name = MacroEntry.first.getIdentifierInfo()->getName();
229 SourceLocation DefineLoc = MacroEntry.first.getLocation();
230 if ((
Name == CPPVar ||
Name == CPPVarUnder) &&
231 SM.isWrittenInSameFile(StartLoc, DefineLoc)) {
232 Check->diag(DefineLoc,
"code/includes outside of area guarded by "
233 "header guard; consider moving it");
242 Check->diag(StartLoc,
"header is missing header guard")
243 << FixItHint::CreateInsertion(
244 StartLoc,
"#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();
294 return "endif // " + HeaderGuard.str();
llvm::SmallString< 256U > Name
std::vector< HeaderHandle > Path
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.