[clang-tools-extra] [clang-tidy] Add modernize-use-shared-ptr-array check (#180441) (PR #199458)
Yanzuo Liu via cfe-commits
cfe-commits at lists.llvm.org
Thu Jun 25 04:47:16 PDT 2026
================
@@ -0,0 +1,378 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "UseSharedPtrArrayCheck.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/ExprCXX.h"
+#include "clang/AST/Stmt.h"
+#include "clang/AST/TypeLoc.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Lex/Lexer.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::modernize {
+
+AST_MATCHER(FunctionDecl, funcHasSingleArrayDeleteBody) {
+ if (Node.getNumParams() != 1 || !Node.hasBody())
+ return false;
+ const ParmVarDecl *Param = Node.getParamDecl(0);
+ const auto *CS = dyn_cast<CompoundStmt>(Node.getBody());
+ if (!CS || CS->size() != 1)
+ return false;
+ const auto *E = dyn_cast<Expr>(CS->body_front());
+ if (!E)
+ return false;
+ const auto *DE = dyn_cast<CXXDeleteExpr>(E->IgnoreParenImpCasts());
+ if (!DE || !DE->isArrayForm())
+ return false;
+ const auto *DRE =
+ dyn_cast<DeclRefExpr>(DE->getArgument()->IgnoreParenImpCasts());
+ return DRE && DRE->getDecl() == Param;
+}
+
+AST_MATCHER(LambdaExpr, lambdaHasSingleArrayDeleteBody) {
+ if (Node.capture_size() != 0)
+ return false;
+ const CXXMethodDecl *CallOp = Node.getCallOperator();
+ if (!CallOp || CallOp->getNumParams() != 1)
+ return false;
+ const ParmVarDecl *Param = CallOp->getParamDecl(0);
+ const auto *CS = dyn_cast<CompoundStmt>(Node.getBody());
+ if (!CS || CS->size() != 1)
+ return false;
+ const auto *E = dyn_cast<Expr>(CS->body_front());
+ if (!E)
+ return false;
+ const auto *DE = dyn_cast<CXXDeleteExpr>(E->IgnoreParenImpCasts());
+ if (!DE || !DE->isArrayForm())
+ return false;
+ const auto *DRE =
+ dyn_cast<DeclRefExpr>(DE->getArgument()->IgnoreParenImpCasts());
+ return DRE && DRE->getDecl() == Param;
+}
+
+void UseSharedPtrArrayCheck::registerMatchers(MatchFinder *Finder) {
+ Finder->addMatcher(
+ cxxConstructExpr(
+ unless(isInTemplateInstantiation()),
+ hasType(qualType(hasDeclaration(classTemplateSpecializationDecl(
+ hasName("::std::shared_ptr"), templateArgumentCountIs(1))))),
+
+ argumentCountIs(2),
+
+ hasArgument(
+ 0, ignoringParenImpCasts(cxxNewExpr(isArray()).bind("newExpr"))),
+
+ hasArgument(
+ 1, ignoringImplicit(anyOf(
+
+ cxxConstructExpr(
+ hasType(qualType(hasCanonicalType(
+ hasDeclaration(classTemplateSpecializationDecl(
+ hasName("::std::default_delete")))))))
+ .bind("defaultDelete"),
+
+ lambdaExpr(lambdaHasSingleArrayDeleteBody())
+ .bind("lambdaDeleter"),
+
+ declRefExpr(to(functionDecl(funcHasSingleArrayDeleteBody())
+ .bind("deleterFunction")))))))
+
+ .bind("sharedPtrCtor"),
+ this);
+}
+
+// From bugprone-smart-ptr-array-mismatch-check
+// Same as SmartPtrArrayMismatchCheck::getConstructedVarOrField.
+static const DeclaratorDecl *
+getConstructedVarOrField(const Expr *FoundConstructExpr, ASTContext &Ctx) {
+ const DynTypedNodeList ConstructParents =
+ Ctx.getParentMapContext().getParents(*FoundConstructExpr);
+ if (ConstructParents.size() != 1)
+ return nullptr;
+ const auto *ParentDecl = ConstructParents.begin()->get<DeclaratorDecl>();
+ if (isa_and_nonnull<VarDecl, FieldDecl>(ParentDecl))
+ return ParentDecl;
+
+ return nullptr;
+}
+
+// Returns a StringRef into the SourceManager-owned buffer; stable for lifetime
+// of the ASTContext.
+static StringRef extractWrittenElementType(const TypeSourceInfo *TSI,
+ SourceManager &SM,
+ const LangOptions &LO) {
+ if (!TSI)
+ return {};
+ const TypeLoc TL = TSI->getTypeLoc().getUnqualifiedLoc();
+ auto TSTypeLoc = TL.getAsAdjusted<TemplateSpecializationTypeLoc>();
+ if (!TSTypeLoc || TSTypeLoc.getNumArgs() < 1)
+ return {};
+ const TypeSourceInfo *ArgTSI = TSTypeLoc.getArgLoc(0).getTypeSourceInfo();
+ if (!ArgTSI)
+ return {};
+ const TypeLoc ArgTL = ArgTSI->getTypeLoc();
+ const CharSourceRange R =
+ CharSourceRange::getTokenRange(ArgTL.getBeginLoc(), ArgTL.getEndLoc());
+ return Lexer::getSourceText(R, SM, LO);
+}
+
+static QualType getDeleterParamPointee(const Expr *DeleterArg) {
+ const Expr *E = DeleterArg->IgnoreParenImpCasts();
+
+ if (const auto *L = dyn_cast<LambdaExpr>(E)) {
+ const CXXMethodDecl *CallOp = L->getCallOperator();
+ if (!CallOp || CallOp->getNumParams() != 1)
+ return {};
+ const QualType P = CallOp->getParamDecl(0)->getType();
+ if (!P->isPointerType())
+ return {};
+ return P->getPointeeType();
+ }
+
+ if (const auto *DRE = dyn_cast<DeclRefExpr>(E)) {
+ if (const auto *FD = dyn_cast<FunctionDecl>(DRE->getDecl())) {
+ if (FD->getNumParams() != 1)
+ return {};
+ const QualType P = FD->getParamDecl(0)->getType();
+ if (!P->isPointerType())
+ return {};
+ return P->getPointeeType();
+ }
+ }
+
+ // default_delete<T[]>: the template argument is T[], extract T.
+ if (const auto *CE = dyn_cast<CXXConstructExpr>(E)) {
+ const auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(
+ CE->getConstructor()->getParent());
+ if (!CTSD || CTSD->getTemplateArgs().size() != 1)
+ return {};
+ const TemplateArgument &Arg = CTSD->getTemplateArgs()[0];
+ if (Arg.getKind() != TemplateArgument::Type)
+ return {};
+ const QualType T = Arg.getAsType();
+ if (!T->isArrayType())
+ return {};
+ return cast<ArrayType>(T.getTypePtr())->getElementType();
+ }
+
+ return {};
+}
+
+// Manual parent walk rather than a matcher because implicit
+// wrappers obscure assignment contexts.
+static bool isInsideAssignment(ASTContext &Ctx, const CXXConstructExpr *Ctor) {
+ DynTypedNode Node = DynTypedNode::create(*Ctor);
+ while (true) {
+ auto Parents = Ctx.getParents(Node);
+ if (Parents.empty())
+ return false;
+
+ bool Advanced = false;
+ for (const DynTypedNode &P : Parents) {
+ if (const auto *Op = P.get<CXXOperatorCallExpr>();
+ Op && Op->getOperator() == OO_Equal)
+ return true;
+
+ // VarDecl indicates initialization rather than assignment.
+ if (P.get<VarDecl>())
+ return false;
+
+ if (!Advanced && (P.get<Expr>() || P.get<CXXBindTemporaryExpr>() ||
+ P.get<MaterializeTemporaryExpr>())) {
+ Node = P;
+ Advanced = true;
+ }
+ }
+
+ if (!Advanced)
+ return false;
+ }
+}
+
+// FixIt 2: remove ", deleter" — from the end of arg 0 to the end of arg 1.
+static std::optional<FixItHint>
+buildRemoveDeleterFix(const CXXConstructExpr *Ctor, SourceManager &SM,
+ const LangOptions &LO) {
+ const Expr *Arg0 = Ctor->getArg(0);
+ const Expr *Arg1 = Ctor->getArg(1);
+
+ const SourceLocation Arg0End =
+ Lexer::getLocForEndOfToken(Arg0->getEndLoc(), 0, SM, LO);
+ const SourceLocation Arg1End =
+ Lexer::getLocForEndOfToken(Arg1->getEndLoc(), 0, SM, LO);
+
+ if (Arg0End.isInvalid() || Arg1End.isInvalid())
+ return std::nullopt;
+
+ return FixItHint::CreateRemoval(
+ CharSourceRange::getCharRange(Arg0End, Arg1End));
+}
+
+void UseSharedPtrArrayCheck::check(const MatchFinder::MatchResult &Result) {
+ const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructExpr>("sharedPtrCtor");
+ assert(Ctor && "sharedPtrCtor must be bound");
+
+ const auto *NewExpr = Result.Nodes.getNodeAs<CXXNewExpr>("newExpr");
+ assert(NewExpr && "newExpr must be bound");
+
+ const auto *CTSD = cast<ClassTemplateSpecializationDecl>(
+ Ctor->getType()->getAsCXXRecordDecl());
+ assert(CTSD->getTemplateArgs().size() == 1 &&
+ "shared_ptr must have exactly one template argument");
+
+ const TemplateArgument &TyArg = CTSD->getTemplateArgs()[0];
+ assert(TyArg.getKind() == TemplateArgument::Type &&
+ "shared_ptr template argument must be a type");
+
+ QualType ElemTy = TyArg.getAsType();
+ if (ElemTy->isArrayType() || ElemTy->isDependentType())
----------------
zwuis wrote:
Use `hasTemplateArgument`/`hasTemplateArgumentLoc` matcher so that we can filter element types in AST matchers.
https://github.com/llvm/llvm-project/pull/199458
More information about the cfe-commits
mailing list