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 | static bool isMacroIdentifier(const IdentifierTable &Idents, |
116 | const Token &ProtoToken) { |
117 | if (!ProtoToken.is(K: tok::TokenKind::raw_identifier)) |
118 | return false; |
119 | |
120 | IdentifierTable::iterator It = Idents.find(ProtoToken.getRawIdentifier()); |
121 | if (It == Idents.end()) |
122 | return false; |
123 | |
124 | return It->second->hadMacroDefinition(); |
125 | } |
126 | |
127 | void RedundantVoidArgCheck::removeVoidArgumentTokens( |
128 | const ast_matchers::MatchFinder::MatchResult &Result, SourceRange Range, |
129 | StringRef GrammarLocation) { |
130 | CharSourceRange CharRange = |
131 | Lexer::makeFileCharRange(Range: CharSourceRange::getTokenRange(R: Range), |
132 | SM: *Result.SourceManager, LangOpts: getLangOpts()); |
133 | |
134 | std::string DeclText = |
135 | Lexer::getSourceText(Range: CharRange, SM: *Result.SourceManager, LangOpts: getLangOpts()) |
136 | .str(); |
137 | Lexer PrototypeLexer(CharRange.getBegin(), getLangOpts(), DeclText.data(), |
138 | DeclText.data(), DeclText.data() + DeclText.size()); |
139 | enum class TokenState { |
140 | Start, |
141 | MacroId, |
142 | MacroLeftParen, |
143 | MacroArguments, |
144 | LeftParen, |
145 | Void, |
146 | }; |
147 | TokenState State = TokenState::Start; |
148 | Token VoidToken; |
149 | Token ProtoToken; |
150 | const IdentifierTable &Idents = Result.Context->Idents; |
151 | int MacroLevel = 0; |
152 | std::string Diagnostic = |
153 | ("redundant void argument list in " + GrammarLocation).str(); |
154 | |
155 | while (!PrototypeLexer.LexFromRawLexer(Result&: ProtoToken)) { |
156 | switch (State) { |
157 | case TokenState::Start: |
158 | if (ProtoToken.is(K: tok::TokenKind::l_paren)) |
159 | State = TokenState::LeftParen; |
160 | else if (isMacroIdentifier(Idents, ProtoToken)) |
161 | State = TokenState::MacroId; |
162 | break; |
163 | case TokenState::MacroId: |
164 | if (ProtoToken.is(K: tok::TokenKind::l_paren)) |
165 | State = TokenState::MacroLeftParen; |
166 | else |
167 | State = TokenState::Start; |
168 | break; |
169 | case TokenState::MacroLeftParen: |
170 | ++MacroLevel; |
171 | if (ProtoToken.is(K: tok::TokenKind::raw_identifier)) { |
172 | if (isMacroIdentifier(Idents, ProtoToken)) |
173 | State = TokenState::MacroId; |
174 | else |
175 | State = TokenState::MacroArguments; |
176 | } else if (ProtoToken.is(K: tok::TokenKind::r_paren)) { |
177 | --MacroLevel; |
178 | if (MacroLevel == 0) |
179 | State = TokenState::Start; |
180 | else |
181 | State = TokenState::MacroId; |
182 | } else |
183 | State = TokenState::MacroArguments; |
184 | break; |
185 | case TokenState::MacroArguments: |
186 | if (isMacroIdentifier(Idents, ProtoToken)) |
187 | State = TokenState::MacroLeftParen; |
188 | else if (ProtoToken.is(K: tok::TokenKind::r_paren)) { |
189 | --MacroLevel; |
190 | if (MacroLevel == 0) |
191 | State = TokenState::Start; |
192 | } |
193 | break; |
194 | case TokenState::LeftParen: |
195 | if (ProtoToken.is(K: tok::TokenKind::raw_identifier)) { |
196 | if (isMacroIdentifier(Idents, ProtoToken)) |
197 | State = TokenState::MacroId; |
198 | else if (ProtoToken.getRawIdentifier() == "void" ) { |
199 | State = TokenState::Void; |
200 | VoidToken = ProtoToken; |
201 | } |
202 | } else if (ProtoToken.is(K: tok::TokenKind::l_paren)) |
203 | State = TokenState::LeftParen; |
204 | else |
205 | State = TokenState::Start; |
206 | break; |
207 | case TokenState::Void: |
208 | State = TokenState::Start; |
209 | if (ProtoToken.is(K: tok::TokenKind::r_paren)) |
210 | removeVoidToken(VoidToken, Diagnostic); |
211 | else if (ProtoToken.is(K: tok::TokenKind::l_paren)) |
212 | State = TokenState::LeftParen; |
213 | break; |
214 | } |
215 | } |
216 | |
217 | if (State == TokenState::Void && ProtoToken.is(K: tok::TokenKind::r_paren)) |
218 | removeVoidToken(VoidToken, Diagnostic); |
219 | } |
220 | |
221 | void RedundantVoidArgCheck::removeVoidToken(Token VoidToken, |
222 | StringRef Diagnostic) { |
223 | SourceLocation VoidLoc = VoidToken.getLocation(); |
224 | diag(Loc: VoidLoc, Description: Diagnostic) << FixItHint::CreateRemoval(RemoveRange: VoidLoc); |
225 | } |
226 | |
227 | void RedundantVoidArgCheck::processTypedefNameDecl( |
228 | const MatchFinder::MatchResult &Result, |
229 | const TypedefNameDecl *TypedefName) { |
230 | if (protoTypeHasNoParms(QT: TypedefName->getUnderlyingType())) |
231 | removeVoidArgumentTokens(Result, Range: TypedefName->getSourceRange(), |
232 | GrammarLocation: isa<TypedefDecl>(Val: TypedefName) ? "typedef" |
233 | : "type alias" ); |
234 | } |
235 | |
236 | void RedundantVoidArgCheck::processFieldDecl( |
237 | const MatchFinder::MatchResult &Result, const FieldDecl *Member) { |
238 | if (protoTypeHasNoParms(Member->getType())) |
239 | removeVoidArgumentTokens(Result, Range: Member->getSourceRange(), |
240 | GrammarLocation: "field declaration" ); |
241 | } |
242 | |
243 | void RedundantVoidArgCheck::processVarDecl( |
244 | const MatchFinder::MatchResult &Result, const VarDecl *Var) { |
245 | if (protoTypeHasNoParms(Var->getType())) { |
246 | SourceLocation Begin = Var->getBeginLoc(); |
247 | if (Var->hasInit()) { |
248 | SourceLocation InitStart = |
249 | Result.SourceManager->getExpansionLoc(Loc: Var->getInit()->getBeginLoc()) |
250 | .getLocWithOffset(-1); |
251 | removeVoidArgumentTokens(Result, Range: SourceRange(Begin, InitStart), |
252 | GrammarLocation: "variable declaration with initializer" ); |
253 | } else |
254 | removeVoidArgumentTokens(Result, Range: Var->getSourceRange(), |
255 | GrammarLocation: "variable declaration" ); |
256 | } |
257 | } |
258 | |
259 | void RedundantVoidArgCheck::processNamedCastExpr( |
260 | const MatchFinder::MatchResult &Result, const CXXNamedCastExpr *NamedCast) { |
261 | if (protoTypeHasNoParms(NamedCast->getTypeAsWritten())) |
262 | removeVoidArgumentTokens( |
263 | Result, |
264 | Range: NamedCast->getTypeInfoAsWritten()->getTypeLoc().getSourceRange(), |
265 | GrammarLocation: "named cast" ); |
266 | } |
267 | |
268 | void RedundantVoidArgCheck::processExplicitCastExpr( |
269 | const MatchFinder::MatchResult &Result, |
270 | const ExplicitCastExpr *ExplicitCast) { |
271 | if (protoTypeHasNoParms(QT: ExplicitCast->getTypeAsWritten())) |
272 | removeVoidArgumentTokens(Result, Range: ExplicitCast->getSourceRange(), |
273 | GrammarLocation: "cast expression" ); |
274 | } |
275 | |
276 | void RedundantVoidArgCheck::processLambdaExpr( |
277 | const MatchFinder::MatchResult &Result, const LambdaExpr *Lambda) { |
278 | if (Lambda->getLambdaClass()->getLambdaCallOperator()->getNumParams() == 0 && |
279 | Lambda->hasExplicitParameters()) { |
280 | SourceManager *SM = Result.SourceManager; |
281 | TypeLoc TL = Lambda->getLambdaClass()->getLambdaTypeInfo()->getTypeLoc(); |
282 | removeVoidArgumentTokens(Result, |
283 | Range: {SM->getSpellingLoc(Loc: TL.getBeginLoc()), |
284 | SM->getSpellingLoc(Loc: TL.getEndLoc())}, |
285 | GrammarLocation: "lambda expression" ); |
286 | } |
287 | } |
288 | |
289 | } // namespace clang::tidy::modernize |
290 | |