|  | //===--- 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<LambdaExpr>("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<CallExpr>("bind")) { | 
|  | const auto *Callee = Result.Nodes.getNodeAs<FunctionDecl>("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<FieldDecl>("field"); | 
|  | assert(Field); | 
|  |  | 
|  | diag(Field->getLocation(), | 
|  | "class member of type '%0' that stores captured 'this'", | 
|  | DiagnosticIDs::Note) | 
|  | << Field->getType().getAsString(); | 
|  | } | 
|  |  | 
|  | } // namespace clang::tidy::bugprone |