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
13using namespace clang::ast_matchers;
14
15namespace clang::tidy::modernize {
16
17namespace {
18
19// Determine if the given QualType is a nullary function or pointer to same.
20bool 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
30const char FunctionId[] = "function";
31const char TypedefId[] = "typedef";
32const char FieldId[] = "field";
33const char VarId[] = "var";
34const char NamedCastId[] = "named-cast";
35const char CStyleCastId[] = "c-style-cast";
36const char ExplicitCastId[] = "explicit-cast";
37const char LambdaId[] = "lambda";
38
39} // namespace
40
41void RedundantVoidArgCheck::registerMatchers(MatchFinder *Finder) {
42 Finder->addMatcher(NodeMatch: functionDecl(parameterCountIs(N: 0), unless(isImplicit()),
43 unless(isInstantiated()), unless(isExternC()))
44 .bind(ID: FunctionId),
45 Action: this);
46 Finder->addMatcher(NodeMatch: typedefNameDecl(unless(isImplicit())).bind(ID: TypedefId),
47 Action: this);
48 auto ParenFunctionType = parenType(innerType(functionType()));
49 auto PointerToFunctionType = pointee(ParenFunctionType);
50 auto FunctionOrMemberPointer =
51 anyOf(hasType(InnerMatcher: pointerType(PointerToFunctionType)),
52 hasType(InnerMatcher: memberPointerType(PointerToFunctionType)));
53 Finder->addMatcher(NodeMatch: fieldDecl(FunctionOrMemberPointer).bind(ID: FieldId), Action: this);
54 Finder->addMatcher(NodeMatch: varDecl(FunctionOrMemberPointer).bind(ID: VarId), Action: this);
55 auto CastDestinationIsFunction =
56 hasDestinationType(InnerMatcher: pointsTo(InnerMatcher: ParenFunctionType));
57 Finder->addMatcher(
58 NodeMatch: cStyleCastExpr(CastDestinationIsFunction).bind(ID: CStyleCastId), Action: this);
59 Finder->addMatcher(
60 NodeMatch: cxxStaticCastExpr(CastDestinationIsFunction).bind(ID: NamedCastId), Action: this);
61 Finder->addMatcher(
62 NodeMatch: cxxReinterpretCastExpr(CastDestinationIsFunction).bind(ID: NamedCastId),
63 Action: this);
64 Finder->addMatcher(
65 NodeMatch: cxxConstCastExpr(CastDestinationIsFunction).bind(ID: NamedCastId), Action: this);
66 Finder->addMatcher(NodeMatch: lambdaExpr().bind(ID: LambdaId), Action: this);
67}
68
69void RedundantVoidArgCheck::check(const MatchFinder::MatchResult &Result) {
70 const BoundNodes &Nodes = Result.Nodes;
71 if (const auto *Function = Nodes.getNodeAs<FunctionDecl>(ID: FunctionId))
72 processFunctionDecl(Result, Function);
73 else if (const auto *TypedefName =
74 Nodes.getNodeAs<TypedefNameDecl>(ID: TypedefId))
75 processTypedefNameDecl(Result, Typedef: TypedefName);
76 else if (const auto *Member = Nodes.getNodeAs<FieldDecl>(ID: FieldId))
77 processFieldDecl(Result, Member);
78 else if (const auto *Var = Nodes.getNodeAs<VarDecl>(ID: VarId))
79 processVarDecl(Result, Var);
80 else if (const auto *NamedCast =
81 Nodes.getNodeAs<CXXNamedCastExpr>(ID: NamedCastId))
82 processNamedCastExpr(Result, NamedCast);
83 else if (const auto *CStyleCast =
84 Nodes.getNodeAs<CStyleCastExpr>(ID: CStyleCastId))
85 processExplicitCastExpr(Result, CStyleCast);
86 else if (const auto *ExplicitCast =
87 Nodes.getNodeAs<ExplicitCastExpr>(ID: ExplicitCastId))
88 processExplicitCastExpr(Result, ExplicitCast);
89 else if (const auto *Lambda = Nodes.getNodeAs<LambdaExpr>(ID: LambdaId))
90 processLambdaExpr(Result, Lambda);
91}
92
93void RedundantVoidArgCheck::processFunctionDecl(
94 const MatchFinder::MatchResult &Result, const FunctionDecl *Function) {
95 const auto *Method = dyn_cast<CXXMethodDecl>(Val: 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(Loc: End))
105 End = Result.SourceManager->getExpansionLoc(Loc: End);
106 End = End.getLocWithOffset(Offset: -1);
107 }
108 removeVoidArgumentTokens(Result, Range: SourceRange(Start, End),
109 GrammarLocation: "function definition");
110 } else
111 removeVoidArgumentTokens(Result, Range: SourceRange(Start, End),
112 GrammarLocation: "function declaration");
113}
114
115bool isMacroIdentifier(const IdentifierTable &Idents, const Token &ProtoToken) {
116 if (!ProtoToken.is(K: 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
126void RedundantVoidArgCheck::removeVoidArgumentTokens(
127 const ast_matchers::MatchFinder::MatchResult &Result, SourceRange Range,
128 StringRef GrammarLocation) {
129 CharSourceRange CharRange =
130 Lexer::makeFileCharRange(Range: CharSourceRange::getTokenRange(R: Range),
131 SM: *Result.SourceManager, LangOpts: getLangOpts());
132
133 std::string DeclText =
134 Lexer::getSourceText(Range: CharRange, SM: *Result.SourceManager, LangOpts: 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(Result&: ProtoToken)) {
155 switch (State) {
156 case TokenState::Start:
157 if (ProtoToken.is(K: 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(K: 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(K: tok::TokenKind::raw_identifier)) {
171 if (isMacroIdentifier(Idents, ProtoToken))
172 State = TokenState::MacroId;
173 else
174 State = TokenState::MacroArguments;
175 } else if (ProtoToken.is(K: 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(K: 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(K: 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(K: 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(K: tok::TokenKind::r_paren))
209 removeVoidToken(VoidToken, Diagnostic);
210 else if (ProtoToken.is(K: tok::TokenKind::l_paren))
211 State = TokenState::LeftParen;
212 break;
213 }
214 }
215
216 if (State == TokenState::Void && ProtoToken.is(K: tok::TokenKind::r_paren))
217 removeVoidToken(VoidToken, Diagnostic);
218}
219
220void RedundantVoidArgCheck::removeVoidToken(Token VoidToken,
221 StringRef Diagnostic) {
222 SourceLocation VoidLoc = VoidToken.getLocation();
223 diag(Loc: VoidLoc, Description: Diagnostic) << FixItHint::CreateRemoval(RemoveRange: VoidLoc);
224}
225
226void RedundantVoidArgCheck::processTypedefNameDecl(
227 const MatchFinder::MatchResult &Result,
228 const TypedefNameDecl *TypedefName) {
229 if (protoTypeHasNoParms(QT: TypedefName->getUnderlyingType()))
230 removeVoidArgumentTokens(Result, Range: TypedefName->getSourceRange(),
231 GrammarLocation: isa<TypedefDecl>(Val: TypedefName) ? "typedef"
232 : "type alias");
233}
234
235void RedundantVoidArgCheck::processFieldDecl(
236 const MatchFinder::MatchResult &Result, const FieldDecl *Member) {
237 if (protoTypeHasNoParms(Member->getType()))
238 removeVoidArgumentTokens(Result, Range: Member->getSourceRange(),
239 GrammarLocation: "field declaration");
240}
241
242void 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(Loc: Var->getInit()->getBeginLoc())
249 .getLocWithOffset(-1);
250 removeVoidArgumentTokens(Result, Range: SourceRange(Begin, InitStart),
251 GrammarLocation: "variable declaration with initializer");
252 } else
253 removeVoidArgumentTokens(Result, Range: Var->getSourceRange(),
254 GrammarLocation: "variable declaration");
255 }
256}
257
258void RedundantVoidArgCheck::processNamedCastExpr(
259 const MatchFinder::MatchResult &Result, const CXXNamedCastExpr *NamedCast) {
260 if (protoTypeHasNoParms(NamedCast->getTypeAsWritten()))
261 removeVoidArgumentTokens(
262 Result,
263 Range: NamedCast->getTypeInfoAsWritten()->getTypeLoc().getSourceRange(),
264 GrammarLocation: "named cast");
265}
266
267void RedundantVoidArgCheck::processExplicitCastExpr(
268 const MatchFinder::MatchResult &Result,
269 const ExplicitCastExpr *ExplicitCast) {
270 if (protoTypeHasNoParms(QT: ExplicitCast->getTypeAsWritten()))
271 removeVoidArgumentTokens(Result, Range: ExplicitCast->getSourceRange(),
272 GrammarLocation: "cast expression");
273}
274
275void 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 Range: {SM->getSpellingLoc(Loc: TL.getBeginLoc()),
283 SM->getSpellingLoc(Loc: TL.getEndLoc())},
284 GrammarLocation: "lambda expression");
285 }
286}
287
288} // namespace clang::tidy::modernize
289

source code of clang-tools-extra/clang-tidy/modernize/RedundantVoidArgCheck.cpp