//===--- 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) { if (!getLangOpts().CPlusPlus) return; // 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( binaryOperator(anyOf(hasOperatorName("=="), hasOperatorName("!=")), has(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())))), 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("copyAssignmentOperator"); diag(MatchedDecl->getLocation(), "operator=() does not handle self-assignment properly"); } } // namespace bugprone } // namespace tidy } // namespace clang