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;
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;
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 const 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 const auto MatchesToken = [&MacroNameTok](
const EnumMacro &Macro) {
374 llvm::find_if(Enums, [MatchesToken](
const MacroList &MacroList) {
375 return llvm::any_of(MacroList, MatchesToken);
377 if (It != Enums.end())
380 clearLastMacroLocation();
381 CurrentFile->GuardScanner = IncludeGuard::None;
387 if (CurrentFile->ConditionScopes == 0 &&
388 CurrentFile->GuardScanner == IncludeGuard::DefineGuard)
393 assert(CurrentFile->ConditionScopes > 0);
394 --CurrentFile->ConditionScopes;
398static bool textEquals(
const char (&Needle)[N],
const char *HayStack) {
399 return StringRef{HayStack, N - 1} == Needle;
402template <
size_t N>
static size_t len(
const char (&)[N]) {
return N - 1; }
405 PragmaIntroducerKind Introducer) {
406 if (CurrentFile->GuardScanner != IncludeGuard::FileChanged)
409 bool Invalid =
false;
410 const char *Text = SM.getCharacterData(
411 Lexer::getLocForEndOfToken(Loc, 0, SM, LangOpts), &Invalid);
415 while (*Text && std::isspace(*Text))
421 Text +=
len(
"pragma");
422 while (*Text && std::isspace(*Text))
426 CurrentFile->GuardScanner = IncludeGuard::IfGuard;
429void MacroToEnumCallbacks::invalidateExpressionNames() {
430 for (
const std::string &Id : ExpressionNames) {
431 llvm::erase_if(Enums, [Id](
const MacroList &MacroList) {
432 return llvm::any_of(MacroList, [&Id](
const EnumMacro &Macro) {
440 invalidateExpressionNames();
445 llvm::erase_if(Enums, [Range](
const MacroList &MacroList) {
446 return llvm::any_of(MacroList, [Range](
const EnumMacro &Macro) {
447 return Macro.Directive->getLocation() >= Range.getBegin() &&
448 Macro.Directive->getLocation() <= Range.getEnd();
453void MacroToEnumCallbacks::issueDiagnostics() {
454 for (
const MacroList &MacroList : Enums) {
455 if (MacroList.empty())
458 for (
const EnumMacro &Macro : MacroList)
459 warnMacroEnum(Macro);
461 fixEnumMacro(MacroList);
465void MacroToEnumCallbacks::warnMacroEnum(
const EnumMacro &Macro)
const {
466 Check->diag(Macro.Directive->getLocation(),
467 "macro '%0' defines an integral constant; prefer an enum instead")
471void MacroToEnumCallbacks::fixEnumMacro(
const MacroList &MacroList)
const {
472 SourceLocation Begin =
473 MacroList.front().Directive->getMacroInfo()->getDefinitionLoc();
474 const StringRef LineEnding =
475 SM.getBufferData(SM.getFileID(Begin)).detectEOL();
477 Begin = SM.translateLineCol(SM.getFileID(Begin),
478 SM.getSpellingLineNumber(Begin), 1);
479 const DiagnosticBuilder Diagnostic =
480 Check->diag(Begin,
"replace macro with enum")
481 << FixItHint::CreateInsertion(Begin,
482 (llvm::Twine(
"enum {") + LineEnding).str());
484 for (
size_t I = 0U; I < MacroList.size(); ++I) {
485 const EnumMacro &Macro = MacroList[I];
486 const SourceLocation DefineEnd =
487 Macro.Directive->getMacroInfo()->getDefinitionLoc();
488 const SourceLocation DefineBegin = SM.translateLineCol(
489 SM.getFileID(DefineEnd), SM.getSpellingLineNumber(DefineEnd), 1);
490 CharSourceRange DefineRange;
491 DefineRange.setBegin(DefineBegin);
492 DefineRange.setEnd(DefineEnd);
493 Diagnostic << FixItHint::CreateRemoval(DefineRange);
495 const SourceLocation NameEnd = Lexer::getLocForEndOfToken(
496 Macro.Directive->getMacroInfo()->getDefinitionLoc(), 0, SM, LangOpts);
497 Diagnostic << FixItHint::CreateInsertion(NameEnd,
" =");
499 const SourceLocation ValueEnd = Lexer::getLocForEndOfToken(
500 Macro.Directive->getMacroInfo()->getDefinitionEndLoc(), 0, SM,
502 if (I < MacroList.size() - 1)
503 Diagnostic << FixItHint::CreateInsertion(ValueEnd,
",");
506 SourceLocation End = Lexer::getLocForEndOfToken(
507 MacroList.back().Directive->getMacroInfo()->getDefinitionEndLoc(), 0, SM,
509 End = SM.translateLineCol(SM.getFileID(End),
510 SM.getSpellingLineNumber(End) + 1, 1);
511 Diagnostic << FixItHint::CreateInsertion(
512 End, (llvm::Twine(
"};") + LineEnding).str());
517 Preprocessor *ModuleExpanderPP) {
519 std::make_unique<MacroToEnumCallbacks>(
this, getLangOpts(), SM);
520 PPCallback = Callback.get();
521 PP->addPPCallbacks(std::move(Callback));
526 const auto TopLevelDecl = hasParent(translationUnitDecl());
527 Finder->addMatcher(decl(TopLevelDecl).bind(
"top"),
this);
531 return Range.getBegin().isValid() && Range.getEnd().isValid();
534static bool empty(SourceRange Range) {
535 return Range.getBegin() == Range.getEnd();
539 const ast_matchers::MatchFinder::MatchResult &Result) {
540 auto *TLDecl = Result.Nodes.getNodeAs<Decl>(
"top");
541 if (TLDecl ==
nullptr)
544 SourceRange Range = TLDecl->getSourceRange();
545 if (
auto *TemplateFn = Result.Nodes.getNodeAs<FunctionTemplateDecl>(
"top");
546 TemplateFn && TemplateFn->isThisDeclarationADefinition() &&
547 TemplateFn->hasBody())
548 Range = SourceRange{TemplateFn->getBeginLoc(),
549 TemplateFn->getUnderlyingDecl()->getBodyRBrace()};
552 PPCallback->invalidateRange(Range);
static GeneratorRegistry::Add< MDGenerator > MD(MDGenerator::Format, "Generator for Markdown output.")
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])