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