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