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