brintos

brintos / llvm-project-archived public Read only

0
0
Text · 16.8 KiB · f4e8947 Raw
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