https://github.com/ahatanak updated https://github.com/llvm/llvm-project/pull/110048
>From b9a8339220635b9a0b2d77a11d80b496a18a917a Mon Sep 17 00:00:00 2001 From: Akira Hatanaka <ahata...@gmail.com> Date: Wed, 25 Sep 2024 14:47:05 -0700 Subject: [PATCH] [NFC] Add implicit cast kinds for function pointer conversions The new cast kinds are needed to distinguish between no-op conversions and conversions from pointers to noexcept functions to pointers to functions without noexcept as the latter can cause function pointers to be re-signed on arm64e. See https://github.com/llvm/llvm-project/pull/109056 for background. --- .../bugprone/SwappedArgumentsCheck.cpp | 2 ++ .../ProTypeCstyleCastCheck.cpp | 4 ++- .../google/AvoidCStyleCastsCheck.cpp | 7 ++++- .../clang-tidy/modernize/LoopConvertCheck.cpp | 4 ++- .../ImplicitConversionInLoopCheck.cpp | 4 ++- .../MakeMemberFunctionConstCheck.cpp | 9 ++++-- .../clang-tidy/utils/DeclRefExprUtils.cpp | 2 ++ clang-tools-extra/clangd/Hover.cpp | 2 ++ clang/include/clang/AST/IgnoreExpr.h | 4 ++- clang/include/clang/AST/OperationKinds.def | 8 +++++ .../lib/ARCMigrate/TransBlockObjCVariable.cpp | 6 ++-- clang/lib/AST/ByteCode/Compiler.cpp | 4 +++ clang/lib/AST/Expr.cpp | 28 +++++++++++++---- clang/lib/AST/ExprCXX.cpp | 6 +++- clang/lib/AST/ExprConstant.cpp | 17 +++++++++-- clang/lib/Analysis/CFG.cpp | 2 ++ clang/lib/Analysis/ExprMutationAnalyzer.cpp | 12 ++++---- clang/lib/Analysis/FlowSensitive/Transfer.cpp | 8 +++-- clang/lib/Analysis/ThreadSafety.cpp | 4 ++- clang/lib/Analysis/ThreadSafetyCommon.cpp | 2 ++ clang/lib/CodeGen/CGDecl.cpp | 2 ++ clang/lib/CodeGen/CGExpr.cpp | 6 +++- clang/lib/CodeGen/CGExprAgg.cpp | 4 +++ clang/lib/CodeGen/CGExprComplex.cpp | 2 ++ clang/lib/CodeGen/CGExprConstant.cpp | 2 ++ clang/lib/CodeGen/CGExprScalar.cpp | 4 ++- clang/lib/Edit/RewriteObjCFoundationAPI.cpp | 2 ++ clang/lib/Sema/CheckExprLifetime.cpp | 2 ++ clang/lib/Sema/SemaChecking.cpp | 4 +++ clang/lib/Sema/SemaDecl.cpp | 4 ++- clang/lib/Sema/SemaDeclCXX.cpp | 4 ++- clang/lib/Sema/SemaExpr.cpp | 2 ++ clang/lib/Sema/SemaExprCXX.cpp | 6 +++- clang/lib/Sema/SemaInit.cpp | 4 +-- clang/lib/Sema/SemaOverload.cpp | 6 +++- clang/lib/Sema/SemaStmt.cpp | 2 ++ clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp | 2 ++ clang/lib/StaticAnalyzer/Core/SValBuilder.cpp | 2 ++ .../ast-dump-function-pointer-conversion.cpp | 30 +++++++++++++++++++ .../dcl.decl/dcl.init/dcl.init.ref/p4-ast.cpp | 4 +-- 40 files changed, 193 insertions(+), 36 deletions(-) create mode 100644 clang/test/AST/ast-dump-function-pointer-conversion.cpp diff --git a/clang-tools-extra/clang-tidy/bugprone/SwappedArgumentsCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/SwappedArgumentsCheck.cpp index 8989444dde1300..acb7112fa2abed 100644 --- a/clang-tools-extra/clang-tidy/bugprone/SwappedArgumentsCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/SwappedArgumentsCheck.cpp @@ -27,6 +27,8 @@ void SwappedArgumentsCheck::registerMatchers(MatchFinder *Finder) { static const Expr *ignoreNoOpCasts(const Expr *E) { if (auto *Cast = dyn_cast<CastExpr>(E)) if (Cast->getCastKind() == CK_LValueToRValue || + Cast->getCastKind() == CK_FunctionPointerConversion || + Cast->getCastKind() == CK_MemberFunctionPointerConversion || Cast->getCastKind() == CK_NoOp) return ignoreNoOpCasts(Cast->getSubExpr()); return E; diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/ProTypeCstyleCastCheck.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/ProTypeCstyleCastCheck.cpp index 5e255dcaacd262..bab08496e90e86 100644 --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/ProTypeCstyleCastCheck.cpp +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/ProTypeCstyleCastCheck.cpp @@ -90,7 +90,9 @@ void ProTypeCstyleCastCheck::check(const MatchFinder::MatchResult &Result) { return; } - if (MatchedCast->getCastKind() == CK_NoOp && + if ((MatchedCast->getCastKind() == CK_NoOp || + MatchedCast->getCastKind() == CK_FunctionPointerConversion || + MatchedCast->getCastKind() == CK_MemberFunctionPointerConversion) && needsConstCast(SourceType, MatchedCast->getType())) { diag(MatchedCast->getBeginLoc(), "do not use C-style cast to cast away constness"); diff --git a/clang-tools-extra/clang-tidy/google/AvoidCStyleCastsCheck.cpp b/clang-tools-extra/clang-tidy/google/AvoidCStyleCastsCheck.cpp index 3109bbb3724c79..888f7b122b82c3 100644 --- a/clang-tools-extra/clang-tidy/google/AvoidCStyleCastsCheck.cpp +++ b/clang-tools-extra/clang-tidy/google/AvoidCStyleCastsCheck.cpp @@ -123,7 +123,10 @@ void AvoidCStyleCastsCheck::check(const MatchFinder::MatchResult &Result) { DestTypeAsWritten->isRecordType() && !DestTypeAsWritten->isElaboratedTypeSpecifier(); - if (CastExpr->getCastKind() == CK_NoOp && !FnToFnCast) { + if ((CastExpr->getCastKind() == CK_NoOp || + CastExpr->getCastKind() == CK_FunctionPointerConversion || + CastExpr->getCastKind() == CK_MemberFunctionPointerConversion) && + !FnToFnCast) { // Function pointer/reference casts may be needed to resolve ambiguities in // case of overloaded functions, so detection of redundant casts is trickier // in this case. Don't emit "redundant cast" warnings for function @@ -201,6 +204,8 @@ void AvoidCStyleCastsCheck::check(const MatchFinder::MatchResult &Result) { } return; case CK_NoOp: + case CK_FunctionPointerConversion: + case CK_MemberFunctionPointerConversion: if (FnToFnCast) { ReplaceWithNamedCast("static_cast"); return; diff --git a/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp b/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp index 1c6a1618ebbc4f..801d97ef2988ba 100644 --- a/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp @@ -500,7 +500,9 @@ static bool canBeModified(ASTContext *Context, const Expr *E) { if (Parents.size() != 1) return true; if (const auto *Cast = Parents[0].get<ImplicitCastExpr>()) { - if ((Cast->getCastKind() == CK_NoOp && + if (((Cast->getCastKind() == CK_NoOp || + Cast->getCastKind() == CK_FunctionPointerConversion || + Cast->getCastKind() == CK_MemberFunctionPointerConversion) && Context->hasSameType(Cast->getType(), E->getType().withConst())) || (Cast->getCastKind() == CK_LValueToRValue && !Cast->getType().isNull() && Cast->getType()->isFundamentalType())) diff --git a/clang-tools-extra/clang-tidy/performance/ImplicitConversionInLoopCheck.cpp b/clang-tools-extra/clang-tidy/performance/ImplicitConversionInLoopCheck.cpp index 86fca0722dcd82..4fc32e1d1572eb 100644 --- a/clang-tools-extra/clang-tidy/performance/ImplicitConversionInLoopCheck.cpp +++ b/clang-tools-extra/clang-tidy/performance/ImplicitConversionInLoopCheck.cpp @@ -24,7 +24,9 @@ namespace clang::tidy::performance { // case we skip the first cast expr. static bool isNonTrivialImplicitCast(const Stmt *ST) { if (const auto *ICE = dyn_cast<ImplicitCastExpr>(ST)) { - return (ICE->getCastKind() != CK_NoOp) || + return (ICE->getCastKind() != CK_NoOp && + ICE->getCastKind() != CK_FunctionPointerConversion && + ICE->getCastKind() != CK_MemberFunctionPointerConversion) || isNonTrivialImplicitCast(ICE->getSubExpr()); } return false; diff --git a/clang-tools-extra/clang-tidy/readability/MakeMemberFunctionConstCheck.cpp b/clang-tools-extra/clang-tidy/readability/MakeMemberFunctionConstCheck.cpp index d42fcba70e81b4..751cd637a02334 100644 --- a/clang-tools-extra/clang-tidy/readability/MakeMemberFunctionConstCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/MakeMemberFunctionConstCheck.cpp @@ -97,7 +97,9 @@ class FindUsageOfThis : public RecursiveASTVisitor<FindUsageOfThis> { // (possibly `-UnaryOperator Deref) // `-CXXThisExpr 'S *' this bool visitUser(const ImplicitCastExpr *Cast) { - if (Cast->getCastKind() != CK_NoOp) + if (Cast->getCastKind() != CK_NoOp && + Cast->getCastKind() != CK_FunctionPointerConversion && + Cast->getCastKind() != CK_MemberFunctionPointerConversion) return false; // Stop traversal. // Only allow NoOp cast to 'const S' or 'const S *'. @@ -159,7 +161,10 @@ class FindUsageOfThis : public RecursiveASTVisitor<FindUsageOfThis> { if (Cast->getCastKind() == CK_LValueToRValue) return true; - if (Cast->getCastKind() == CK_NoOp && Cast->getType().isConstQualified()) + if ((Cast->getCastKind() == CK_NoOp || + Cast->getCastKind() == CK_FunctionPointerConversion || + Cast->getCastKind() == CK_MemberFunctionPointerConversion) && + Cast->getType().isConstQualified()) return true; } diff --git a/clang-tools-extra/clang-tidy/utils/DeclRefExprUtils.cpp b/clang-tools-extra/clang-tidy/utils/DeclRefExprUtils.cpp index 106feb7fb41720..87e523eaaa7718 100644 --- a/clang-tools-extra/clang-tidy/utils/DeclRefExprUtils.cpp +++ b/clang-tools-extra/clang-tidy/utils/DeclRefExprUtils.cpp @@ -240,6 +240,8 @@ AST_MATCHER_P(DeclRefExpr, doesNotMutateObject, int, Indirections) { case CK_BaseToDerived: case CK_DerivedToBase: case CK_UncheckedDerivedToBase: + case CK_FunctionPointerConversion: + case CK_MemberFunctionPointerConversion: case CK_Dynamic: case CK_BaseToDerivedMemberPointer: case CK_DerivedToBaseMemberPointer: diff --git a/clang-tools-extra/clangd/Hover.cpp b/clang-tools-extra/clangd/Hover.cpp index 298fa79e3fd0ba..5cb2ecd1d55726 100644 --- a/clang-tools-extra/clangd/Hover.cpp +++ b/clang-tools-extra/clangd/Hover.cpp @@ -1118,6 +1118,8 @@ void maybeAddCalleeArgInfo(const SelectionTree::Node *N, HoverInfo &HI, if (const auto *ImplicitCast = CastNode->ASTNode.get<ImplicitCastExpr>()) { switch (ImplicitCast->getCastKind()) { case CK_NoOp: + case CK_FunctionPointerConversion: + case CK_MemberFunctionPointerConversion: case CK_DerivedToBase: case CK_UncheckedDerivedToBase: // If it was a reference before, it's still a reference. diff --git a/clang/include/clang/AST/IgnoreExpr.h b/clang/include/clang/AST/IgnoreExpr.h index 917bada61fa6fd..967c381dfa9dc5 100644 --- a/clang/include/clang/AST/IgnoreExpr.h +++ b/clang/include/clang/AST/IgnoreExpr.h @@ -102,7 +102,9 @@ inline Expr *IgnoreBaseCastsSingleStep(Expr *E) { if (auto *CE = dyn_cast<CastExpr>(E)) if (CE->getCastKind() == CK_DerivedToBase || CE->getCastKind() == CK_UncheckedDerivedToBase || - CE->getCastKind() == CK_NoOp) + CE->getCastKind() == CK_NoOp || + CE->getCastKind() == CK_FunctionPointerConversion || + CE->getCastKind() == CK_MemberFunctionPointerConversion) return CE->getSubExpr(); return E; diff --git a/clang/include/clang/AST/OperationKinds.def b/clang/include/clang/AST/OperationKinds.def index 8788b8ff0ef0a4..25cd0dbb5e3a3b 100644 --- a/clang/include/clang/AST/OperationKinds.def +++ b/clang/include/clang/AST/OperationKinds.def @@ -84,6 +84,14 @@ CAST_OPERATION(LValueToRValue) /// void () noexcept -> void () CAST_OPERATION(NoOp) +/// CK_FunctionPointerConversion - A conversion from pointer/reference to +/// noexcept function to pointer/reference to function. +CAST_OPERATION(FunctionPointerConversion) + +/// CK_MemberFunctionPointerConversion - A conversion from pointer to noexcept +/// member function to pointer to member function. +CAST_OPERATION(MemberFunctionPointerConversion) + /// CK_BaseToDerived - A conversion from a C++ class pointer/reference /// to a derived class pointer/reference. /// B *b = static_cast<B*>(a); diff --git a/clang/lib/ARCMigrate/TransBlockObjCVariable.cpp b/clang/lib/ARCMigrate/TransBlockObjCVariable.cpp index 1e4db33135b6a1..7295f064849ea6 100644 --- a/clang/lib/ARCMigrate/TransBlockObjCVariable.cpp +++ b/clang/lib/ARCMigrate/TransBlockObjCVariable.cpp @@ -53,8 +53,10 @@ class RootBlockObjCVarRewriter : if (ref->getDecl() == Var) { if (castE->getCastKind() == CK_LValueToRValue) return true; // Using the value of the variable. - if (castE->getCastKind() == CK_NoOp && castE->isLValue() && - Var->getASTContext().getLangOpts().CPlusPlus) + if ((castE->getCastKind() == CK_NoOp || + castE->getCastKind() == CK_FunctionPointerConversion || + castE->getCastKind() == CK_MemberFunctionPointerConversion) && + castE->isLValue() && Var->getASTContext().getLangOpts().CPlusPlus) return true; // Binding to const C++ reference. } } diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp index e54b6568d7060b..7d448d0e1cbeb2 100644 --- a/clang/lib/AST/ByteCode/Compiler.cpp +++ b/clang/lib/AST/ByteCode/Compiler.cpp @@ -429,6 +429,8 @@ bool Compiler<Emitter>::VisitCastExpr(const CastExpr *CE) { case CK_FunctionToPointerDecay: case CK_NonAtomicToAtomic: case CK_NoOp: + case CK_FunctionPointerConversion: + case CK_MemberFunctionPointerConversion: case CK_UserDefinedConversion: case CK_AddressSpaceConversion: case CK_CPointerToObjCPointerCast: @@ -3161,6 +3163,8 @@ bool Compiler<Emitter>::VisitCXXNewExpr(const CXXNewExpr *E) { for (; auto *ICE = dyn_cast<ImplicitCastExpr>(Stripped); Stripped = ICE->getSubExpr()) if (ICE->getCastKind() != CK_NoOp && + ICE->getCastKind() != CK_FunctionPointerConversion && + ICE->getCastKind() != CK_MemberFunctionPointerConversion && ICE->getCastKind() != CK_IntegralCast) break; diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp index 2e463fc00c6b68..c62ffa9bf7df59 100644 --- a/clang/lib/AST/Expr.cpp +++ b/clang/lib/AST/Expr.cpp @@ -98,7 +98,9 @@ const Expr *Expr::skipRValueSubobjectAdjustments( continue; } - if (CE->getCastKind() == CK_NoOp) { + if (CE->getCastKind() == CK_NoOp || + CE->getCastKind() == CK_FunctionPointerConversion || + CE->getCastKind() == CK_MemberFunctionPointerConversion) { E = CE->getSubExpr(); continue; } @@ -1926,6 +1928,8 @@ bool CastExpr::CastConsistency() const { case CK_Dependent: case CK_LValueToRValue: case CK_NoOp: + case CK_FunctionPointerConversion: + case CK_MemberFunctionPointerConversion: case CK_AtomicToNonAtomic: case CK_NonAtomicToAtomic: case CK_PointerToBoolean: @@ -3188,7 +3192,9 @@ static const Expr *skipTemporaryBindingsNoOpCastsAndParens(const Expr *E) { E = M->getSubExpr(); while (const ImplicitCastExpr *ICE = dyn_cast<ImplicitCastExpr>(E)) { - if (ICE->getCastKind() == CK_NoOp) + if (ICE->getCastKind() == CK_NoOp || + ICE->getCastKind() == CK_FunctionPointerConversion || + ICE->getCastKind() == CK_MemberFunctionPointerConversion) E = ICE->getSubExpr(); else break; @@ -3198,7 +3204,9 @@ static const Expr *skipTemporaryBindingsNoOpCastsAndParens(const Expr *E) { E = BE->getSubExpr(); while (const ImplicitCastExpr *ICE = dyn_cast<ImplicitCastExpr>(E)) { - if (ICE->getCastKind() == CK_NoOp) + if (ICE->getCastKind() == CK_NoOp || + ICE->getCastKind() == CK_FunctionPointerConversion || + ICE->getCastKind() == CK_MemberFunctionPointerConversion) E = ICE->getSubExpr(); else break; @@ -3263,6 +3271,8 @@ bool Expr::isImplicitCXXThis() const { if (const ImplicitCastExpr *ICE = dyn_cast<ImplicitCastExpr>(E)) { if (ICE->getCastKind() == CK_NoOp || + ICE->getCastKind() == CK_FunctionPointerConversion || + ICE->getCastKind() == CK_MemberFunctionPointerConversion || ICE->getCastKind() == CK_LValueToRValue || ICE->getCastKind() == CK_DerivedToBase || ICE->getCastKind() == CK_UncheckedDerivedToBase) { @@ -3478,6 +3488,8 @@ bool Expr::isConstantInitializer(ASTContext &Ctx, bool IsForRef, // Handle misc casts we want to ignore. if (CE->getCastKind() == CK_NoOp || + CE->getCastKind() == CK_FunctionPointerConversion || + CE->getCastKind() == CK_MemberFunctionPointerConversion || CE->getCastKind() == CK_LValueToRValue || CE->getCastKind() == CK_ToUnion || CE->getCastKind() == CK_ConstructorConversion || @@ -4113,7 +4125,10 @@ FieldDecl *Expr::getSourceBitField() { while (ImplicitCastExpr *ICE = dyn_cast<ImplicitCastExpr>(E)) { if (ICE->getCastKind() == CK_LValueToRValue || - (ICE->isGLValue() && ICE->getCastKind() == CK_NoOp)) + (ICE->isGLValue() && + (ICE->getCastKind() == CK_NoOp || + ICE->getCastKind() == CK_FunctionPointerConversion || + ICE->getCastKind() == CK_MemberFunctionPointerConversion))) E = ICE->getSubExpr()->IgnoreParens(); else break; @@ -4167,7 +4182,10 @@ bool Expr::refersToVectorElement() const { const Expr *E = this->IgnoreParens(); while (const ImplicitCastExpr *ICE = dyn_cast<ImplicitCastExpr>(E)) { - if (ICE->isGLValue() && ICE->getCastKind() == CK_NoOp) + if (ICE->isGLValue() && + (ICE->getCastKind() == CK_NoOp || + ICE->getCastKind() == CK_FunctionPointerConversion || + ICE->getCastKind() == CK_MemberFunctionPointerConversion)) E = ICE->getSubExpr()->IgnoreParens(); else break; diff --git a/clang/lib/AST/ExprCXX.cpp b/clang/lib/AST/ExprCXX.cpp index 83ce404add5f50..a3f09786960b1f 100644 --- a/clang/lib/AST/ExprCXX.cpp +++ b/clang/lib/AST/ExprCXX.cpp @@ -344,8 +344,12 @@ QualType CXXDeleteExpr::getDestroyedType() const { while (const auto *ICE = dyn_cast<ImplicitCastExpr>(Arg)) { if (ICE->getCastKind() == CK_DerivedToBase || ICE->getCastKind() == CK_UncheckedDerivedToBase || - ICE->getCastKind() == CK_NoOp) { + ICE->getCastKind() == CK_NoOp || + ICE->getCastKind() == CK_FunctionPointerConversion || + ICE->getCastKind() == CK_MemberFunctionPointerConversion) { assert((ICE->getCastKind() == CK_NoOp || + ICE->getCastKind() == CK_FunctionPointerConversion || + ICE->getCastKind() == CK_MemberFunctionPointerConversion || getOperatorDelete()->isDestroyingOperatorDelete()) && "only a destroying operator delete can have a converted arg"); Arg = ICE->getSubExpr(); diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 6387e375dda79c..28f829e612be47 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -6247,7 +6247,9 @@ static bool MaybeHandleUnionActiveMemberChange(EvalInfo &Info, } else if (auto *ICE = dyn_cast<ImplicitCastExpr>(E)) { // Step over a derived-to-base conversion. E = ICE->getSubExpr(); - if (ICE->getCastKind() == CK_NoOp) + if (ICE->getCastKind() == CK_NoOp || + ICE->getCastKind() == CK_FunctionPointerConversion || + ICE->getCastKind() == CK_MemberFunctionPointerConversion) continue; if (ICE->getCastKind() != CK_DerivedToBase && ICE->getCastKind() != CK_UncheckedDerivedToBase) @@ -8301,6 +8303,8 @@ class ExprEvaluatorBase } case CK_NoOp: + case CK_FunctionPointerConversion: + case CK_MemberFunctionPointerConversion: case CK_UserDefinedConversion: return StmtVisitorTy::Visit(E->getSubExpr()); @@ -10074,6 +10078,8 @@ bool PointerExprEvaluator::VisitCXXNewExpr(const CXXNewExpr *E) { for (; auto *ICE = dyn_cast<ImplicitCastExpr>(Stripped); Stripped = ICE->getSubExpr()) if (ICE->getCastKind() != CK_NoOp && + ICE->getCastKind() != CK_FunctionPointerConversion && + ICE->getCastKind() != CK_MemberFunctionPointerConversion && ICE->getCastKind() != CK_IntegralCast) break; @@ -12210,8 +12216,9 @@ static const Expr *ignorePointerCastsAndParens(const Expr *E) { // We only conservatively allow a few kinds of casts, because this code is // inherently a simple solution that seeks to support the common case. auto CastKind = Cast->getCastKind(); - if (CastKind != CK_NoOp && CastKind != CK_BitCast && - CastKind != CK_AddressSpaceConversion) + if (CastKind != CK_NoOp && CastKind != CK_FunctionPointerConversion && + CastKind != CK_MemberFunctionPointerConversion && + CastKind != CK_BitCast && CastKind != CK_AddressSpaceConversion) return NoParens; const auto *SubExpr = Cast->getSubExpr(); @@ -14448,6 +14455,8 @@ bool IntExprEvaluator::VisitCastExpr(const CastExpr *E) { QualType SrcType = SubExpr->getType(); switch (E->getCastKind()) { + case CK_FunctionPointerConversion: + case CK_MemberFunctionPointerConversion: case CK_BaseToDerived: case CK_DerivedToBase: case CK_UncheckedDerivedToBase: @@ -15288,6 +15297,8 @@ bool ComplexExprEvaluator::VisitCastExpr(const CastExpr *E) { case CK_BaseToDerived: case CK_DerivedToBase: case CK_UncheckedDerivedToBase: + case CK_FunctionPointerConversion: + case CK_MemberFunctionPointerConversion: case CK_Dynamic: case CK_ToUnion: case CK_ArrayToPointerDecay: diff --git a/clang/lib/Analysis/CFG.cpp b/clang/lib/Analysis/CFG.cpp index f678ac6f2ff36a..baa5f96f8cbdbe 100644 --- a/clang/lib/Analysis/CFG.cpp +++ b/clang/lib/Analysis/CFG.cpp @@ -1491,6 +1491,8 @@ void CFGBuilder::findConstructionContexts( // Should we support other implicit cast kinds? switch (Cast->getCastKind()) { case CK_NoOp: + case CK_FunctionPointerConversion: + case CK_MemberFunctionPointerConversion: case CK_ConstructorConversion: findConstructionContexts(Layer, Cast->getSubExpr()); break; diff --git a/clang/lib/Analysis/ExprMutationAnalyzer.cpp b/clang/lib/Analysis/ExprMutationAnalyzer.cpp index 6d726ae44104ed..8f19bd7e918706 100644 --- a/clang/lib/Analysis/ExprMutationAnalyzer.cpp +++ b/clang/lib/Analysis/ExprMutationAnalyzer.cpp @@ -377,11 +377,13 @@ ExprMutationAnalyzer::Analyzer::findDirectMutation(const Expr *Exp) { // We're assuming 'Exp' is mutated as soon as its address is taken, though in // theory we can follow the pointer and see whether it escaped `Stm` or is // dereferenced and then mutated. This is left for future improvements. - const auto AsAmpersandOperand = - unaryOperator(hasOperatorName("&"), - // A NoOp implicit cast is adding const. - unless(hasParent(implicitCastExpr(hasCastKind(CK_NoOp)))), - hasUnaryOperand(canResolveToExpr(Exp))); + const auto AsAmpersandOperand = unaryOperator( + hasOperatorName("&"), + // A NoOp implicit cast is adding const. + unless(hasParent(implicitCastExpr( + anyOf(hasCastKind(CK_NoOp), hasCastKind(CK_FunctionPointerConversion), + hasCastKind(CK_MemberFunctionPointerConversion))))), + hasUnaryOperand(canResolveToExpr(Exp))); const auto AsPointerFromArrayDecay = castExpr( hasCastKind(CK_ArrayToPointerDecay), unless(hasParent(arraySubscriptExpr())), has(canResolveToExpr(Exp))); diff --git a/clang/lib/Analysis/FlowSensitive/Transfer.cpp b/clang/lib/Analysis/FlowSensitive/Transfer.cpp index 9c54eb16d22246..b58d40df7f07fc 100644 --- a/clang/lib/Analysis/FlowSensitive/Transfer.cpp +++ b/clang/lib/Analysis/FlowSensitive/Transfer.cpp @@ -322,7 +322,9 @@ class TransferVisitor : public ConstStmtVisitor<TransferVisitor> { case CK_UserDefinedConversion: // FIXME: Add tests that excercise CK_UncheckedDerivedToBase, // CK_ConstructorConversion, and CK_UserDefinedConversion. - case CK_NoOp: { + case CK_NoOp: + case CK_FunctionPointerConversion: + case CK_MemberFunctionPointerConversion: { // FIXME: Consider making `Environment::getStorageLocation` skip noop // expressions (this and other similar expressions in the file) instead // of assigning them storage locations. @@ -679,7 +681,9 @@ class TransferVisitor : public ConstStmtVisitor<TransferVisitor> { } void VisitCXXStaticCastExpr(const CXXStaticCastExpr *S) { - if (S->getCastKind() == CK_NoOp) { + if (S->getCastKind() == CK_NoOp || + S->getCastKind() == CK_FunctionPointerConversion || + S->getCastKind() == CK_MemberFunctionPointerConversion) { const Expr *SubExpr = S->getSubExpr(); assert(SubExpr != nullptr); diff --git a/clang/lib/Analysis/ThreadSafety.cpp b/clang/lib/Analysis/ThreadSafety.cpp index 5577f45aa5217f..8c3396e2d9da41 100644 --- a/clang/lib/Analysis/ThreadSafety.cpp +++ b/clang/lib/Analysis/ThreadSafety.cpp @@ -2103,7 +2103,9 @@ void BuildLockset::VisitCXXConstructExpr(const CXXConstructExpr *Exp) { static const Expr *UnpackConstruction(const Expr *E) { if (auto *CE = dyn_cast<CastExpr>(E)) - if (CE->getCastKind() == CK_NoOp) + if (CE->getCastKind() == CK_NoOp || + CE->getCastKind() == CK_FunctionPointerConversion || + CE->getCastKind() == CK_MemberFunctionPointerConversion) E = CE->getSubExpr()->IgnoreParens(); if (auto *CE = dyn_cast<CastExpr>(E)) if (CE->getCastKind() == CK_ConstructorConversion || diff --git a/clang/lib/Analysis/ThreadSafetyCommon.cpp b/clang/lib/Analysis/ThreadSafetyCommon.cpp index cbcfefdc525490..aa54fe4ea66d5b 100644 --- a/clang/lib/Analysis/ThreadSafetyCommon.cpp +++ b/clang/lib/Analysis/ThreadSafetyCommon.cpp @@ -620,6 +620,8 @@ til::SExpr *SExprBuilder::translateCastExpr(const CastExpr *CE, // return new (Arena) til::Load(E0); } case CK_NoOp: + case CK_FunctionPointerConversion: + case CK_MemberFunctionPointerConversion: case CK_DerivedToBase: case CK_UncheckedDerivedToBase: case CK_ArrayToPointerDecay: diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp index 563f728e29d781..e035ae819eecc7 100644 --- a/clang/lib/CodeGen/CGDecl.cpp +++ b/clang/lib/CodeGen/CGDecl.cpp @@ -697,6 +697,8 @@ static bool tryEmitARCCopyWeakInit(CodeGenFunction &CGF, switch (castExpr->getCastKind()) { // Look through casts that don't require representation changes. case CK_NoOp: + case CK_FunctionPointerConversion: + case CK_MemberFunctionPointerConversion: case CK_BitCast: case CK_BlockPointerToObjCPointerCast: needsCast = true; diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp index 9166db4c74128c..98d8bb39efd5b5 100644 --- a/clang/lib/CodeGen/CGExpr.cpp +++ b/clang/lib/CodeGen/CGExpr.cpp @@ -1297,6 +1297,8 @@ static Address EmitPointerWithAlignment(const Expr *E, LValueBaseInfo *BaseInfo, // Non-converting casts (but not C's implicit conversion from void*). case CK_BitCast: case CK_NoOp: + case CK_FunctionPointerConversion: + case CK_MemberFunctionPointerConversion: case CK_AddressSpaceConversion: if (auto PtrTy = CE->getSubExpr()->getType()->getAs<PointerType>()) { if (PtrTy->getPointeeType()->isVoidType()) @@ -5292,6 +5294,7 @@ LValue CodeGenFunction::EmitCastLValue(const CastExpr *E) { case CK_IntegralComplexToBoolean: case CK_IntegralComplexCast: case CK_IntegralComplexToFloatingComplex: + case CK_MemberFunctionPointerConversion: case CK_DerivedToBaseMemberPointer: case CK_BaseToDerivedMemberPointer: case CK_MemberPointerToBoolean: @@ -5339,7 +5342,8 @@ LValue CodeGenFunction::EmitCastLValue(const CastExpr *E) { case CK_LValueToRValue: return EmitLValue(E->getSubExpr()); - case CK_NoOp: { + case CK_NoOp: + case CK_FunctionPointerConversion: { // CK_NoOp can model a qualification conversion, which can remove an array // bound and change the IR type. // FIXME: Once pointee types are removed from IR, remove this. diff --git a/clang/lib/CodeGen/CGExprAgg.cpp b/clang/lib/CodeGen/CGExprAgg.cpp index bbfc6672ecc25a..b30410da255e12 100644 --- a/clang/lib/CodeGen/CGExprAgg.cpp +++ b/clang/lib/CodeGen/CGExprAgg.cpp @@ -905,6 +905,8 @@ void AggExprEmitter::VisitCastExpr(CastExpr *E) { case CK_FunctionToPointerDecay: case CK_NullToPointer: case CK_NullToMemberPointer: + case CK_FunctionPointerConversion: + case CK_MemberFunctionPointerConversion: case CK_BaseToDerivedMemberPointer: case CK_DerivedToBaseMemberPointer: case CK_MemberPointerToBoolean: @@ -1424,6 +1426,8 @@ static bool castPreservesZero(const CastExpr *CE) { switch (CE->getCastKind()) { // No-ops. case CK_NoOp: + case CK_FunctionPointerConversion: + case CK_MemberFunctionPointerConversion: case CK_UserDefinedConversion: case CK_ConstructorConversion: case CK_BitCast: diff --git a/clang/lib/CodeGen/CGExprComplex.cpp b/clang/lib/CodeGen/CGExprComplex.cpp index fef26e7b4ccdbd..794b06ebf29935 100644 --- a/clang/lib/CodeGen/CGExprComplex.cpp +++ b/clang/lib/CodeGen/CGExprComplex.cpp @@ -560,6 +560,8 @@ ComplexPairTy ComplexExprEmitter::EmitCast(CastKind CK, Expr *Op, } case CK_BitCast: + case CK_FunctionPointerConversion: + case CK_MemberFunctionPointerConversion: case CK_BaseToDerived: case CK_DerivedToBase: case CK_UncheckedDerivedToBase: diff --git a/clang/lib/CodeGen/CGExprConstant.cpp b/clang/lib/CodeGen/CGExprConstant.cpp index dd65080a840446..c76bbbfeb0fd59 100644 --- a/clang/lib/CodeGen/CGExprConstant.cpp +++ b/clang/lib/CodeGen/CGExprConstant.cpp @@ -1163,6 +1163,8 @@ class ConstExprEmitter case CK_AtomicToNonAtomic: case CK_NonAtomicToAtomic: case CK_NoOp: + case CK_FunctionPointerConversion: + case CK_MemberFunctionPointerConversion: case CK_ConstructorConversion: return Visit(subExpr, destType); diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp index b7f5b932c56b6f..78f4a03cb5e12b 100644 --- a/clang/lib/CodeGen/CGExprScalar.cpp +++ b/clang/lib/CodeGen/CGExprScalar.cpp @@ -2418,7 +2418,9 @@ Value *ScalarExprEmitter::VisitCastExpr(CastExpr *CE) { case CK_UserDefinedConversion: return Visit(const_cast<Expr*>(E)); - case CK_NoOp: { + case CK_NoOp: + case CK_FunctionPointerConversion: + case CK_MemberFunctionPointerConversion: { return CE->changesVolatileQualification() ? EmitLoadOfLValue(CE) : Visit(const_cast<Expr *>(E)); } diff --git a/clang/lib/Edit/RewriteObjCFoundationAPI.cpp b/clang/lib/Edit/RewriteObjCFoundationAPI.cpp index 81797c8c4dc75a..8443f47c2199b6 100644 --- a/clang/lib/Edit/RewriteObjCFoundationAPI.cpp +++ b/clang/lib/Edit/RewriteObjCFoundationAPI.cpp @@ -999,6 +999,8 @@ static bool rewriteToNumericBoxedExpression(const ObjCMessageExpr *Msg, switch (ICE->getCastKind()) { case CK_LValueToRValue: case CK_NoOp: + case CK_FunctionPointerConversion: + case CK_MemberFunctionPointerConversion: case CK_UserDefinedConversion: case CK_HLSLArrayRValue: break; diff --git a/clang/lib/Sema/CheckExprLifetime.cpp b/clang/lib/Sema/CheckExprLifetime.cpp index 009b8d000e6b0e..164973591649ae 100644 --- a/clang/lib/Sema/CheckExprLifetime.cpp +++ b/clang/lib/Sema/CheckExprLifetime.cpp @@ -816,6 +816,8 @@ static void visitLocalsRetainedByInitializer(IndirectLocalPath &Path, // We assume that casts to 'bool' do not preserve enough information to // retain a local object. case CK_NoOp: + case CK_FunctionPointerConversion: + case CK_MemberFunctionPointerConversion: case CK_BitCast: case CK_BaseToDerived: case CK_DerivedToBase: diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index af1dc21594da8a..70fa37b295f190 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -12975,6 +12975,8 @@ std::optional<std::pair< default: break; case CK_NoOp: + case CK_FunctionPointerConversion: + case CK_MemberFunctionPointerConversion: return getBaseAlignmentAndOffsetFromLValue(From, Ctx); case CK_UncheckedDerivedToBase: case CK_DerivedToBase: { @@ -13070,6 +13072,8 @@ std::optional<std::pair< default: break; case CK_NoOp: + case CK_FunctionPointerConversion: + case CK_MemberFunctionPointerConversion: return getBaseAlignmentAndOffsetFromPtr(From, Ctx); case CK_ArrayToPointerDecay: return getBaseAlignmentAndOffsetFromLValue(From, Ctx); diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 1bf0e800a36228..5a69b99d0a16a9 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -12733,7 +12733,9 @@ namespace { if (ILE->getNumInits() == 1) ArgExpr = ILE->getInit(0); if (ImplicitCastExpr *ICE = dyn_cast<ImplicitCastExpr>(ArgExpr)) - if (ICE->getCastKind() == CK_NoOp) + if (ICE->getCastKind() == CK_NoOp || + ICE->getCastKind() == CK_FunctionPointerConversion || + ICE->getCastKind() == CK_MemberFunctionPointerConversion) ArgExpr = ICE->getSubExpr(); HandleValue(ArgExpr); return; diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index d8cdfcf8c6ec05..59f4d5511b19e3 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -3878,7 +3878,9 @@ namespace { if (ILE->getNumInits() == 1) ArgExpr = ILE->getInit(0); if (ImplicitCastExpr *ICE = dyn_cast<ImplicitCastExpr>(ArgExpr)) - if (ICE->getCastKind() == CK_NoOp) + if (ICE->getCastKind() == CK_NoOp || + ICE->getCastKind() == CK_FunctionPointerConversion || + ICE->getCastKind() == CK_MemberFunctionPointerConversion) ArgExpr = ICE->getSubExpr(); HandleValue(ArgExpr, false /*AddressOf*/); return; diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 66df9c969256a2..79f8ea167d4f4b 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -19446,6 +19446,8 @@ static ExprResult rebuildPotentialResultsAsNonOdrUsed(Sema &S, Expr *E, // can be found. switch (ICE->getCastKind()) { case CK_NoOp: + case CK_FunctionPointerConversion: + case CK_MemberFunctionPointerConversion: case CK_DerivedToBase: case CK_UncheckedDerivedToBase: { ExprResult Sub = Rebuild(ICE->getSubExpr()); diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index ac3fe6ab8f9bd0..db0cfce7f3f177 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -4871,7 +4871,11 @@ Sema::PerformImplicitConversion(Expr *From, QualType ToType, if (CheckExceptionSpecCompatibility(From, ToType)) return ExprError(); - From = ImpCastExprToType(From, ToType, CK_NoOp, VK_PRValue, + From = ImpCastExprToType(From, ToType, + ToType->isMemberFunctionPointerType() + ? CK_MemberFunctionPointerConversion + : CK_FunctionPointerConversion, + VK_PRValue, /*BasePath=*/nullptr, CCK) .get(); break; diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp index 4d11f2a43fcc6b..dc28b66f35f900 100644 --- a/clang/lib/Sema/SemaInit.cpp +++ b/clang/lib/Sema/SemaInit.cpp @@ -8016,8 +8016,8 @@ ExprResult InitializationSequence::Perform(Sema &S, case SK_FunctionReferenceConversion: assert(CurInit.get()->isLValue() && "function reference should be lvalue"); - CurInit = - S.ImpCastExprToType(CurInit.get(), Step->Type, CK_NoOp, VK_LValue); + CurInit = S.ImpCastExprToType(CurInit.get(), Step->Type, + CK_FunctionPointerConversion, VK_LValue); break; case SK_AtomicConversion: { diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp index d304f322aced64..2e36f652baa5df 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -330,6 +330,8 @@ static const Expr *IgnoreNarrowingConversion(ASTContext &Ctx, while (auto *ICE = dyn_cast<ImplicitCastExpr>(Converted)) { switch (ICE->getCastKind()) { case CK_NoOp: + case CK_FunctionPointerConversion: + case CK_MemberFunctionPointerConversion: case CK_IntegralCast: case CK_IntegralToBoolean: case CK_IntegralToFloating: @@ -14206,7 +14208,9 @@ ExprResult Sema::BuildCXXMemberCallExpr(Expr *E, NamedDecl *FoundDecl, // was a LambdaExpr. Expr *SubE = E; auto *CE = dyn_cast<CastExpr>(SubE); - if (CE && CE->getCastKind() == CK_NoOp) + if (CE && (CE->getCastKind() == CK_NoOp || + CE->getCastKind() == CK_FunctionPointerConversion || + CE->getCastKind() == CK_MemberFunctionPointerConversion)) SubE = CE->getSubExpr(); SubE = SubE->IgnoreParens(); if (auto *BE = dyn_cast<CXXBindTemporaryExpr>(SubE)) diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp index 9e235a46707cd4..202909924cabc8 100644 --- a/clang/lib/Sema/SemaStmt.cpp +++ b/clang/lib/Sema/SemaStmt.cpp @@ -282,6 +282,8 @@ void Sema::DiagnoseUnusedExprResult(const Stmt *S, unsigned DiagID) { E = WarnExpr; if (const auto *Cast = dyn_cast<CastExpr>(E)) if (Cast->getCastKind() == CK_NoOp || + Cast->getCastKind() == CK_FunctionPointerConversion || + Cast->getCastKind() == CK_MemberFunctionPointerConversion || Cast->getCastKind() == CK_ConstructorConversion || Cast->getCastKind() == CK_IntegralCast) E = Cast->getSubExpr()->IgnoreImpCasts(); diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp index 7a900780384a91..ad021d12a7fd43 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp @@ -327,6 +327,8 @@ void ExprEngine::VisitCast(const CastExpr *CastE, const Expr *Ex, case CK_NonAtomicToAtomic: // True no-ops. case CK_NoOp: + case CK_FunctionPointerConversion: + case CK_MemberFunctionPointerConversion: case CK_ConstructorConversion: case CK_UserDefinedConversion: case CK_FunctionToPointerDecay: diff --git a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp index cb5fcbade2cfc2..7b0af9b9d57879 100644 --- a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp +++ b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp @@ -397,6 +397,8 @@ std::optional<SVal> SValBuilder::getConstantVal(const Expr *E) { case CK_ArrayToPointerDecay: case CK_IntegralToPointer: case CK_NoOp: + case CK_FunctionPointerConversion: + case CK_MemberFunctionPointerConversion: case CK_BitCast: { const Expr *SE = CE->getSubExpr(); std::optional<SVal> Val = getConstantVal(SE); diff --git a/clang/test/AST/ast-dump-function-pointer-conversion.cpp b/clang/test/AST/ast-dump-function-pointer-conversion.cpp new file mode 100644 index 00000000000000..4c545e5bc40524 --- /dev/null +++ b/clang/test/AST/ast-dump-function-pointer-conversion.cpp @@ -0,0 +1,30 @@ +// RUN: %clang_cc1 -std=c++17 -ast-dump %s | FileCheck --check-prefixes=CHECK,CXX17 %s +// RUN: %clang_cc1 -std=c++11 -ast-dump %s | FileCheck %s + +void f() noexcept; + +struct S { + void m() noexcept; +}; + +// CHECK: FunctionDecl {{.*}} testFunctionPointerConversion 'void ()' +// CHECK-NEXT: CompoundStmt +// CHECK-NEXT: DeclStmt +// CHECK-NEXT: VarDecl {{.*}} fp 'void (*)()' cinit +// CXX17-NEXT: ImplicitCastExpr {{.*}} 'void (*)()' <FunctionPointerConversion> +// CHECK-NEXT: UnaryOperator {{.*}} 'void (*)() noexcept' prefix '&' +// CHECK-NEXT: DeclRefExpr {{.*}} 'void () noexcept' lvalue Function {{.*}} 'f' 'void () noexcept' +void testFunctionPointerConversion() { + void (*fp)() = &f; +} + +// CHECK: FunctionDecl {{.*}} testMemberFunctionPointerConversion 'void ()' +// CHECK-NEXT: CompoundStmt +// CHECK-NEXT: DeclStmt +// CHECK-NEXT: VarDecl {{.*}} mfp 'void (S::*)()' cinit +// CXX17-NEXT: ImplicitCastExpr {{.*}} 'void (S::*)()' <MemberFunctionPointerConversion> +// CHECK-NEXT: UnaryOperator {{.*}} 'void (S::*)() noexcept' prefix '&' +// CHECK-NEXT: DeclRefExpr {{.*}} 'void () noexcept' CXXMethod {{.*}} 'm' 'void () noexcept' +void testMemberFunctionPointerConversion() { + void (S::*mfp)() = &S::m; +} diff --git a/clang/test/CXX/dcl.decl/dcl.init/dcl.init.ref/p4-ast.cpp b/clang/test/CXX/dcl.decl/dcl.init/dcl.init.ref/p4-ast.cpp index 32c4ddd921bba1..a6209337a08864 100644 --- a/clang/test/CXX/dcl.decl/dcl.init/dcl.init.ref/p4-ast.cpp +++ b/clang/test/CXX/dcl.decl/dcl.init/dcl.init.ref/p4-ast.cpp @@ -3,7 +3,7 @@ void f() noexcept; // CHECK: VarDecl {{.*}} ref 'void (&)()' cinit -// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void ()' lvalue <NoOp> +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void ()' lvalue <FunctionPointerConversion> // CHECK-NEXT: DeclRefExpr {{.*}} 'void () noexcept' lvalue Function {{.*}} 'f' 'void () noexcept' void (&ref)() = f; @@ -13,6 +13,6 @@ struct X { } x; // CHECK: VarDecl {{.*}} xp 'void (&)()' cinit -// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void ()' lvalue <NoOp> +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void ()' lvalue <FunctionPointerConversion> // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void () noexcept' lvalue <UserDefinedConversion> void (&xp)() = x; _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits