1 //===--- OwningMemoryCheck.cpp - clang-tidy--------------------------------===//
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
7 //===----------------------------------------------------------------------===//
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"
17 using namespace clang::ast_matchers
;
18 using namespace clang::ast_matchers::internal
;
20 namespace clang::tidy::cppcoreguidelines
{
23 AST_MATCHER_P(LambdaExpr
, hasCallOperator
, Matcher
<CXXMethodDecl
>,
25 return InnerMatcher
.matches(*Node
.getCallOperator(), Finder
, Builder
);
28 AST_MATCHER_P(LambdaExpr
, hasLambdaBody
, Matcher
<Stmt
>, InnerMatcher
) {
29 return InnerMatcher
.matches(*Node
.getBody(), Finder
, Builder
);
33 void OwningMemoryCheck::storeOptions(ClangTidyOptions::OptionMap
&Opts
) {
34 Options
.store(Opts
, "LegacyResourceProducers", LegacyResourceProducers
);
35 Options
.store(Opts
, "LegacyResourceConsumers", LegacyResourceConsumers
);
38 /// Match common cases, where the owner semantic is relevant, like function
39 /// calls, delete expressions and others.
40 void OwningMemoryCheck::registerMatchers(MatchFinder
*Finder
) {
41 const auto OwnerDecl
= typeAliasTemplateDecl(hasName("::gsl::owner"));
42 const auto IsOwnerType
= hasType(OwnerDecl
);
44 const auto LegacyCreatorFunctions
=
45 hasAnyName(utils::options::parseStringList(LegacyResourceProducers
));
46 const auto LegacyConsumerFunctions
=
47 hasAnyName(utils::options::parseStringList(LegacyResourceConsumers
));
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 caught 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
);
62 const auto CreatesOwner
=
65 functionDecl(returns(qualType(hasDeclaration(OwnerDecl
)))))),
66 CreatesLegacyOwner
, LegacyOwnerCast
);
68 const auto ConsideredOwner
= eachOf(IsOwnerType
, CreatesOwner
);
69 const auto ScopeDeclaration
= anyOf(translationUnitDecl(), namespaceDecl(),
70 recordDecl(), functionDecl());
72 // Find delete expressions that delete non-owners.
75 cxxDeleteExpr(hasDescendant(declRefExpr(unless(ConsideredOwner
))
76 .bind("deleted_variable")))
77 .bind("delete_expr")),
80 // Ignoring the implicit casts is vital because the legacy owners do not work
81 // with the 'owner<>' annotation and therefore always implicitly cast to the
82 // legacy type (even 'void *').
84 // Furthermore, legacy owner functions are assumed to use raw pointers for
85 // resources. This check assumes that all pointer arguments of a legacy
86 // functions shall be 'gsl::owner<>'.
88 traverse(TK_AsIs
, callExpr(callee(LegacyOwnerConsumers
),
90 unless(ignoringImpCasts(ConsideredOwner
)),
91 hasType(pointerType()))))
92 .bind("legacy_consumer")),
95 // Matching assignment to owners, with the rhs not being an owner nor creating
99 binaryOperator(isAssignmentOperator(), hasLHS(IsOwnerType
),
100 hasRHS(unless(ConsideredOwner
)))
101 .bind("owner_assignment")),
104 // Matching initialization of owners with non-owners, nor creating owners.
108 varDecl(hasInitializer(unless(ConsideredOwner
)), IsOwnerType
)
109 .bind("owner_initialization"))),
112 const auto HasConstructorInitializerForOwner
=
113 has(cxxConstructorDecl(forEachConstructorInitializer(
115 isMemberInitializer(), forField(IsOwnerType
),
117 // Avoid templatesdeclaration with
118 // excluding parenListExpr.
119 allOf(unless(ConsideredOwner
), unless(parenListExpr()))))
120 .bind("owner_member_initializer"))));
122 // Match class member initialization that expects owners, but does not get
125 traverse(TK_AsIs
, cxxRecordDecl(HasConstructorInitializerForOwner
)),
128 // Matching on assignment operations where the RHS is a newly created owner,
129 // but the LHS is not an owner.
130 Finder
->addMatcher(binaryOperator(isAssignmentOperator(),
131 hasLHS(unless(IsOwnerType
)),
132 hasRHS(CreatesOwner
))
133 .bind("bad_owner_creation_assignment"),
136 // Matching on initialization operations where the initial value is a newly
137 // created owner, but the LHS is not an owner.
139 traverse(TK_AsIs
, namedDecl(varDecl(hasInitializer(CreatesOwner
),
141 .bind("bad_owner_creation_variable"))),
144 // Match on all function calls that expect owners as arguments, but didn't
147 callExpr(forEachArgumentWithParam(
148 expr(unless(ConsideredOwner
)).bind("expected_owner_argument"),
149 parmVarDecl(IsOwnerType
))),
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"))),
160 auto IsNotInSubLambda
= stmt(
162 stmt(anyOf(equalsBoundNode("body"), lambdaExpr())).bind("scope")),
163 hasAncestor(stmt(equalsBoundNode("scope"), equalsBoundNode("body"))));
165 // Matching on functions, that return an owner/resource, but don't declare
166 // their return type as owner.
169 decl().bind("function_decl"),
171 stmt(stmt().bind("body"),
173 returnStmt(hasReturnValue(ConsideredOwner
),
174 // Ignore sub-lambda expressions
176 // Ignore sub-functions
177 hasAncestor(functionDecl().bind("context")),
178 hasAncestor(functionDecl(
179 equalsBoundNode("context"),
180 equalsBoundNode("function_decl"))))
181 .bind("bad_owner_return")))),
182 returns(qualType(unless(hasDeclaration(OwnerDecl
))).bind("result"))),
185 // Matching on lambdas, that return an owner/resource, but don't declare
186 // their return type as owner.
189 hasAncestor(decl(ScopeDeclaration
).bind("scope-decl")),
191 stmt(stmt().bind("body"),
194 hasReturnValue(ConsideredOwner
),
195 // Ignore sub-lambdas
197 // Ignore sub-functions
198 hasAncestor(decl(ScopeDeclaration
).bind("context")),
199 hasAncestor(decl(equalsBoundNode("context"),
200 equalsBoundNode("scope-decl"))))
201 .bind("bad_owner_return")))),
202 hasCallOperator(returns(
203 qualType(unless(hasDeclaration(OwnerDecl
))).bind("result"))))
207 // Match on classes that have an owner as member, but don't declare a
208 // destructor to properly release the owner.
211 has(fieldDecl(IsOwnerType
).bind("undestructed_owner_member")),
212 anyOf(unless(has(cxxDestructorDecl())),
213 has(cxxDestructorDecl(anyOf(isDefaulted(), isDeleted())))))
214 .bind("non_destructor_class"),
218 void OwningMemoryCheck::check(const MatchFinder::MatchResult
&Result
) {
219 const auto &Nodes
= Result
.Nodes
;
221 bool CheckExecuted
= false;
222 CheckExecuted
|= handleDeletion(Nodes
);
223 CheckExecuted
|= handleLegacyConsumers(Nodes
);
224 CheckExecuted
|= handleExpectedOwner(Nodes
);
225 CheckExecuted
|= handleAssignmentAndInit(Nodes
);
226 CheckExecuted
|= handleAssignmentFromNewOwner(Nodes
);
227 CheckExecuted
|= handleReturnValues(Nodes
);
228 CheckExecuted
|= handleOwnerMembers(Nodes
);
231 assert(CheckExecuted
&&
232 "None of the subroutines executed, logic error in matcher!");
235 bool OwningMemoryCheck::handleDeletion(const BoundNodes
&Nodes
) {
236 // Result of delete matchers.
237 const auto *DeleteStmt
= Nodes
.getNodeAs
<CXXDeleteExpr
>("delete_expr");
238 const auto *DeletedVariable
=
239 Nodes
.getNodeAs
<DeclRefExpr
>("deleted_variable");
241 // Deletion of non-owners, with `delete variable;`
243 diag(DeleteStmt
->getBeginLoc(),
244 "deleting a pointer through a type that is "
245 "not marked 'gsl::owner<>'; consider using a "
246 "smart pointer instead")
247 << DeletedVariable
->getSourceRange();
249 // FIXME: The declaration of the variable that was deleted can be
251 const ValueDecl
*Decl
= DeletedVariable
->getDecl();
252 diag(Decl
->getBeginLoc(), "variable declared here", DiagnosticIDs::Note
)
253 << Decl
->getSourceRange();
260 bool OwningMemoryCheck::handleLegacyConsumers(const BoundNodes
&Nodes
) {
261 // Result of matching for legacy consumer-functions like `::free()`.
262 const auto *LegacyConsumer
= Nodes
.getNodeAs
<CallExpr
>("legacy_consumer");
264 // FIXME: `freopen` should be handled separately because it takes the filename
265 // as a pointer, which should not be an owner. The argument that is an owner
266 // is known and the false positive coming from the filename can be avoided.
267 if (LegacyConsumer
) {
268 diag(LegacyConsumer
->getBeginLoc(),
269 "calling legacy resource function without passing a 'gsl::owner<>'")
270 << LegacyConsumer
->getSourceRange();
276 bool OwningMemoryCheck::handleExpectedOwner(const BoundNodes
&Nodes
) {
277 // Result of function call matchers.
278 const auto *ExpectedOwner
= Nodes
.getNodeAs
<Expr
>("expected_owner_argument");
280 // Expected function argument to be owner.
282 diag(ExpectedOwner
->getBeginLoc(),
283 "expected argument of type 'gsl::owner<>'; got %0")
284 << ExpectedOwner
->getType() << ExpectedOwner
->getSourceRange();
290 /// Assignment and initialization of owner variables.
291 bool OwningMemoryCheck::handleAssignmentAndInit(const BoundNodes
&Nodes
) {
292 const auto *OwnerAssignment
=
293 Nodes
.getNodeAs
<BinaryOperator
>("owner_assignment");
294 const auto *OwnerInitialization
=
295 Nodes
.getNodeAs
<VarDecl
>("owner_initialization");
296 const auto *OwnerInitializer
=
297 Nodes
.getNodeAs
<CXXCtorInitializer
>("owner_member_initializer");
299 // Assignments to owners.
300 if (OwnerAssignment
) {
301 diag(OwnerAssignment
->getBeginLoc(),
302 "expected assignment source to be of type 'gsl::owner<>'; got %0")
303 << OwnerAssignment
->getRHS()->getType()
304 << OwnerAssignment
->getSourceRange();
308 // Initialization of owners.
309 if (OwnerInitialization
) {
310 diag(OwnerInitialization
->getBeginLoc(),
311 "expected initialization with value of type 'gsl::owner<>'; got %0")
312 << OwnerInitialization
->getAnyInitializer()->getType()
313 << OwnerInitialization
->getSourceRange();
317 // Initializer of class constructors that initialize owners.
318 if (OwnerInitializer
) {
319 diag(OwnerInitializer
->getSourceLocation(),
320 "expected initialization of owner member variable with value of type "
321 "'gsl::owner<>'; got %0")
322 // FIXME: the expression from getInit has type 'void', but the type
323 // of the supplied argument would be of interest.
324 << OwnerInitializer
->getInit()->getType()
325 << OwnerInitializer
->getSourceRange();
331 /// Problematic assignment and initializations, since the assigned value is a
332 /// newly created owner.
333 bool OwningMemoryCheck::handleAssignmentFromNewOwner(const BoundNodes
&Nodes
) {
334 const auto *BadOwnerAssignment
=
335 Nodes
.getNodeAs
<BinaryOperator
>("bad_owner_creation_assignment");
336 const auto *BadOwnerInitialization
=
337 Nodes
.getNodeAs
<VarDecl
>("bad_owner_creation_variable");
339 const auto *BadOwnerArgument
=
340 Nodes
.getNodeAs
<Expr
>("bad_owner_creation_argument");
341 const auto *BadOwnerParameter
=
342 Nodes
.getNodeAs
<ParmVarDecl
>("bad_owner_creation_parameter");
344 // Bad assignments to non-owners, where the RHS is a newly created owner.
345 if (BadOwnerAssignment
) {
346 diag(BadOwnerAssignment
->getBeginLoc(),
347 "assigning newly created 'gsl::owner<>' to non-owner %0")
348 << BadOwnerAssignment
->getLHS()->getType()
349 << BadOwnerAssignment
->getSourceRange();
353 // Bad initialization of non-owners, where the RHS is a newly created owner.
354 if (BadOwnerInitialization
) {
355 diag(BadOwnerInitialization
->getBeginLoc(),
356 "initializing non-owner %0 with a newly created 'gsl::owner<>'")
357 << BadOwnerInitialization
->getType()
358 << BadOwnerInitialization
->getSourceRange();
360 // FIXME: FixitHint to rewrite the type of the initialized variable
361 // as 'gsl::owner<OriginalType>'
365 // Function call, where one arguments is a newly created owner, but the
366 // parameter type is not.
367 if (BadOwnerArgument
) {
368 assert(BadOwnerParameter
&&
369 "parameter for the problematic argument not found");
370 diag(BadOwnerArgument
->getBeginLoc(), "initializing non-owner argument of "
371 "type %0 with a newly created "
373 << BadOwnerParameter
->getType() << BadOwnerArgument
->getSourceRange();
379 bool OwningMemoryCheck::handleReturnValues(const BoundNodes
&Nodes
) {
380 // Function return statements, that are owners/resources, but the function
381 // declaration does not declare its return value as owner.
382 const auto *BadReturnType
= Nodes
.getNodeAs
<ReturnStmt
>("bad_owner_return");
383 const auto *ResultType
= Nodes
.getNodeAs
<QualType
>("result");
385 // Function return values, that should be owners but aren't.
387 // The returned value is a resource or variable that was not annotated with
388 // owner<> and the function return type is not owner<>.
389 diag(BadReturnType
->getBeginLoc(),
390 "returning a newly created resource of "
391 "type %0 or 'gsl::owner<>' from a "
392 "%select{function|lambda}1 whose return type is not 'gsl::owner<>'")
393 << *ResultType
<< (Nodes
.getNodeAs
<Expr
>("lambda") != nullptr)
394 << BadReturnType
->getSourceRange();
396 // FIXME: Rewrite the return type as 'gsl::owner<OriginalType>'
402 bool OwningMemoryCheck::handleOwnerMembers(const BoundNodes
&Nodes
) {
403 // Classes, that have owners as member, but do not declare destructors
405 const auto *BadClass
= Nodes
.getNodeAs
<CXXRecordDecl
>("non_destructor_class");
407 // Classes, that contains owners, but do not declare destructors.
409 const auto *DeclaredOwnerMember
=
410 Nodes
.getNodeAs
<FieldDecl
>("undestructed_owner_member");
411 assert(DeclaredOwnerMember
&&
412 "match on class with bad destructor but without a declared owner");
414 diag(DeclaredOwnerMember
->getBeginLoc(),
415 "member variable of type 'gsl::owner<>' requires the class %0 to "
416 "implement a destructor to release the owned resource")
423 } // namespace clang::tidy::cppcoreguidelines