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