clang-tools 10.0.0

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