clang-tools  16.0.0git
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 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
clang::tidy::ClangTidyOptions::OptionMap
llvm::StringMap< ClangTidyValue > OptionMap
Definition: ClangTidyOptions.h:115
clang::ast_matchers
Definition: AbseilMatcher.h:14
Decl
const FunctionDecl * Decl
Definition: AvoidBindCheck.cpp:100
clang::clangd::CompletionItemKind::Function
@ Function
OwningMemoryCheck.h
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
clang::tidy::utils::options::parseStringList
std::vector< StringRef > parseStringList(StringRef Option)
Parse a semicolon separated list of strings.
Definition: OptionsUtils.cpp:19
clang::clangd::check
bool check(llvm::StringRef File, const ThreadsafeFS &TFS, const ClangdLSPServer::Options &Opts)
Definition: Check.cpp:418