12#include "clang/AST/ASTContext.h"
13#include "clang/ASTMatchers/ASTMatchFinder.h"
14#include "clang/Lex/Preprocessor.h"
15#include "llvm/ADT/STLExtras.h"
26 const std::string Buffer{Text};
27 Lexer Lex(Loc, Options, Buffer.c_str(), Buffer.c_str(),
28 Buffer.c_str() + Buffer.size());
30 bool SeenHash =
false;
31 while (!Lex.LexFromRawLexer(Tok)) {
32 if (Tok.getKind() == tok::hash && !SeenHash) {
41 enum class WhiteSpace {
49 WhiteSpace State = WhiteSpace::Nothing;
50 for (
const char C : Text) {
53 if (State == WhiteSpace::CR)
56 State = State == WhiteSpace::CRLF ? WhiteSpace::CRLFCR : WhiteSpace::CR;
60 if (State == WhiteSpace::LF || State == WhiteSpace::CRLFCR)
63 State = State == WhiteSpace::CR ? WhiteSpace::CRLF : WhiteSpace::LF;
67 State = WhiteSpace::Nothing;
76 return Tok.is(tok::raw_identifier) ? Tok.getRawIdentifier()
77 : Tok.getIdentifierInfo()->getName();
83 EnumMacro(Token Name,
const MacroDirective *Directive)
84 : Name(Name), Directive(Directive) {}
87 const MacroDirective *Directive;
90using MacroList = SmallVector<EnumMacro>;
92enum class IncludeGuard { None, FileChanged, IfGuard, DefineGuard };
95 FileState() =
default;
97 int ConditionScopes = 0;
98 unsigned int LastLine = 0;
99 IncludeGuard GuardScanner = IncludeGuard::None;
100 SourceLocation LastMacroLocation;
108 const SourceManager &SM)
109 : Check(Check), LangOpts(LangOptions), SM(SM) {}
111 void FileChanged(SourceLocation Loc, FileChangeReason Reason,
112 SrcMgr::CharacteristicKind FileType,
113 FileID PrevFID)
override;
116 StringRef FileName,
bool IsAngled,
117 CharSourceRange FilenameRange,
118 OptionalFileEntryRef File, StringRef SearchPath,
119 StringRef RelativePath,
const Module *SuggestedModule,
121 SrcMgr::CharacteristicKind FileType)
override {
122 clearCurrentEnum(HashLoc);
127 const MacroDirective *MD)
override;
130 void MacroUndefined(
const Token &MacroNameTok,
const MacroDefinition &MD,
131 const MacroDirective *Undef)
override;
140 void If(SourceLocation Loc, SourceRange ConditionRange,
141 ConditionValueKind ConditionValue)
override {
143 checkCondition(ConditionRange);
145 void Ifndef(SourceLocation Loc,
const Token &MacroNameTok,
146 const MacroDefinition &MD)
override {
148 checkName(MacroNameTok);
150 void Ifdef(SourceLocation Loc,
const Token &MacroNameTok,
151 const MacroDefinition &MD)
override {
153 checkName(MacroNameTok);
155 void Elif(SourceLocation Loc, SourceRange ConditionRange,
156 ConditionValueKind ConditionValue, SourceLocation IfLoc)
override {
157 checkCondition(ConditionRange);
159 void Elifdef(SourceLocation Loc,
const Token &MacroNameTok,
160 const MacroDefinition &MD)
override {
161 checkName(MacroNameTok);
163 void Elifdef(SourceLocation Loc, SourceRange ConditionRange,
164 SourceLocation IfLoc)
override {
165 PPCallbacks::Elifdef(Loc, ConditionRange, IfLoc);
167 void Elifndef(SourceLocation Loc,
const Token &MacroNameTok,
168 const MacroDefinition &MD)
override {
169 checkName(MacroNameTok);
171 void Elifndef(SourceLocation Loc, SourceRange ConditionRange,
172 SourceLocation IfLoc)
override {
173 PPCallbacks::Elifndef(Loc, ConditionRange, IfLoc);
175 void Endif(SourceLocation Loc, SourceLocation IfLoc)
override;
177 PragmaIntroducerKind Introducer)
override;
186 if (Enums.empty() || !Enums.back().empty())
187 Enums.emplace_back();
189 bool insideConditional()
const {
190 return (CurrentFile->GuardScanner == IncludeGuard::DefineGuard &&
191 CurrentFile->ConditionScopes > 1) ||
192 (CurrentFile->GuardScanner != IncludeGuard::DefineGuard &&
193 CurrentFile->ConditionScopes > 0);
195 bool isConsecutiveMacro(
const MacroDirective *MD)
const;
196 void rememberLastMacroLocation(
const MacroDirective *MD) {
197 CurrentFile->LastLine = SM.getSpellingLineNumber(MD->getLocation());
198 CurrentFile->LastMacroLocation = Lexer::getLocForEndOfToken(
199 MD->getMacroInfo()->getDefinitionEndLoc(), 0, SM, LangOpts);
201 void clearLastMacroLocation() {
202 CurrentFile->LastLine = 0;
203 CurrentFile->LastMacroLocation = SourceLocation{};
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);
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;
226bool MacroToEnumCallbacks::isConsecutiveMacro(
const MacroDirective *MD)
const {
227 if (CurrentFile->LastMacroLocation.isInvalid())
230 const SourceLocation Loc = MD->getLocation();
231 if (CurrentFile->LastLine + 1 == SM.getSpellingLineNumber(Loc))
234 const SourceLocation Define =
235 SM.translateLineCol(SM.getFileID(Loc), SM.getSpellingLineNumber(Loc), 1);
236 const CharSourceRange BetweenMacros{
237 SourceRange{CurrentFile->LastMacroLocation, Define},
true};
238 const CharSourceRange CharRange =
239 Lexer::makeFileCharRange(BetweenMacros, SM, LangOpts);
240 const StringRef BetweenText = Lexer::getSourceText(CharRange, SM, LangOpts);
244void MacroToEnumCallbacks::clearCurrentEnum(SourceLocation Loc) {
246 if (!Enums.empty() && !Enums.back().empty() &&
247 SM.getSpellingLineNumber(Loc) == CurrentFile->LastLine + 1)
250 clearLastMacroLocation();
253void MacroToEnumCallbacks::conditionStart(
const SourceLocation &Loc) {
254 ++CurrentFile->ConditionScopes;
255 clearCurrentEnum(Loc);
256 if (CurrentFile->GuardScanner == IncludeGuard::FileChanged)
257 CurrentFile->GuardScanner = IncludeGuard::IfGuard;
260void MacroToEnumCallbacks::checkCondition(SourceRange Range) {
261 const CharSourceRange CharRange = Lexer::makeFileCharRange(
262 CharSourceRange::getTokenRange(Range), SM, LangOpts);
263 std::string
Text = Lexer::getSourceText(CharRange, SM, LangOpts).str();
264 Lexer Lex(CharRange.getBegin(), LangOpts,
Text.data(),
Text.data(),
269 End = Lex.LexFromRawLexer(Tok);
270 if (Tok.is(tok::raw_identifier) &&
271 Tok.getRawIdentifier().str() !=
"defined")
276void MacroToEnumCallbacks::checkName(
const Token &MacroNameTok) {
277 rememberExpressionName(MacroNameTok);
280 llvm::erase_if(Enums, [&Id](
const MacroList &MacroList) {
281 return llvm::any_of(MacroList, [&Id](
const EnumMacro &Macro) {
287void MacroToEnumCallbacks::rememberExpressionName(
const Token &Tok) {
289 auto Pos = llvm::lower_bound(ExpressionNames, Id);
290 if (Pos == ExpressionNames.end() || *Pos != Id)
291 ExpressionNames.insert(Pos, Id);
294void MacroToEnumCallbacks::rememberExpressionTokens(
295 ArrayRef<Token> MacroTokens) {
296 for (
const Token Tok : MacroTokens)
297 if (Tok.isAnyIdentifier())
298 rememberExpressionName(Tok);
302 FileChangeReason Reason,
303 SrcMgr::CharacteristicKind FileType,
306 if (Reason == EnterFile) {
307 Files.emplace_back();
308 if (!SM.isInMainFile(Loc))
309 Files.back().GuardScanner = IncludeGuard::FileChanged;
310 }
else if (Reason == ExitFile) {
311 assert(CurrentFile->ConditionScopes == 0);
314 CurrentFile = &Files.back();
317bool MacroToEnumCallbacks::isInitializer(ArrayRef<Token> MacroTokens) {
319 const bool Matched = Matcher.match();
320 const bool IsC = !LangOpts.CPlusPlus;
331 const MacroDirective *MD) {
333 if (CurrentFile->GuardScanner == IncludeGuard::IfGuard) {
334 CurrentFile->GuardScanner = IncludeGuard::DefineGuard;
338 if (insideConditional())
341 if (SM.getFilename(MD->getLocation()).empty())
344 const MacroInfo *Info = MD->getMacroInfo();
345 const ArrayRef<Token> MacroTokens = Info->tokens();
346 if (Info->isBuiltinMacro() || MacroTokens.empty())
348 if (Info->isFunctionLike()) {
349 rememberExpressionTokens(MacroTokens);
353 if (!isInitializer(MacroTokens))
356 if (!isConsecutiveMacro(MD))
358 Enums.back().emplace_back(MacroNameTok, MD);
359 rememberLastMacroLocation(MD);
365 const MacroDefinition &MD,
366 const MacroDirective *Undef) {
367 rememberExpressionName(MacroNameTok);
369 auto MatchesToken = [&MacroNameTok](
const EnumMacro &Macro) {
373 auto *It = llvm::find_if(Enums, [MatchesToken](
const MacroList &MacroList) {
374 return llvm::any_of(MacroList, MatchesToken);
376 if (It != Enums.end())
379 clearLastMacroLocation();
380 CurrentFile->GuardScanner = IncludeGuard::None;
386 if (CurrentFile->ConditionScopes == 0 &&
387 CurrentFile->GuardScanner == IncludeGuard::DefineGuard)
392 assert(CurrentFile->ConditionScopes > 0);
393 --CurrentFile->ConditionScopes;
397static bool textEquals(
const char (&Needle)[N],
const char *HayStack) {
398 return StringRef{HayStack, N - 1} == Needle;
401template <
size_t N>
static size_t len(
const char (&)[N]) {
return N - 1; }
404 PragmaIntroducerKind Introducer) {
405 if (CurrentFile->GuardScanner != IncludeGuard::FileChanged)
408 bool Invalid =
false;
409 const char *Text = SM.getCharacterData(
410 Lexer::getLocForEndOfToken(Loc, 0, SM, LangOpts), &Invalid);
414 while (*Text && std::isspace(*Text))
420 Text +=
len(
"pragma");
421 while (*Text && std::isspace(*Text))
425 CurrentFile->GuardScanner = IncludeGuard::IfGuard;
428void MacroToEnumCallbacks::invalidateExpressionNames() {
429 for (
const std::string &Id : ExpressionNames) {
430 llvm::erase_if(Enums, [Id](
const MacroList &MacroList) {
431 return llvm::any_of(MacroList, [&Id](
const EnumMacro &Macro) {
439 invalidateExpressionNames();
444 llvm::erase_if(Enums, [Range](
const MacroList &MacroList) {
445 return llvm::any_of(MacroList, [Range](
const EnumMacro &Macro) {
446 return Macro.Directive->getLocation() >= Range.getBegin() &&
447 Macro.Directive->getLocation() <= Range.getEnd();
452void MacroToEnumCallbacks::issueDiagnostics() {
453 for (
const MacroList &MacroList : Enums) {
454 if (MacroList.empty())
457 for (
const EnumMacro &Macro : MacroList)
458 warnMacroEnum(Macro);
460 fixEnumMacro(MacroList);
464void MacroToEnumCallbacks::warnMacroEnum(
const EnumMacro &Macro)
const {
465 Check->diag(Macro.Directive->getLocation(),
466 "macro '%0' defines an integral constant; prefer an enum instead")
470void MacroToEnumCallbacks::fixEnumMacro(
const MacroList &MacroList)
const {
471 SourceLocation Begin =
472 MacroList.front().Directive->getMacroInfo()->getDefinitionLoc();
473 Begin = SM.translateLineCol(SM.getFileID(Begin),
474 SM.getSpellingLineNumber(Begin), 1);
475 const DiagnosticBuilder Diagnostic =
476 Check->diag(Begin,
"replace macro with enum")
477 << FixItHint::CreateInsertion(Begin,
"enum {\n");
479 for (
size_t I = 0U; I < MacroList.size(); ++I) {
480 const EnumMacro &Macro = MacroList[I];
481 const SourceLocation DefineEnd =
482 Macro.Directive->getMacroInfo()->getDefinitionLoc();
483 const SourceLocation DefineBegin = SM.translateLineCol(
484 SM.getFileID(DefineEnd), SM.getSpellingLineNumber(DefineEnd), 1);
485 CharSourceRange DefineRange;
486 DefineRange.setBegin(DefineBegin);
487 DefineRange.setEnd(DefineEnd);
488 Diagnostic << FixItHint::CreateRemoval(DefineRange);
490 const SourceLocation NameEnd = Lexer::getLocForEndOfToken(
491 Macro.Directive->getMacroInfo()->getDefinitionLoc(), 0, SM, LangOpts);
492 Diagnostic << FixItHint::CreateInsertion(NameEnd,
" =");
494 const SourceLocation ValueEnd = Lexer::getLocForEndOfToken(
495 Macro.Directive->getMacroInfo()->getDefinitionEndLoc(), 0, SM,
497 if (I < MacroList.size() - 1)
498 Diagnostic << FixItHint::CreateInsertion(ValueEnd,
",");
501 SourceLocation End = Lexer::getLocForEndOfToken(
502 MacroList.back().Directive->getMacroInfo()->getDefinitionEndLoc(), 0, SM,
504 End = SM.translateLineCol(SM.getFileID(End),
505 SM.getSpellingLineNumber(End) + 1, 1);
506 Diagnostic << FixItHint::CreateInsertion(End,
"};\n");
511 Preprocessor *ModuleExpanderPP) {
513 std::make_unique<MacroToEnumCallbacks>(
this, getLangOpts(), SM);
514 PPCallback = Callback.get();
515 PP->addPPCallbacks(std::move(Callback));
520 auto TopLevelDecl = hasParent(translationUnitDecl());
521 Finder->addMatcher(decl(TopLevelDecl).bind(
"top"),
this);
525 return Range.getBegin().isValid() && Range.getEnd().isValid();
528static bool empty(SourceRange Range) {
529 return Range.getBegin() == Range.getEnd();
533 const ast_matchers::MatchFinder::MatchResult &Result) {
534 auto *TLDecl = Result.Nodes.getNodeAs<Decl>(
"top");
535 if (TLDecl ==
nullptr)
538 SourceRange Range = TLDecl->getSourceRange();
539 if (
auto *TemplateFn = Result.Nodes.getNodeAs<FunctionTemplateDecl>(
"top")) {
540 if (TemplateFn->isThisDeclarationADefinition() && TemplateFn->hasBody())
541 Range = SourceRange{TemplateFn->getBeginLoc(),
542 TemplateFn->getUnderlyingDecl()->getBodyRBrace()};
546 PPCallback->invalidateRange(Range);
void MacroDefined(const Token &MacroNameTok, const MacroDirective *MD) override
void PragmaDirective(SourceLocation Loc, PragmaIntroducerKind Introducer) override
void invalidateRange(SourceRange Range)
void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok, StringRef FileName, bool IsAngled, CharSourceRange FilenameRange, OptionalFileEntryRef File, StringRef SearchPath, StringRef RelativePath, const Module *SuggestedModule, bool ModuleImported, SrcMgr::CharacteristicKind FileType) override
MacroToEnumCallbacks(MacroToEnumCheck *Check, const LangOptions &LangOptions, const SourceManager &SM)
void Elifdef(SourceLocation Loc, const Token &MacroNameTok, const MacroDefinition &MD) override
void If(SourceLocation Loc, SourceRange ConditionRange, ConditionValueKind ConditionValue) override
void Elif(SourceLocation Loc, SourceRange ConditionRange, ConditionValueKind ConditionValue, SourceLocation IfLoc) override
void MacroUndefined(const Token &MacroNameTok, const MacroDefinition &MD, const MacroDirective *Undef) override
void Ifndef(SourceLocation Loc, const Token &MacroNameTok, const MacroDefinition &MD) override
void FileChanged(SourceLocation Loc, FileChangeReason Reason, SrcMgr::CharacteristicKind FileType, FileID PrevFID) override
void Elifndef(SourceLocation Loc, const Token &MacroNameTok, const MacroDefinition &MD) override
void Endif(SourceLocation Loc, SourceLocation IfLoc) override
void Elifdef(SourceLocation Loc, SourceRange ConditionRange, SourceLocation IfLoc) override
void Elifndef(SourceLocation Loc, SourceRange ConditionRange, SourceLocation IfLoc) override
void EndOfMainFile() override
void Ifdef(SourceLocation Loc, const Token &MacroNameTok, const MacroDefinition &MD) override
Replaces groups of related macros with an unscoped anonymous enum.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) override
void registerMatchers(ast_matchers::MatchFinder *Finder) override
static bool empty(SourceRange Range)
static bool hasOnlyComments(SourceLocation Loc, const LangOptions &Options, StringRef Text)
static bool isValid(SourceRange Range)
static StringRef getTokenName(const Token &Tok)
static bool textEquals(const char(&Needle)[N], const char *HayStack)
static size_t len(const char(&)[N])