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