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

Provided by KDAB

Privacy Policy
Update your C++ knowledge – Modern C++11/14/17 Training
Find out more

source code of clang-tools-extra/clang-tidy/cppcoreguidelines/OwningMemoryCheck.cpp