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