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. |
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 | |
41 | void 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 | |
69 | void 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 | |
93 | void 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 | |
115 | bool 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 | |
126 | void 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 | |
220 | void RedundantVoidArgCheck::removeVoidToken(Token VoidToken, |
221 | StringRef Diagnostic) { |
222 | SourceLocation VoidLoc = VoidToken.getLocation(); |
223 | diag(Loc: VoidLoc, Description: Diagnostic) << FixItHint::CreateRemoval(RemoveRange: VoidLoc); |
224 | } |
225 | |
226 | void 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 | |
235 | void 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 | |
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(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 | |
258 | void 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 | |
267 | void 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 | |
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 | 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 | |