clang-tools  11.0.0
OwningMemoryCheck.cpp
Go to the documentation of this file.
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(
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 
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 
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 
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
clang::tidy::bugprone::hasAnyListedName
static Matcher< TypedefDecl > hasAnyListedName(const std::string &Names)
Definition: SignedCharMisuseCheck.cpp:23
clang::ast_matchers
Definition: AbseilMatcher.h:14
Decl
const FunctionDecl * Decl
Definition: AvoidBindCheck.cpp:100
clang::tidy::utils::options::parseStringList
std::vector< std::string > parseStringList(StringRef Option)
Parse a semicolon separated list of strings.
Definition: OptionsUtils.cpp:18
clang::clangd::CompletionItemKind::Function
OwningMemoryCheck.h
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
clang::tidy::ClangTidyOptions::OptionMap
std::map< std::string, ClangTidyValue > OptionMap
Definition: ClangTidyOptions.h:111