1 | //===--- StringviewNullptrCheck.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 "StringviewNullptrCheck.h" |
10 | #include "../utils/TransformerClangTidyCheck.h" |
11 | #include "clang/AST/ASTContext.h" |
12 | #include "clang/AST/Decl.h" |
13 | #include "clang/AST/OperationKinds.h" |
14 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
15 | #include "clang/ASTMatchers/ASTMatchers.h" |
16 | #include "clang/Tooling/Transformer/RangeSelector.h" |
17 | #include "clang/Tooling/Transformer/RewriteRule.h" |
18 | #include "clang/Tooling/Transformer/Stencil.h" |
19 | #include "llvm/ADT/StringRef.h" |
20 | |
21 | namespace clang::tidy::bugprone { |
22 | |
23 | using namespace ::clang::ast_matchers; |
24 | using namespace ::clang::transformer; |
25 | |
26 | namespace { |
27 | |
28 | AST_MATCHER_P(InitListExpr, initCountIs, unsigned, N) { |
29 | return Node.getNumInits() == N; |
30 | } |
31 | |
32 | AST_MATCHER(clang::VarDecl, isDirectInitialization) { |
33 | return Node.getInitStyle() != clang::VarDecl::InitializationStyle::CInit; |
34 | } |
35 | |
36 | } // namespace |
37 | |
38 | RewriteRuleWith<std::string> StringviewNullptrCheckImpl() { |
39 | auto construction_warning = |
40 | cat(Parts: "constructing basic_string_view from null is undefined; replace with " |
41 | "the default constructor" ); |
42 | auto static_cast_warning = |
43 | cat(Parts: "casting to basic_string_view from null is undefined; replace with " |
44 | "the empty string" ); |
45 | auto argument_construction_warning = |
46 | cat(Parts: "passing null as basic_string_view is undefined; replace with the " |
47 | "empty string" ); |
48 | auto assignment_warning = |
49 | cat(Parts: "assignment to basic_string_view from null is undefined; replace " |
50 | "with the default constructor" ); |
51 | auto relative_comparison_warning = |
52 | cat(Parts: "comparing basic_string_view to null is undefined; replace with the " |
53 | "empty string" ); |
54 | auto equality_comparison_warning = |
55 | cat(Parts: "comparing basic_string_view to null is undefined; replace with the " |
56 | "emptiness query" ); |
57 | |
58 | // Matches declarations and expressions of type `basic_string_view` |
59 | auto HasBasicStringViewType = hasType(InnerMatcher: hasUnqualifiedDesugaredType(InnerMatcher: recordType( |
60 | hasDeclaration(InnerMatcher: cxxRecordDecl(hasName(Name: "::std::basic_string_view" )))))); |
61 | |
62 | // Matches `nullptr` and `(nullptr)` binding to a pointer |
63 | auto NullLiteral = implicitCastExpr( |
64 | hasCastKind(Kind: clang::CK_NullToPointer), |
65 | hasSourceExpression(InnerMatcher: ignoringParens(InnerMatcher: cxxNullPtrLiteralExpr()))); |
66 | |
67 | // Matches `{nullptr}` and `{(nullptr)}` binding to a pointer |
68 | auto NullInitList = initListExpr(initCountIs(N: 1), hasInit(N: 0, InnerMatcher: NullLiteral)); |
69 | |
70 | // Matches `{}` |
71 | auto EmptyInitList = initListExpr(initCountIs(N: 0)); |
72 | |
73 | // Matches null construction without `basic_string_view` type spelling |
74 | auto BasicStringViewConstructingFromNullExpr = |
75 | cxxConstructExpr( |
76 | HasBasicStringViewType, argumentCountIs(N: 1), |
77 | hasAnyArgument(/* `hasArgument` would skip over parens */ InnerMatcher: anyOf( |
78 | NullLiteral, NullInitList, EmptyInitList)), |
79 | unless(cxxTemporaryObjectExpr(/* filters out type spellings */)), |
80 | has(expr().bind(ID: "null_arg_expr" ))) |
81 | .bind(ID: "construct_expr" ); |
82 | |
83 | // `std::string_view(null_arg_expr)` |
84 | auto HandleTemporaryCXXFunctionalCastExpr = |
85 | makeRule(M: cxxFunctionalCastExpr(hasSourceExpression( |
86 | InnerMatcher: BasicStringViewConstructingFromNullExpr)), |
87 | Edits: remove(S: node(ID: "null_arg_expr" )), Metadata: construction_warning); |
88 | |
89 | // `std::string_view{null_arg_expr}` and `(std::string_view){null_arg_expr}` |
90 | auto HandleTemporaryCXXTemporaryObjectExprAndCompoundLiteralExpr = makeRule( |
91 | M: cxxTemporaryObjectExpr(cxxConstructExpr( |
92 | HasBasicStringViewType, argumentCountIs(N: 1), |
93 | hasAnyArgument(/* `hasArgument` would skip over parens */ InnerMatcher: anyOf( |
94 | NullLiteral, NullInitList, EmptyInitList)), |
95 | has(expr().bind(ID: "null_arg_expr" )))), |
96 | Edits: remove(S: node(ID: "null_arg_expr" )), Metadata: construction_warning); |
97 | |
98 | // `(std::string_view) null_arg_expr` |
99 | auto HandleTemporaryCStyleCastExpr = makeRule( |
100 | M: cStyleCastExpr( |
101 | hasSourceExpression(InnerMatcher: BasicStringViewConstructingFromNullExpr)), |
102 | Edits: changeTo(Target: node(ID: "null_arg_expr" ), Replacement: cat(Parts: "{}" )), Metadata: construction_warning); |
103 | |
104 | // `static_cast<std::string_view>(null_arg_expr)` |
105 | auto HandleTemporaryCXXStaticCastExpr = makeRule( |
106 | M: cxxStaticCastExpr( |
107 | hasSourceExpression(InnerMatcher: BasicStringViewConstructingFromNullExpr)), |
108 | Edits: changeTo(Target: node(ID: "null_arg_expr" ), Replacement: cat(Parts: "\"\"" )), Metadata: static_cast_warning); |
109 | |
110 | // `std::string_view sv = null_arg_expr;` |
111 | auto HandleStackCopyInitialization = makeRule( |
112 | M: varDecl(HasBasicStringViewType, |
113 | hasInitializer(InnerMatcher: ignoringImpCasts( |
114 | InnerMatcher: cxxConstructExpr(BasicStringViewConstructingFromNullExpr, |
115 | unless(isListInitialization())))), |
116 | unless(isDirectInitialization())), |
117 | Edits: changeTo(Target: node(ID: "null_arg_expr" ), Replacement: cat(Parts: "{}" )), Metadata: construction_warning); |
118 | |
119 | // `std::string_view sv = {null_arg_expr};` |
120 | auto HandleStackCopyListInitialization = |
121 | makeRule(M: varDecl(HasBasicStringViewType, |
122 | hasInitializer(InnerMatcher: cxxConstructExpr( |
123 | BasicStringViewConstructingFromNullExpr, |
124 | isListInitialization())), |
125 | unless(isDirectInitialization())), |
126 | Edits: remove(S: node(ID: "null_arg_expr" )), Metadata: construction_warning); |
127 | |
128 | // `std::string_view sv(null_arg_expr);` |
129 | auto HandleStackDirectInitialization = |
130 | makeRule(M: varDecl(HasBasicStringViewType, |
131 | hasInitializer(InnerMatcher: cxxConstructExpr( |
132 | BasicStringViewConstructingFromNullExpr, |
133 | unless(isListInitialization()))), |
134 | isDirectInitialization()) |
135 | .bind(ID: "var_decl" ), |
136 | Edits: changeTo(Target: node(ID: "construct_expr" ), Replacement: cat(Parts: name(ID: "var_decl" ))), |
137 | Metadata: construction_warning); |
138 | |
139 | // `std::string_view sv{null_arg_expr};` |
140 | auto HandleStackDirectListInitialization = |
141 | makeRule(M: varDecl(HasBasicStringViewType, |
142 | hasInitializer(InnerMatcher: cxxConstructExpr( |
143 | BasicStringViewConstructingFromNullExpr, |
144 | isListInitialization())), |
145 | isDirectInitialization()), |
146 | Edits: remove(S: node(ID: "null_arg_expr" )), Metadata: construction_warning); |
147 | |
148 | // `struct S { std::string_view sv = null_arg_expr; };` |
149 | auto HandleFieldInClassCopyInitialization = makeRule( |
150 | M: fieldDecl(HasBasicStringViewType, |
151 | hasInClassInitializer(InnerMatcher: ignoringImpCasts( |
152 | InnerMatcher: cxxConstructExpr(BasicStringViewConstructingFromNullExpr, |
153 | unless(isListInitialization()))))), |
154 | Edits: changeTo(Target: node(ID: "null_arg_expr" ), Replacement: cat(Parts: "{}" )), Metadata: construction_warning); |
155 | |
156 | // `struct S { std::string_view sv = {null_arg_expr}; };` and |
157 | // `struct S { std::string_view sv{null_arg_expr}; };` |
158 | auto HandleFieldInClassCopyListAndDirectListInitialization = makeRule( |
159 | M: fieldDecl(HasBasicStringViewType, |
160 | hasInClassInitializer(InnerMatcher: ignoringImpCasts( |
161 | InnerMatcher: cxxConstructExpr(BasicStringViewConstructingFromNullExpr, |
162 | isListInitialization())))), |
163 | Edits: remove(S: node(ID: "null_arg_expr" )), Metadata: construction_warning); |
164 | |
165 | // `class C { std::string_view sv; C() : sv(null_arg_expr) {} };` |
166 | auto HandleConstructorDirectInitialization = |
167 | makeRule(M: cxxCtorInitializer(forField(InnerMatcher: fieldDecl(HasBasicStringViewType)), |
168 | withInitializer(InnerMatcher: cxxConstructExpr( |
169 | BasicStringViewConstructingFromNullExpr, |
170 | unless(isListInitialization())))), |
171 | Edits: remove(S: node(ID: "null_arg_expr" )), Metadata: construction_warning); |
172 | |
173 | // `class C { std::string_view sv; C() : sv{null_arg_expr} {} };` |
174 | auto HandleConstructorDirectListInitialization = |
175 | makeRule(M: cxxCtorInitializer(forField(InnerMatcher: fieldDecl(HasBasicStringViewType)), |
176 | withInitializer(InnerMatcher: cxxConstructExpr( |
177 | BasicStringViewConstructingFromNullExpr, |
178 | isListInitialization()))), |
179 | Edits: remove(S: node(ID: "null_arg_expr" )), Metadata: construction_warning); |
180 | |
181 | // `void f(std::string_view sv = null_arg_expr);` |
182 | auto HandleDefaultArgumentCopyInitialization = makeRule( |
183 | M: parmVarDecl(HasBasicStringViewType, |
184 | hasInitializer(InnerMatcher: ignoringImpCasts( |
185 | InnerMatcher: cxxConstructExpr(BasicStringViewConstructingFromNullExpr, |
186 | unless(isListInitialization()))))), |
187 | Edits: changeTo(Target: node(ID: "null_arg_expr" ), Replacement: cat(Parts: "{}" )), Metadata: construction_warning); |
188 | |
189 | // `void f(std::string_view sv = {null_arg_expr});` |
190 | auto HandleDefaultArgumentCopyListInitialization = |
191 | makeRule(M: parmVarDecl(HasBasicStringViewType, |
192 | hasInitializer(InnerMatcher: cxxConstructExpr( |
193 | BasicStringViewConstructingFromNullExpr, |
194 | isListInitialization()))), |
195 | Edits: remove(S: node(ID: "null_arg_expr" )), Metadata: construction_warning); |
196 | |
197 | // `new std::string_view(null_arg_expr)` |
198 | auto HandleHeapDirectInitialization = makeRule( |
199 | M: cxxNewExpr(has(cxxConstructExpr(BasicStringViewConstructingFromNullExpr, |
200 | unless(isListInitialization()))), |
201 | unless(isArray()), unless(hasAnyPlacementArg(InnerMatcher: anything()))), |
202 | Edits: remove(S: node(ID: "null_arg_expr" )), Metadata: construction_warning); |
203 | |
204 | // `new std::string_view{null_arg_expr}` |
205 | auto HandleHeapDirectListInitialization = makeRule( |
206 | M: cxxNewExpr(has(cxxConstructExpr(BasicStringViewConstructingFromNullExpr, |
207 | isListInitialization())), |
208 | unless(isArray()), unless(hasAnyPlacementArg(InnerMatcher: anything()))), |
209 | Edits: remove(S: node(ID: "null_arg_expr" )), Metadata: construction_warning); |
210 | |
211 | // `function(null_arg_expr)` |
212 | auto HandleFunctionArgumentInitialization = |
213 | makeRule(M: callExpr(hasAnyArgument(InnerMatcher: ignoringImpCasts( |
214 | InnerMatcher: BasicStringViewConstructingFromNullExpr)), |
215 | unless(cxxOperatorCallExpr())), |
216 | Edits: changeTo(Target: node(ID: "construct_expr" ), Replacement: cat(Parts: "\"\"" )), |
217 | Metadata: argument_construction_warning); |
218 | |
219 | // `sv = null_arg_expr` |
220 | auto HandleAssignment = makeRule( |
221 | M: cxxOperatorCallExpr(hasOverloadedOperatorName(Name: "=" ), |
222 | hasRHS(InnerMatcher: materializeTemporaryExpr( |
223 | has(BasicStringViewConstructingFromNullExpr)))), |
224 | Edits: changeTo(Target: node(ID: "construct_expr" ), Replacement: cat(Parts: "{}" )), Metadata: assignment_warning); |
225 | |
226 | // `sv < null_arg_expr` |
227 | auto HandleRelativeComparison = makeRule( |
228 | M: cxxOperatorCallExpr(hasAnyOverloadedOperatorName("<" , "<=" , ">" , ">=" ), |
229 | hasEitherOperand(InnerMatcher: ignoringImpCasts( |
230 | InnerMatcher: BasicStringViewConstructingFromNullExpr))), |
231 | Edits: changeTo(Target: node(ID: "construct_expr" ), Replacement: cat(Parts: "\"\"" )), |
232 | Metadata: relative_comparison_warning); |
233 | |
234 | // `sv == null_arg_expr` |
235 | auto HandleEmptyEqualityComparison = makeRule( |
236 | M: cxxOperatorCallExpr( |
237 | hasOverloadedOperatorName(Name: "==" ), |
238 | hasOperands(Matcher1: ignoringImpCasts(InnerMatcher: BasicStringViewConstructingFromNullExpr), |
239 | Matcher2: traverse(TK: clang::TK_IgnoreUnlessSpelledInSource, |
240 | InnerMatcher: expr().bind(ID: "instance" )))) |
241 | .bind(ID: "root" ), |
242 | Edits: changeTo(Target: node(ID: "root" ), Replacement: cat(Parts: access(BaseId: "instance" , Member: cat(Parts: "empty" )), Parts: "()" )), |
243 | Metadata: equality_comparison_warning); |
244 | |
245 | // `sv != null_arg_expr` |
246 | auto HandleNonEmptyEqualityComparison = makeRule( |
247 | M: cxxOperatorCallExpr( |
248 | hasOverloadedOperatorName(Name: "!=" ), |
249 | hasOperands(Matcher1: ignoringImpCasts(InnerMatcher: BasicStringViewConstructingFromNullExpr), |
250 | Matcher2: traverse(TK: clang::TK_IgnoreUnlessSpelledInSource, |
251 | InnerMatcher: expr().bind(ID: "instance" )))) |
252 | .bind(ID: "root" ), |
253 | Edits: changeTo(Target: node(ID: "root" ), Replacement: cat(Parts: "!" , Parts: access(BaseId: "instance" , Member: cat(Parts: "empty" )), Parts: "()" )), |
254 | Metadata: equality_comparison_warning); |
255 | |
256 | // `return null_arg_expr;` |
257 | auto HandleReturnStatement = makeRule( |
258 | M: returnStmt(hasReturnValue( |
259 | InnerMatcher: ignoringImpCasts(InnerMatcher: BasicStringViewConstructingFromNullExpr))), |
260 | Edits: changeTo(Target: node(ID: "construct_expr" ), Replacement: cat(Parts: "{}" )), Metadata: construction_warning); |
261 | |
262 | // `T(null_arg_expr)` |
263 | auto HandleConstructorInvocation = |
264 | makeRule(M: cxxConstructExpr( |
265 | hasAnyArgument(/* `hasArgument` would skip over parens */ |
266 | InnerMatcher: ignoringImpCasts( |
267 | InnerMatcher: BasicStringViewConstructingFromNullExpr)), |
268 | unless(HasBasicStringViewType)), |
269 | Edits: changeTo(Target: node(ID: "construct_expr" ), Replacement: cat(Parts: "\"\"" )), |
270 | Metadata: argument_construction_warning); |
271 | |
272 | return applyFirst( |
273 | Rules: {HandleTemporaryCXXFunctionalCastExpr, |
274 | HandleTemporaryCXXTemporaryObjectExprAndCompoundLiteralExpr, |
275 | HandleTemporaryCStyleCastExpr, |
276 | HandleTemporaryCXXStaticCastExpr, |
277 | HandleStackCopyInitialization, |
278 | HandleStackCopyListInitialization, |
279 | HandleStackDirectInitialization, |
280 | HandleStackDirectListInitialization, |
281 | HandleFieldInClassCopyInitialization, |
282 | HandleFieldInClassCopyListAndDirectListInitialization, |
283 | HandleConstructorDirectInitialization, |
284 | HandleConstructorDirectListInitialization, |
285 | HandleDefaultArgumentCopyInitialization, |
286 | HandleDefaultArgumentCopyListInitialization, |
287 | HandleHeapDirectInitialization, |
288 | HandleHeapDirectListInitialization, |
289 | HandleFunctionArgumentInitialization, |
290 | HandleAssignment, |
291 | HandleRelativeComparison, |
292 | HandleEmptyEqualityComparison, |
293 | HandleNonEmptyEqualityComparison, |
294 | HandleReturnStatement, |
295 | HandleConstructorInvocation}); |
296 | } |
297 | |
298 | StringviewNullptrCheck::StringviewNullptrCheck(StringRef Name, |
299 | ClangTidyContext *Context) |
300 | : utils::TransformerClangTidyCheck(StringviewNullptrCheckImpl(), Name, |
301 | Context) {} |
302 | |
303 | } // namespace clang::tidy::bugprone |
304 | |