150 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 "CapturingThisInMemberVariableCheck.h"10#include "../utils/Matchers.h"11#include "../utils/OptionsUtils.h"12#include "clang/AST/DeclCXX.h"13#include "clang/ASTMatchers/ASTMatchFinder.h"14#include "clang/ASTMatchers/ASTMatchers.h"15#include "clang/ASTMatchers/ASTMatchersMacros.h"16 17using namespace clang::ast_matchers;18 19namespace clang::tidy::bugprone {20 21namespace {22 23AST_MATCHER(CXXRecordDecl, correctHandleCaptureThisLambda) {24 // unresolved25 if (Node.needsOverloadResolutionForCopyConstructor() &&26 Node.needsImplicitCopyConstructor())27 return false;28 if (Node.needsOverloadResolutionForMoveConstructor() &&29 Node.needsImplicitMoveConstructor())30 return false;31 if (Node.needsOverloadResolutionForCopyAssignment() &&32 Node.needsImplicitCopyAssignment())33 return false;34 if (Node.needsOverloadResolutionForMoveAssignment() &&35 Node.needsImplicitMoveAssignment())36 return false;37 // default but not deleted38 if (Node.hasSimpleCopyConstructor())39 return false;40 if (Node.hasSimpleMoveConstructor())41 return false;42 if (Node.hasSimpleCopyAssignment())43 return false;44 if (Node.hasSimpleMoveAssignment())45 return false;46 47 for (const CXXConstructorDecl *C : Node.ctors()) {48 if (C->isCopyOrMoveConstructor() && C->isDefaulted() && !C->isDeleted())49 return false;50 }51 for (const CXXMethodDecl *M : Node.methods()) {52 if (M->isCopyAssignmentOperator())53 llvm::errs() << M->isDeleted() << "\n";54 if (M->isCopyAssignmentOperator() && M->isDefaulted() && !M->isDeleted())55 return false;56 if (M->isMoveAssignmentOperator() && M->isDefaulted() && !M->isDeleted())57 return false;58 }59 // FIXME: find ways to identifier correct handle capture this lambda60 return true;61}62 63} // namespace64 65constexpr const char *DefaultFunctionWrapperTypes =66 "::std::function;::std::move_only_function;::boost::function";67constexpr const char *DefaultBindFunctions =68 "::std::bind;::boost::bind;::std::bind_front;::std::bind_back;"69 "::boost::compat::bind_front;::boost::compat::bind_back";70 71CapturingThisInMemberVariableCheck::CapturingThisInMemberVariableCheck(72 StringRef Name, ClangTidyContext *Context)73 : ClangTidyCheck(Name, Context),74 FunctionWrapperTypes(utils::options::parseStringList(75 Options.get("FunctionWrapperTypes", DefaultFunctionWrapperTypes))),76 BindFunctions(utils::options::parseStringList(77 Options.get("BindFunctions", DefaultBindFunctions))) {}78void CapturingThisInMemberVariableCheck::storeOptions(79 ClangTidyOptions::OptionMap &Opts) {80 Options.store(Opts, "FunctionWrapperTypes",81 utils::options::serializeStringList(FunctionWrapperTypes));82 Options.store(Opts, "BindFunctions",83 utils::options::serializeStringList(BindFunctions));84}85 86void CapturingThisInMemberVariableCheck::registerMatchers(MatchFinder *Finder) {87 auto IsStdFunctionField =88 fieldDecl(hasType(cxxRecordDecl(89 matchers::matchesAnyListedName(FunctionWrapperTypes))))90 .bind("field");91 auto CaptureThis = lambdaCapture(anyOf(92 // [this]93 capturesThis(),94 // [self = this]95 capturesVar(varDecl(hasInitializer(cxxThisExpr())))));96 auto IsLambdaCapturingThis =97 lambdaExpr(hasAnyCapture(CaptureThis)).bind("lambda");98 99 auto IsBindCapturingThis =100 callExpr(101 callee(functionDecl(matchers::matchesAnyListedName(BindFunctions))102 .bind("callee")),103 hasAnyArgument(cxxThisExpr()))104 .bind("bind");105 106 auto IsInitWithLambdaOrBind =107 anyOf(IsLambdaCapturingThis, IsBindCapturingThis,108 cxxConstructExpr(hasArgument(109 0, anyOf(IsLambdaCapturingThis, IsBindCapturingThis))));110 111 Finder->addMatcher(112 cxxRecordDecl(113 anyOf(has(cxxConstructorDecl(114 unless(isCopyConstructor()), unless(isMoveConstructor()),115 hasAnyConstructorInitializer(cxxCtorInitializer(116 isMemberInitializer(), forField(IsStdFunctionField),117 withInitializer(IsInitWithLambdaOrBind))))),118 has(fieldDecl(IsStdFunctionField,119 hasInClassInitializer(IsInitWithLambdaOrBind)))),120 unless(correctHandleCaptureThisLambda())),121 this);122}123void CapturingThisInMemberVariableCheck::check(124 const MatchFinder::MatchResult &Result) {125 if (const auto *Lambda = Result.Nodes.getNodeAs<LambdaExpr>("lambda")) {126 diag(Lambda->getBeginLoc(),127 "'this' captured by a lambda and stored in a class member variable; "128 "disable implicit class copying/moving to prevent potential "129 "use-after-free");130 } else if (const auto *Bind = Result.Nodes.getNodeAs<CallExpr>("bind")) {131 const auto *Callee = Result.Nodes.getNodeAs<FunctionDecl>("callee");132 assert(Callee);133 diag(Bind->getBeginLoc(),134 "'this' captured by a '%0' call and stored in a class member "135 "variable; disable implicit class copying/moving to prevent potential "136 "use-after-free")137 << Callee->getQualifiedNameAsString();138 }139 140 const auto *Field = Result.Nodes.getNodeAs<FieldDecl>("field");141 assert(Field);142 143 diag(Field->getLocation(),144 "class member of type '%0' that stores captured 'this'",145 DiagnosticIDs::Note)146 << Field->getType().getAsString();147}148 149} // namespace clang::tidy::bugprone150