================ @@ -0,0 +1,212 @@ +//===----------------------------------------------------------------------===// +// +// 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 "UseAggregateCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/DeclCXX.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::modernize { + +/// Check whether \p Ctor is a trivial forwarding constructor: each parameter +/// is used to initialise the corresponding member (in declaration order) and +/// the body is empty. +static bool isTrivialForwardingConstructor(const CXXConstructorDecl *Ctor) { + if (!Ctor || !Ctor->hasBody()) + return false; + + // Body must be an empty compound statement. + const auto *Body = dyn_cast<CompoundStmt>(Ctor->getBody()); + if (!Body || !Body->body_empty()) + return false; + + const CXXRecordDecl *Record = Ctor->getParent(); + + // Collect non-static data members in declaration order. + SmallVector<const FieldDecl *, 8> Fields; + for (const auto *Field : Record->fields()) + Fields.push_back(Field); + + // Number of parameters must match number of fields. + if (Ctor->getNumParams() != Fields.size()) + return false; + + // Number of member initializers must match number of fields (no base + // class inits, no extra inits). + unsigned NumMemberInits = 0; + for (const auto *Init : Ctor->inits()) + if (Init->isMemberInitializer()) + ++NumMemberInits; + else + return false; // base class or delegating init -- bail out + if (NumMemberInits != Fields.size()) + return false; + + // Walk initializers and check each one initializes the matching field + // from the matching parameter. + unsigned FieldIdx = 0; + for (const auto *Init : Ctor->inits()) { + if (!Init->isMemberInitializer()) + return false; + + // Must match the field at the current position. + if (Init->getMember() != Fields[FieldIdx]) + return false; + + const Expr *InitExpr = Init->getInit()->IgnoreImplicit(); + + // Handle CXXConstructExpr wrapping the parameter (for class types). + if (const auto *Construct = dyn_cast<CXXConstructExpr>(InitExpr)) { + if (Construct->getNumArgs() != 1) + return false; + // Must be a copy or move constructor call. + const CXXConstructorDecl *InitCtor = Construct->getConstructor(); + if (!InitCtor->isCopyOrMoveConstructor()) + return false; + InitExpr = Construct->getArg(0)->IgnoreImplicit(); + } + + // The init expression must be a DeclRefExpr to the corresponding param. + const auto *DRE = dyn_cast<DeclRefExpr>(InitExpr); + if (!DRE) + return false; + const auto *PVD = dyn_cast<ParmVarDecl>(DRE->getDecl()); + if (!PVD || PVD != Ctor->getParamDecl(FieldIdx)) + return false; + + ++FieldIdx; + } + + return true; +} + +/// Check whether the class would be a valid aggregate if all user-declared +/// constructors were removed. +static bool canBeAggregate(const CXXRecordDecl *Record, + const LangOptions &LangOpts) { + if (!Record || !Record->hasDefinition()) + return false; + + // Must not have virtual functions. + if (Record->isPolymorphic()) + return false; + + // Must not have private or protected non-static data members. + for (const auto *Field : Record->fields()) + if (Field->getAccess() != AS_public) + return false; + + // Must not have virtual, private, or protected base classes. + for (const auto &Base : Record->bases()) { + if (Base.isVirtual()) + return false; + if (Base.getAccessSpecifier() != AS_public) + return false; + } + + // C++17 and later allow non-virtual public base classes in aggregates. + // Before C++17, aggregates cannot have base classes at all. + if (!LangOpts.CPlusPlus17 && Record->getNumBases() > 0) + return false; + + return true; +} + +void UseAggregateCheck::registerMatchers(MatchFinder *Finder) { + // Match class/struct definitions that have at least one user-provided + // constructor. + Finder->addMatcher( + cxxRecordDecl( + isDefinition(), unless(isImplicit()), unless(isLambda()), + has(cxxConstructorDecl(isUserProvided(), unless(isDeleted())))) + .bind("record"), + this); +} + +void UseAggregateCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Record = Result.Nodes.getNodeAs<CXXRecordDecl>("record"); + if (!Record) + return; + + // Skip records in system headers. + if (Record->getLocation().isInvalid() || + Result.SourceManager->isInSystemHeader(Record->getLocation())) + return; + + // Skip template specializations to avoid false positives. + if (isa<ClassTemplateSpecializationDecl>(Record)) + return; + + // Skip if no fields (empty structs are already aggregates by default). + if (Record->field_empty()) + return; + + // Check aggregate preconditions (ignoring constructors). + if (!canBeAggregate(Record, getLangOpts())) + return; + + // Collect all user-declared constructors. + SmallVector<const CXXConstructorDecl *, 4> UserCtors; + for (const auto *Decl : Record->decls()) { ---------------- zeyi2 wrote:
Can we use `Record->ctors()` directly? https://github.com/llvm/llvm-project/pull/182061 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
