150
|
1 //===--- OwningMemoryCheck.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 "OwningMemoryCheck.h"
|
|
10 #include "../utils/Matchers.h"
|
|
11 #include "../utils/OptionsUtils.h"
|
|
12 #include "clang/AST/ASTContext.h"
|
|
13 #include "clang/ASTMatchers/ASTMatchFinder.h"
|
|
14 #include <string>
|
|
15 #include <vector>
|
|
16
|
|
17 using namespace clang::ast_matchers;
|
|
18 using namespace clang::ast_matchers::internal;
|
|
19
|
|
20 namespace clang {
|
|
21 namespace tidy {
|
|
22 namespace cppcoreguidelines {
|
|
23
|
|
24 // FIXME: Copied from 'NoMallocCheck.cpp'. Has to be refactored into 'util' or
|
|
25 // something like that.
|
|
26 namespace {
|
|
27 Matcher<FunctionDecl> hasAnyListedName(const std::string &FunctionNames) {
|
|
28 const std::vector<std::string> NameList =
|
|
29 utils::options::parseStringList(FunctionNames);
|
|
30 return hasAnyName(std::vector<StringRef>(NameList.begin(), NameList.end()));
|
|
31 }
|
|
32 } // namespace
|
|
33
|
|
34 void OwningMemoryCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
|
|
35 Options.store(Opts, "LegacyResourceProducers", LegacyResourceProducers);
|
|
36 Options.store(Opts, "LegacyResourceConsumers", LegacyResourceConsumers);
|
|
37 }
|
|
38
|
|
39 /// Match common cases, where the owner semantic is relevant, like function
|
|
40 /// calls, delete expressions and others.
|
|
41 void OwningMemoryCheck::registerMatchers(MatchFinder *Finder) {
|
|
42 const auto OwnerDecl = typeAliasTemplateDecl(hasName("::gsl::owner"));
|
|
43 const auto IsOwnerType = hasType(OwnerDecl);
|
|
44
|
|
45 const auto LegacyCreatorFunctions = hasAnyListedName(LegacyResourceProducers);
|
|
46 const auto LegacyConsumerFunctions =
|
|
47 hasAnyListedName(LegacyResourceConsumers);
|
|
48
|
|
49 // Legacy functions that are use for resource management but cannot be
|
|
50 // updated to use `gsl::owner<>`, like standard C memory management.
|
|
51 const auto CreatesLegacyOwner =
|
|
52 callExpr(callee(functionDecl(LegacyCreatorFunctions)));
|
|
53 // C-style functions like `::malloc()` sometimes create owners as void*
|
|
54 // which is expected to be cast to the correct type in C++. This case
|
|
55 // must be catched explicitly.
|
|
56 const auto LegacyOwnerCast =
|
|
57 castExpr(hasSourceExpression(CreatesLegacyOwner));
|
|
58 // Functions that do manual resource management but cannot be updated to use
|
|
59 // owner. Best example is `::free()`.
|
|
60 const auto LegacyOwnerConsumers = functionDecl(LegacyConsumerFunctions);
|
|
61
|
|
62 const auto CreatesOwner =
|
|
63 anyOf(cxxNewExpr(),
|
|
64 callExpr(callee(
|
|
65 functionDecl(returns(qualType(hasDeclaration(OwnerDecl)))))),
|
|
66 CreatesLegacyOwner, LegacyOwnerCast);
|
|
67
|
|
68 const auto ConsideredOwner = eachOf(IsOwnerType, CreatesOwner);
|
|
69
|
|
70 // Find delete expressions that delete non-owners.
|
|
71 Finder->addMatcher(
|
173
|
72 traverse(ast_type_traits::TK_AsIs,
|
|
73 cxxDeleteExpr(hasDescendant(declRefExpr(unless(ConsideredOwner))
|
|
74 .bind("deleted_variable")))
|
|
75 .bind("delete_expr")),
|
150
|
76 this);
|
|
77
|
|
78 // Ignoring the implicit casts is vital because the legacy owners do not work
|
|
79 // with the 'owner<>' annotation and therefore always implicitly cast to the
|
|
80 // legacy type (even 'void *').
|
|
81 //
|
|
82 // Furthermore, legacy owner functions are assumed to use raw pointers for
|
|
83 // resources. This check assumes that all pointer arguments of a legacy
|
|
84 // functions shall be 'gsl::owner<>'.
|
|
85 Finder->addMatcher(
|
173
|
86 traverse(ast_type_traits::TK_AsIs,
|
|
87 callExpr(callee(LegacyOwnerConsumers),
|
|
88 hasAnyArgument(
|
|
89 expr(unless(ignoringImpCasts(ConsideredOwner)),
|
|
90 hasType(pointerType()))))
|
|
91 .bind("legacy_consumer")),
|
150
|
92 this);
|
|
93
|
|
94 // Matching assignment to owners, with the rhs not being an owner nor creating
|
|
95 // one.
|
173
|
96 Finder->addMatcher(
|
|
97 traverse(ast_type_traits::TK_AsIs,
|
|
98 binaryOperator(isAssignmentOperator(), hasLHS(IsOwnerType),
|
|
99 hasRHS(unless(ConsideredOwner)))
|
|
100 .bind("owner_assignment")),
|
|
101 this);
|
150
|
102
|
|
103 // Matching initialization of owners with non-owners, nor creating owners.
|
|
104 Finder->addMatcher(
|
173
|
105 traverse(ast_type_traits::TK_AsIs,
|
|
106 namedDecl(
|
|
107 varDecl(hasInitializer(unless(ConsideredOwner)), IsOwnerType)
|
|
108 .bind("owner_initialization"))),
|
150
|
109 this);
|
|
110
|
|
111 const auto HasConstructorInitializerForOwner =
|
|
112 has(cxxConstructorDecl(forEachConstructorInitializer(
|
|
113 cxxCtorInitializer(
|
|
114 isMemberInitializer(), forField(IsOwnerType),
|
|
115 withInitializer(
|
|
116 // Avoid templatesdeclaration with
|
|
117 // excluding parenListExpr.
|
|
118 allOf(unless(ConsideredOwner), unless(parenListExpr()))))
|
|
119 .bind("owner_member_initializer"))));
|
|
120
|
|
121 // Match class member initialization that expects owners, but does not get
|
|
122 // them.
|
173
|
123 Finder->addMatcher(traverse(ast_type_traits::TK_AsIs,
|
|
124 cxxRecordDecl(HasConstructorInitializerForOwner)),
|
|
125 this);
|
150
|
126
|
|
127 // Matching on assignment operations where the RHS is a newly created owner,
|
|
128 // but the LHS is not an owner.
|
173
|
129 Finder->addMatcher(binaryOperator(isAssignmentOperator(),
|
150
|
130 hasLHS(unless(IsOwnerType)),
|
|
131 hasRHS(CreatesOwner))
|
|
132 .bind("bad_owner_creation_assignment"),
|
|
133 this);
|
|
134
|
|
135 // Matching on initialization operations where the initial value is a newly
|
|
136 // created owner, but the LHS is not an owner.
|
|
137 Finder->addMatcher(
|
173
|
138 traverse(
|
|
139 ast_type_traits::TK_AsIs,
|
|
140 namedDecl(
|
|
141 varDecl(eachOf(allOf(hasInitializer(CreatesOwner),
|
|
142 unless(IsOwnerType)),
|
|
143 allOf(hasInitializer(ConsideredOwner),
|
|
144 hasType(autoType().bind("deduced_type")))))
|
|
145 .bind("bad_owner_creation_variable"))),
|
150
|
146 this);
|
|
147
|
|
148 // Match on all function calls that expect owners as arguments, but didn't
|
|
149 // get them.
|
|
150 Finder->addMatcher(
|
|
151 callExpr(forEachArgumentWithParam(
|
|
152 expr(unless(ConsideredOwner)).bind("expected_owner_argument"),
|
|
153 parmVarDecl(IsOwnerType))),
|
|
154 this);
|
|
155
|
|
156 // Matching for function calls where one argument is a created owner, but the
|
|
157 // parameter type is not an owner.
|
|
158 Finder->addMatcher(callExpr(forEachArgumentWithParam(
|
|
159 expr(CreatesOwner).bind("bad_owner_creation_argument"),
|
|
160 parmVarDecl(unless(IsOwnerType))
|
|
161 .bind("bad_owner_creation_parameter"))),
|
|
162 this);
|
|
163
|
|
164 // Matching on functions, that return an owner/resource, but don't declare
|
|
165 // their return type as owner.
|
|
166 Finder->addMatcher(
|
|
167 functionDecl(hasDescendant(returnStmt(hasReturnValue(ConsideredOwner))
|
|
168 .bind("bad_owner_return")),
|
|
169 unless(returns(qualType(hasDeclaration(OwnerDecl)))))
|
|
170 .bind("function_decl"),
|
|
171 this);
|
|
172
|
|
173 // Match on classes that have an owner as member, but don't declare a
|
|
174 // destructor to properly release the owner.
|
|
175 Finder->addMatcher(
|
|
176 cxxRecordDecl(
|
|
177 has(fieldDecl(IsOwnerType).bind("undestructed_owner_member")),
|
|
178 anyOf(unless(has(cxxDestructorDecl())),
|
|
179 has(cxxDestructorDecl(anyOf(isDefaulted(), isDeleted())))))
|
|
180 .bind("non_destructor_class"),
|
|
181 this);
|
|
182 }
|
|
183
|
|
184 void OwningMemoryCheck::check(const MatchFinder::MatchResult &Result) {
|
|
185 const auto &Nodes = Result.Nodes;
|
|
186
|
|
187 bool CheckExecuted = false;
|
|
188 CheckExecuted |= handleDeletion(Nodes);
|
|
189 CheckExecuted |= handleLegacyConsumers(Nodes);
|
|
190 CheckExecuted |= handleExpectedOwner(Nodes);
|
|
191 CheckExecuted |= handleAssignmentAndInit(Nodes);
|
|
192 CheckExecuted |= handleAssignmentFromNewOwner(Nodes);
|
|
193 CheckExecuted |= handleReturnValues(Nodes);
|
|
194 CheckExecuted |= handleOwnerMembers(Nodes);
|
|
195
|
|
196 assert(CheckExecuted &&
|
|
197 "None of the subroutines executed, logic error in matcher!");
|
|
198 }
|
|
199
|
|
200 bool OwningMemoryCheck::handleDeletion(const BoundNodes &Nodes) {
|
|
201 // Result of delete matchers.
|
|
202 const auto *DeleteStmt = Nodes.getNodeAs<CXXDeleteExpr>("delete_expr");
|
|
203 const auto *DeletedVariable =
|
|
204 Nodes.getNodeAs<DeclRefExpr>("deleted_variable");
|
|
205
|
|
206 // Deletion of non-owners, with `delete variable;`
|
|
207 if (DeleteStmt) {
|
|
208 diag(DeleteStmt->getBeginLoc(),
|
|
209 "deleting a pointer through a type that is "
|
|
210 "not marked 'gsl::owner<>'; consider using a "
|
|
211 "smart pointer instead")
|
|
212 << DeletedVariable->getSourceRange();
|
|
213
|
|
214 // FIXME: The declaration of the variable that was deleted can be
|
|
215 // rewritten.
|
|
216 const ValueDecl *Decl = DeletedVariable->getDecl();
|
|
217 diag(Decl->getBeginLoc(), "variable declared here", DiagnosticIDs::Note)
|
|
218 << Decl->getSourceRange();
|
|
219
|
|
220 return true;
|
|
221 }
|
|
222 return false;
|
|
223 }
|
|
224
|
|
225 bool OwningMemoryCheck::handleLegacyConsumers(const BoundNodes &Nodes) {
|
|
226 // Result of matching for legacy consumer-functions like `::free()`.
|
|
227 const auto *LegacyConsumer = Nodes.getNodeAs<CallExpr>("legacy_consumer");
|
|
228
|
173
|
229 // FIXME: `freopen` should be handled separately because it takes the filename
|
150
|
230 // as a pointer, which should not be an owner. The argument that is an owner
|
|
231 // is known and the false positive coming from the filename can be avoided.
|
|
232 if (LegacyConsumer) {
|
|
233 diag(LegacyConsumer->getBeginLoc(),
|
|
234 "calling legacy resource function without passing a 'gsl::owner<>'")
|
|
235 << LegacyConsumer->getSourceRange();
|
|
236 return true;
|
|
237 }
|
|
238 return false;
|
|
239 }
|
|
240
|
|
241 bool OwningMemoryCheck::handleExpectedOwner(const BoundNodes &Nodes) {
|
|
242 // Result of function call matchers.
|
|
243 const auto *ExpectedOwner = Nodes.getNodeAs<Expr>("expected_owner_argument");
|
|
244
|
|
245 // Expected function argument to be owner.
|
|
246 if (ExpectedOwner) {
|
|
247 diag(ExpectedOwner->getBeginLoc(),
|
|
248 "expected argument of type 'gsl::owner<>'; got %0")
|
|
249 << ExpectedOwner->getType() << ExpectedOwner->getSourceRange();
|
|
250 return true;
|
|
251 }
|
|
252 return false;
|
|
253 }
|
|
254
|
|
255 /// Assignment and initialization of owner variables.
|
|
256 bool OwningMemoryCheck::handleAssignmentAndInit(const BoundNodes &Nodes) {
|
|
257 const auto *OwnerAssignment =
|
|
258 Nodes.getNodeAs<BinaryOperator>("owner_assignment");
|
|
259 const auto *OwnerInitialization =
|
|
260 Nodes.getNodeAs<VarDecl>("owner_initialization");
|
|
261 const auto *OwnerInitializer =
|
|
262 Nodes.getNodeAs<CXXCtorInitializer>("owner_member_initializer");
|
|
263
|
|
264 // Assignments to owners.
|
|
265 if (OwnerAssignment) {
|
|
266 diag(OwnerAssignment->getBeginLoc(),
|
|
267 "expected assignment source to be of type 'gsl::owner<>'; got %0")
|
|
268 << OwnerAssignment->getRHS()->getType()
|
|
269 << OwnerAssignment->getSourceRange();
|
|
270 return true;
|
|
271 }
|
|
272
|
|
273 // Initialization of owners.
|
|
274 if (OwnerInitialization) {
|
|
275 diag(OwnerInitialization->getBeginLoc(),
|
|
276 "expected initialization with value of type 'gsl::owner<>'; got %0")
|
|
277 << OwnerInitialization->getAnyInitializer()->getType()
|
|
278 << OwnerInitialization->getSourceRange();
|
|
279 return true;
|
|
280 }
|
|
281
|
|
282 // Initializer of class constructors that initialize owners.
|
|
283 if (OwnerInitializer) {
|
|
284 diag(OwnerInitializer->getSourceLocation(),
|
|
285 "expected initialization of owner member variable with value of type "
|
|
286 "'gsl::owner<>'; got %0")
|
|
287 // FIXME: the expression from getInit has type 'void', but the type
|
|
288 // of the supplied argument would be of interest.
|
|
289 << OwnerInitializer->getInit()->getType()
|
|
290 << OwnerInitializer->getSourceRange();
|
|
291 return true;
|
|
292 }
|
|
293 return false;
|
|
294 }
|
|
295
|
|
296 /// Problematic assignment and initializations, since the assigned value is a
|
|
297 /// newly created owner.
|
|
298 bool OwningMemoryCheck::handleAssignmentFromNewOwner(const BoundNodes &Nodes) {
|
|
299 const auto *BadOwnerAssignment =
|
|
300 Nodes.getNodeAs<BinaryOperator>("bad_owner_creation_assignment");
|
|
301 const auto *BadOwnerInitialization =
|
|
302 Nodes.getNodeAs<VarDecl>("bad_owner_creation_variable");
|
|
303
|
|
304 const auto *BadOwnerArgument =
|
|
305 Nodes.getNodeAs<Expr>("bad_owner_creation_argument");
|
|
306 const auto *BadOwnerParameter =
|
|
307 Nodes.getNodeAs<ParmVarDecl>("bad_owner_creation_parameter");
|
|
308
|
|
309 // Bad assignments to non-owners, where the RHS is a newly created owner.
|
|
310 if (BadOwnerAssignment) {
|
|
311 diag(BadOwnerAssignment->getBeginLoc(),
|
|
312 "assigning newly created 'gsl::owner<>' to non-owner %0")
|
|
313 << BadOwnerAssignment->getLHS()->getType()
|
|
314 << BadOwnerAssignment->getSourceRange();
|
|
315 return true;
|
|
316 }
|
|
317
|
|
318 // Bad initialization of non-owners, where the RHS is a newly created owner.
|
|
319 if (BadOwnerInitialization) {
|
|
320 diag(BadOwnerInitialization->getBeginLoc(),
|
|
321 "initializing non-owner %0 with a newly created 'gsl::owner<>'")
|
|
322 << BadOwnerInitialization->getType()
|
|
323 << BadOwnerInitialization->getSourceRange();
|
|
324
|
|
325 // FIXME: FixitHint to rewrite the type of the initialized variable
|
|
326 // as 'gsl::owner<OriginalType>'
|
|
327
|
|
328 // If the type of the variable was deduced, the wrapping owner typedef is
|
|
329 // eliminated, therefore the check emits a special note for that case.
|
|
330 if (Nodes.getNodeAs<AutoType>("deduced_type")) {
|
|
331 diag(BadOwnerInitialization->getBeginLoc(),
|
|
332 "type deduction did not result in an owner", DiagnosticIDs::Note);
|
|
333 }
|
|
334 return true;
|
|
335 }
|
|
336
|
|
337 // Function call, where one arguments is a newly created owner, but the
|
|
338 // parameter type is not.
|
|
339 if (BadOwnerArgument) {
|
|
340 assert(BadOwnerParameter &&
|
|
341 "parameter for the problematic argument not found");
|
|
342 diag(BadOwnerArgument->getBeginLoc(), "initializing non-owner argument of "
|
|
343 "type %0 with a newly created "
|
|
344 "'gsl::owner<>'")
|
|
345 << BadOwnerParameter->getType() << BadOwnerArgument->getSourceRange();
|
|
346 return true;
|
|
347 }
|
|
348 return false;
|
|
349 }
|
|
350
|
|
351 bool OwningMemoryCheck::handleReturnValues(const BoundNodes &Nodes) {
|
|
352 // Function return statements, that are owners/resources, but the function
|
|
353 // declaration does not declare its return value as owner.
|
|
354 const auto *BadReturnType = Nodes.getNodeAs<ReturnStmt>("bad_owner_return");
|
|
355 const auto *Function = Nodes.getNodeAs<FunctionDecl>("function_decl");
|
|
356
|
|
357 // Function return values, that should be owners but aren't.
|
|
358 if (BadReturnType) {
|
|
359 // The returned value is a resource or variable that was not annotated with
|
|
360 // owner<> and the function return type is not owner<>.
|
|
361 diag(BadReturnType->getBeginLoc(),
|
|
362 "returning a newly created resource of "
|
|
363 "type %0 or 'gsl::owner<>' from a "
|
|
364 "function whose return type is not 'gsl::owner<>'")
|
|
365 << Function->getReturnType() << BadReturnType->getSourceRange();
|
|
366
|
|
367 // FIXME: Rewrite the return type as 'gsl::owner<OriginalType>'
|
|
368 return true;
|
|
369 }
|
|
370 return false;
|
|
371 }
|
|
372
|
|
373 bool OwningMemoryCheck::handleOwnerMembers(const BoundNodes &Nodes) {
|
|
374 // Classes, that have owners as member, but do not declare destructors
|
|
375 // accordingly.
|
|
376 const auto *BadClass = Nodes.getNodeAs<CXXRecordDecl>("non_destructor_class");
|
|
377
|
|
378 // Classes, that contains owners, but do not declare destructors.
|
|
379 if (BadClass) {
|
|
380 const auto *DeclaredOwnerMember =
|
|
381 Nodes.getNodeAs<FieldDecl>("undestructed_owner_member");
|
|
382 assert(DeclaredOwnerMember &&
|
|
383 "match on class with bad destructor but without a declared owner");
|
|
384
|
|
385 diag(DeclaredOwnerMember->getBeginLoc(),
|
|
386 "member variable of type 'gsl::owner<>' requires the class %0 to "
|
|
387 "implement a destructor to release the owned resource")
|
|
388 << BadClass;
|
|
389 return true;
|
|
390 }
|
|
391 return false;
|
|
392 }
|
|
393
|
|
394 } // namespace cppcoreguidelines
|
|
395 } // namespace tidy
|
|
396 } // namespace clang
|