| //===--- UnhandledSelfAssignmentCheck.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 "UnhandledSelfAssignmentCheck.h" |
| #include "clang/AST/ASTContext.h" |
| #include "clang/ASTMatchers/ASTMatchFinder.h" |
| |
| using namespace clang::ast_matchers; |
| |
| namespace clang { |
| namespace tidy { |
| namespace bugprone { |
| |
| UnhandledSelfAssignmentCheck::UnhandledSelfAssignmentCheck( |
| StringRef Name, ClangTidyContext *Context) |
| : ClangTidyCheck(Name, Context), |
| WarnOnlyIfThisHasSuspiciousField( |
| Options.get("WarnOnlyIfThisHasSuspiciousField", true)) {} |
| |
| void UnhandledSelfAssignmentCheck::storeOptions( |
| ClangTidyOptions::OptionMap &Opts) { |
| Options.store(Opts, "WarnOnlyIfThisHasSuspiciousField", |
| WarnOnlyIfThisHasSuspiciousField); |
| } |
| |
| void UnhandledSelfAssignmentCheck::registerMatchers(MatchFinder *Finder) { |
| // We don't care about deleted, default or implicit operator implementations. |
| const auto IsUserDefined = cxxMethodDecl( |
| isDefinition(), unless(anyOf(isDeleted(), isImplicit(), isDefaulted()))); |
| |
| // We don't need to worry when a copy assignment operator gets the other |
| // object by value. |
| const auto HasReferenceParam = |
| cxxMethodDecl(hasParameter(0, parmVarDecl(hasType(referenceType())))); |
| |
| // Self-check: Code compares something with 'this' pointer. We don't check |
| // whether it is actually the parameter what we compare. |
| const auto HasNoSelfCheck = cxxMethodDecl(unless(hasDescendant( |
| binaryOperation(hasAnyOperatorName("==", "!="), |
| hasEitherOperand(ignoringParenCasts(cxxThisExpr())))))); |
| |
| // Both copy-and-swap and copy-and-move method creates a copy first and |
| // assign it to 'this' with swap or move. |
| // In the non-template case, we can search for the copy constructor call. |
| const auto HasNonTemplateSelfCopy = cxxMethodDecl( |
| ofClass(cxxRecordDecl(unless(hasAncestor(classTemplateDecl())))), |
| traverse(TK_AsIs, |
| hasDescendant(cxxConstructExpr(hasDeclaration(cxxConstructorDecl( |
| isCopyConstructor(), ofClass(equalsBoundNode("class")))))))); |
| |
| // In the template case, we need to handle two separate cases: 1) a local |
| // variable is created with the copy, 2) copy is created only as a temporary |
| // object. |
| const auto HasTemplateSelfCopy = cxxMethodDecl( |
| ofClass(cxxRecordDecl(hasAncestor(classTemplateDecl()))), |
| anyOf(hasDescendant( |
| varDecl(hasType(cxxRecordDecl(equalsBoundNode("class"))), |
| hasDescendant(parenListExpr()))), |
| hasDescendant(cxxUnresolvedConstructExpr(hasDescendant(declRefExpr( |
| hasType(cxxRecordDecl(equalsBoundNode("class"))))))))); |
| |
| // If inside the copy assignment operator another assignment operator is |
| // called on 'this' we assume that self-check might be handled inside |
| // this nested operator. |
| const auto HasNoNestedSelfAssign = |
| cxxMethodDecl(unless(hasDescendant(cxxMemberCallExpr(callee(cxxMethodDecl( |
| hasName("operator="), ofClass(equalsBoundNode("class")))))))); |
| |
| DeclarationMatcher AdditionalMatcher = cxxMethodDecl(); |
| if (WarnOnlyIfThisHasSuspiciousField) { |
| // Matcher for standard smart pointers. |
| const auto SmartPointerType = qualType(hasUnqualifiedDesugaredType( |
| recordType(hasDeclaration(classTemplateSpecializationDecl( |
| hasAnyName("::std::shared_ptr", "::std::unique_ptr", |
| "::std::weak_ptr", "::std::auto_ptr"), |
| templateArgumentCountIs(1)))))); |
| |
| // We will warn only if the class has a pointer or a C array field which |
| // probably causes a problem during self-assignment (e.g. first resetting |
| // the pointer member, then trying to access the object pointed by the |
| // pointer, or memcpy overlapping arrays). |
| AdditionalMatcher = cxxMethodDecl(ofClass(cxxRecordDecl( |
| has(fieldDecl(anyOf(hasType(pointerType()), hasType(SmartPointerType), |
| hasType(arrayType()))))))); |
| } |
| |
| Finder->addMatcher(cxxMethodDecl(ofClass(cxxRecordDecl().bind("class")), |
| isCopyAssignmentOperator(), IsUserDefined, |
| HasReferenceParam, HasNoSelfCheck, |
| unless(HasNonTemplateSelfCopy), |
| unless(HasTemplateSelfCopy), |
| HasNoNestedSelfAssign, AdditionalMatcher) |
| .bind("copyAssignmentOperator"), |
| this); |
| } |
| |
| void UnhandledSelfAssignmentCheck::check( |
| const MatchFinder::MatchResult &Result) { |
| const auto *MatchedDecl = |
| Result.Nodes.getNodeAs<CXXMethodDecl>("copyAssignmentOperator"); |
| diag(MatchedDecl->getLocation(), |
| "operator=() does not handle self-assignment properly"); |
| } |
| |
| } // namespace bugprone |
| } // namespace tidy |
| } // namespace clang |