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
22void OwningMemoryCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
23 Options.store(Options&: Opts, LocalName: "LegacyResourceProducers", Value: LegacyResourceProducers);
24 Options.store(Options&: Opts, LocalName: "LegacyResourceConsumers", Value: LegacyResourceConsumers);
25}
26
27/// Match common cases, where the owner semantic is relevant, like function
28/// calls, delete expressions and others.
29void OwningMemoryCheck::registerMatchers(MatchFinder *Finder) {
30 const auto OwnerDecl = typeAliasTemplateDecl(hasName(Name: "::gsl::owner"));
31 const auto IsOwnerType = hasType(InnerMatcher: OwnerDecl);
32
33 const auto LegacyCreatorFunctions =
34 hasAnyName(utils::options::parseStringList(Option: LegacyResourceProducers));
35 const auto LegacyConsumerFunctions =
36 hasAnyName(utils::options::parseStringList(Option: LegacyResourceConsumers));
37
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(InnerMatcher: 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(InnerMatcher: 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);
50
51 const auto CreatesOwner =
52 anyOf(cxxNewExpr(),
53 callExpr(callee(
54 InnerMatcher: functionDecl(returns(InnerMatcher: qualType(hasDeclaration(InnerMatcher: OwnerDecl)))))),
55 CreatesLegacyOwner, LegacyOwnerCast);
56
57 const auto ConsideredOwner = eachOf(IsOwnerType, CreatesOwner);
58
59 // Find delete expressions that delete non-owners.
60 Finder->addMatcher(
61 NodeMatch: traverse(TK: TK_AsIs,
62 InnerMatcher: cxxDeleteExpr(hasDescendant(declRefExpr(unless(ConsideredOwner))
63 .bind(ID: "deleted_variable")))
64 .bind(ID: "delete_expr")),
65 Action: this);
66
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 *').
70 //
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<>'.
74 Finder->addMatcher(
75 NodeMatch: traverse(TK: TK_AsIs, InnerMatcher: callExpr(callee(InnerMatcher: LegacyOwnerConsumers),
76 hasAnyArgument(InnerMatcher: expr(
77 unless(ignoringImpCasts(InnerMatcher: ConsideredOwner)),
78 hasType(InnerMatcher: pointerType()))))
79 .bind(ID: "legacy_consumer")),
80 Action: this);
81
82 // Matching assignment to owners, with the rhs not being an owner nor creating
83 // one.
84 Finder->addMatcher(
85 NodeMatch: traverse(TK: TK_AsIs,
86 InnerMatcher: binaryOperator(isAssignmentOperator(), hasLHS(InnerMatcher: IsOwnerType),
87 hasRHS(InnerMatcher: unless(ConsideredOwner)))
88 .bind(ID: "owner_assignment")),
89 Action: this);
90
91 // Matching initialization of owners with non-owners, nor creating owners.
92 Finder->addMatcher(
93 NodeMatch: traverse(TK: TK_AsIs,
94 InnerMatcher: namedDecl(
95 varDecl(hasInitializer(InnerMatcher: unless(ConsideredOwner)), IsOwnerType)
96 .bind(ID: "owner_initialization"))),
97 Action: this);
98
99 const auto HasConstructorInitializerForOwner =
100 has(cxxConstructorDecl(forEachConstructorInitializer(
101 InnerMatcher: cxxCtorInitializer(
102 isMemberInitializer(), forField(InnerMatcher: IsOwnerType),
103 withInitializer(
104 // Avoid templatesdeclaration with
105 // excluding parenListExpr.
106 InnerMatcher: allOf(unless(ConsideredOwner), unless(parenListExpr()))))
107 .bind(ID: "owner_member_initializer"))));
108
109 // Match class member initialization that expects owners, but does not get
110 // them.
111 Finder->addMatcher(
112 NodeMatch: traverse(TK: TK_AsIs, InnerMatcher: cxxRecordDecl(HasConstructorInitializerForOwner)),
113 Action: this);
114
115 // Matching on assignment operations where the RHS is a newly created owner,
116 // but the LHS is not an owner.
117 Finder->addMatcher(NodeMatch: binaryOperator(isAssignmentOperator(),
118 hasLHS(InnerMatcher: unless(IsOwnerType)),
119 hasRHS(InnerMatcher: CreatesOwner))
120 .bind(ID: "bad_owner_creation_assignment"),
121 Action: this);
122
123 // Matching on initialization operations where the initial value is a newly
124 // created owner, but the LHS is not an owner.
125 Finder->addMatcher(
126 NodeMatch: traverse(TK: TK_AsIs, InnerMatcher: namedDecl(varDecl(hasInitializer(InnerMatcher: CreatesOwner),
127 unless(IsOwnerType))
128 .bind(ID: "bad_owner_creation_variable"))),
129 Action: this);
130
131 // Match on all function calls that expect owners as arguments, but didn't
132 // get them.
133 Finder->addMatcher(
134 NodeMatch: callExpr(forEachArgumentWithParam(
135 ArgMatcher: expr(unless(ConsideredOwner)).bind(ID: "expected_owner_argument"),
136 ParamMatcher: parmVarDecl(IsOwnerType))),
137 Action: this);
138
139 // Matching for function calls where one argument is a created owner, but the
140 // parameter type is not an owner.
141 Finder->addMatcher(NodeMatch: callExpr(forEachArgumentWithParam(
142 ArgMatcher: expr(CreatesOwner).bind(ID: "bad_owner_creation_argument"),
143 ParamMatcher: parmVarDecl(unless(IsOwnerType))
144 .bind(ID: "bad_owner_creation_parameter"))),
145 Action: this);
146
147 // Matching on functions, that return an owner/resource, but don't declare
148 // their return type as owner.
149 Finder->addMatcher(
150 NodeMatch: functionDecl(hasDescendant(returnStmt(hasReturnValue(InnerMatcher: ConsideredOwner))
151 .bind(ID: "bad_owner_return")),
152 unless(returns(InnerMatcher: qualType(hasDeclaration(InnerMatcher: OwnerDecl)))))
153 .bind(ID: "function_decl"),
154 Action: this);
155
156 // Match on classes that have an owner as member, but don't declare a
157 // destructor to properly release the owner.
158 Finder->addMatcher(
159 NodeMatch: cxxRecordDecl(
160 has(fieldDecl(IsOwnerType).bind(ID: "undestructed_owner_member")),
161 anyOf(unless(has(cxxDestructorDecl())),
162 has(cxxDestructorDecl(anyOf(isDefaulted(), isDeleted())))))
163 .bind(ID: "non_destructor_class"),
164 Action: this);
165}
166
167void OwningMemoryCheck::check(const MatchFinder::MatchResult &Result) {
168 const auto &Nodes = Result.Nodes;
169
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);
178
179 (void)CheckExecuted;
180 assert(CheckExecuted &&
181 "None of the subroutines executed, logic error in matcher!");
182}
183
184bool OwningMemoryCheck::handleDeletion(const BoundNodes &Nodes) {
185 // Result of delete matchers.
186 const auto *DeleteStmt = Nodes.getNodeAs<CXXDeleteExpr>(ID: "delete_expr");
187 const auto *DeletedVariable =
188 Nodes.getNodeAs<DeclRefExpr>(ID: "deleted_variable");
189
190 // Deletion of non-owners, with `delete variable;`
191 if (DeleteStmt) {
192 diag(Loc: DeleteStmt->getBeginLoc(),
193 Description: "deleting a pointer through a type that is "
194 "not marked 'gsl::owner<>'; consider using a "
195 "smart pointer instead")
196 << DeletedVariable->getSourceRange();
197
198 // FIXME: The declaration of the variable that was deleted can be
199 // rewritten.
200 const ValueDecl *Decl = DeletedVariable->getDecl();
201 diag(Decl->getBeginLoc(), "variable declared here", DiagnosticIDs::Note)
202 << Decl->getSourceRange();
203
204 return true;
205 }
206 return false;
207}
208
209bool OwningMemoryCheck::handleLegacyConsumers(const BoundNodes &Nodes) {
210 // Result of matching for legacy consumer-functions like `::free()`.
211 const auto *LegacyConsumer = Nodes.getNodeAs<CallExpr>(ID: "legacy_consumer");
212
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(Loc: LegacyConsumer->getBeginLoc(),
218 Description: "calling legacy resource function without passing a 'gsl::owner<>'")
219 << LegacyConsumer->getSourceRange();
220 return true;
221 }
222 return false;
223}
224
225bool OwningMemoryCheck::handleExpectedOwner(const BoundNodes &Nodes) {
226 // Result of function call matchers.
227 const auto *ExpectedOwner = Nodes.getNodeAs<Expr>(ID: "expected_owner_argument");
228
229 // Expected function argument to be owner.
230 if (ExpectedOwner) {
231 diag(ExpectedOwner->getBeginLoc(),
232 "expected argument of type 'gsl::owner<>'; got %0")
233 << ExpectedOwner->getType() << ExpectedOwner->getSourceRange();
234 return true;
235 }
236 return false;
237}
238
239/// Assignment and initialization of owner variables.
240bool OwningMemoryCheck::handleAssignmentAndInit(const BoundNodes &Nodes) {
241 const auto *OwnerAssignment =
242 Nodes.getNodeAs<BinaryOperator>(ID: "owner_assignment");
243 const auto *OwnerInitialization =
244 Nodes.getNodeAs<VarDecl>(ID: "owner_initialization");
245 const auto *OwnerInitializer =
246 Nodes.getNodeAs<CXXCtorInitializer>(ID: "owner_member_initializer");
247
248 // Assignments to owners.
249 if (OwnerAssignment) {
250 diag(Loc: OwnerAssignment->getBeginLoc(),
251 Description: "expected assignment source to be of type 'gsl::owner<>'; got %0")
252 << OwnerAssignment->getRHS()->getType()
253 << OwnerAssignment->getSourceRange();
254 return true;
255 }
256
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();
263 return true;
264 }
265
266 // Initializer of class constructors that initialize owners.
267 if (OwnerInitializer) {
268 diag(Loc: OwnerInitializer->getSourceLocation(),
269 Description: "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();
275 return true;
276 }
277 return false;
278}
279
280/// Problematic assignment and initializations, since the assigned value is a
281/// newly created owner.
282bool OwningMemoryCheck::handleAssignmentFromNewOwner(const BoundNodes &Nodes) {
283 const auto *BadOwnerAssignment =
284 Nodes.getNodeAs<BinaryOperator>(ID: "bad_owner_creation_assignment");
285 const auto *BadOwnerInitialization =
286 Nodes.getNodeAs<VarDecl>(ID: "bad_owner_creation_variable");
287
288 const auto *BadOwnerArgument =
289 Nodes.getNodeAs<Expr>(ID: "bad_owner_creation_argument");
290 const auto *BadOwnerParameter =
291 Nodes.getNodeAs<ParmVarDecl>(ID: "bad_owner_creation_parameter");
292
293 // Bad assignments to non-owners, where the RHS is a newly created owner.
294 if (BadOwnerAssignment) {
295 diag(Loc: BadOwnerAssignment->getBeginLoc(),
296 Description: "assigning newly created 'gsl::owner<>' to non-owner %0")
297 << BadOwnerAssignment->getLHS()->getType()
298 << BadOwnerAssignment->getSourceRange();
299 return true;
300 }
301
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();
308
309 // FIXME: FixitHint to rewrite the type of the initialized variable
310 // as 'gsl::owner<OriginalType>'
311 return true;
312 }
313
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 "
321 "'gsl::owner<>'")
322 << BadOwnerParameter->getType() << BadOwnerArgument->getSourceRange();
323 return true;
324 }
325 return false;
326}
327
328bool 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>(ID: "bad_owner_return");
332 const auto *Function = Nodes.getNodeAs<FunctionDecl>(ID: "function_decl");
333
334 // Function return values, that should be owners but aren't.
335 if (BadReturnType) {
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(Loc: BadReturnType->getBeginLoc(),
339 Description: "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();
343
344 // FIXME: Rewrite the return type as 'gsl::owner<OriginalType>'
345 return true;
346 }
347 return false;
348}
349
350bool OwningMemoryCheck::handleOwnerMembers(const BoundNodes &Nodes) {
351 // Classes, that have owners as member, but do not declare destructors
352 // accordingly.
353 const auto *BadClass = Nodes.getNodeAs<CXXRecordDecl>(ID: "non_destructor_class");
354
355 // Classes, that contains owners, but do not declare destructors.
356 if (BadClass) {
357 const auto *DeclaredOwnerMember =
358 Nodes.getNodeAs<FieldDecl>(ID: "undestructed_owner_member");
359 assert(DeclaredOwnerMember &&
360 "match on class with bad destructor but without a declared owner");
361
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")
365 << BadClass;
366 return true;
367 }
368 return false;
369}
370
371} // namespace clang::tidy::cppcoreguidelines
372

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