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

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