12#include "clang/AST/ASTContext.h"
13#include "clang/ASTMatchers/ASTMatchFinder.h"
14#include "clang/Lex/Preprocessor.h"
15#include "llvm/ADT/STLExtras.h"
27 std::string Buffer{
Text};
28 Lexer Lex(
Loc, Options, Buffer.c_str(), Buffer.c_str(),
29 Buffer.c_str() + Buffer.size());
31 bool SeenHash =
false;
32 while (!Lex.LexFromRawLexer(Tok)) {
33 if (Tok.getKind() == tok::hash && !SeenHash) {
42 enum class WhiteSpace {
50 WhiteSpace State = WhiteSpace::Nothing;
54 if (State == WhiteSpace::CR)
57 State = State == WhiteSpace::CRLF ? WhiteSpace::CRLFCR : WhiteSpace::CR;
61 if (State == WhiteSpace::LF || State == WhiteSpace::CRLFCR)
64 State = State == WhiteSpace::CR ? WhiteSpace::CRLF : WhiteSpace::LF;
68 State = WhiteSpace::Nothing;
77 return Tok.is(tok::raw_identifier) ? Tok.getRawIdentifier()
78 : Tok.getIdentifierInfo()->getName();
84 EnumMacro(Token Name,
const MacroDirective *Directive)
91using MacroList = SmallVector<EnumMacro>;
93enum class IncludeGuard {
None, FileChanged, IfGuard, DefineGuard };
96 FileState() =
default;
109 const SourceManager &SM)
110 : Check(Check), LangOpts(LangOptions), SM(SM) {}
113 SrcMgr::CharacteristicKind FileType,
114 FileID PrevFID)
override;
118 CharSourceRange FilenameRange,
119 OptionalFileEntryRef File, StringRef SearchPath,
120 StringRef RelativePath,
const Module *SuggestedModule,
122 SrcMgr::CharacteristicKind FileType)
override {
123 clearCurrentEnum(HashLoc);
128 const MacroDirective *MD)
override;
131 void MacroUndefined(
const Token &MacroNameTok,
const MacroDefinition &MD,
132 const MacroDirective *Undef)
override;
141 void If(SourceLocation
Loc, SourceRange ConditionRange,
144 checkCondition(ConditionRange);
146 void Ifndef(SourceLocation
Loc,
const Token &MacroNameTok,
147 const MacroDefinition &MD)
override {
149 checkName(MacroNameTok);
151 void Ifdef(SourceLocation
Loc,
const Token &MacroNameTok,
152 const MacroDefinition &MD)
override {
154 checkName(MacroNameTok);
156 void Elif(SourceLocation
Loc, SourceRange ConditionRange,
157 ConditionValueKind
ConditionValue, SourceLocation IfLoc)
override {
158 checkCondition(ConditionRange);
161 const MacroDefinition &MD)
override {
162 checkName(MacroNameTok);
165 SourceLocation IfLoc)
override {
166 PPCallbacks::Elifdef(
Loc, ConditionRange, IfLoc);
169 const MacroDefinition &MD)
override {
170 checkName(MacroNameTok);
173 SourceLocation IfLoc)
override {
174 PPCallbacks::Elifndef(
Loc, ConditionRange, IfLoc);
176 void Endif(SourceLocation
Loc, SourceLocation IfLoc)
override;
178 PragmaIntroducerKind Introducer)
override;
187 if (Enums.empty() || !Enums.back().empty())
188 Enums.emplace_back();
190 bool insideConditional()
const {
191 return (CurrentFile->GuardScanner == IncludeGuard::DefineGuard &&
192 CurrentFile->ConditionScopes > 1) ||
193 (CurrentFile->GuardScanner != IncludeGuard::DefineGuard &&
194 CurrentFile->ConditionScopes > 0);
196 bool isConsecutiveMacro(
const MacroDirective *MD)
const;
197 void rememberLastMacroLocation(
const MacroDirective *MD) {
198 CurrentFile->LastLine = SM.getSpellingLineNumber(MD->getLocation());
199 CurrentFile->LastMacroLocation = Lexer::getLocForEndOfToken(
200 MD->getMacroInfo()->getDefinitionEndLoc(), 0, SM, LangOpts);
202 void clearLastMacroLocation() {
203 CurrentFile->LastLine = 0;
204 CurrentFile->LastMacroLocation = SourceLocation{};
206 void clearCurrentEnum(SourceLocation
Loc);
207 void conditionStart(
const SourceLocation &
Loc);
208 void checkCondition(SourceRange ConditionRange);
209 void checkName(
const Token &MacroNameTok);
210 void rememberExpressionName(
const Token &Tok);
211 void rememberExpressionTokens(ArrayRef<Token> MacroTokens);
212 void invalidateExpressionNames();
213 void issueDiagnostics();
214 void warnMacroEnum(
const EnumMacro &Macro)
const;
215 void fixEnumMacro(
const MacroList &MacroList)
const;
216 bool isInitializer(ArrayRef<Token> MacroTokens);
218 MacroToEnumCheck *Check;
219 const LangOptions &LangOpts;
220 const SourceManager &SM;
221 SmallVector<MacroList> Enums;
222 SmallVector<FileState> Files;
223 std::vector<std::string> ExpressionNames;
224 FileState *CurrentFile =
nullptr;
227bool MacroToEnumCallbacks::isConsecutiveMacro(
const MacroDirective *MD)
const {
228 if (CurrentFile->LastMacroLocation.isInvalid())
231 SourceLocation
Loc = MD->getLocation();
232 if (CurrentFile->LastLine + 1 == SM.getSpellingLineNumber(
Loc))
235 SourceLocation Define =
236 SM.translateLineCol(SM.getFileID(
Loc), SM.getSpellingLineNumber(
Loc), 1);
237 CharSourceRange BetweenMacros{
238 SourceRange{CurrentFile->LastMacroLocation, Define},
true};
239 CharSourceRange CharRange =
240 Lexer::makeFileCharRange(BetweenMacros, SM, LangOpts);
241 StringRef BetweenText = Lexer::getSourceText(CharRange, SM, LangOpts);
245void MacroToEnumCallbacks::clearCurrentEnum(SourceLocation
Loc) {
247 if (!Enums.empty() && !Enums.back().empty() &&
248 SM.getSpellingLineNumber(
Loc) == CurrentFile->LastLine + 1)
251 clearLastMacroLocation();
254void MacroToEnumCallbacks::conditionStart(
const SourceLocation &
Loc) {
255 ++CurrentFile->ConditionScopes;
256 clearCurrentEnum(
Loc);
257 if (CurrentFile->GuardScanner == IncludeGuard::FileChanged)
258 CurrentFile->GuardScanner = IncludeGuard::IfGuard;
261void MacroToEnumCallbacks::checkCondition(SourceRange
Range) {
262 CharSourceRange CharRange = Lexer::makeFileCharRange(
263 CharSourceRange::getTokenRange(
Range), SM, LangOpts);
264 std::string
Text = Lexer::getSourceText(CharRange, SM, LangOpts).str();
265 Lexer Lex(CharRange.getBegin(), LangOpts,
Text.data(),
Text.data(),
270 End = Lex.LexFromRawLexer(Tok);
271 if (Tok.is(tok::raw_identifier) &&
272 Tok.getRawIdentifier().str() !=
"defined")
277void MacroToEnumCallbacks::checkName(
const Token &MacroNameTok) {
278 rememberExpressionName(MacroNameTok);
281 llvm::erase_if(Enums, [&
Id](
const MacroList &MacroList) {
282 return llvm::any_of(MacroList, [&
Id](
const EnumMacro &Macro) {
288void MacroToEnumCallbacks::rememberExpressionName(
const Token &Tok) {
290 auto Pos = llvm::lower_bound(ExpressionNames,
Id);
291 if (
Pos == ExpressionNames.end() || *
Pos !=
Id) {
292 ExpressionNames.insert(
Pos,
Id);
296void MacroToEnumCallbacks::rememberExpressionTokens(
297 ArrayRef<Token> MacroTokens) {
298 for (Token Tok : MacroTokens) {
299 if (Tok.isAnyIdentifier())
300 rememberExpressionName(Tok);
305 FileChangeReason Reason,
306 SrcMgr::CharacteristicKind FileType,
309 if (Reason == EnterFile) {
310 Files.emplace_back();
311 if (!SM.isInMainFile(
Loc))
312 Files.back().GuardScanner = IncludeGuard::FileChanged;
313 }
else if (Reason == ExitFile) {
314 assert(CurrentFile->ConditionScopes == 0);
317 CurrentFile = &Files.back();
320bool MacroToEnumCallbacks::isInitializer(ArrayRef<Token> MacroTokens)
323 bool Matched = Matcher.match();
324 bool isC = !LangOpts.CPlusPlus;
336 const MacroDirective *MD) {
338 if (CurrentFile->GuardScanner == IncludeGuard::IfGuard) {
339 CurrentFile->GuardScanner = IncludeGuard::DefineGuard;
343 if (insideConditional())
346 if (SM.getFilename(MD->getLocation()).empty())
349 const MacroInfo *
Info = MD->getMacroInfo();
350 ArrayRef<Token> MacroTokens =
Info->tokens();
351 if (
Info->isBuiltinMacro() || MacroTokens.empty())
353 if (
Info->isFunctionLike()) {
354 rememberExpressionTokens(MacroTokens);
358 if (!isInitializer(MacroTokens))
361 if (!isConsecutiveMacro(MD))
363 Enums.back().emplace_back(MacroNameTok, MD);
364 rememberLastMacroLocation(MD);
370 const MacroDefinition &MD,
371 const MacroDirective *Undef) {
372 rememberExpressionName(MacroNameTok);
374 auto MatchesToken = [&MacroNameTok](
const EnumMacro &Macro) {
378 auto It = llvm::find_if(Enums, [MatchesToken](
const MacroList &MacroList) {
379 return llvm::any_of(MacroList, MatchesToken);
381 if (It != Enums.end())
384 clearLastMacroLocation();
385 CurrentFile->GuardScanner = IncludeGuard::None;
391 if (CurrentFile->ConditionScopes == 0 &&
392 CurrentFile->GuardScanner == IncludeGuard::DefineGuard)
397 assert(CurrentFile->ConditionScopes > 0);
398 --CurrentFile->ConditionScopes;
404bool textEquals(
const char (&Needle)[N],
const char *HayStack) {
405 return StringRef{HayStack, N - 1} == Needle;
408template <
size_t N>
size_t len(
const char (&)[N]) {
return N - 1; }
413 PragmaIntroducerKind Introducer) {
414 if (CurrentFile->GuardScanner != IncludeGuard::FileChanged)
417 bool Invalid =
false;
418 const char *
Text = SM.getCharacterData(
419 Lexer::getLocForEndOfToken(
Loc, 0, SM, LangOpts), &Invalid);
423 while (*
Text && std::isspace(*
Text))
426 if (textEquals(
"pragma",
Text))
429 Text += len(
"pragma");
430 while (*
Text && std::isspace(*
Text))
433 if (textEquals(
"once",
Text))
434 CurrentFile->GuardScanner = IncludeGuard::IfGuard;
437void MacroToEnumCallbacks::invalidateExpressionNames() {
438 for (
const std::string &
Id : ExpressionNames) {
439 llvm::erase_if(Enums, [
Id](
const MacroList &MacroList) {
440 return llvm::any_of(MacroList, [&
Id](
const EnumMacro &Macro) {
448 invalidateExpressionNames();
453 llvm::erase_if(Enums, [
Range](
const MacroList &MacroList) {
454 return llvm::any_of(MacroList, [
Range](
const EnumMacro &Macro) {
455 return Macro.Directive->getLocation() >=
Range.getBegin() &&
456 Macro.Directive->getLocation() <=
Range.getEnd();
461void MacroToEnumCallbacks::issueDiagnostics() {
462 for (
const MacroList &MacroList : Enums) {
463 if (MacroList.empty())
466 for (
const EnumMacro &Macro : MacroList)
467 warnMacroEnum(Macro);
469 fixEnumMacro(MacroList);
473void MacroToEnumCallbacks::warnMacroEnum(
const EnumMacro &Macro)
const {
474 Check->
diag(Macro.Directive->getLocation(),
475 "macro '%0' defines an integral constant; prefer an enum instead")
479void MacroToEnumCallbacks::fixEnumMacro(
const MacroList &MacroList)
const {
480 SourceLocation Begin =
481 MacroList.front().Directive->getMacroInfo()->getDefinitionLoc();
482 Begin = SM.translateLineCol(SM.getFileID(Begin),
483 SM.getSpellingLineNumber(Begin), 1);
485 Check->
diag(Begin,
"replace macro with enum")
486 << FixItHint::CreateInsertion(Begin,
"enum {\n");
488 for (
size_t I = 0U; I < MacroList.size(); ++I) {
489 const EnumMacro &Macro = MacroList[I];
490 SourceLocation DefineEnd =
491 Macro.Directive->getMacroInfo()->getDefinitionLoc();
492 SourceLocation DefineBegin = SM.translateLineCol(
493 SM.getFileID(DefineEnd), SM.getSpellingLineNumber(DefineEnd), 1);
494 CharSourceRange DefineRange;
495 DefineRange.setBegin(DefineBegin);
496 DefineRange.setEnd(DefineEnd);
497 Diagnostic << FixItHint::CreateRemoval(DefineRange);
499 SourceLocation NameEnd = Lexer::getLocForEndOfToken(
500 Macro.Directive->getMacroInfo()->getDefinitionLoc(), 0, SM, LangOpts);
501 Diagnostic << FixItHint::CreateInsertion(NameEnd,
" =");
503 SourceLocation ValueEnd = Lexer::getLocForEndOfToken(
504 Macro.Directive->getMacroInfo()->getDefinitionEndLoc(), 0, SM,
506 if (I < MacroList.size() - 1)
507 Diagnostic << FixItHint::CreateInsertion(ValueEnd,
",");
510 SourceLocation End = Lexer::getLocForEndOfToken(
511 MacroList.back().Directive->getMacroInfo()->getDefinitionEndLoc(), 0, SM,
513 End = SM.translateLineCol(SM.getFileID(End),
514 SM.getSpellingLineNumber(End) + 1, 1);
515 Diagnostic << FixItHint::CreateInsertion(End,
"};\n");
520 Preprocessor *ModuleExpanderPP) {
521 auto Callback = std::make_unique<MacroToEnumCallbacks>(
this,
getLangOpts(), SM);
522 PPCallback = Callback.get();
523 PP->addPPCallbacks(std::move(Callback));
527 using namespace ast_matchers;
528 auto TopLevelDecl = hasParent(translationUnitDecl());
529 Finder->addMatcher(decl(TopLevelDecl).bind(
"top"),
this);
533 return Range.getBegin().isValid() &&
Range.getEnd().isValid();
541 const ast_matchers::MatchFinder::MatchResult &Result) {
542 auto *TLDecl = Result.Nodes.getNodeAs<
Decl>(
"top");
543 if (TLDecl ==
nullptr)
546 SourceRange
Range = TLDecl->getSourceRange();
547 if (
auto *TemplateFn = Result.Nodes.getNodeAs<FunctionTemplateDecl>(
"top")) {
548 if (TemplateFn->isThisDeclarationADefinition() && TemplateFn->hasBody())
549 Range = SourceRange{TemplateFn->getBeginLoc(),
550 TemplateFn->getUnderlyingDecl()->getBodyRBrace()};
const FunctionDecl * Decl
llvm::SmallString< 256U > Name
DiagnosticCallback Diagnostic
CharSourceRange Range
SourceRange for the file name.
bool IsAngled
true if this was an include with angle brackets
SourceLocation LastMacroLocation
const MacroDirective * Directive
IncludeGuard GuardScanner
clang::PPCallbacks::ConditionValueKind ConditionValue
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
const LangOptions & getLangOpts() const
Returns the language options from the context.
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
ClangTidyChecks that register ASTMatchers should do the actual work in here.
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) override
Override this to register PPCallbacks in the preprocessor.
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
@ None
Mix between the two parameters is not possible.
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)