422 lines · cpp
1//===----------------------------------------------------------------------===//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-exception6//7//===----------------------------------------------------------------------===//8 9#include "OwningMemoryCheck.h"10#include "../utils/OptionsUtils.h"11#include "clang/AST/ASTContext.h"12#include "clang/ASTMatchers/ASTMatchFinder.h"13#include <string>14 15using namespace clang::ast_matchers;16using namespace clang::ast_matchers::internal;17 18namespace clang::tidy::cppcoreguidelines {19 20namespace {21AST_MATCHER_P(LambdaExpr, hasCallOperator, Matcher<CXXMethodDecl>,22 InnerMatcher) {23 return InnerMatcher.matches(*Node.getCallOperator(), Finder, Builder);24}25 26AST_MATCHER_P(LambdaExpr, hasLambdaBody, Matcher<Stmt>, InnerMatcher) {27 return InnerMatcher.matches(*Node.getBody(), Finder, Builder);28}29} // namespace30 31void OwningMemoryCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {32 Options.store(Opts, "LegacyResourceProducers", LegacyResourceProducers);33 Options.store(Opts, "LegacyResourceConsumers", LegacyResourceConsumers);34}35 36/// Match common cases, where the owner semantic is relevant, like function37/// calls, delete expressions and others.38void OwningMemoryCheck::registerMatchers(MatchFinder *Finder) {39 const auto OwnerDecl = typeAliasTemplateDecl(hasName("::gsl::owner"));40 const auto IsOwnerType = hasType(OwnerDecl);41 42 const auto LegacyCreatorFunctions =43 hasAnyName(utils::options::parseStringList(LegacyResourceProducers));44 const auto LegacyConsumerFunctions =45 hasAnyName(utils::options::parseStringList(LegacyResourceConsumers));46 47 // Legacy functions that are use for resource management but cannot be48 // updated to use `gsl::owner<>`, like standard C memory management.49 const auto CreatesLegacyOwner =50 callExpr(callee(functionDecl(LegacyCreatorFunctions)));51 // C-style functions like `::malloc()` sometimes create owners as void*52 // which is expected to be cast to the correct type in C++. This case53 // must be caught explicitly.54 const auto LegacyOwnerCast =55 castExpr(hasSourceExpression(CreatesLegacyOwner));56 // Functions that do manual resource management but cannot be updated to use57 // owner. Best example is `::free()`.58 const auto LegacyOwnerConsumers = functionDecl(LegacyConsumerFunctions);59 60 const auto CreatesOwner =61 anyOf(cxxNewExpr(),62 callExpr(callee(63 functionDecl(returns(qualType(hasDeclaration(OwnerDecl)))))),64 CreatesLegacyOwner, LegacyOwnerCast);65 66 const auto ConsideredOwner = eachOf(IsOwnerType, CreatesOwner);67 const auto ScopeDeclaration = anyOf(translationUnitDecl(), namespaceDecl(),68 recordDecl(), functionDecl());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 work79 // with the 'owner<>' annotation and therefore always implicitly cast to the80 // legacy type (even 'void *').81 //82 // Furthermore, legacy owner functions are assumed to use raw pointers for83 // resources. This check assumes that all pointer arguments of a legacy84 // 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 creating94 // 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 with116 // 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 get121 // 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 newly135 // created owner, but the LHS is not an owner.136 Finder->addMatcher(137 traverse(TK_AsIs, namedDecl(varDecl(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't143 // 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 the151 // 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 auto IsNotInSubLambda = stmt(159 hasAncestor(160 stmt(anyOf(equalsBoundNode("body"), lambdaExpr())).bind("scope")),161 hasAncestor(stmt(equalsBoundNode("scope"), equalsBoundNode("body"))));162 163 // Matching on functions, that return an owner/resource, but don't declare164 // their return type as owner.165 Finder->addMatcher(166 functionDecl(167 decl().bind("function_decl"),168 hasBody(169 stmt(stmt().bind("body"),170 hasDescendant(171 returnStmt(hasReturnValue(ConsideredOwner),172 // Ignore sub-lambda expressions173 IsNotInSubLambda,174 // Ignore sub-functions175 hasAncestor(functionDecl().bind("context")),176 hasAncestor(functionDecl(177 equalsBoundNode("context"),178 equalsBoundNode("function_decl"))))179 .bind("bad_owner_return")))),180 returns(qualType(unless(hasDeclaration(OwnerDecl))).bind("result"))),181 this);182 183 // Matching on lambdas, that return an owner/resource, but don't declare184 // their return type as owner.185 Finder->addMatcher(186 lambdaExpr(187 hasAncestor(decl(ScopeDeclaration).bind("scope-decl")),188 hasLambdaBody(189 stmt(stmt().bind("body"),190 hasDescendant(191 returnStmt(192 hasReturnValue(ConsideredOwner),193 // Ignore sub-lambdas194 IsNotInSubLambda,195 // Ignore sub-functions196 hasAncestor(decl(ScopeDeclaration).bind("context")),197 hasAncestor(decl(equalsBoundNode("context"),198 equalsBoundNode("scope-decl"))))199 .bind("bad_owner_return")))),200 hasCallOperator(returns(201 qualType(unless(hasDeclaration(OwnerDecl))).bind("result"))))202 .bind("lambda"),203 this);204 205 // Match on classes that have an owner as member, but don't declare a206 // destructor to properly release the owner.207 Finder->addMatcher(208 cxxRecordDecl(209 has(fieldDecl(IsOwnerType).bind("undestructed_owner_member")),210 anyOf(unless(has(cxxDestructorDecl())),211 has(cxxDestructorDecl(anyOf(isDefaulted(), isDeleted())))))212 .bind("non_destructor_class"),213 this);214}215 216void OwningMemoryCheck::check(const MatchFinder::MatchResult &Result) {217 const auto &Nodes = Result.Nodes;218 219 bool CheckExecuted = false;220 CheckExecuted |= handleDeletion(Nodes);221 CheckExecuted |= handleLegacyConsumers(Nodes);222 CheckExecuted |= handleExpectedOwner(Nodes);223 CheckExecuted |= handleAssignmentAndInit(Nodes);224 CheckExecuted |= handleAssignmentFromNewOwner(Nodes);225 CheckExecuted |= handleReturnValues(Nodes);226 CheckExecuted |= handleOwnerMembers(Nodes);227 228 (void)CheckExecuted;229 assert(CheckExecuted &&230 "None of the subroutines executed, logic error in matcher!");231}232 233bool OwningMemoryCheck::handleDeletion(const BoundNodes &Nodes) {234 // Result of delete matchers.235 const auto *DeleteStmt = Nodes.getNodeAs<CXXDeleteExpr>("delete_expr");236 const auto *DeletedVariable =237 Nodes.getNodeAs<DeclRefExpr>("deleted_variable");238 239 // Deletion of non-owners, with `delete variable;`240 if (DeleteStmt) {241 diag(DeleteStmt->getBeginLoc(),242 "deleting a pointer through a type that is "243 "not marked 'gsl::owner<>'; consider using a "244 "smart pointer instead")245 << DeletedVariable->getSourceRange();246 247 // FIXME: The declaration of the variable that was deleted can be248 // rewritten.249 const ValueDecl *Decl = DeletedVariable->getDecl();250 diag(Decl->getBeginLoc(), "variable declared here", DiagnosticIDs::Note)251 << Decl->getSourceRange();252 253 return true;254 }255 return false;256}257 258bool OwningMemoryCheck::handleLegacyConsumers(const BoundNodes &Nodes) {259 // Result of matching for legacy consumer-functions like `::free()`.260 const auto *LegacyConsumer = Nodes.getNodeAs<CallExpr>("legacy_consumer");261 262 // FIXME: `freopen` should be handled separately because it takes the filename263 // as a pointer, which should not be an owner. The argument that is an owner264 // is known and the false positive coming from the filename can be avoided.265 if (LegacyConsumer) {266 diag(LegacyConsumer->getBeginLoc(),267 "calling legacy resource function without passing a 'gsl::owner<>'")268 << LegacyConsumer->getSourceRange();269 return true;270 }271 return false;272}273 274bool OwningMemoryCheck::handleExpectedOwner(const BoundNodes &Nodes) {275 // Result of function call matchers.276 const auto *ExpectedOwner = Nodes.getNodeAs<Expr>("expected_owner_argument");277 278 // Expected function argument to be owner.279 if (ExpectedOwner) {280 diag(ExpectedOwner->getBeginLoc(),281 "expected argument of type 'gsl::owner<>'; got %0")282 << ExpectedOwner->getType() << ExpectedOwner->getSourceRange();283 return true;284 }285 return false;286}287 288/// Assignment and initialization of owner variables.289bool OwningMemoryCheck::handleAssignmentAndInit(const BoundNodes &Nodes) {290 const auto *OwnerAssignment =291 Nodes.getNodeAs<BinaryOperator>("owner_assignment");292 const auto *OwnerInitialization =293 Nodes.getNodeAs<VarDecl>("owner_initialization");294 const auto *OwnerInitializer =295 Nodes.getNodeAs<CXXCtorInitializer>("owner_member_initializer");296 297 // Assignments to owners.298 if (OwnerAssignment) {299 diag(OwnerAssignment->getBeginLoc(),300 "expected assignment source to be of type 'gsl::owner<>'; got %0")301 << OwnerAssignment->getRHS()->getType()302 << OwnerAssignment->getSourceRange();303 return true;304 }305 306 // Initialization of owners.307 if (OwnerInitialization) {308 diag(OwnerInitialization->getBeginLoc(),309 "expected initialization with value of type 'gsl::owner<>'; got %0")310 << OwnerInitialization->getAnyInitializer()->getType()311 << OwnerInitialization->getSourceRange();312 return true;313 }314 315 // Initializer of class constructors that initialize owners.316 if (OwnerInitializer) {317 diag(OwnerInitializer->getSourceLocation(),318 "expected initialization of owner member variable with value of type "319 "'gsl::owner<>'; got %0")320 // FIXME: the expression from getInit has type 'void', but the type321 // of the supplied argument would be of interest.322 << OwnerInitializer->getInit()->getType()323 << OwnerInitializer->getSourceRange();324 return true;325 }326 return false;327}328 329/// Problematic assignment and initializations, since the assigned value is a330/// newly created owner.331bool OwningMemoryCheck::handleAssignmentFromNewOwner(const BoundNodes &Nodes) {332 const auto *BadOwnerAssignment =333 Nodes.getNodeAs<BinaryOperator>("bad_owner_creation_assignment");334 const auto *BadOwnerInitialization =335 Nodes.getNodeAs<VarDecl>("bad_owner_creation_variable");336 337 const auto *BadOwnerArgument =338 Nodes.getNodeAs<Expr>("bad_owner_creation_argument");339 const auto *BadOwnerParameter =340 Nodes.getNodeAs<ParmVarDecl>("bad_owner_creation_parameter");341 342 // Bad assignments to non-owners, where the RHS is a newly created owner.343 if (BadOwnerAssignment) {344 diag(BadOwnerAssignment->getBeginLoc(),345 "assigning newly created 'gsl::owner<>' to non-owner %0")346 << BadOwnerAssignment->getLHS()->getType()347 << BadOwnerAssignment->getSourceRange();348 return true;349 }350 351 // Bad initialization of non-owners, where the RHS is a newly created owner.352 if (BadOwnerInitialization) {353 diag(BadOwnerInitialization->getBeginLoc(),354 "initializing non-owner %0 with a newly created 'gsl::owner<>'")355 << BadOwnerInitialization->getType()356 << BadOwnerInitialization->getSourceRange();357 358 // FIXME: FixitHint to rewrite the type of the initialized variable359 // as 'gsl::owner<OriginalType>'360 return true;361 }362 363 // Function call, where one arguments is a newly created owner, but the364 // parameter type is not.365 if (BadOwnerArgument) {366 assert(BadOwnerParameter &&367 "parameter for the problematic argument not found");368 diag(BadOwnerArgument->getBeginLoc(), "initializing non-owner argument of "369 "type %0 with a newly created "370 "'gsl::owner<>'")371 << BadOwnerParameter->getType() << BadOwnerArgument->getSourceRange();372 return true;373 }374 return false;375}376 377bool OwningMemoryCheck::handleReturnValues(const BoundNodes &Nodes) {378 // Function return statements, that are owners/resources, but the function379 // declaration does not declare its return value as owner.380 const auto *BadReturnType = Nodes.getNodeAs<ReturnStmt>("bad_owner_return");381 const auto *ResultType = Nodes.getNodeAs<QualType>("result");382 383 // Function return values, that should be owners but aren't.384 if (BadReturnType) {385 // The returned value is a resource or variable that was not annotated with386 // owner<> and the function return type is not owner<>.387 diag(BadReturnType->getBeginLoc(),388 "returning a newly created resource of "389 "type %0 or 'gsl::owner<>' from a "390 "%select{function|lambda}1 whose return type is not 'gsl::owner<>'")391 << *ResultType << (Nodes.getNodeAs<Expr>("lambda") != nullptr)392 << BadReturnType->getSourceRange();393 394 // FIXME: Rewrite the return type as 'gsl::owner<OriginalType>'395 return true;396 }397 return false;398}399 400bool OwningMemoryCheck::handleOwnerMembers(const BoundNodes &Nodes) {401 // Classes, that have owners as member, but do not declare destructors402 // accordingly.403 const auto *BadClass = Nodes.getNodeAs<CXXRecordDecl>("non_destructor_class");404 405 // Classes, that contains owners, but do not declare destructors.406 if (BadClass) {407 const auto *DeclaredOwnerMember =408 Nodes.getNodeAs<FieldDecl>("undestructed_owner_member");409 assert(DeclaredOwnerMember &&410 "match on class with bad destructor but without a declared owner");411 412 diag(DeclaredOwnerMember->getBeginLoc(),413 "member variable of type 'gsl::owner<>' requires the class %0 to "414 "implement a destructor to release the owned resource")415 << BadClass;416 return true;417 }418 return false;419}420 421} // namespace clang::tidy::cppcoreguidelines422