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 390