clang-tools 19.0.0git
OwningMemoryCheck.cpp
Go to the documentation of this file.
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
21
22namespace {
23AST_MATCHER_P(LambdaExpr, hasCallOperator, Matcher<CXXMethodDecl>,
24 InnerMatcher) {
25 return InnerMatcher.matches(*Node.getCallOperator(), Finder, Builder);
26}
27
28AST_MATCHER_P(LambdaExpr, hasLambdaBody, Matcher<Stmt>, InnerMatcher) {
29 return InnerMatcher.matches(*Node.getBody(), Finder, Builder);
30}
31} // namespace
32
34 Options.store(Opts, "LegacyResourceProducers", LegacyResourceProducers);
35 Options.store(Opts, "LegacyResourceConsumers", 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("::gsl::owner"));
42 const auto IsOwnerType = hasType(OwnerDecl);
43
44 const auto LegacyCreatorFunctions =
45 hasAnyName(utils::options::parseStringList(LegacyResourceProducers));
46 const auto LegacyConsumerFunctions =
47 hasAnyName(utils::options::parseStringList(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(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);
61
62 const auto CreatesOwner =
63 anyOf(cxxNewExpr(),
64 callExpr(callee(
65 functionDecl(returns(qualType(hasDeclaration(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 traverse(TK_AsIs,
75 cxxDeleteExpr(hasDescendant(declRefExpr(unless(ConsideredOwner))
76 .bind("deleted_variable")))
77 .bind("delete_expr")),
78 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 traverse(TK_AsIs, callExpr(callee(LegacyOwnerConsumers),
89 hasAnyArgument(expr(
90 unless(ignoringImpCasts(ConsideredOwner)),
91 hasType(pointerType()))))
92 .bind("legacy_consumer")),
93 this);
94
95 // Matching assignment to owners, with the rhs not being an owner nor creating
96 // one.
97 Finder->addMatcher(
98 traverse(TK_AsIs,
99 binaryOperator(isAssignmentOperator(), hasLHS(IsOwnerType),
100 hasRHS(unless(ConsideredOwner)))
101 .bind("owner_assignment")),
102 this);
103
104 // Matching initialization of owners with non-owners, nor creating owners.
105 Finder->addMatcher(
106 traverse(TK_AsIs,
107 namedDecl(
108 varDecl(hasInitializer(unless(ConsideredOwner)), IsOwnerType)
109 .bind("owner_initialization"))),
110 this);
111
112 const auto HasConstructorInitializerForOwner =
113 has(cxxConstructorDecl(forEachConstructorInitializer(
114 cxxCtorInitializer(
115 isMemberInitializer(), forField(IsOwnerType),
116 withInitializer(
117 // Avoid templatesdeclaration with
118 // excluding parenListExpr.
119 allOf(unless(ConsideredOwner), 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(
125 traverse(TK_AsIs, cxxRecordDecl(HasConstructorInitializerForOwner)),
126 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(binaryOperator(isAssignmentOperator(),
131 hasLHS(unless(IsOwnerType)),
132 hasRHS(CreatesOwner))
133 .bind("bad_owner_creation_assignment"),
134 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 traverse(TK_AsIs, namedDecl(varDecl(hasInitializer(CreatesOwner),
140 unless(IsOwnerType))
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 auto IsNotInSubLambda = stmt(
161 hasAncestor(
162 stmt(anyOf(equalsBoundNode("body"), lambdaExpr())).bind("scope")),
163 hasAncestor(stmt(equalsBoundNode("scope"), equalsBoundNode("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 functionDecl(
169 decl().bind("function_decl"),
170 hasBody(
171 stmt(stmt().bind("body"),
172 hasDescendant(
173 returnStmt(hasReturnValue(ConsideredOwner),
174 // Ignore sub-lambda expressions
175 IsNotInSubLambda,
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"))),
183 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 lambdaExpr(
189 hasAncestor(decl(ScopeDeclaration).bind("scope-decl")),
190 hasLambdaBody(
191 stmt(stmt().bind("body"),
192 hasDescendant(
193 returnStmt(
194 hasReturnValue(ConsideredOwner),
195 // Ignore sub-lambdas
196 IsNotInSubLambda,
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"))))
204 .bind("lambda"),
205 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 cxxRecordDecl(
211 has(fieldDecl(IsOwnerType).bind("undestructed_owner_member")),
212 anyOf(unless(has(cxxDestructorDecl())),
213 has(cxxDestructorDecl(anyOf(isDefaulted(), isDeleted())))))
214 .bind("non_destructor_class"),
215 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>("delete_expr");
238 const auto *DeletedVariable =
239 Nodes.getNodeAs<DeclRefExpr>("deleted_variable");
240
241 // Deletion of non-owners, with `delete variable;`
242 if (DeleteStmt) {
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();
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>("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(LegacyConsumer->getBeginLoc(),
269 "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>("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>("owner_assignment");
294 const auto *OwnerInitialization =
295 Nodes.getNodeAs<VarDecl>("owner_initialization");
296 const auto *OwnerInitializer =
297 Nodes.getNodeAs<CXXCtorInitializer>("owner_member_initializer");
298
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();
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(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();
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>("bad_owner_creation_assignment");
336 const auto *BadOwnerInitialization =
337 Nodes.getNodeAs<VarDecl>("bad_owner_creation_variable");
338
339 const auto *BadOwnerArgument =
340 Nodes.getNodeAs<Expr>("bad_owner_creation_argument");
341 const auto *BadOwnerParameter =
342 Nodes.getNodeAs<ParmVarDecl>("bad_owner_creation_parameter");
343
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();
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>("bad_owner_return");
383 const auto *ResultType = Nodes.getNodeAs<QualType>("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(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();
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>("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>("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
const FunctionDecl * Decl
CodeCompletionBuilder Builder
::clang::DynTypedNode Node
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Make configuration of checker discoverable.
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Match common cases, where the owner semantic is relevant, like function calls, delete expressions and...
AST_MATCHER_P(UserDefinedLiteral, hasLiteral, clang::ast_matchers::internal::Matcher< Expr >, InnerMatcher)
std::vector< StringRef > parseStringList(StringRef Option)
Parse a semicolon separated list of strings.
llvm::StringMap< ClangTidyValue > OptionMap