1//===--- MacroToEnumCheck.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 "MacroToEnumCheck.h"
10#include "IntegralLiteralExpressionMatcher.h"
11
12#include "clang/AST/ASTContext.h"
13#include "clang/ASTMatchers/ASTMatchFinder.h"
14#include "clang/Lex/Preprocessor.h"
15#include "llvm/ADT/STLExtras.h"
16#include <cassert>
17#include <cctype>
18#include <string>
19
20namespace clang::tidy::modernize {
21
22static bool hasOnlyComments(SourceLocation Loc, const LangOptions &Options,
23 StringRef Text) {
24 // Use a lexer to look for tokens; if we find something other than a single
25 // hash, then there were intervening tokens between macro definitions.
26 std::string Buffer{Text};
27 Lexer Lex(Loc, Options, Buffer.c_str(), Buffer.c_str(),
28 Buffer.c_str() + Buffer.size());
29 Token Tok;
30 bool SeenHash = false;
31 while (!Lex.LexFromRawLexer(Result&: Tok)) {
32 if (Tok.getKind() == tok::hash && !SeenHash) {
33 SeenHash = true;
34 continue;
35 }
36 return false;
37 }
38
39 // Everything in between was whitespace, so now just look for two blank lines,
40 // consisting of two consecutive EOL sequences, either '\n', '\r' or '\r\n'.
41 enum class WhiteSpace {
42 Nothing,
43 CR,
44 LF,
45 CRLF,
46 CRLFCR,
47 };
48
49 WhiteSpace State = WhiteSpace::Nothing;
50 for (char C : Text) {
51 switch (C) {
52 case '\r':
53 if (State == WhiteSpace::CR)
54 return false;
55
56 State = State == WhiteSpace::CRLF ? WhiteSpace::CRLFCR : WhiteSpace::CR;
57 break;
58
59 case '\n':
60 if (State == WhiteSpace::LF || State == WhiteSpace::CRLFCR)
61 return false;
62
63 State = State == WhiteSpace::CR ? WhiteSpace::CRLF : WhiteSpace::LF;
64 break;
65
66 default:
67 State = WhiteSpace::Nothing;
68 break;
69 }
70 }
71
72 return true;
73}
74
75static StringRef getTokenName(const Token &Tok) {
76 return Tok.is(K: tok::raw_identifier) ? Tok.getRawIdentifier()
77 : Tok.getIdentifierInfo()->getName();
78}
79
80namespace {
81
82struct EnumMacro {
83 EnumMacro(Token Name, const MacroDirective *Directive)
84 : Name(Name), Directive(Directive) {}
85
86 Token Name;
87 const MacroDirective *Directive;
88};
89
90using MacroList = SmallVector<EnumMacro>;
91
92enum class IncludeGuard { None, FileChanged, IfGuard, DefineGuard };
93
94struct FileState {
95 FileState() = default;
96
97 int ConditionScopes = 0;
98 unsigned int LastLine = 0;
99 IncludeGuard GuardScanner = IncludeGuard::None;
100 SourceLocation LastMacroLocation;
101};
102
103} // namespace
104
105class MacroToEnumCallbacks : public PPCallbacks {
106public:
107 MacroToEnumCallbacks(MacroToEnumCheck *Check, const LangOptions &LangOptions,
108 const SourceManager &SM)
109 : Check(Check), LangOpts(LangOptions), SM(SM) {}
110
111 void FileChanged(SourceLocation Loc, FileChangeReason Reason,
112 SrcMgr::CharacteristicKind FileType,
113 FileID PrevFID) override;
114
115 void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok,
116 StringRef FileName, bool IsAngled,
117 CharSourceRange FilenameRange,
118 OptionalFileEntryRef File, StringRef SearchPath,
119 StringRef RelativePath, const Module *SuggestedModule,
120 bool ModuleImported,
121 SrcMgr::CharacteristicKind FileType) override {
122 clearCurrentEnum(Loc: HashLoc);
123 }
124
125 // Keep track of macro definitions that look like enums.
126 void MacroDefined(const Token &MacroNameTok,
127 const MacroDirective *MD) override;
128
129 // Undefining an enum-like macro results in the enum set being dropped.
130 void MacroUndefined(const Token &MacroNameTok, const MacroDefinition &MD,
131 const MacroDirective *Undef) override;
132
133 // Conditional compilation clears any adjacent enum-like macros.
134 // Macros used in conditional expressions clear any adjacent enum-like
135 // macros.
136 // Include guards are either
137 // #if !defined(GUARD)
138 // or
139 // #ifndef GUARD
140 void If(SourceLocation Loc, SourceRange ConditionRange,
141 ConditionValueKind ConditionValue) override {
142 conditionStart(Loc);
143 checkCondition(ConditionRange);
144 }
145 void Ifndef(SourceLocation Loc, const Token &MacroNameTok,
146 const MacroDefinition &MD) override {
147 conditionStart(Loc);
148 checkName(MacroNameTok);
149 }
150 void Ifdef(SourceLocation Loc, const Token &MacroNameTok,
151 const MacroDefinition &MD) override {
152 conditionStart(Loc);
153 checkName(MacroNameTok);
154 }
155 void Elif(SourceLocation Loc, SourceRange ConditionRange,
156 ConditionValueKind ConditionValue, SourceLocation IfLoc) override {
157 checkCondition(ConditionRange);
158 }
159 void Elifdef(SourceLocation Loc, const Token &MacroNameTok,
160 const MacroDefinition &MD) override {
161 checkName(MacroNameTok);
162 }
163 void Elifdef(SourceLocation Loc, SourceRange ConditionRange,
164 SourceLocation IfLoc) override {
165 PPCallbacks::Elifdef(Loc, ConditionRange, IfLoc);
166 }
167 void Elifndef(SourceLocation Loc, const Token &MacroNameTok,
168 const MacroDefinition &MD) override {
169 checkName(MacroNameTok);
170 }
171 void Elifndef(SourceLocation Loc, SourceRange ConditionRange,
172 SourceLocation IfLoc) override {
173 PPCallbacks::Elifndef(Loc, ConditionRange, IfLoc);
174 }
175 void Endif(SourceLocation Loc, SourceLocation IfLoc) override;
176 void PragmaDirective(SourceLocation Loc,
177 PragmaIntroducerKind Introducer) override;
178
179 // After we've seen everything, issue warnings and fix-its.
180 void EndOfMainFile() override;
181
182 void invalidateRange(SourceRange Range);
183
184private:
185 void newEnum() {
186 if (Enums.empty() || !Enums.back().empty())
187 Enums.emplace_back();
188 }
189 bool insideConditional() const {
190 return (CurrentFile->GuardScanner == IncludeGuard::DefineGuard &&
191 CurrentFile->ConditionScopes > 1) ||
192 (CurrentFile->GuardScanner != IncludeGuard::DefineGuard &&
193 CurrentFile->ConditionScopes > 0);
194 }
195 bool isConsecutiveMacro(const MacroDirective *MD) const;
196 void rememberLastMacroLocation(const MacroDirective *MD) {
197 CurrentFile->LastLine = SM.getSpellingLineNumber(Loc: MD->getLocation());
198 CurrentFile->LastMacroLocation = Lexer::getLocForEndOfToken(
199 Loc: MD->getMacroInfo()->getDefinitionEndLoc(), Offset: 0, SM, LangOpts);
200 }
201 void clearLastMacroLocation() {
202 CurrentFile->LastLine = 0;
203 CurrentFile->LastMacroLocation = SourceLocation{};
204 }
205 void clearCurrentEnum(SourceLocation Loc);
206 void conditionStart(const SourceLocation &Loc);
207 void checkCondition(SourceRange ConditionRange);
208 void checkName(const Token &MacroNameTok);
209 void rememberExpressionName(const Token &Tok);
210 void rememberExpressionTokens(ArrayRef<Token> MacroTokens);
211 void invalidateExpressionNames();
212 void issueDiagnostics();
213 void warnMacroEnum(const EnumMacro &Macro) const;
214 void fixEnumMacro(const MacroList &MacroList) const;
215 bool isInitializer(ArrayRef<Token> MacroTokens);
216
217 MacroToEnumCheck *Check;
218 const LangOptions &LangOpts;
219 const SourceManager &SM;
220 SmallVector<MacroList> Enums;
221 SmallVector<FileState> Files;
222 std::vector<std::string> ExpressionNames;
223 FileState *CurrentFile = nullptr;
224};
225
226bool MacroToEnumCallbacks::isConsecutiveMacro(const MacroDirective *MD) const {
227 if (CurrentFile->LastMacroLocation.isInvalid())
228 return false;
229
230 SourceLocation Loc = MD->getLocation();
231 if (CurrentFile->LastLine + 1 == SM.getSpellingLineNumber(Loc))
232 return true;
233
234 SourceLocation Define =
235 SM.translateLineCol(FID: SM.getFileID(SpellingLoc: Loc), Line: SM.getSpellingLineNumber(Loc), Col: 1);
236 CharSourceRange BetweenMacros{
237 SourceRange{CurrentFile->LastMacroLocation, Define}, true};
238 CharSourceRange CharRange =
239 Lexer::makeFileCharRange(Range: BetweenMacros, SM, LangOpts);
240 StringRef BetweenText = Lexer::getSourceText(Range: CharRange, SM, LangOpts);
241 return hasOnlyComments(Loc: Define, Options: LangOpts, Text: BetweenText);
242}
243
244void MacroToEnumCallbacks::clearCurrentEnum(SourceLocation Loc) {
245 // Only drop the most recent Enum set if the directive immediately follows.
246 if (!Enums.empty() && !Enums.back().empty() &&
247 SM.getSpellingLineNumber(Loc) == CurrentFile->LastLine + 1)
248 Enums.pop_back();
249
250 clearLastMacroLocation();
251}
252
253void MacroToEnumCallbacks::conditionStart(const SourceLocation &Loc) {
254 ++CurrentFile->ConditionScopes;
255 clearCurrentEnum(Loc);
256 if (CurrentFile->GuardScanner == IncludeGuard::FileChanged)
257 CurrentFile->GuardScanner = IncludeGuard::IfGuard;
258}
259
260void MacroToEnumCallbacks::checkCondition(SourceRange Range) {
261 CharSourceRange CharRange = Lexer::makeFileCharRange(
262 Range: CharSourceRange::getTokenRange(R: Range), SM, LangOpts);
263 std::string Text = Lexer::getSourceText(Range: CharRange, SM, LangOpts).str();
264 Lexer Lex(CharRange.getBegin(), LangOpts, Text.data(), Text.data(),
265 Text.data() + Text.size());
266 Token Tok;
267 bool End = false;
268 while (!End) {
269 End = Lex.LexFromRawLexer(Result&: Tok);
270 if (Tok.is(K: tok::raw_identifier) &&
271 Tok.getRawIdentifier().str() != "defined")
272 checkName(MacroNameTok: Tok);
273 }
274}
275
276void MacroToEnumCallbacks::checkName(const Token &MacroNameTok) {
277 rememberExpressionName(Tok: MacroNameTok);
278
279 StringRef Id = getTokenName(Tok: MacroNameTok);
280 llvm::erase_if(C&: Enums, P: [&Id](const MacroList &MacroList) {
281 return llvm::any_of(Range: MacroList, P: [&Id](const EnumMacro &Macro) {
282 return getTokenName(Tok: Macro.Name) == Id;
283 });
284 });
285}
286
287void MacroToEnumCallbacks::rememberExpressionName(const Token &Tok) {
288 std::string Id = getTokenName(Tok).str();
289 auto Pos = llvm::lower_bound(Range&: ExpressionNames, Value&: Id);
290 if (Pos == ExpressionNames.end() || *Pos != Id) {
291 ExpressionNames.insert(position: Pos, x: Id);
292 }
293}
294
295void MacroToEnumCallbacks::rememberExpressionTokens(
296 ArrayRef<Token> MacroTokens) {
297 for (Token Tok : MacroTokens) {
298 if (Tok.isAnyIdentifier())
299 rememberExpressionName(Tok);
300 }
301}
302
303void MacroToEnumCallbacks::FileChanged(SourceLocation Loc,
304 FileChangeReason Reason,
305 SrcMgr::CharacteristicKind FileType,
306 FileID PrevFID) {
307 newEnum();
308 if (Reason == EnterFile) {
309 Files.emplace_back();
310 if (!SM.isInMainFile(Loc))
311 Files.back().GuardScanner = IncludeGuard::FileChanged;
312 } else if (Reason == ExitFile) {
313 assert(CurrentFile->ConditionScopes == 0);
314 Files.pop_back();
315 }
316 CurrentFile = &Files.back();
317}
318
319bool MacroToEnumCallbacks::isInitializer(ArrayRef<Token> MacroTokens) {
320 IntegralLiteralExpressionMatcher Matcher(MacroTokens, LangOpts.C99 == 0);
321 bool Matched = Matcher.match();
322 bool IsC = !LangOpts.CPlusPlus;
323 if (IsC && (Matcher.largestLiteralSize() != LiteralSize::Int &&
324 Matcher.largestLiteralSize() != LiteralSize::UnsignedInt))
325 return false;
326
327 return Matched;
328}
329
330// Any defined but rejected macro is scanned for identifiers that
331// are to be excluded as enums.
332void MacroToEnumCallbacks::MacroDefined(const Token &MacroNameTok,
333 const MacroDirective *MD) {
334 // Include guards are never candidates for becoming an enum.
335 if (CurrentFile->GuardScanner == IncludeGuard::IfGuard) {
336 CurrentFile->GuardScanner = IncludeGuard::DefineGuard;
337 return;
338 }
339
340 if (insideConditional())
341 return;
342
343 if (SM.getFilename(SpellingLoc: MD->getLocation()).empty())
344 return;
345
346 const MacroInfo *Info = MD->getMacroInfo();
347 ArrayRef<Token> MacroTokens = Info->tokens();
348 if (Info->isBuiltinMacro() || MacroTokens.empty())
349 return;
350 if (Info->isFunctionLike()) {
351 rememberExpressionTokens(MacroTokens);
352 return;
353 }
354
355 if (!isInitializer(MacroTokens))
356 return;
357
358 if (!isConsecutiveMacro(MD))
359 newEnum();
360 Enums.back().emplace_back(Args: MacroNameTok, Args&: MD);
361 rememberLastMacroLocation(MD);
362}
363
364// Any macro that is undefined removes all adjacent macros from consideration as
365// an enum and starts a new enum scan.
366void MacroToEnumCallbacks::MacroUndefined(const Token &MacroNameTok,
367 const MacroDefinition &MD,
368 const MacroDirective *Undef) {
369 rememberExpressionName(Tok: MacroNameTok);
370
371 auto MatchesToken = [&MacroNameTok](const EnumMacro &Macro) {
372 return getTokenName(Tok: Macro.Name) == getTokenName(Tok: MacroNameTok);
373 };
374
375 auto *It = llvm::find_if(Range&: Enums, P: [MatchesToken](const MacroList &MacroList) {
376 return llvm::any_of(Range: MacroList, P: MatchesToken);
377 });
378 if (It != Enums.end())
379 Enums.erase(CI: It);
380
381 clearLastMacroLocation();
382 CurrentFile->GuardScanner = IncludeGuard::None;
383}
384
385void MacroToEnumCallbacks::Endif(SourceLocation Loc, SourceLocation IfLoc) {
386 // The if directive for the include guard isn't counted in the
387 // ConditionScopes.
388 if (CurrentFile->ConditionScopes == 0 &&
389 CurrentFile->GuardScanner == IncludeGuard::DefineGuard)
390 return;
391
392 // We don't need to clear the current enum because the start of the
393 // conditional block already took care of that.
394 assert(CurrentFile->ConditionScopes > 0);
395 --CurrentFile->ConditionScopes;
396}
397
398namespace {
399
400template <size_t N>
401bool textEquals(const char (&Needle)[N], const char *HayStack) {
402 return StringRef{HayStack, N - 1} == Needle;
403}
404
405template <size_t N> size_t len(const char (&)[N]) { return N - 1; }
406
407} // namespace
408
409void MacroToEnumCallbacks::PragmaDirective(SourceLocation Loc,
410 PragmaIntroducerKind Introducer) {
411 if (CurrentFile->GuardScanner != IncludeGuard::FileChanged)
412 return;
413
414 bool Invalid = false;
415 const char *Text = SM.getCharacterData(
416 SL: Lexer::getLocForEndOfToken(Loc, Offset: 0, SM, LangOpts), Invalid: &Invalid);
417 if (Invalid)
418 return;
419
420 while (*Text && std::isspace(*Text))
421 ++Text;
422
423 if (textEquals(Needle: "pragma", HayStack: Text))
424 return;
425
426 Text += len("pragma");
427 while (*Text && std::isspace(*Text))
428 ++Text;
429
430 if (textEquals(Needle: "once", HayStack: Text))
431 CurrentFile->GuardScanner = IncludeGuard::IfGuard;
432}
433
434void MacroToEnumCallbacks::invalidateExpressionNames() {
435 for (const std::string &Id : ExpressionNames) {
436 llvm::erase_if(C&: Enums, P: [Id](const MacroList &MacroList) {
437 return llvm::any_of(Range: MacroList, P: [&Id](const EnumMacro &Macro) {
438 return getTokenName(Tok: Macro.Name) == Id;
439 });
440 });
441 }
442}
443
444void MacroToEnumCallbacks::EndOfMainFile() {
445 invalidateExpressionNames();
446 issueDiagnostics();
447}
448
449void MacroToEnumCallbacks::invalidateRange(SourceRange Range) {
450 llvm::erase_if(C&: Enums, P: [Range](const MacroList &MacroList) {
451 return llvm::any_of(Range: MacroList, P: [Range](const EnumMacro &Macro) {
452 return Macro.Directive->getLocation() >= Range.getBegin() &&
453 Macro.Directive->getLocation() <= Range.getEnd();
454 });
455 });
456}
457
458void MacroToEnumCallbacks::issueDiagnostics() {
459 for (const MacroList &MacroList : Enums) {
460 if (MacroList.empty())
461 continue;
462
463 for (const EnumMacro &Macro : MacroList)
464 warnMacroEnum(Macro);
465
466 fixEnumMacro(MacroList);
467 }
468}
469
470void MacroToEnumCallbacks::warnMacroEnum(const EnumMacro &Macro) const {
471 Check->diag(Loc: Macro.Directive->getLocation(),
472 Description: "macro '%0' defines an integral constant; prefer an enum instead")
473 << getTokenName(Tok: Macro.Name);
474}
475
476void MacroToEnumCallbacks::fixEnumMacro(const MacroList &MacroList) const {
477 SourceLocation Begin =
478 MacroList.front().Directive->getMacroInfo()->getDefinitionLoc();
479 Begin = SM.translateLineCol(FID: SM.getFileID(SpellingLoc: Begin),
480 Line: SM.getSpellingLineNumber(Loc: Begin), Col: 1);
481 DiagnosticBuilder Diagnostic =
482 Check->diag(Loc: Begin, Description: "replace macro with enum")
483 << FixItHint::CreateInsertion(InsertionLoc: Begin, Code: "enum {\n");
484
485 for (size_t I = 0U; I < MacroList.size(); ++I) {
486 const EnumMacro &Macro = MacroList[I];
487 SourceLocation DefineEnd =
488 Macro.Directive->getMacroInfo()->getDefinitionLoc();
489 SourceLocation DefineBegin = SM.translateLineCol(
490 FID: SM.getFileID(SpellingLoc: DefineEnd), Line: SM.getSpellingLineNumber(Loc: DefineEnd), Col: 1);
491 CharSourceRange DefineRange;
492 DefineRange.setBegin(DefineBegin);
493 DefineRange.setEnd(DefineEnd);
494 Diagnostic << FixItHint::CreateRemoval(RemoveRange: DefineRange);
495
496 SourceLocation NameEnd = Lexer::getLocForEndOfToken(
497 Loc: Macro.Directive->getMacroInfo()->getDefinitionLoc(), Offset: 0, SM, LangOpts);
498 Diagnostic << FixItHint::CreateInsertion(InsertionLoc: NameEnd, Code: " =");
499
500 SourceLocation ValueEnd = Lexer::getLocForEndOfToken(
501 Loc: Macro.Directive->getMacroInfo()->getDefinitionEndLoc(), Offset: 0, SM,
502 LangOpts);
503 if (I < MacroList.size() - 1)
504 Diagnostic << FixItHint::CreateInsertion(InsertionLoc: ValueEnd, Code: ",");
505 }
506
507 SourceLocation End = Lexer::getLocForEndOfToken(
508 Loc: MacroList.back().Directive->getMacroInfo()->getDefinitionEndLoc(), Offset: 0, SM,
509 LangOpts);
510 End = SM.translateLineCol(FID: SM.getFileID(SpellingLoc: End),
511 Line: SM.getSpellingLineNumber(Loc: End) + 1, Col: 1);
512 Diagnostic << FixItHint::CreateInsertion(InsertionLoc: End, Code: "};\n");
513}
514
515void MacroToEnumCheck::registerPPCallbacks(const SourceManager &SM,
516 Preprocessor *PP,
517 Preprocessor *ModuleExpanderPP) {
518 auto Callback =
519 std::make_unique<MacroToEnumCallbacks>(args: this, args: getLangOpts(), args: SM);
520 PPCallback = Callback.get();
521 PP->addPPCallbacks(C: std::move(Callback));
522}
523
524void MacroToEnumCheck::registerMatchers(ast_matchers::MatchFinder *Finder) {
525 using namespace ast_matchers;
526 auto TopLevelDecl = hasParent(translationUnitDecl());
527 Finder->addMatcher(NodeMatch: decl(TopLevelDecl).bind(ID: "top"), Action: this);
528}
529
530static bool isValid(SourceRange Range) {
531 return Range.getBegin().isValid() && Range.getEnd().isValid();
532}
533
534static bool empty(SourceRange Range) {
535 return Range.getBegin() == Range.getEnd();
536}
537
538void MacroToEnumCheck::check(
539 const ast_matchers::MatchFinder::MatchResult &Result) {
540 auto *TLDecl = Result.Nodes.getNodeAs<Decl>(ID: "top");
541 if (TLDecl == nullptr)
542 return;
543
544 SourceRange Range = TLDecl->getSourceRange();
545 if (auto *TemplateFn = Result.Nodes.getNodeAs<FunctionTemplateDecl>(ID: "top")) {
546 if (TemplateFn->isThisDeclarationADefinition() && TemplateFn->hasBody())
547 Range = SourceRange{TemplateFn->getBeginLoc(),
548 TemplateFn->getUnderlyingDecl()->getBodyRBrace()};
549 }
550
551 if (isValid(Range) && !empty(Range))
552 PPCallback->invalidateRange(Range);
553}
554
555} // namespace clang::tidy::modernize
556

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of clang-tools-extra/clang-tidy/modernize/MacroToEnumCheck.cpp