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