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