1 //===--- UseEqualsDefaultCheck.cpp - clang-tidy----------------------------===// 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-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "UseEqualsDefaultCheck.h" 10 #include "../utils/LexerUtils.h" 11 #include "../utils/Matchers.h" 12 #include "clang/AST/ASTContext.h" 13 #include "clang/ASTMatchers/ASTMatchFinder.h" 14 #include "clang/Lex/Lexer.h" 15 #include <optional> 16 17 using namespace clang::ast_matchers; 18 19 namespace clang::tidy::modernize { 20 21 static const char SpecialFunction[] = "SpecialFunction"; 22 23 /// Finds all the named non-static fields of \p Record. 24 static std::set<const FieldDecl *> 25 getAllNamedFields(const CXXRecordDecl *Record) { 26 std::set<const FieldDecl *> Result; 27 for (const auto *Field : Record->fields()) { 28 // Static data members are not in this range. 29 if (Field->isUnnamedBitfield()) 30 continue; 31 Result.insert(Field); 32 } 33 return Result; 34 } 35 36 /// Returns the names of the direct bases of \p Record, both virtual and 37 /// non-virtual. 38 static std::set<const Type *> getAllDirectBases(const CXXRecordDecl *Record) { 39 std::set<const Type *> Result; 40 for (auto Base : Record->bases()) { 41 // CXXBaseSpecifier. 42 const auto *BaseType = Base.getTypeSourceInfo()->getType().getTypePtr(); 43 Result.insert(BaseType); 44 } 45 return Result; 46 } 47 48 /// Returns a matcher that matches member expressions where the base is 49 /// the variable declared as \p Var and the accessed member is the one declared 50 /// as \p Field. 51 internal::Matcher<Expr> accessToFieldInVar(const FieldDecl *Field, 52 const ValueDecl *Var) { 53 return ignoringImpCasts( 54 memberExpr(hasObjectExpression(declRefExpr(to(varDecl(equalsNode(Var))))), 55 member(fieldDecl(equalsNode(Field))))); 56 } 57 58 /// Check that the given constructor has copy signature and that it 59 /// copy-initializes all its bases and members. 60 static bool isCopyConstructorAndCanBeDefaulted(ASTContext *Context, 61 const CXXConstructorDecl *Ctor) { 62 // An explicitly-defaulted constructor cannot have default arguments. 63 if (Ctor->getMinRequiredArguments() != 1) 64 return false; 65 66 const auto *Record = Ctor->getParent(); 67 const auto *Param = Ctor->getParamDecl(0); 68 69 // Base classes and members that have to be copied. 70 auto BasesToInit = getAllDirectBases(Record); 71 auto FieldsToInit = getAllNamedFields(Record); 72 73 // Ensure that all the bases are copied. 74 for (const auto *Base : BasesToInit) { 75 // The initialization of a base class should be a call to a copy 76 // constructor of the base. 77 if (match( 78 traverse(TK_AsIs, 79 cxxConstructorDecl( 80 forEachConstructorInitializer(cxxCtorInitializer( 81 isBaseInitializer(), 82 withInitializer(cxxConstructExpr( 83 hasType(equalsNode(Base)), 84 hasDeclaration( 85 cxxConstructorDecl(isCopyConstructor())), 86 argumentCountIs(1), 87 hasArgument(0, declRefExpr(to(varDecl( 88 equalsNode(Param))))))))))), 89 *Ctor, *Context) 90 .empty()) 91 return false; 92 } 93 94 // Ensure that all the members are copied. 95 for (const auto *Field : FieldsToInit) { 96 auto AccessToFieldInParam = accessToFieldInVar(Field, Param); 97 // The initialization is a CXXConstructExpr for class types. 98 if (match(traverse( 99 TK_AsIs, 100 cxxConstructorDecl( 101 forEachConstructorInitializer(cxxCtorInitializer( 102 isMemberInitializer(), forField(equalsNode(Field)), 103 withInitializer(anyOf( 104 AccessToFieldInParam, 105 initListExpr(has(AccessToFieldInParam)), 106 cxxConstructExpr( 107 hasDeclaration( 108 cxxConstructorDecl(isCopyConstructor())), 109 argumentCountIs(1), 110 hasArgument(0, AccessToFieldInParam)))))))), 111 *Ctor, *Context) 112 .empty()) 113 return false; 114 } 115 116 // Ensure that we don't do anything else, like initializing an indirect base. 117 return Ctor->getNumCtorInitializers() == 118 BasesToInit.size() + FieldsToInit.size(); 119 } 120 121 /// Checks that the given method is an overloading of the assignment 122 /// operator, has copy signature, returns a reference to "*this" and copies 123 /// all its members and subobjects. 124 static bool isCopyAssignmentAndCanBeDefaulted(ASTContext *Context, 125 const CXXMethodDecl *Operator) { 126 const auto *Record = Operator->getParent(); 127 const auto *Param = Operator->getParamDecl(0); 128 129 // Base classes and members that have to be copied. 130 auto BasesToInit = getAllDirectBases(Record); 131 auto FieldsToInit = getAllNamedFields(Record); 132 133 const auto *Compound = cast<CompoundStmt>(Operator->getBody()); 134 135 // The assignment operator definition has to end with the following return 136 // statement: 137 // return *this; 138 if (Compound->body_empty() || 139 match(traverse( 140 TK_AsIs, 141 returnStmt(has(ignoringParenImpCasts(unaryOperator( 142 hasOperatorName("*"), hasUnaryOperand(cxxThisExpr())))))), 143 *Compound->body_back(), *Context) 144 .empty()) 145 return false; 146 147 // Ensure that all the bases are copied. 148 for (const auto *Base : BasesToInit) { 149 // Assignment operator of a base class: 150 // Base::operator=(Other); 151 // 152 // Clang translates this into: 153 // ((Base*)this)->operator=((Base)Other); 154 // 155 // So we are looking for a member call that fulfills: 156 if (match(traverse( 157 TK_AsIs, 158 compoundStmt(has(ignoringParenImpCasts(cxxMemberCallExpr( 159 // - The object is an implicit cast of 'this' to a 160 // pointer to 161 // a base class. 162 onImplicitObjectArgument(implicitCastExpr( 163 hasImplicitDestinationType(hasCanonicalType(pointsTo( 164 type(equalsNode(Base->getCanonicalTypeInternal() 165 .getTypePtr()))))), 166 hasSourceExpression(cxxThisExpr()))), 167 // - The called method is the operator=. 168 callee(cxxMethodDecl(isCopyAssignmentOperator())), 169 // - The argument is (an implicit cast to a Base of) 170 // the argument taken by "Operator". 171 argumentCountIs(1), 172 hasArgument( 173 0, declRefExpr(to(varDecl(equalsNode(Param)))))))))), 174 *Compound, *Context) 175 .empty()) 176 return false; 177 } 178 179 // Ensure that all the members are copied. 180 for (const auto *Field : FieldsToInit) { 181 // The assignment of data members: 182 // Field = Other.Field; 183 // Is a BinaryOperator in non-class types, and a CXXOperatorCallExpr 184 // otherwise. 185 auto LHS = memberExpr(hasObjectExpression(cxxThisExpr()), 186 member(fieldDecl(equalsNode(Field)))); 187 auto RHS = accessToFieldInVar(Field, Param); 188 if (match(traverse(TK_AsIs, 189 compoundStmt(has(ignoringParenImpCasts(binaryOperation( 190 hasOperatorName("="), hasLHS(LHS), hasRHS(RHS)))))), 191 *Compound, *Context) 192 .empty()) 193 return false; 194 } 195 196 // Ensure that we don't do anything else. 197 return Compound->size() == BasesToInit.size() + FieldsToInit.size() + 1; 198 } 199 200 /// Returns false if the body has any non-whitespace character. 201 static bool bodyEmpty(const ASTContext *Context, const CompoundStmt *Body) { 202 bool Invalid = false; 203 StringRef Text = Lexer::getSourceText( 204 CharSourceRange::getCharRange(Body->getLBracLoc().getLocWithOffset(1), 205 Body->getRBracLoc()), 206 Context->getSourceManager(), Context->getLangOpts(), &Invalid); 207 return !Invalid && std::strspn(Text.data(), " \t\r\n") == Text.size(); 208 } 209 210 UseEqualsDefaultCheck::UseEqualsDefaultCheck(StringRef Name, 211 ClangTidyContext *Context) 212 : ClangTidyCheck(Name, Context), 213 IgnoreMacros(Options.getLocalOrGlobal("IgnoreMacros", true)) {} 214 215 void UseEqualsDefaultCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { 216 Options.store(Opts, "IgnoreMacros", IgnoreMacros); 217 } 218 219 namespace { 220 AST_MATCHER(CXXMethodDecl, isOutOfLine) { return Node.isOutOfLine(); } 221 } // namespace 222 223 void UseEqualsDefaultCheck::registerMatchers(MatchFinder *Finder) { 224 // Skip unions/union-like classes since their constructors behave differently 225 // when defaulted vs. empty. 226 auto IsUnionLikeClass = recordDecl( 227 anyOf(isUnion(), 228 has(fieldDecl(isImplicit(), hasType(cxxRecordDecl(isUnion())))))); 229 230 const LangOptions &LangOpts = getLangOpts(); 231 auto IsPublicOrOutOfLineUntilCPP20 = 232 LangOpts.CPlusPlus20 233 ? cxxConstructorDecl() 234 : cxxConstructorDecl(anyOf(isOutOfLine(), isPublic())); 235 236 // Destructor. 237 Finder->addMatcher( 238 cxxDestructorDecl(isDefinition(), unless(ofClass(IsUnionLikeClass))) 239 .bind(SpecialFunction), 240 this); 241 // Constructor. 242 Finder->addMatcher( 243 cxxConstructorDecl( 244 isDefinition(), unless(ofClass(IsUnionLikeClass)), 245 unless(hasParent(functionTemplateDecl())), 246 anyOf( 247 // Default constructor. 248 allOf(parameterCountIs(0), 249 unless(hasAnyConstructorInitializer(isWritten())), 250 unless(isVariadic()), IsPublicOrOutOfLineUntilCPP20), 251 // Copy constructor. 252 allOf(isCopyConstructor(), 253 // Discard constructors that can be used as a copy 254 // constructor because all the other arguments have 255 // default values. 256 parameterCountIs(1)))) 257 .bind(SpecialFunction), 258 this); 259 // Copy-assignment operator. 260 Finder->addMatcher( 261 cxxMethodDecl(isDefinition(), isCopyAssignmentOperator(), 262 unless(ofClass(IsUnionLikeClass)), 263 unless(hasParent(functionTemplateDecl())), 264 // isCopyAssignmentOperator() allows the parameter to be 265 // passed by value, and in this case it cannot be 266 // defaulted. 267 hasParameter(0, hasType(lValueReferenceType())), 268 // isCopyAssignmentOperator() allows non lvalue reference 269 // return types, and in this case it cannot be defaulted. 270 returns(qualType(hasCanonicalType( 271 allOf(lValueReferenceType(pointee(type())), 272 unless(matchers::isReferenceToConst())))))) 273 .bind(SpecialFunction), 274 this); 275 } 276 277 void UseEqualsDefaultCheck::check(const MatchFinder::MatchResult &Result) { 278 // Both CXXConstructorDecl and CXXDestructorDecl inherit from CXXMethodDecl. 279 const auto *SpecialFunctionDecl = 280 Result.Nodes.getNodeAs<CXXMethodDecl>(SpecialFunction); 281 282 if (IgnoreMacros && SpecialFunctionDecl->getLocation().isMacroID()) 283 return; 284 285 // Discard explicitly deleted/defaulted special member functions and those 286 // that are not user-provided (automatically generated). 287 if (SpecialFunctionDecl->isDeleted() || 288 SpecialFunctionDecl->isExplicitlyDefaulted() || 289 SpecialFunctionDecl->isLateTemplateParsed() || 290 SpecialFunctionDecl->isTemplateInstantiation() || 291 !SpecialFunctionDecl->isUserProvided() || !SpecialFunctionDecl->hasBody()) 292 return; 293 294 const auto *Body = dyn_cast<CompoundStmt>(SpecialFunctionDecl->getBody()); 295 if (!Body) 296 return; 297 298 // If there is code inside the body, don't warn. 299 if (!SpecialFunctionDecl->isCopyAssignmentOperator() && !Body->body_empty()) 300 return; 301 302 // If body contain any preprocesor derictives, don't warn. 303 if (IgnoreMacros && utils::lexer::rangeContainsExpansionsOrDirectives( 304 Body->getSourceRange(), *Result.SourceManager, 305 Result.Context->getLangOpts())) 306 return; 307 308 // If there are comments inside the body, don't do the change. 309 bool ApplyFix = SpecialFunctionDecl->isCopyAssignmentOperator() || 310 bodyEmpty(Result.Context, Body); 311 312 std::vector<FixItHint> RemoveInitializers; 313 unsigned MemberType = 0; 314 if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(SpecialFunctionDecl)) { 315 if (Ctor->getNumParams() == 0) { 316 MemberType = 0; 317 } else { 318 if (!isCopyConstructorAndCanBeDefaulted(Result.Context, Ctor)) 319 return; 320 MemberType = 1; 321 // If there are constructor initializers, they must be removed. 322 for (const auto *Init : Ctor->inits()) { 323 RemoveInitializers.emplace_back( 324 FixItHint::CreateRemoval(Init->getSourceRange())); 325 } 326 } 327 } else if (isa<CXXDestructorDecl>(SpecialFunctionDecl)) { 328 MemberType = 2; 329 } else { 330 if (!isCopyAssignmentAndCanBeDefaulted(Result.Context, SpecialFunctionDecl)) 331 return; 332 MemberType = 3; 333 } 334 335 // The location of the body is more useful inside a macro as spelling and 336 // expansion locations are reported. 337 SourceLocation Location = SpecialFunctionDecl->getLocation(); 338 if (Location.isMacroID()) 339 Location = Body->getBeginLoc(); 340 341 auto Diag = diag( 342 Location, 343 "use '= default' to define a trivial %select{default constructor|copy " 344 "constructor|destructor|copy-assignment operator}0"); 345 Diag << MemberType; 346 347 if (ApplyFix) { 348 SourceLocation UnifiedEnd = utils::lexer::getUnifiedEndLoc( 349 *Body, Result.Context->getSourceManager(), 350 Result.Context->getLangOpts()); 351 // Skipping comments, check for a semicolon after Body->getSourceRange() 352 std::optional<Token> Token = utils::lexer::findNextTokenSkippingComments( 353 UnifiedEnd, Result.Context->getSourceManager(), 354 Result.Context->getLangOpts()); 355 StringRef Replacement = 356 Token && Token->is(tok::semi) ? "= default" : "= default;"; 357 Diag << FixItHint::CreateReplacement(Body->getSourceRange(), Replacement) 358 << RemoveInitializers; 359 } 360 } 361 362 } // namespace clang::tidy::modernize 363