• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
hasAnyListedName(const std::string & FunctionNames)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 
storeOptions(ClangTidyOptions::OptionMap & Opts)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.
registerMatchers(MatchFinder * Finder)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(
72       traverse(ast_type_traits::TK_AsIs,
73                cxxDeleteExpr(hasDescendant(declRefExpr(unless(ConsideredOwner))
74                                                .bind("deleted_variable")))
75                    .bind("delete_expr")),
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(
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")),
92       this);
93 
94   // Matching assignment to owners, with the rhs not being an owner nor creating
95   // one.
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);
102 
103   // Matching initialization of owners with non-owners, nor creating owners.
104   Finder->addMatcher(
105       traverse(ast_type_traits::TK_AsIs,
106                namedDecl(
107                    varDecl(hasInitializer(unless(ConsideredOwner)), IsOwnerType)
108                        .bind("owner_initialization"))),
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.
123   Finder->addMatcher(traverse(ast_type_traits::TK_AsIs,
124                               cxxRecordDecl(HasConstructorInitializerForOwner)),
125                      this);
126 
127   // Matching on assignment operations where the RHS is a newly created owner,
128   // but the LHS is not an owner.
129   Finder->addMatcher(binaryOperator(isAssignmentOperator(),
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(
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"))),
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 
check(const MatchFinder::MatchResult & Result)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 
handleDeletion(const BoundNodes & Nodes)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 
handleLegacyConsumers(const BoundNodes & Nodes)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 
229   // FIXME: `freopen` should be handled separately because it takes the filename
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 
handleExpectedOwner(const BoundNodes & Nodes)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.
handleAssignmentAndInit(const BoundNodes & Nodes)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.
handleAssignmentFromNewOwner(const BoundNodes & Nodes)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 
handleReturnValues(const BoundNodes & Nodes)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 
handleOwnerMembers(const BoundNodes & Nodes)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
397