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
{
22 void OwningMemoryCheck::storeOptions(ClangTidyOptions::OptionMap
&Opts
) {
23 Options
.store(Opts
, "LegacyResourceProducers", LegacyResourceProducers
);
24 Options
.store(Opts
, "LegacyResourceConsumers", LegacyResourceConsumers
);
27 /// Match common cases, where the owner semantic is relevant, like function
28 /// calls, delete expressions and others.
29 void OwningMemoryCheck::registerMatchers(MatchFinder
*Finder
) {
30 const auto OwnerDecl
= typeAliasTemplateDecl(hasName("::gsl::owner"));
31 const auto IsOwnerType
= hasType(OwnerDecl
);
33 const auto LegacyCreatorFunctions
=
34 hasAnyName(utils::options::parseStringList(LegacyResourceProducers
));
35 const auto LegacyConsumerFunctions
=
36 hasAnyName(utils::options::parseStringList(LegacyResourceConsumers
));
38 // Legacy functions that are use for resource management but cannot be
39 // updated to use `gsl::owner<>`, like standard C memory management.
40 const auto CreatesLegacyOwner
=
41 callExpr(callee(functionDecl(LegacyCreatorFunctions
)));
42 // C-style functions like `::malloc()` sometimes create owners as void*
43 // which is expected to be cast to the correct type in C++. This case
44 // must be caught explicitly.
45 const auto LegacyOwnerCast
=
46 castExpr(hasSourceExpression(CreatesLegacyOwner
));
47 // Functions that do manual resource management but cannot be updated to use
48 // owner. Best example is `::free()`.
49 const auto LegacyOwnerConsumers
= functionDecl(LegacyConsumerFunctions
);
51 const auto CreatesOwner
=
54 functionDecl(returns(qualType(hasDeclaration(OwnerDecl
)))))),
55 CreatesLegacyOwner
, LegacyOwnerCast
);
57 const auto ConsideredOwner
= eachOf(IsOwnerType
, CreatesOwner
);
59 // Find delete expressions that delete non-owners.
62 cxxDeleteExpr(hasDescendant(declRefExpr(unless(ConsideredOwner
))
63 .bind("deleted_variable")))
64 .bind("delete_expr")),
67 // Ignoring the implicit casts is vital because the legacy owners do not work
68 // with the 'owner<>' annotation and therefore always implicitly cast to the
69 // legacy type (even 'void *').
71 // Furthermore, legacy owner functions are assumed to use raw pointers for
72 // resources. This check assumes that all pointer arguments of a legacy
73 // functions shall be 'gsl::owner<>'.
75 traverse(TK_AsIs
, callExpr(callee(LegacyOwnerConsumers
),
77 unless(ignoringImpCasts(ConsideredOwner
)),
78 hasType(pointerType()))))
79 .bind("legacy_consumer")),
82 // Matching assignment to owners, with the rhs not being an owner nor creating
86 binaryOperator(isAssignmentOperator(), hasLHS(IsOwnerType
),
87 hasRHS(unless(ConsideredOwner
)))
88 .bind("owner_assignment")),
91 // Matching initialization of owners with non-owners, nor creating owners.
95 varDecl(hasInitializer(unless(ConsideredOwner
)), IsOwnerType
)
96 .bind("owner_initialization"))),
99 const auto HasConstructorInitializerForOwner
=
100 has(cxxConstructorDecl(forEachConstructorInitializer(
102 isMemberInitializer(), forField(IsOwnerType
),
104 // Avoid templatesdeclaration with
105 // excluding parenListExpr.
106 allOf(unless(ConsideredOwner
), unless(parenListExpr()))))
107 .bind("owner_member_initializer"))));
109 // Match class member initialization that expects owners, but does not get
112 traverse(TK_AsIs
, cxxRecordDecl(HasConstructorInitializerForOwner
)),
115 // Matching on assignment operations where the RHS is a newly created owner,
116 // but the LHS is not an owner.
117 Finder
->addMatcher(binaryOperator(isAssignmentOperator(),
118 hasLHS(unless(IsOwnerType
)),
119 hasRHS(CreatesOwner
))
120 .bind("bad_owner_creation_assignment"),
123 // Matching on initialization operations where the initial value is a newly
124 // created owner, but the LHS is not an owner.
126 traverse(TK_AsIs
, namedDecl(varDecl(hasInitializer(CreatesOwner
),
128 .bind("bad_owner_creation_variable"))),
131 // Match on all function calls that expect owners as arguments, but didn't
134 callExpr(forEachArgumentWithParam(
135 expr(unless(ConsideredOwner
)).bind("expected_owner_argument"),
136 parmVarDecl(IsOwnerType
))),
139 // Matching for function calls where one argument is a created owner, but the
140 // parameter type is not an owner.
141 Finder
->addMatcher(callExpr(forEachArgumentWithParam(
142 expr(CreatesOwner
).bind("bad_owner_creation_argument"),
143 parmVarDecl(unless(IsOwnerType
))
144 .bind("bad_owner_creation_parameter"))),
147 // Matching on functions, that return an owner/resource, but don't declare
148 // their return type as owner.
150 functionDecl(hasDescendant(returnStmt(hasReturnValue(ConsideredOwner
))
151 .bind("bad_owner_return")),
152 unless(returns(qualType(hasDeclaration(OwnerDecl
)))))
153 .bind("function_decl"),
156 // Match on classes that have an owner as member, but don't declare a
157 // destructor to properly release the owner.
160 has(fieldDecl(IsOwnerType
).bind("undestructed_owner_member")),
161 anyOf(unless(has(cxxDestructorDecl())),
162 has(cxxDestructorDecl(anyOf(isDefaulted(), isDeleted())))))
163 .bind("non_destructor_class"),
167 void OwningMemoryCheck::check(const MatchFinder::MatchResult
&Result
) {
168 const auto &Nodes
= Result
.Nodes
;
170 bool CheckExecuted
= false;
171 CheckExecuted
|= handleDeletion(Nodes
);
172 CheckExecuted
|= handleLegacyConsumers(Nodes
);
173 CheckExecuted
|= handleExpectedOwner(Nodes
);
174 CheckExecuted
|= handleAssignmentAndInit(Nodes
);
175 CheckExecuted
|= handleAssignmentFromNewOwner(Nodes
);
176 CheckExecuted
|= handleReturnValues(Nodes
);
177 CheckExecuted
|= handleOwnerMembers(Nodes
);
180 assert(CheckExecuted
&&
181 "None of the subroutines executed, logic error in matcher!");
184 bool OwningMemoryCheck::handleDeletion(const BoundNodes
&Nodes
) {
185 // Result of delete matchers.
186 const auto *DeleteStmt
= Nodes
.getNodeAs
<CXXDeleteExpr
>("delete_expr");
187 const auto *DeletedVariable
=
188 Nodes
.getNodeAs
<DeclRefExpr
>("deleted_variable");
190 // Deletion of non-owners, with `delete variable;`
192 diag(DeleteStmt
->getBeginLoc(),
193 "deleting a pointer through a type that is "
194 "not marked 'gsl::owner<>'; consider using a "
195 "smart pointer instead")
196 << DeletedVariable
->getSourceRange();
198 // FIXME: The declaration of the variable that was deleted can be
200 const ValueDecl
*Decl
= DeletedVariable
->getDecl();
201 diag(Decl
->getBeginLoc(), "variable declared here", DiagnosticIDs::Note
)
202 << Decl
->getSourceRange();
209 bool OwningMemoryCheck::handleLegacyConsumers(const BoundNodes
&Nodes
) {
210 // Result of matching for legacy consumer-functions like `::free()`.
211 const auto *LegacyConsumer
= Nodes
.getNodeAs
<CallExpr
>("legacy_consumer");
213 // FIXME: `freopen` should be handled separately because it takes the filename
214 // as a pointer, which should not be an owner. The argument that is an owner
215 // is known and the false positive coming from the filename can be avoided.
216 if (LegacyConsumer
) {
217 diag(LegacyConsumer
->getBeginLoc(),
218 "calling legacy resource function without passing a 'gsl::owner<>'")
219 << LegacyConsumer
->getSourceRange();
225 bool OwningMemoryCheck::handleExpectedOwner(const BoundNodes
&Nodes
) {
226 // Result of function call matchers.
227 const auto *ExpectedOwner
= Nodes
.getNodeAs
<Expr
>("expected_owner_argument");
229 // Expected function argument to be owner.
231 diag(ExpectedOwner
->getBeginLoc(),
232 "expected argument of type 'gsl::owner<>'; got %0")
233 << ExpectedOwner
->getType() << ExpectedOwner
->getSourceRange();
239 /// Assignment and initialization of owner variables.
240 bool OwningMemoryCheck::handleAssignmentAndInit(const BoundNodes
&Nodes
) {
241 const auto *OwnerAssignment
=
242 Nodes
.getNodeAs
<BinaryOperator
>("owner_assignment");
243 const auto *OwnerInitialization
=
244 Nodes
.getNodeAs
<VarDecl
>("owner_initialization");
245 const auto *OwnerInitializer
=
246 Nodes
.getNodeAs
<CXXCtorInitializer
>("owner_member_initializer");
248 // Assignments to owners.
249 if (OwnerAssignment
) {
250 diag(OwnerAssignment
->getBeginLoc(),
251 "expected assignment source to be of type 'gsl::owner<>'; got %0")
252 << OwnerAssignment
->getRHS()->getType()
253 << OwnerAssignment
->getSourceRange();
257 // Initialization of owners.
258 if (OwnerInitialization
) {
259 diag(OwnerInitialization
->getBeginLoc(),
260 "expected initialization with value of type 'gsl::owner<>'; got %0")
261 << OwnerInitialization
->getAnyInitializer()->getType()
262 << OwnerInitialization
->getSourceRange();
266 // Initializer of class constructors that initialize owners.
267 if (OwnerInitializer
) {
268 diag(OwnerInitializer
->getSourceLocation(),
269 "expected initialization of owner member variable with value of type "
270 "'gsl::owner<>'; got %0")
271 // FIXME: the expression from getInit has type 'void', but the type
272 // of the supplied argument would be of interest.
273 << OwnerInitializer
->getInit()->getType()
274 << OwnerInitializer
->getSourceRange();
280 /// Problematic assignment and initializations, since the assigned value is a
281 /// newly created owner.
282 bool OwningMemoryCheck::handleAssignmentFromNewOwner(const BoundNodes
&Nodes
) {
283 const auto *BadOwnerAssignment
=
284 Nodes
.getNodeAs
<BinaryOperator
>("bad_owner_creation_assignment");
285 const auto *BadOwnerInitialization
=
286 Nodes
.getNodeAs
<VarDecl
>("bad_owner_creation_variable");
288 const auto *BadOwnerArgument
=
289 Nodes
.getNodeAs
<Expr
>("bad_owner_creation_argument");
290 const auto *BadOwnerParameter
=
291 Nodes
.getNodeAs
<ParmVarDecl
>("bad_owner_creation_parameter");
293 // Bad assignments to non-owners, where the RHS is a newly created owner.
294 if (BadOwnerAssignment
) {
295 diag(BadOwnerAssignment
->getBeginLoc(),
296 "assigning newly created 'gsl::owner<>' to non-owner %0")
297 << BadOwnerAssignment
->getLHS()->getType()
298 << BadOwnerAssignment
->getSourceRange();
302 // Bad initialization of non-owners, where the RHS is a newly created owner.
303 if (BadOwnerInitialization
) {
304 diag(BadOwnerInitialization
->getBeginLoc(),
305 "initializing non-owner %0 with a newly created 'gsl::owner<>'")
306 << BadOwnerInitialization
->getType()
307 << BadOwnerInitialization
->getSourceRange();
309 // FIXME: FixitHint to rewrite the type of the initialized variable
310 // as 'gsl::owner<OriginalType>'
314 // Function call, where one arguments is a newly created owner, but the
315 // parameter type is not.
316 if (BadOwnerArgument
) {
317 assert(BadOwnerParameter
&&
318 "parameter for the problematic argument not found");
319 diag(BadOwnerArgument
->getBeginLoc(), "initializing non-owner argument of "
320 "type %0 with a newly created "
322 << BadOwnerParameter
->getType() << BadOwnerArgument
->getSourceRange();
328 bool OwningMemoryCheck::handleReturnValues(const BoundNodes
&Nodes
) {
329 // Function return statements, that are owners/resources, but the function
330 // declaration does not declare its return value as owner.
331 const auto *BadReturnType
= Nodes
.getNodeAs
<ReturnStmt
>("bad_owner_return");
332 const auto *Function
= Nodes
.getNodeAs
<FunctionDecl
>("function_decl");
334 // Function return values, that should be owners but aren't.
336 // The returned value is a resource or variable that was not annotated with
337 // owner<> and the function return type is not owner<>.
338 diag(BadReturnType
->getBeginLoc(),
339 "returning a newly created resource of "
340 "type %0 or 'gsl::owner<>' from a "
341 "function whose return type is not 'gsl::owner<>'")
342 << Function
->getReturnType() << BadReturnType
->getSourceRange();
344 // FIXME: Rewrite the return type as 'gsl::owner<OriginalType>'
350 bool OwningMemoryCheck::handleOwnerMembers(const BoundNodes
&Nodes
) {
351 // Classes, that have owners as member, but do not declare destructors
353 const auto *BadClass
= Nodes
.getNodeAs
<CXXRecordDecl
>("non_destructor_class");
355 // Classes, that contains owners, but do not declare destructors.
357 const auto *DeclaredOwnerMember
=
358 Nodes
.getNodeAs
<FieldDecl
>("undestructed_owner_member");
359 assert(DeclaredOwnerMember
&&
360 "match on class with bad destructor but without a declared owner");
362 diag(DeclaredOwnerMember
->getBeginLoc(),
363 "member variable of type 'gsl::owner<>' requires the class %0 to "
364 "implement a destructor to release the owned resource")
371 } // namespace clang::tidy::cppcoreguidelines