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