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