//===--- CapturingThisInMemberVariableCheck.cpp - clang-tidy --------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "CapturingThisInMemberVariableCheck.h" #include "../utils/Matchers.h" #include "../utils/OptionsUtils.h" #include "clang/AST/DeclCXX.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/ASTMatchers/ASTMatchersMacros.h" using namespace clang::ast_matchers; namespace clang::tidy::bugprone { namespace { AST_MATCHER(CXXRecordDecl, correctHandleCaptureThisLambda) { // unresolved if (Node.needsOverloadResolutionForCopyConstructor() && Node.needsImplicitCopyConstructor()) return false; if (Node.needsOverloadResolutionForMoveConstructor() && Node.needsImplicitMoveConstructor()) return false; if (Node.needsOverloadResolutionForCopyAssignment() && Node.needsImplicitCopyAssignment()) return false; if (Node.needsOverloadResolutionForMoveAssignment() && Node.needsImplicitMoveAssignment()) return false; // default but not deleted if (Node.hasSimpleCopyConstructor()) return false; if (Node.hasSimpleMoveConstructor()) return false; if (Node.hasSimpleCopyAssignment()) return false; if (Node.hasSimpleMoveAssignment()) return false; for (CXXConstructorDecl const *C : Node.ctors()) { if (C->isCopyOrMoveConstructor() && C->isDefaulted() && !C->isDeleted()) return false; } for (CXXMethodDecl const *M : Node.methods()) { if (M->isCopyAssignmentOperator()) llvm::errs() << M->isDeleted() << "\n"; if (M->isCopyAssignmentOperator() && M->isDefaulted() && !M->isDeleted()) return false; if (M->isMoveAssignmentOperator() && M->isDefaulted() && !M->isDeleted()) return false; } // FIXME: find ways to identifier correct handle capture this lambda return true; } } // namespace constexpr const char *DefaultFunctionWrapperTypes = "::std::function;::std::move_only_function;::boost::function"; constexpr const char *DefaultBindFunctions = "::std::bind;::boost::bind;::std::bind_front;::std::bind_back;" "::boost::compat::bind_front;::boost::compat::bind_back"; CapturingThisInMemberVariableCheck::CapturingThisInMemberVariableCheck( StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), FunctionWrapperTypes(utils::options::parseStringList( Options.get("FunctionWrapperTypes", DefaultFunctionWrapperTypes))), BindFunctions(utils::options::parseStringList( Options.get("BindFunctions", DefaultBindFunctions))) {} void CapturingThisInMemberVariableCheck::storeOptions( ClangTidyOptions::OptionMap &Opts) { Options.store(Opts, "FunctionWrapperTypes", utils::options::serializeStringList(FunctionWrapperTypes)); Options.store(Opts, "BindFunctions", utils::options::serializeStringList(BindFunctions)); } void CapturingThisInMemberVariableCheck::registerMatchers(MatchFinder *Finder) { auto IsStdFunctionField = fieldDecl(hasType(cxxRecordDecl( matchers::matchesAnyListedName(FunctionWrapperTypes)))) .bind("field"); auto CaptureThis = lambdaCapture(anyOf( // [this] capturesThis(), // [self = this] capturesVar(varDecl(hasInitializer(cxxThisExpr()))))); auto IsLambdaCapturingThis = lambdaExpr(hasAnyCapture(CaptureThis)).bind("lambda"); auto IsBindCapturingThis = callExpr( callee(functionDecl(matchers::matchesAnyListedName(BindFunctions)) .bind("callee")), hasAnyArgument(cxxThisExpr())) .bind("bind"); auto IsInitWithLambdaOrBind = anyOf(IsLambdaCapturingThis, IsBindCapturingThis, cxxConstructExpr(hasArgument( 0, anyOf(IsLambdaCapturingThis, IsBindCapturingThis)))); Finder->addMatcher( cxxRecordDecl( anyOf(has(cxxConstructorDecl( unless(isCopyConstructor()), unless(isMoveConstructor()), hasAnyConstructorInitializer(cxxCtorInitializer( isMemberInitializer(), forField(IsStdFunctionField), withInitializer(IsInitWithLambdaOrBind))))), has(fieldDecl(IsStdFunctionField, hasInClassInitializer(IsInitWithLambdaOrBind)))), unless(correctHandleCaptureThisLambda())), this); } void CapturingThisInMemberVariableCheck::check( const MatchFinder::MatchResult &Result) { if (const auto *Lambda = Result.Nodes.getNodeAs("lambda")) { diag(Lambda->getBeginLoc(), "'this' captured by a lambda and stored in a class member variable; " "disable implicit class copying/moving to prevent potential " "use-after-free"); } else if (const auto *Bind = Result.Nodes.getNodeAs("bind")) { const auto *Callee = Result.Nodes.getNodeAs("callee"); assert(Callee); diag(Bind->getBeginLoc(), "'this' captured by a '%0' call and stored in a class member " "variable; disable implicit class copying/moving to prevent potential " "use-after-free") << Callee->getQualifiedNameAsString(); } const auto *Field = Result.Nodes.getNodeAs("field"); assert(Field); diag(Field->getLocation(), "class member of type '%0' that stores captured 'this'", DiagnosticIDs::Note) << Field->getType().getAsString(); } } // namespace clang::tidy::bugprone