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