12 #include "clang/AST/ASTContext.h"
13 #include "clang/ASTMatchers/ASTMatchFinder.h"
14 #include "clang/Lex/Preprocessor.h"
15 #include "llvm/ADT/STLExtras.h"
29 std::string Buffer{
Text};
30 Lexer Lex(
Loc, Options, Buffer.c_str(), Buffer.c_str(),
31 Buffer.c_str() + Buffer.size());
33 bool SeenHash =
false;
34 while (!Lex.LexFromRawLexer(Tok)) {
35 if (Tok.getKind() == tok::hash && !SeenHash) {
44 enum class WhiteSpace {
52 WhiteSpace State = WhiteSpace::Nothing;
56 if (State == WhiteSpace::CR)
59 State = State == WhiteSpace::CRLF ? WhiteSpace::CRLFCR : WhiteSpace::CR;
63 if (State == WhiteSpace::LF || State == WhiteSpace::CRLFCR)
66 State = State == WhiteSpace::CR ? WhiteSpace::CRLF : WhiteSpace::LF;
70 State = WhiteSpace::Nothing;
79 return Tok.is(tok::raw_identifier) ? Tok.getRawIdentifier()
80 : Tok.getIdentifierInfo()->getName();
93 using MacroList = SmallVector<EnumMacro>;
95 enum class IncludeGuard {
None, FileChanged, IfGuard, DefineGuard };
112 const SourceManager &SM)
113 : Check(Check), LangOpts(LangOptions), SM(SM) {}
116 SrcMgr::CharacteristicKind FileType,
117 FileID PrevFID)
override;
121 CharSourceRange FilenameRange,
122 Optional<FileEntryRef> File, StringRef SearchPath,
123 StringRef RelativePath,
const Module *Imported,
124 SrcMgr::CharacteristicKind FileType)
override {
125 clearCurrentEnum(HashLoc);
130 const MacroDirective *
MD)
override;
134 const MacroDirective *Undef)
override;
143 void If(SourceLocation
Loc, SourceRange ConditionRange,
146 checkCondition(ConditionRange);
148 void Ifndef(SourceLocation
Loc,
const Token &MacroNameTok,
149 const MacroDefinition &
MD)
override {
151 checkName(MacroNameTok);
153 void Ifdef(SourceLocation
Loc,
const Token &MacroNameTok,
154 const MacroDefinition &
MD)
override {
156 checkName(MacroNameTok);
158 void Elif(SourceLocation
Loc, SourceRange ConditionRange,
159 ConditionValueKind
ConditionValue, SourceLocation IfLoc)
override {
160 checkCondition(ConditionRange);
163 const MacroDefinition &
MD)
override {
164 checkName(MacroNameTok);
167 SourceLocation IfLoc)
override {
168 PPCallbacks::Elifdef(
Loc, ConditionRange, IfLoc);
171 const MacroDefinition &
MD)
override {
172 checkName(MacroNameTok);
175 SourceLocation IfLoc)
override {
176 PPCallbacks::Elifndef(
Loc, ConditionRange, IfLoc);
178 void Endif(SourceLocation
Loc, SourceLocation IfLoc)
override;
180 PragmaIntroducerKind Introducer)
override;
189 if (Enums.empty() || !Enums.back().empty())
190 Enums.emplace_back();
192 bool insideConditional()
const {
193 return (CurrentFile->GuardScanner == IncludeGuard::DefineGuard &&
194 CurrentFile->ConditionScopes > 1) ||
195 (CurrentFile->GuardScanner != IncludeGuard::DefineGuard &&
196 CurrentFile->ConditionScopes > 0);
198 bool isConsecutiveMacro(
const MacroDirective *
MD)
const;
199 void rememberLastMacroLocation(
const MacroDirective *
MD) {
200 CurrentFile->LastLine = SM.getSpellingLineNumber(
MD->getLocation());
201 CurrentFile->LastMacroLocation = Lexer::getLocForEndOfToken(
202 MD->getMacroInfo()->getDefinitionEndLoc(), 0, SM, LangOpts);
204 void clearLastMacroLocation() {
205 CurrentFile->LastLine = 0;
206 CurrentFile->LastMacroLocation = SourceLocation{};
208 void clearCurrentEnum(SourceLocation
Loc);
209 void conditionStart(
const SourceLocation &
Loc);
210 void checkCondition(SourceRange ConditionRange);
211 void checkName(
const Token &MacroNameTok);
212 void rememberExpressionName(
const Token &Tok);
213 void rememberExpressionTokens(ArrayRef<Token> MacroTokens);
214 void invalidateExpressionNames();
215 void issueDiagnostics();
216 void warnMacroEnum(
const EnumMacro &Macro)
const;
217 void fixEnumMacro(
const MacroList &MacroList)
const;
218 bool isInitializer(ArrayRef<Token> MacroTokens);
220 MacroToEnumCheck *Check;
221 const LangOptions &LangOpts;
222 const SourceManager &SM;
223 SmallVector<MacroList> Enums;
224 SmallVector<FileState> Files;
225 std::vector<std::string> ExpressionNames;
226 FileState *CurrentFile =
nullptr;
229 bool MacroToEnumCallbacks::isConsecutiveMacro(
const MacroDirective *
MD)
const {
230 if (CurrentFile->LastMacroLocation.isInvalid())
233 SourceLocation
Loc =
MD->getLocation();
234 if (CurrentFile->LastLine + 1 == SM.getSpellingLineNumber(
Loc))
237 SourceLocation Define =
238 SM.translateLineCol(SM.getFileID(
Loc), SM.getSpellingLineNumber(
Loc), 1);
239 CharSourceRange BetweenMacros{
240 SourceRange{CurrentFile->LastMacroLocation, Define},
true};
241 CharSourceRange CharRange =
242 Lexer::makeFileCharRange(BetweenMacros, SM, LangOpts);
247 void MacroToEnumCallbacks::clearCurrentEnum(SourceLocation
Loc) {
249 if (!Enums.empty() && !Enums.back().empty() &&
250 SM.getSpellingLineNumber(
Loc) == CurrentFile->LastLine + 1)
253 clearLastMacroLocation();
256 void MacroToEnumCallbacks::conditionStart(
const SourceLocation &
Loc) {
257 ++CurrentFile->ConditionScopes;
258 clearCurrentEnum(
Loc);
259 if (CurrentFile->GuardScanner == IncludeGuard::FileChanged)
260 CurrentFile->GuardScanner = IncludeGuard::IfGuard;
263 void MacroToEnumCallbacks::checkCondition(SourceRange
Range) {
264 CharSourceRange CharRange = Lexer::makeFileCharRange(
265 CharSourceRange::getTokenRange(
Range), SM, LangOpts);
267 Lexer Lex(CharRange.getBegin(), LangOpts,
Text.data(),
Text.data(),
272 End = Lex.LexFromRawLexer(Tok);
273 if (Tok.is(tok::raw_identifier) &&
274 Tok.getRawIdentifier().str() !=
"defined")
279 void MacroToEnumCallbacks::checkName(
const Token &MacroNameTok) {
280 rememberExpressionName(MacroNameTok);
283 llvm::erase_if(Enums, [&Id](
const MacroList &MacroList) {
284 return llvm::any_of(MacroList, [&Id](
const EnumMacro &Macro) {
290 void MacroToEnumCallbacks::rememberExpressionName(
const Token &Tok) {
292 auto Pos = llvm::lower_bound(ExpressionNames, Id);
293 if (
Pos == ExpressionNames.end() || *
Pos != Id) {
294 ExpressionNames.insert(
Pos, Id);
298 void MacroToEnumCallbacks::rememberExpressionTokens(
299 ArrayRef<Token> MacroTokens) {
300 for (Token Tok : MacroTokens) {
301 if (Tok.isAnyIdentifier())
302 rememberExpressionName(Tok);
307 FileChangeReason Reason,
308 SrcMgr::CharacteristicKind FileType,
311 if (Reason == EnterFile) {
312 Files.emplace_back();
313 if (!SM.isInMainFile(
Loc))
314 Files.back().GuardScanner = IncludeGuard::FileChanged;
315 }
else if (Reason == ExitFile) {
316 assert(CurrentFile->ConditionScopes == 0);
319 CurrentFile = &Files.back();
322 bool MacroToEnumCallbacks::isInitializer(ArrayRef<Token> MacroTokens)
325 bool Matched = Matcher.match();
326 bool isC = !LangOpts.CPlusPlus;
338 const MacroDirective *
MD) {
340 if (CurrentFile->GuardScanner == IncludeGuard::IfGuard) {
341 CurrentFile->GuardScanner = IncludeGuard::DefineGuard;
345 if (insideConditional())
348 if (SM.getFilename(
MD->getLocation()).empty())
351 const MacroInfo *
Info =
MD->getMacroInfo();
352 ArrayRef<Token> MacroTokens =
Info->tokens();
353 if (
Info->isBuiltinMacro() || MacroTokens.empty())
355 if (
Info->isFunctionLike()) {
356 rememberExpressionTokens(MacroTokens);
360 if (!isInitializer(MacroTokens))
363 if (!isConsecutiveMacro(
MD))
365 Enums.back().emplace_back(MacroNameTok,
MD);
366 rememberLastMacroLocation(
MD);
372 const MacroDefinition &
MD,
373 const MacroDirective *Undef) {
374 rememberExpressionName(MacroNameTok);
376 auto MatchesToken = [&MacroNameTok](
const EnumMacro &Macro) {
380 auto It = llvm::find_if(Enums, [MatchesToken](
const MacroList &MacroList) {
381 return llvm::any_of(MacroList, MatchesToken);
383 if (It != Enums.end())
386 clearLastMacroLocation();
387 CurrentFile->GuardScanner = IncludeGuard::None;
393 if (CurrentFile->ConditionScopes == 0 &&
394 CurrentFile->GuardScanner == IncludeGuard::DefineGuard)
399 assert(CurrentFile->ConditionScopes > 0);
400 --CurrentFile->ConditionScopes;
406 bool textEquals(
const char (&Needle)[N],
const char *HayStack) {
407 return StringRef{HayStack, N - 1} == Needle;
410 template <
size_t N>
size_t len(
const char (&)[N]) {
return N - 1; }
415 PragmaIntroducerKind Introducer) {
416 if (CurrentFile->GuardScanner != IncludeGuard::FileChanged)
419 bool Invalid =
false;
420 const char *
Text = SM.getCharacterData(
421 Lexer::getLocForEndOfToken(
Loc, 0, SM, LangOpts), &Invalid);
425 while (*
Text && std::isspace(*
Text))
428 if (textEquals(
"pragma",
Text))
431 Text += len(
"pragma");
432 while (*
Text && std::isspace(*
Text))
435 if (textEquals(
"once",
Text))
436 CurrentFile->GuardScanner = IncludeGuard::IfGuard;
439 void MacroToEnumCallbacks::invalidateExpressionNames() {
440 for (
const std::string &Id : ExpressionNames) {
441 llvm::erase_if(Enums, [Id](
const MacroList &MacroList) {
442 return llvm::any_of(MacroList, [&Id](
const EnumMacro &Macro) {
450 invalidateExpressionNames();
455 llvm::erase_if(Enums, [
Range](
const MacroList &MacroList) {
456 return llvm::any_of(MacroList, [
Range](
const EnumMacro &Macro) {
457 return Macro.Directive->getLocation() >=
Range.getBegin() &&
458 Macro.Directive->getLocation() <=
Range.getEnd();
463 void MacroToEnumCallbacks::issueDiagnostics() {
464 for (
const MacroList &MacroList : Enums) {
465 if (MacroList.empty())
468 for (
const EnumMacro &Macro : MacroList)
469 warnMacroEnum(Macro);
471 fixEnumMacro(MacroList);
475 void MacroToEnumCallbacks::warnMacroEnum(
const EnumMacro &Macro)
const {
476 Check->
diag(Macro.Directive->getLocation(),
477 "macro '%0' defines an integral constant; prefer an enum instead")
481 void MacroToEnumCallbacks::fixEnumMacro(
const MacroList &MacroList)
const {
482 SourceLocation Begin =
483 MacroList.front().Directive->getMacroInfo()->getDefinitionLoc();
484 Begin = SM.translateLineCol(SM.getFileID(Begin),
485 SM.getSpellingLineNumber(Begin), 1);
487 Check->
diag(Begin,
"replace macro with enum")
488 << FixItHint::CreateInsertion(Begin,
"enum {\n");
490 for (
size_t I = 0u; I < MacroList.size(); ++I) {
491 const EnumMacro &Macro = MacroList[I];
492 SourceLocation DefineEnd =
493 Macro.Directive->getMacroInfo()->getDefinitionLoc();
494 SourceLocation DefineBegin = SM.translateLineCol(
495 SM.getFileID(DefineEnd), SM.getSpellingLineNumber(DefineEnd), 1);
496 CharSourceRange DefineRange;
497 DefineRange.setBegin(DefineBegin);
498 DefineRange.setEnd(DefineEnd);
499 Diagnostic << FixItHint::CreateRemoval(DefineRange);
501 SourceLocation NameEnd = Lexer::getLocForEndOfToken(
502 Macro.Directive->getMacroInfo()->getDefinitionLoc(), 0, SM, LangOpts);
503 Diagnostic << FixItHint::CreateInsertion(NameEnd,
" =");
505 SourceLocation ValueEnd = Lexer::getLocForEndOfToken(
506 Macro.Directive->getMacroInfo()->getDefinitionEndLoc(), 0, SM,
508 if (I < MacroList.size() - 1)
509 Diagnostic << FixItHint::CreateInsertion(ValueEnd,
",");
512 SourceLocation End = Lexer::getLocForEndOfToken(
513 MacroList.back().Directive->getMacroInfo()->getDefinitionEndLoc(), 0, SM,
515 End = SM.translateLineCol(SM.getFileID(End),
516 SM.getSpellingLineNumber(End) + 1, 1);
517 Diagnostic << FixItHint::CreateInsertion(End,
"};\n");
522 Preprocessor *ModuleExpanderPP) {
529 using namespace ast_matchers;
530 auto TopLevelDecl = hasParent(translationUnitDecl());
531 Finder->addMatcher(decl(TopLevelDecl).bind(
"top"),
this);
535 return Range.getBegin().isValid() &&
Range.getEnd().isValid();
543 const ast_matchers::MatchFinder::MatchResult &Result) {
544 auto *TLDecl = Result.Nodes.getNodeAs<
Decl>(
"top");
545 if (TLDecl ==
nullptr)
548 SourceRange
Range = TLDecl->getSourceRange();
549 if (
auto *TemplateFn = Result.Nodes.getNodeAs<FunctionTemplateDecl>(
"top")) {
550 if (TemplateFn->isThisDeclarationADefinition() && TemplateFn->hasBody())
551 Range = SourceRange{TemplateFn->getBeginLoc(),
552 TemplateFn->getUnderlyingDecl()->getBodyRBrace()};