xref: /llvm-project/clang-tools-extra/clang-tidy/modernize/RedundantVoidArgCheck.cpp (revision 7d2ea6c422d3f5712b7253407005e1a465a76946)
1 //===- RedundantVoidArgCheck.cpp - clang-tidy -----------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "RedundantVoidArgCheck.h"
10 #include "clang/Frontend/CompilerInstance.h"
11 #include "clang/Lex/Lexer.h"
12 
13 using namespace clang::ast_matchers;
14 
15 namespace clang::tidy::modernize {
16 
17 namespace {
18 
19 // Determine if the given QualType is a nullary function or pointer to same.
protoTypeHasNoParms(QualType QT)20 bool protoTypeHasNoParms(QualType QT) {
21   if (const auto *PT = QT->getAs<PointerType>())
22     QT = PT->getPointeeType();
23   if (auto *MPT = QT->getAs<MemberPointerType>())
24     QT = MPT->getPointeeType();
25   if (const auto *FP = QT->getAs<FunctionProtoType>())
26     return FP->getNumParams() == 0;
27   return false;
28 }
29 
30 const char FunctionId[] = "function";
31 const char TypedefId[] = "typedef";
32 const char FieldId[] = "field";
33 const char VarId[] = "var";
34 const char NamedCastId[] = "named-cast";
35 const char CStyleCastId[] = "c-style-cast";
36 const char ExplicitCastId[] = "explicit-cast";
37 const char LambdaId[] = "lambda";
38 
39 } // namespace
40 
registerMatchers(MatchFinder * Finder)41 void RedundantVoidArgCheck::registerMatchers(MatchFinder *Finder) {
42   Finder->addMatcher(functionDecl(parameterCountIs(0), unless(isImplicit()),
43                                   unless(isInstantiated()), unless(isExternC()))
44                          .bind(FunctionId),
45                      this);
46   Finder->addMatcher(typedefNameDecl(unless(isImplicit())).bind(TypedefId),
47                      this);
48   auto ParenFunctionType = parenType(innerType(functionType()));
49   auto PointerToFunctionType = pointee(ParenFunctionType);
50   auto FunctionOrMemberPointer =
51       anyOf(hasType(pointerType(PointerToFunctionType)),
52             hasType(memberPointerType(PointerToFunctionType)));
53   Finder->addMatcher(fieldDecl(FunctionOrMemberPointer).bind(FieldId), this);
54   Finder->addMatcher(varDecl(FunctionOrMemberPointer).bind(VarId), this);
55   auto CastDestinationIsFunction =
56       hasDestinationType(pointsTo(ParenFunctionType));
57   Finder->addMatcher(
58       cStyleCastExpr(CastDestinationIsFunction).bind(CStyleCastId), this);
59   Finder->addMatcher(
60       cxxStaticCastExpr(CastDestinationIsFunction).bind(NamedCastId), this);
61   Finder->addMatcher(
62       cxxReinterpretCastExpr(CastDestinationIsFunction).bind(NamedCastId),
63       this);
64   Finder->addMatcher(
65       cxxConstCastExpr(CastDestinationIsFunction).bind(NamedCastId), this);
66   Finder->addMatcher(lambdaExpr().bind(LambdaId), this);
67 }
68 
check(const MatchFinder::MatchResult & Result)69 void RedundantVoidArgCheck::check(const MatchFinder::MatchResult &Result) {
70   const BoundNodes &Nodes = Result.Nodes;
71   if (const auto *Function = Nodes.getNodeAs<FunctionDecl>(FunctionId))
72     processFunctionDecl(Result, Function);
73   else if (const auto *TypedefName =
74                Nodes.getNodeAs<TypedefNameDecl>(TypedefId))
75     processTypedefNameDecl(Result, TypedefName);
76   else if (const auto *Member = Nodes.getNodeAs<FieldDecl>(FieldId))
77     processFieldDecl(Result, Member);
78   else if (const auto *Var = Nodes.getNodeAs<VarDecl>(VarId))
79     processVarDecl(Result, Var);
80   else if (const auto *NamedCast =
81                Nodes.getNodeAs<CXXNamedCastExpr>(NamedCastId))
82     processNamedCastExpr(Result, NamedCast);
83   else if (const auto *CStyleCast =
84                Nodes.getNodeAs<CStyleCastExpr>(CStyleCastId))
85     processExplicitCastExpr(Result, CStyleCast);
86   else if (const auto *ExplicitCast =
87                Nodes.getNodeAs<ExplicitCastExpr>(ExplicitCastId))
88     processExplicitCastExpr(Result, ExplicitCast);
89   else if (const auto *Lambda = Nodes.getNodeAs<LambdaExpr>(LambdaId))
90     processLambdaExpr(Result, Lambda);
91 }
92 
processFunctionDecl(const MatchFinder::MatchResult & Result,const FunctionDecl * Function)93 void RedundantVoidArgCheck::processFunctionDecl(
94     const MatchFinder::MatchResult &Result, const FunctionDecl *Function) {
95   const auto *Method = dyn_cast<CXXMethodDecl>(Function);
96   SourceLocation Start = Method && Method->getParent()->isLambda()
97                              ? Method->getBeginLoc()
98                              : Function->getLocation();
99   SourceLocation End = Function->getEndLoc();
100   if (Function->isThisDeclarationADefinition()) {
101     if (const Stmt *Body = Function->getBody()) {
102       End = Body->getBeginLoc();
103       if (End.isMacroID() &&
104           Result.SourceManager->isAtStartOfImmediateMacroExpansion(End))
105         End = Result.SourceManager->getExpansionLoc(End);
106       End = End.getLocWithOffset(-1);
107     }
108     removeVoidArgumentTokens(Result, SourceRange(Start, End),
109                              "function definition");
110   } else
111     removeVoidArgumentTokens(Result, SourceRange(Start, End),
112                              "function declaration");
113 }
114 
isMacroIdentifier(const IdentifierTable & Idents,const Token & ProtoToken)115 bool isMacroIdentifier(const IdentifierTable &Idents, const Token &ProtoToken) {
116   if (!ProtoToken.is(tok::TokenKind::raw_identifier))
117     return false;
118 
119   IdentifierTable::iterator It = Idents.find(ProtoToken.getRawIdentifier());
120   if (It == Idents.end())
121     return false;
122 
123   return It->second->hadMacroDefinition();
124 }
125 
removeVoidArgumentTokens(const ast_matchers::MatchFinder::MatchResult & Result,SourceRange Range,StringRef GrammarLocation)126 void RedundantVoidArgCheck::removeVoidArgumentTokens(
127     const ast_matchers::MatchFinder::MatchResult &Result, SourceRange Range,
128     StringRef GrammarLocation) {
129   CharSourceRange CharRange =
130       Lexer::makeFileCharRange(CharSourceRange::getTokenRange(Range),
131                                *Result.SourceManager, getLangOpts());
132 
133   std::string DeclText =
134       Lexer::getSourceText(CharRange, *Result.SourceManager, getLangOpts())
135           .str();
136   Lexer PrototypeLexer(CharRange.getBegin(), getLangOpts(), DeclText.data(),
137                        DeclText.data(), DeclText.data() + DeclText.size());
138   enum class TokenState {
139     Start,
140     MacroId,
141     MacroLeftParen,
142     MacroArguments,
143     LeftParen,
144     Void,
145   };
146   TokenState State = TokenState::Start;
147   Token VoidToken;
148   Token ProtoToken;
149   const IdentifierTable &Idents = Result.Context->Idents;
150   int MacroLevel = 0;
151   std::string Diagnostic =
152       ("redundant void argument list in " + GrammarLocation).str();
153 
154   while (!PrototypeLexer.LexFromRawLexer(ProtoToken)) {
155     switch (State) {
156     case TokenState::Start:
157       if (ProtoToken.is(tok::TokenKind::l_paren))
158         State = TokenState::LeftParen;
159       else if (isMacroIdentifier(Idents, ProtoToken))
160         State = TokenState::MacroId;
161       break;
162     case TokenState::MacroId:
163       if (ProtoToken.is(tok::TokenKind::l_paren))
164         State = TokenState::MacroLeftParen;
165       else
166         State = TokenState::Start;
167       break;
168     case TokenState::MacroLeftParen:
169       ++MacroLevel;
170       if (ProtoToken.is(tok::TokenKind::raw_identifier)) {
171         if (isMacroIdentifier(Idents, ProtoToken))
172           State = TokenState::MacroId;
173         else
174           State = TokenState::MacroArguments;
175       } else if (ProtoToken.is(tok::TokenKind::r_paren)) {
176         --MacroLevel;
177         if (MacroLevel == 0)
178           State = TokenState::Start;
179         else
180           State = TokenState::MacroId;
181       } else
182         State = TokenState::MacroArguments;
183       break;
184     case TokenState::MacroArguments:
185       if (isMacroIdentifier(Idents, ProtoToken))
186         State = TokenState::MacroLeftParen;
187       else if (ProtoToken.is(tok::TokenKind::r_paren)) {
188         --MacroLevel;
189         if (MacroLevel == 0)
190           State = TokenState::Start;
191       }
192       break;
193     case TokenState::LeftParen:
194       if (ProtoToken.is(tok::TokenKind::raw_identifier)) {
195         if (isMacroIdentifier(Idents, ProtoToken))
196           State = TokenState::MacroId;
197         else if (ProtoToken.getRawIdentifier() == "void") {
198           State = TokenState::Void;
199           VoidToken = ProtoToken;
200         }
201       } else if (ProtoToken.is(tok::TokenKind::l_paren))
202         State = TokenState::LeftParen;
203       else
204         State = TokenState::Start;
205       break;
206     case TokenState::Void:
207       State = TokenState::Start;
208       if (ProtoToken.is(tok::TokenKind::r_paren))
209         removeVoidToken(VoidToken, Diagnostic);
210       else if (ProtoToken.is(tok::TokenKind::l_paren))
211         State = TokenState::LeftParen;
212       break;
213     }
214   }
215 
216   if (State == TokenState::Void && ProtoToken.is(tok::TokenKind::r_paren))
217     removeVoidToken(VoidToken, Diagnostic);
218 }
219 
removeVoidToken(Token VoidToken,StringRef Diagnostic)220 void RedundantVoidArgCheck::removeVoidToken(Token VoidToken,
221                                             StringRef Diagnostic) {
222   SourceLocation VoidLoc = VoidToken.getLocation();
223   diag(VoidLoc, Diagnostic) << FixItHint::CreateRemoval(VoidLoc);
224 }
225 
processTypedefNameDecl(const MatchFinder::MatchResult & Result,const TypedefNameDecl * TypedefName)226 void RedundantVoidArgCheck::processTypedefNameDecl(
227     const MatchFinder::MatchResult &Result,
228     const TypedefNameDecl *TypedefName) {
229   if (protoTypeHasNoParms(TypedefName->getUnderlyingType()))
230     removeVoidArgumentTokens(Result, TypedefName->getSourceRange(),
231                              isa<TypedefDecl>(TypedefName) ? "typedef"
232                                                            : "type alias");
233 }
234 
processFieldDecl(const MatchFinder::MatchResult & Result,const FieldDecl * Member)235 void RedundantVoidArgCheck::processFieldDecl(
236     const MatchFinder::MatchResult &Result, const FieldDecl *Member) {
237   if (protoTypeHasNoParms(Member->getType()))
238     removeVoidArgumentTokens(Result, Member->getSourceRange(),
239                              "field declaration");
240 }
241 
processVarDecl(const MatchFinder::MatchResult & Result,const VarDecl * Var)242 void RedundantVoidArgCheck::processVarDecl(
243     const MatchFinder::MatchResult &Result, const VarDecl *Var) {
244   if (protoTypeHasNoParms(Var->getType())) {
245     SourceLocation Begin = Var->getBeginLoc();
246     if (Var->hasInit()) {
247       SourceLocation InitStart =
248           Result.SourceManager->getExpansionLoc(Var->getInit()->getBeginLoc())
249               .getLocWithOffset(-1);
250       removeVoidArgumentTokens(Result, SourceRange(Begin, InitStart),
251                                "variable declaration with initializer");
252     } else
253       removeVoidArgumentTokens(Result, Var->getSourceRange(),
254                                "variable declaration");
255   }
256 }
257 
processNamedCastExpr(const MatchFinder::MatchResult & Result,const CXXNamedCastExpr * NamedCast)258 void RedundantVoidArgCheck::processNamedCastExpr(
259     const MatchFinder::MatchResult &Result, const CXXNamedCastExpr *NamedCast) {
260   if (protoTypeHasNoParms(NamedCast->getTypeAsWritten()))
261     removeVoidArgumentTokens(
262         Result,
263         NamedCast->getTypeInfoAsWritten()->getTypeLoc().getSourceRange(),
264         "named cast");
265 }
266 
processExplicitCastExpr(const MatchFinder::MatchResult & Result,const ExplicitCastExpr * ExplicitCast)267 void RedundantVoidArgCheck::processExplicitCastExpr(
268     const MatchFinder::MatchResult &Result,
269     const ExplicitCastExpr *ExplicitCast) {
270   if (protoTypeHasNoParms(ExplicitCast->getTypeAsWritten()))
271     removeVoidArgumentTokens(Result, ExplicitCast->getSourceRange(),
272                              "cast expression");
273 }
274 
processLambdaExpr(const MatchFinder::MatchResult & Result,const LambdaExpr * Lambda)275 void RedundantVoidArgCheck::processLambdaExpr(
276     const MatchFinder::MatchResult &Result, const LambdaExpr *Lambda) {
277   if (Lambda->getLambdaClass()->getLambdaCallOperator()->getNumParams() == 0 &&
278       Lambda->hasExplicitParameters()) {
279     SourceManager *SM = Result.SourceManager;
280     TypeLoc TL = Lambda->getLambdaClass()->getLambdaTypeInfo()->getTypeLoc();
281     removeVoidArgumentTokens(Result,
282                              {SM->getSpellingLoc(TL.getBeginLoc()),
283                               SM->getSpellingLoc(TL.getEndLoc())},
284                              "lambda expression");
285   }
286 }
287 
288 } // namespace clang::tidy::modernize
289