clang-tools 8.0.0

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