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 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;
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 SourceLocation Loc = MD->getLocation();
231 if (CurrentFile->LastLine + 1 == SM.getSpellingLineNumber(Loc))
234 SourceLocation Define =
235 SM.translateLineCol(SM.getFileID(Loc), SM.getSpellingLineNumber(Loc), 1);
236 CharSourceRange BetweenMacros{
237 SourceRange{CurrentFile->LastMacroLocation, Define},
true};
238 CharSourceRange CharRange =
239 Lexer::makeFileCharRange(BetweenMacros, SM, LangOpts);
240 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 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);
295void MacroToEnumCallbacks::rememberExpressionTokens(
296 ArrayRef<Token> MacroTokens) {
297 for (Token Tok : MacroTokens) {
298 if (Tok.isAnyIdentifier())
299 rememberExpressionName(Tok);
304 FileChangeReason Reason,
305 SrcMgr::CharacteristicKind FileType,
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);
316 CurrentFile = &Files.back();
319bool MacroToEnumCallbacks::isInitializer(ArrayRef<Token> MacroTokens) {
321 bool Matched = Matcher.match();
322 bool IsC = !LangOpts.CPlusPlus;
333 const MacroDirective *MD) {
335 if (CurrentFile->GuardScanner == IncludeGuard::IfGuard) {
336 CurrentFile->GuardScanner = IncludeGuard::DefineGuard;
340 if (insideConditional())
343 if (SM.getFilename(MD->getLocation()).empty())
346 const MacroInfo *Info = MD->getMacroInfo();
347 ArrayRef<Token> MacroTokens = Info->tokens();
348 if (Info->isBuiltinMacro() || MacroTokens.empty())
350 if (Info->isFunctionLike()) {
351 rememberExpressionTokens(MacroTokens);
355 if (!isInitializer(MacroTokens))
358 if (!isConsecutiveMacro(MD))
360 Enums.back().emplace_back(MacroNameTok, MD);
361 rememberLastMacroLocation(MD);
367 const MacroDefinition &MD,
368 const MacroDirective *Undef) {
369 rememberExpressionName(MacroNameTok);
371 auto MatchesToken = [&MacroNameTok](
const EnumMacro &Macro) {
375 auto *It = llvm::find_if(Enums, [MatchesToken](
const MacroList &MacroList) {
376 return llvm::any_of(MacroList, MatchesToken);
378 if (It != Enums.end())
381 clearLastMacroLocation();
382 CurrentFile->GuardScanner = IncludeGuard::None;
388 if (CurrentFile->ConditionScopes == 0 &&
389 CurrentFile->GuardScanner == IncludeGuard::DefineGuard)
394 assert(CurrentFile->ConditionScopes > 0);
395 --CurrentFile->ConditionScopes;
399static bool textEquals(
const char (&Needle)[N],
const char *HayStack) {
400 return StringRef{HayStack, N - 1} == Needle;
403template <
size_t N>
static size_t len(
const char (&)[N]) {
return N - 1; }
406 PragmaIntroducerKind Introducer) {
407 if (CurrentFile->GuardScanner != IncludeGuard::FileChanged)
410 bool Invalid =
false;
411 const char *Text = SM.getCharacterData(
412 Lexer::getLocForEndOfToken(Loc, 0, SM, LangOpts), &Invalid);
416 while (*Text && std::isspace(*Text))
422 Text +=
len(
"pragma");
423 while (*Text && std::isspace(*Text))
427 CurrentFile->GuardScanner = IncludeGuard::IfGuard;
430void MacroToEnumCallbacks::invalidateExpressionNames() {
431 for (
const std::string &Id : ExpressionNames) {
432 llvm::erase_if(Enums, [Id](
const MacroList &MacroList) {
433 return llvm::any_of(MacroList, [&Id](
const EnumMacro &Macro) {
441 invalidateExpressionNames();
446 llvm::erase_if(Enums, [Range](
const MacroList &MacroList) {
447 return llvm::any_of(MacroList, [Range](
const EnumMacro &Macro) {
448 return Macro.Directive->getLocation() >= Range.getBegin() &&
449 Macro.Directive->getLocation() <= Range.getEnd();
454void MacroToEnumCallbacks::issueDiagnostics() {
455 for (
const MacroList &MacroList : Enums) {
456 if (MacroList.empty())
459 for (
const EnumMacro &Macro : MacroList)
460 warnMacroEnum(Macro);
462 fixEnumMacro(MacroList);
466void MacroToEnumCallbacks::warnMacroEnum(
const EnumMacro &Macro)
const {
467 Check->diag(Macro.Directive->getLocation(),
468 "macro '%0' defines an integral constant; prefer an enum instead")
472void MacroToEnumCallbacks::fixEnumMacro(
const MacroList &MacroList)
const {
473 SourceLocation Begin =
474 MacroList.front().Directive->getMacroInfo()->getDefinitionLoc();
475 Begin = SM.translateLineCol(SM.getFileID(Begin),
476 SM.getSpellingLineNumber(Begin), 1);
477 DiagnosticBuilder Diagnostic =
478 Check->diag(Begin,
"replace macro with enum")
479 << FixItHint::CreateInsertion(Begin,
"enum {\n");
481 for (
size_t I = 0U; I < MacroList.size(); ++I) {
482 const EnumMacro &Macro = MacroList[I];
483 SourceLocation DefineEnd =
484 Macro.Directive->getMacroInfo()->getDefinitionLoc();
485 SourceLocation DefineBegin = SM.translateLineCol(
486 SM.getFileID(DefineEnd), SM.getSpellingLineNumber(DefineEnd), 1);
487 CharSourceRange DefineRange;
488 DefineRange.setBegin(DefineBegin);
489 DefineRange.setEnd(DefineEnd);
490 Diagnostic << FixItHint::CreateRemoval(DefineRange);
492 SourceLocation NameEnd = Lexer::getLocForEndOfToken(
493 Macro.Directive->getMacroInfo()->getDefinitionLoc(), 0, SM, LangOpts);
494 Diagnostic << FixItHint::CreateInsertion(NameEnd,
" =");
496 SourceLocation ValueEnd = Lexer::getLocForEndOfToken(
497 Macro.Directive->getMacroInfo()->getDefinitionEndLoc(), 0, SM,
499 if (I < MacroList.size() - 1)
500 Diagnostic << FixItHint::CreateInsertion(ValueEnd,
",");
503 SourceLocation End = Lexer::getLocForEndOfToken(
504 MacroList.back().Directive->getMacroInfo()->getDefinitionEndLoc(), 0, SM,
506 End = SM.translateLineCol(SM.getFileID(End),
507 SM.getSpellingLineNumber(End) + 1, 1);
508 Diagnostic << FixItHint::CreateInsertion(End,
"};\n");
513 Preprocessor *ModuleExpanderPP) {
515 std::make_unique<MacroToEnumCallbacks>(
this, getLangOpts(), SM);
516 PPCallback = Callback.get();
517 PP->addPPCallbacks(std::move(Callback));
522 auto TopLevelDecl = hasParent(translationUnitDecl());
523 Finder->addMatcher(decl(TopLevelDecl).bind(
"top"),
this);
527 return Range.getBegin().isValid() && Range.getEnd().isValid();
530static bool empty(SourceRange Range) {
531 return Range.getBegin() == Range.getEnd();
535 const ast_matchers::MatchFinder::MatchResult &Result) {
536 auto *TLDecl = Result.Nodes.getNodeAs<Decl>(
"top");
537 if (TLDecl ==
nullptr)
540 SourceRange Range = TLDecl->getSourceRange();
541 if (
auto *TemplateFn = Result.Nodes.getNodeAs<FunctionTemplateDecl>(
"top")) {
542 if (TemplateFn->isThisDeclarationADefinition() && TemplateFn->hasBody())
543 Range = SourceRange{TemplateFn->getBeginLoc(),
544 TemplateFn->getUnderlyingDecl()->getBodyRBrace()};
548 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])