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 *Imported,
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,
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);
160 const MacroDefinition &MD)
override {
161 checkName(MacroNameTok);
164 SourceLocation IfLoc)
override {
165 PPCallbacks::Elifdef(
Loc, ConditionRange, IfLoc);
168 const MacroDefinition &MD)
override {
169 checkName(MacroNameTok);
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)
322 bool Matched = Matcher.match();
323 bool isC = !LangOpts.CPlusPlus;
335 const MacroDirective *MD) {
337 if (CurrentFile->GuardScanner == IncludeGuard::IfGuard) {
338 CurrentFile->GuardScanner = IncludeGuard::DefineGuard;
342 if (insideConditional())
345 if (SM.getFilename(MD->getLocation()).empty())
348 const MacroInfo *
Info = MD->getMacroInfo();
349 ArrayRef<Token> MacroTokens =
Info->tokens();
350 if (
Info->isBuiltinMacro() || MacroTokens.empty())
352 if (
Info->isFunctionLike()) {
353 rememberExpressionTokens(MacroTokens);
357 if (!isInitializer(MacroTokens))
360 if (!isConsecutiveMacro(MD))
362 Enums.back().emplace_back(MacroNameTok, MD);
363 rememberLastMacroLocation(MD);
369 const MacroDefinition &MD,
370 const MacroDirective *Undef) {
371 rememberExpressionName(MacroNameTok);
373 auto MatchesToken = [&MacroNameTok](
const EnumMacro &Macro) {
377 auto It = llvm::find_if(Enums, [MatchesToken](
const MacroList &MacroList) {
378 return llvm::any_of(MacroList, MatchesToken);
380 if (It != Enums.end())
383 clearLastMacroLocation();
384 CurrentFile->GuardScanner = IncludeGuard::None;
390 if (CurrentFile->ConditionScopes == 0 &&
391 CurrentFile->GuardScanner == IncludeGuard::DefineGuard)
396 assert(CurrentFile->ConditionScopes > 0);
397 --CurrentFile->ConditionScopes;
403bool textEquals(
const char (&Needle)[N],
const char *HayStack) {
404 return StringRef{HayStack, N - 1} == Needle;
407template <
size_t N>
size_t len(
const char (&)[N]) {
return N - 1; }
412 PragmaIntroducerKind Introducer) {
413 if (CurrentFile->GuardScanner != IncludeGuard::FileChanged)
416 bool Invalid =
false;
417 const char *
Text = SM.getCharacterData(
418 Lexer::getLocForEndOfToken(
Loc, 0, SM, LangOpts), &Invalid);
422 while (*
Text && std::isspace(*
Text))
425 if (textEquals(
"pragma",
Text))
428 Text += len(
"pragma");
429 while (*
Text && std::isspace(*
Text))
432 if (textEquals(
"once",
Text))
433 CurrentFile->GuardScanner = IncludeGuard::IfGuard;
436void MacroToEnumCallbacks::invalidateExpressionNames() {
437 for (
const std::string &
Id : ExpressionNames) {
438 llvm::erase_if(Enums, [
Id](
const MacroList &MacroList) {
439 return llvm::any_of(MacroList, [&
Id](
const EnumMacro &Macro) {
447 invalidateExpressionNames();
452 llvm::erase_if(Enums, [
Range](
const MacroList &MacroList) {
453 return llvm::any_of(MacroList, [
Range](
const EnumMacro &Macro) {
454 return Macro.Directive->getLocation() >=
Range.getBegin() &&
455 Macro.Directive->getLocation() <=
Range.getEnd();
460void MacroToEnumCallbacks::issueDiagnostics() {
461 for (
const MacroList &MacroList : Enums) {
462 if (MacroList.empty())
465 for (
const EnumMacro &Macro : MacroList)
466 warnMacroEnum(Macro);
468 fixEnumMacro(MacroList);
472void MacroToEnumCallbacks::warnMacroEnum(
const EnumMacro &Macro)
const {
473 Check->
diag(Macro.Directive->getLocation(),
474 "macro '%0' defines an integral constant; prefer an enum instead")
478void MacroToEnumCallbacks::fixEnumMacro(
const MacroList &MacroList)
const {
479 SourceLocation Begin =
480 MacroList.front().Directive->getMacroInfo()->getDefinitionLoc();
481 Begin = SM.translateLineCol(SM.getFileID(Begin),
482 SM.getSpellingLineNumber(Begin), 1);
484 Check->
diag(Begin,
"replace macro with enum")
485 << FixItHint::CreateInsertion(Begin,
"enum {\n");
487 for (
size_t I = 0U; I < MacroList.size(); ++I) {
488 const EnumMacro &Macro = MacroList[I];
489 SourceLocation DefineEnd =
490 Macro.Directive->getMacroInfo()->getDefinitionLoc();
491 SourceLocation DefineBegin = SM.translateLineCol(
492 SM.getFileID(DefineEnd), SM.getSpellingLineNumber(DefineEnd), 1);
493 CharSourceRange DefineRange;
494 DefineRange.setBegin(DefineBegin);
495 DefineRange.setEnd(DefineEnd);
496 Diagnostic << FixItHint::CreateRemoval(DefineRange);
498 SourceLocation NameEnd = Lexer::getLocForEndOfToken(
499 Macro.Directive->getMacroInfo()->getDefinitionLoc(), 0, SM, LangOpts);
500 Diagnostic << FixItHint::CreateInsertion(NameEnd,
" =");
502 SourceLocation ValueEnd = Lexer::getLocForEndOfToken(
503 Macro.Directive->getMacroInfo()->getDefinitionEndLoc(), 0, SM,
505 if (I < MacroList.size() - 1)
506 Diagnostic << FixItHint::CreateInsertion(ValueEnd,
",");
509 SourceLocation End = Lexer::getLocForEndOfToken(
510 MacroList.back().Directive->getMacroInfo()->getDefinitionEndLoc(), 0, SM,
512 End = SM.translateLineCol(SM.getFileID(End),
513 SM.getSpellingLineNumber(End) + 1, 1);
514 Diagnostic << FixItHint::CreateInsertion(End,
"};\n");
519 Preprocessor *ModuleExpanderPP) {
520 auto Callback = std::make_unique<MacroToEnumCallbacks>(
this,
getLangOpts(), SM);
521 PPCallback = Callback.get();
522 PP->addPPCallbacks(std::move(Callback));
526 using namespace ast_matchers;
527 auto TopLevelDecl = hasParent(translationUnitDecl());
528 Finder->addMatcher(decl(TopLevelDecl).bind(
"top"),
this);
532 return Range.getBegin().isValid() &&
Range.getEnd().isValid();
540 const ast_matchers::MatchFinder::MatchResult &Result) {
541 auto *TLDecl = Result.Nodes.getNodeAs<
Decl>(
"top");
542 if (TLDecl ==
nullptr)
545 SourceRange
Range = TLDecl->getSourceRange();
546 if (
auto *TemplateFn = Result.Nodes.getNodeAs<FunctionTemplateDecl>(
"top")) {
547 if (TemplateFn->isThisDeclarationADefinition() && TemplateFn->hasBody())
548 Range = SourceRange{TemplateFn->getBeginLoc(),
549 TemplateFn->getUnderlyingDecl()->getBodyRBrace()};
const FunctionDecl * Decl
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)
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 InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok, StringRef FileName, bool IsAngled, CharSourceRange FilenameRange, OptionalFileEntryRef File, StringRef SearchPath, StringRef RelativePath, const Module *Imported, SrcMgr::CharacteristicKind FileType) 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)