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