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