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