133 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 "UnhandledSelfAssignmentCheck.h"10#include "clang/AST/ASTContext.h"11#include "clang/ASTMatchers/ASTMatchFinder.h"12 13using namespace clang::ast_matchers;14 15namespace clang::tidy::bugprone {16 17UnhandledSelfAssignmentCheck::UnhandledSelfAssignmentCheck(18 StringRef Name, ClangTidyContext *Context)19 : ClangTidyCheck(Name, Context),20 WarnOnlyIfThisHasSuspiciousField(21 Options.get("WarnOnlyIfThisHasSuspiciousField", true)) {}22 23void UnhandledSelfAssignmentCheck::storeOptions(24 ClangTidyOptions::OptionMap &Opts) {25 Options.store(Opts, "WarnOnlyIfThisHasSuspiciousField",26 WarnOnlyIfThisHasSuspiciousField);27}28 29void UnhandledSelfAssignmentCheck::registerMatchers(MatchFinder *Finder) {30 // We don't care about deleted, default or implicit operator implementations.31 const auto IsUserDefined = cxxMethodDecl(32 isDefinition(), unless(anyOf(isDeleted(), isImplicit(), isDefaulted())));33 34 // We don't need to worry when a copy assignment operator gets the other35 // object by value.36 const auto HasReferenceParam =37 cxxMethodDecl(hasParameter(0, parmVarDecl(hasType(referenceType()))));38 39 // Self-check: Code compares something with 'this' pointer. We don't check40 // whether it is actually the parameter what we compare.41 const auto HasNoSelfCheck = cxxMethodDecl(unless(hasDescendant(42 binaryOperation(hasAnyOperatorName("==", "!="),43 hasEitherOperand(ignoringParenCasts(cxxThisExpr()))))));44 45 // Both copy-and-swap and copy-and-move method creates a copy first and46 // assign it to 'this' with swap or move.47 // In the non-template case, we can search for the copy constructor call.48 const auto HasNonTemplateSelfCopy = cxxMethodDecl(49 ofClass(cxxRecordDecl(unless(hasAncestor(classTemplateDecl())))),50 traverse(TK_AsIs,51 hasDescendant(cxxConstructExpr(hasDeclaration(cxxConstructorDecl(52 isCopyConstructor(), ofClass(equalsBoundNode("class"))))))));53 54 // In the template case, we need to handle two separate cases: 1) a local55 // variable is created with the copy, 2) copy is created only as a temporary56 // object.57 const auto HasTemplateSelfCopy = cxxMethodDecl(58 ofClass(cxxRecordDecl(hasAncestor(classTemplateDecl()))),59 anyOf(hasDescendant(60 varDecl(hasType(cxxRecordDecl(equalsBoundNode("class"))),61 hasDescendant(parenListExpr()))),62 hasDescendant(cxxUnresolvedConstructExpr(hasDescendant(declRefExpr(63 hasType(cxxRecordDecl(equalsBoundNode("class")))))))));64 65 // If inside the copy assignment operator another assignment operator is66 // called on 'this' we assume that self-check might be handled inside67 // this nested operator.68 const auto HasNoNestedSelfAssign =69 cxxMethodDecl(unless(hasDescendant(cxxMemberCallExpr(callee(cxxMethodDecl(70 hasName("operator="), ofClass(equalsBoundNode("class"))))))));71 72 // Checking that some kind of constructor is called and followed by a `swap`:73 // T& operator=(const T& other) {74 // T tmp{this->internal_data(), some, other, args};75 // swap(tmp);76 // return *this;77 // }78 const auto HasCopyAndSwap = cxxMethodDecl(79 ofClass(cxxRecordDecl()),80 hasBody(compoundStmt(81 hasDescendant(82 varDecl(hasType(cxxRecordDecl(equalsBoundNode("class"))))83 .bind("tmp_var")),84 hasDescendant(stmt(anyOf(85 cxxMemberCallExpr(hasArgument(86 0, declRefExpr(to(varDecl(equalsBoundNode("tmp_var")))))),87 callExpr(88 callee(functionDecl(hasName("swap"))), argumentCountIs(2),89 hasAnyArgument(90 declRefExpr(to(varDecl(equalsBoundNode("tmp_var"))))),91 hasAnyArgument(unaryOperator(has(cxxThisExpr()),92 hasOperatorName("*"))))))))));93 94 DeclarationMatcher AdditionalMatcher = cxxMethodDecl();95 if (WarnOnlyIfThisHasSuspiciousField) {96 // Matcher for standard smart pointers.97 const auto SmartPointerType = qualType(hasUnqualifiedDesugaredType(98 recordType(hasDeclaration(classTemplateSpecializationDecl(99 anyOf(allOf(hasAnyName("::std::shared_ptr", "::std::weak_ptr",100 "::std::auto_ptr"),101 templateArgumentCountIs(1)),102 allOf(hasName("::std::unique_ptr"),103 templateArgumentCountIs(2))))))));104 105 // We will warn only if the class has a pointer or a C array field which106 // probably causes a problem during self-assignment (e.g. first resetting107 // the pointer member, then trying to access the object pointed by the108 // pointer, or memcpy overlapping arrays).109 AdditionalMatcher = cxxMethodDecl(ofClass(cxxRecordDecl(110 has(fieldDecl(anyOf(hasType(pointerType()), hasType(SmartPointerType),111 hasType(arrayType())))))));112 }113 114 Finder->addMatcher(115 cxxMethodDecl(116 ofClass(cxxRecordDecl().bind("class")), isCopyAssignmentOperator(),117 IsUserDefined, HasReferenceParam, HasNoSelfCheck,118 unless(HasNonTemplateSelfCopy), unless(HasTemplateSelfCopy),119 unless(HasCopyAndSwap), HasNoNestedSelfAssign, AdditionalMatcher)120 .bind("copyAssignmentOperator"),121 this);122}123 124void UnhandledSelfAssignmentCheck::check(125 const MatchFinder::MatchResult &Result) {126 const auto *MatchedDecl =127 Result.Nodes.getNodeAs<CXXMethodDecl>("copyAssignmentOperator");128 diag(MatchedDecl->getLocation(),129 "operator=() does not handle self-assignment properly");130}131 132} // namespace clang::tidy::bugprone133