1//===--- HeaderGuard.cpp - clang-tidy -------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "HeaderGuard.h"
10#include "clang/Frontend/CompilerInstance.h"
11#include "clang/Lex/PPCallbacks.h"
12#include "clang/Lex/Preprocessor.h"
13#include "llvm/Support/Path.h"
14
15namespace clang::tidy::utils {
16
17/// canonicalize a path by removing ./ and ../ components.
18static std::string cleanPath(StringRef Path) {
19 SmallString<256> Result = Path;
20 llvm::sys::path::remove_dots(path&: Result, remove_dot_dot: true);
21 return std::string(Result);
22}
23
24namespace {
25class HeaderGuardPPCallbacks : public PPCallbacks {
26public:
27 HeaderGuardPPCallbacks(Preprocessor *PP, HeaderGuardCheck *Check)
28 : PP(PP), Check(Check) {}
29
30 void FileChanged(SourceLocation Loc, FileChangeReason Reason,
31 SrcMgr::CharacteristicKind FileType,
32 FileID PrevFID) override {
33 // Record all files we enter. We'll need them to diagnose headers without
34 // guards.
35 SourceManager &SM = PP->getSourceManager();
36 if (Reason == EnterFile && FileType == SrcMgr::C_User) {
37 if (OptionalFileEntryRef FE =
38 SM.getFileEntryRefForID(FID: SM.getFileID(SpellingLoc: Loc))) {
39 std::string FileName = cleanPath(Path: FE->getName());
40 Files[FileName] = *FE;
41 }
42 }
43 }
44
45 void Ifndef(SourceLocation Loc, const Token &MacroNameTok,
46 const MacroDefinition &MD) override {
47 if (MD)
48 return;
49
50 // Record #ifndefs that succeeded. We also need the Location of the Name.
51 Ifndefs[MacroNameTok.getIdentifierInfo()] =
52 std::make_pair(x&: Loc, y: MacroNameTok.getLocation());
53 }
54
55 void MacroDefined(const Token &MacroNameTok,
56 const MacroDirective *MD) override {
57 // Record all defined macros. We store the whole token to get info on the
58 // name later.
59 Macros.emplace_back(args: MacroNameTok, args: MD->getMacroInfo());
60 }
61
62 void Endif(SourceLocation Loc, SourceLocation IfLoc) override {
63 // Record all #endif and the corresponding #ifs (including #ifndefs).
64 EndIfs[IfLoc] = Loc;
65 }
66
67 void EndOfMainFile() override {
68 // Now that we have all this information from the preprocessor, use it!
69 SourceManager &SM = PP->getSourceManager();
70
71 for (const auto &MacroEntry : Macros) {
72 const MacroInfo *MI = MacroEntry.second;
73
74 // We use clang's header guard detection. This has the advantage of also
75 // emitting a warning for cases where a pseudo header guard is found but
76 // preceded by something blocking the header guard optimization.
77 if (!MI->isUsedForHeaderGuard())
78 continue;
79
80 OptionalFileEntryRef FE =
81 SM.getFileEntryRefForID(FID: SM.getFileID(SpellingLoc: MI->getDefinitionLoc()));
82 std::string FileName = cleanPath(Path: FE->getName());
83 Files.erase(Key: FileName);
84
85 // See if we should check and fix this header guard.
86 if (!Check->shouldFixHeaderGuard(Filename: FileName))
87 continue;
88
89 // Look up Locations for this guard.
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];
94
95 // If the macro Name is not equal to what we can compute, correct it in
96 // the #ifndef and #define.
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);
102
103 // Now look at the #endif. We want a comment with the header guard. Fix it
104 // at the slightest deviation.
105 checkEndifComment(FileName, EndIf, HeaderGuard: NewGuard, FixIts);
106
107 // Bundle all fix-its into one warning. The message depends on whether we
108 // changed the header guard or not.
109 if (!FixIts.empty()) {
110 if (CurHeaderGuard != NewGuard) {
111 Check->diag(Loc: Ifndef, Description: "header guard does not follow preferred style")
112 << FixIts;
113 } else {
114 Check->diag(Loc: EndIf, Description: "#endif for a header guard should reference the "
115 "guard macro in a comment")
116 << FixIts;
117 }
118 }
119 }
120
121 // Emit warnings for headers that are missing guards.
122 checkGuardlessHeaders();
123 clearAllState();
124 }
125
126 bool wouldFixEndifComment(StringRef FileName, SourceLocation EndIf,
127 StringRef HeaderGuard,
128 size_t *EndIfLenPtr = nullptr) {
129 if (!EndIf.isValid())
130 return false;
131 const char *EndIfData = PP->getSourceManager().getCharacterData(SL: EndIf);
132 size_t EndIfLen = std::strcspn(s: EndIfData, reject: "\r\n");
133 if (EndIfLenPtr)
134 *EndIfLenPtr = EndIfLen;
135
136 StringRef EndIfStr(EndIfData, EndIfLen);
137 EndIfStr = EndIfStr.substr(Start: EndIfStr.find_first_not_of(Chars: "#endif \t"));
138
139 // Give up if there's an escaped newline.
140 size_t FindEscapedNewline = EndIfStr.find_last_not_of(C: ' ');
141 if (FindEscapedNewline != StringRef::npos &&
142 EndIfStr[FindEscapedNewline] == '\\')
143 return false;
144
145 bool IsLineComment =
146 EndIfStr.consume_front(Prefix: "//") ||
147 (EndIfStr.consume_front(Prefix: "/*") && EndIfStr.consume_back(Suffix: "*/"));
148 if (!IsLineComment)
149 return Check->shouldSuggestEndifComment(Filename: FileName);
150
151 return EndIfStr.trim() != HeaderGuard;
152 }
153
154 /// Look for header guards that don't match the preferred style. Emit
155 /// fix-its and return the suggested header guard (or the original if no
156 /// change was made.
157 std::string checkHeaderGuardDefinition(SourceLocation Ifndef,
158 SourceLocation Define,
159 SourceLocation EndIf,
160 StringRef FileName,
161 StringRef CurHeaderGuard,
162 std::vector<FixItHint> &FixIts) {
163 std::string CPPVar = Check->getHeaderGuard(Filename: FileName, OldGuard: CurHeaderGuard);
164 CPPVar = Check->sanitizeHeaderGuard(Guard: CPPVar);
165 std::string CPPVarUnder = CPPVar + '_';
166
167 // Allow a trailing underscore if and only if we don't have to change the
168 // endif comment too.
169 if (Ifndef.isValid() && CurHeaderGuard != CPPVar &&
170 (CurHeaderGuard != CPPVarUnder ||
171 wouldFixEndifComment(FileName, EndIf, HeaderGuard: CurHeaderGuard))) {
172 FixIts.push_back(x: FixItHint::CreateReplacement(
173 RemoveRange: CharSourceRange::getTokenRange(
174 B: Ifndef, E: Ifndef.getLocWithOffset(Offset: CurHeaderGuard.size())),
175 Code: CPPVar));
176 FixIts.push_back(x: FixItHint::CreateReplacement(
177 RemoveRange: CharSourceRange::getTokenRange(
178 B: Define, E: Define.getLocWithOffset(Offset: CurHeaderGuard.size())),
179 Code: CPPVar));
180 return CPPVar;
181 }
182 return std::string(CurHeaderGuard);
183 }
184
185 /// Checks the comment after the #endif of a header guard and fixes it
186 /// if it doesn't match \c HeaderGuard.
187 void checkEndifComment(StringRef FileName, SourceLocation EndIf,
188 StringRef HeaderGuard,
189 std::vector<FixItHint> &FixIts) {
190 size_t EndIfLen = 0;
191 if (wouldFixEndifComment(FileName, EndIf, HeaderGuard, EndIfLenPtr: &EndIfLen)) {
192 FixIts.push_back(x: FixItHint::CreateReplacement(
193 RemoveRange: CharSourceRange::getCharRange(B: EndIf,
194 E: EndIf.getLocWithOffset(Offset: EndIfLen)),
195 Code: Check->formatEndIf(HeaderGuard)));
196 }
197 }
198
199 /// Looks for files that were visited but didn't have a header guard.
200 /// Emits a warning with fixits suggesting adding one.
201 void checkGuardlessHeaders() {
202 // Look for header files that didn't have a header guard. Emit a warning and
203 // fix-its to add the guard.
204 // TODO: Insert the guard after top comments.
205 for (const auto &FE : Files) {
206 StringRef FileName = FE.getKey();
207 if (!Check->shouldSuggestToAddHeaderGuard(Filename: FileName))
208 continue;
209
210 SourceManager &SM = PP->getSourceManager();
211 FileID FID = SM.translateFile(SourceFile: FE.getValue());
212 SourceLocation StartLoc = SM.getLocForStartOfFile(FID);
213 if (StartLoc.isInvalid())
214 continue;
215
216 std::string CPPVar = Check->getHeaderGuard(Filename: FileName);
217 CPPVar = Check->sanitizeHeaderGuard(Guard: CPPVar);
218 std::string CPPVarUnder = CPPVar + '_'; // Allow a trailing underscore.
219 // If there's a macro with a name that follows the header guard convention
220 // but was not recognized by the preprocessor as a header guard there must
221 // be code outside of the guarded area. Emit a plain warning without
222 // fix-its.
223 // FIXME: Can we move it into the right spot?
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(Loc1: StartLoc, Loc2: DefineLoc)) {
230 Check->diag(Loc: DefineLoc, Description: "code/includes outside of area guarded by "
231 "header guard; consider moving it");
232 SeenMacro = true;
233 break;
234 }
235 }
236
237 if (SeenMacro)
238 continue;
239
240 Check->diag(Loc: StartLoc, Description: "header is missing header guard")
241 << FixItHint::CreateInsertion(
242 InsertionLoc: StartLoc, Code: "#ifndef " + CPPVar + "\n#define " + CPPVar + "\n\n")
243 << FixItHint::CreateInsertion(
244 InsertionLoc: SM.getLocForEndOfFile(FID),
245 Code: Check->shouldSuggestEndifComment(Filename: FileName)
246 ? "\n#" + Check->formatEndIf(HeaderGuard: CPPVar) + "\n"
247 : "\n#endif\n");
248 }
249 }
250
251private:
252 void clearAllState() {
253 Macros.clear();
254 Files.clear();
255 Ifndefs.clear();
256 EndIfs.clear();
257 }
258
259 std::vector<std::pair<Token, const MacroInfo *>> Macros;
260 llvm::StringMap<const FileEntry *> Files;
261 std::map<const IdentifierInfo *, std::pair<SourceLocation, SourceLocation>>
262 Ifndefs;
263 std::map<SourceLocation, SourceLocation> EndIfs;
264
265 Preprocessor *PP;
266 HeaderGuardCheck *Check;
267};
268} // namespace
269
270void HeaderGuardCheck::registerPPCallbacks(const SourceManager &SM,
271 Preprocessor *PP,
272 Preprocessor *ModuleExpanderPP) {
273 PP->addPPCallbacks(C: std::make_unique<HeaderGuardPPCallbacks>(args&: PP, args: this));
274}
275
276std::string HeaderGuardCheck::sanitizeHeaderGuard(StringRef Guard) {
277 // Only reserved identifiers are allowed to start with an '_'.
278 return Guard.ltrim(Char: '_').str();
279}
280
281bool HeaderGuardCheck::shouldSuggestEndifComment(StringRef FileName) {
282 return utils::isFileExtension(FileName, FileExtensions: HeaderFileExtensions);
283}
284
285bool HeaderGuardCheck::shouldFixHeaderGuard(StringRef FileName) { return true; }
286
287bool HeaderGuardCheck::shouldSuggestToAddHeaderGuard(StringRef FileName) {
288 return utils::isFileExtension(FileName, FileExtensions: HeaderFileExtensions);
289}
290
291std::string HeaderGuardCheck::formatEndIf(StringRef HeaderGuard) {
292 return "endif // " + HeaderGuard.str();
293}
294} // namespace clang::tidy::utils
295

Provided by KDAB

Privacy Policy
Improve your Profiling and Debugging skills
Find out more

source code of clang-tools-extra/clang-tidy/utils/HeaderGuard.cpp