[clang] [Clang] Fix constant eval of assignment operators with an explicit object parameter (PR #142964)
Corentin Jabot via cfe-commits
cfe-commits at lists.llvm.org
Thu Jun 5 06:37:37 PDT 2025
https://github.com/cor3ntin updated https://github.com/llvm/llvm-project/pull/142964
>From 6d98a334d66d6e1ca7ee046e1d84ccb1ad78ebee Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Thu, 5 Jun 2025 15:35:22 +0200
Subject: [PATCH] [Clang] Fix constant eval of assignent operators with an
explicit object param
Fixes #142835
---
clang/docs/ReleaseNotes.rst | 3 +-
clang/lib/AST/ExprConstant.cpp | 64 ++++++++++++-------
.../SemaCXX/cxx2b-deducing-this-constexpr.cpp | 29 +++++++++
3 files changed, 72 insertions(+), 24 deletions(-)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 512071427b65c..747388fb985aa 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -830,6 +830,7 @@ Bug Fixes to C++ Support
- Clang modules now allow a module and its user to differ on TrivialAutoVarInit*
- Fixed an access checking bug when initializing non-aggregates in default arguments (#GH62444), (#GH83608)
- Fixed a pack substitution bug in deducing class template partial specializations. (#GH53609)
+- Fixed a crash when constant evaluating some explicit object member assignment operators. (#GH142835)
Bug Fixes to AST Handling
^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -1049,7 +1050,7 @@ Sanitizers
----------
- ``-fsanitize=vptr`` is no longer a part of ``-fsanitize=undefined``.
-- Sanitizer ignorelists now support the syntax ``src:*=sanitize``,
+- Sanitizer ignorelists now support the syntax ``src:*=sanitize``,
``type:*=sanitize``, ``fun:*=sanitize``, ``global:*=sanitize``,
and ``mainfile:*=sanitize``.
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index ab964e592de80..78dd9770f65f6 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -6549,8 +6549,8 @@ static bool MaybeHandleUnionActiveMemberChange(EvalInfo &Info,
}
static bool EvaluateCallArg(const ParmVarDecl *PVD, const Expr *Arg,
- CallRef Call, EvalInfo &Info,
- bool NonNull = false) {
+ CallRef Call, EvalInfo &Info, bool NonNull = false,
+ APValue **EvaluatedArg = nullptr) {
LValue LV;
// Create the parameter slot and register its destruction. For a vararg
// argument, create a temporary.
@@ -6570,13 +6570,17 @@ static bool EvaluateCallArg(const ParmVarDecl *PVD, const Expr *Arg,
return false;
}
+ if (EvaluatedArg)
+ *EvaluatedArg = &V;
+
return true;
}
/// Evaluate the arguments to a function call.
static bool EvaluateArgs(ArrayRef<const Expr *> Args, CallRef Call,
EvalInfo &Info, const FunctionDecl *Callee,
- bool RightToLeft = false) {
+ bool RightToLeft = false,
+ LValue *ObjectArg = nullptr) {
bool Success = true;
llvm::SmallBitVector ForbiddenNullArgs;
if (Callee->hasAttr<NonNullAttr>()) {
@@ -6599,13 +6603,16 @@ static bool EvaluateArgs(ArrayRef<const Expr *> Args, CallRef Call,
const ParmVarDecl *PVD =
Idx < Callee->getNumParams() ? Callee->getParamDecl(Idx) : nullptr;
bool NonNull = !ForbiddenNullArgs.empty() && ForbiddenNullArgs[Idx];
- if (!EvaluateCallArg(PVD, Args[Idx], Call, Info, NonNull)) {
+ APValue *That = nullptr;
+ if (!EvaluateCallArg(PVD, Args[Idx], Call, Info, NonNull, &That)) {
// If we're checking for a potential constant expression, evaluate all
// initializers even if some of them fail.
if (!Info.noteFailure())
return false;
Success = false;
}
+ if (PVD && PVD->isExplicitObjectParameter() && That && That->isLValue())
+ ObjectArg->setFrom(Info.Ctx, *That);
}
return Success;
}
@@ -6633,14 +6640,15 @@ static bool handleTrivialCopy(EvalInfo &Info, const ParmVarDecl *Param,
/// Evaluate a function call.
static bool HandleFunctionCall(SourceLocation CallLoc,
- const FunctionDecl *Callee, const LValue *This,
- const Expr *E, ArrayRef<const Expr *> Args,
- CallRef Call, const Stmt *Body, EvalInfo &Info,
+ const FunctionDecl *Callee,
+ const LValue *ObjectArg, const Expr *E,
+ ArrayRef<const Expr *> Args, CallRef Call,
+ const Stmt *Body, EvalInfo &Info,
APValue &Result, const LValue *ResultSlot) {
if (!Info.CheckCallLimit(CallLoc))
return false;
- CallStackFrame Frame(Info, E->getSourceRange(), Callee, This, E, Call);
+ CallStackFrame Frame(Info, E->getSourceRange(), Callee, ObjectArg, E, Call);
// For a trivial copy or move assignment, perform an APValue copy. This is
// essential for unions, where the operations performed by the assignment
@@ -6653,16 +6661,20 @@ static bool HandleFunctionCall(SourceLocation CallLoc,
(MD->getParent()->isUnion() ||
(MD->isTrivial() &&
isReadByLvalueToRvalueConversion(MD->getParent())))) {
- assert(This &&
+ unsigned ExplicitOffset = MD->isExplicitObjectMemberFunction() ? 1 : 0;
+ assert(ObjectArg &&
(MD->isCopyAssignmentOperator() || MD->isMoveAssignmentOperator()));
APValue RHSValue;
if (!handleTrivialCopy(Info, MD->getParamDecl(0), Args[0], RHSValue,
MD->getParent()->isUnion()))
return false;
- if (!handleAssignment(Info, Args[0], *This, MD->getThisType(),
+
+ LValue Obj;
+ if (!handleAssignment(Info, Args[ExplicitOffset], *ObjectArg,
+ MD->getFunctionObjectParameterReferenceType(),
RHSValue))
return false;
- This->moveInto(Result);
+ ObjectArg->moveInto(Result);
return true;
} else if (MD && isLambdaCallOperator(MD)) {
// We're in a lambda; determine the lambda capture field maps unless we're
@@ -8289,7 +8301,7 @@ class ExprEvaluatorBase
QualType CalleeType = Callee->getType();
const FunctionDecl *FD = nullptr;
- LValue *This = nullptr, ThisVal;
+ LValue *This = nullptr, ObjectArg;
auto Args = llvm::ArrayRef(E->getArgs(), E->getNumArgs());
bool HasQualifier = false;
@@ -8300,28 +8312,28 @@ class ExprEvaluatorBase
const CXXMethodDecl *Member = nullptr;
if (const MemberExpr *ME = dyn_cast<MemberExpr>(Callee)) {
// Explicit bound member calls, such as x.f() or p->g();
- if (!EvaluateObjectArgument(Info, ME->getBase(), ThisVal))
+ if (!EvaluateObjectArgument(Info, ME->getBase(), ObjectArg))
return false;
Member = dyn_cast<CXXMethodDecl>(ME->getMemberDecl());
if (!Member)
return Error(Callee);
- This = &ThisVal;
+ This = &ObjectArg;
HasQualifier = ME->hasQualifier();
} else if (const BinaryOperator *BE = dyn_cast<BinaryOperator>(Callee)) {
// Indirect bound member calls ('.*' or '->*').
const ValueDecl *D =
- HandleMemberPointerAccess(Info, BE, ThisVal, false);
+ HandleMemberPointerAccess(Info, BE, ObjectArg, false);
if (!D)
return false;
Member = dyn_cast<CXXMethodDecl>(D);
if (!Member)
return Error(Callee);
- This = &ThisVal;
+ This = &ObjectArg;
} else if (const auto *PDE = dyn_cast<CXXPseudoDestructorExpr>(Callee)) {
if (!Info.getLangOpts().CPlusPlus20)
Info.CCEDiag(PDE, diag::note_constexpr_pseudo_destructor);
- return EvaluateObjectArgument(Info, PDE->getBase(), ThisVal) &&
- HandleDestruction(Info, PDE, ThisVal, PDE->getDestroyedType());
+ return EvaluateObjectArgument(Info, PDE->getBase(), ObjectArg) &&
+ HandleDestruction(Info, PDE, ObjectArg, PDE->getDestroyedType());
} else
return Error(Callee);
FD = Member;
@@ -8358,7 +8370,7 @@ class ExprEvaluatorBase
if (const auto *MD = dyn_cast<CXXMethodDecl>(FD))
HasThis = MD->isImplicitObjectMemberFunction();
if (!EvaluateArgs(HasThis ? Args.slice(1) : Args, Call, Info, FD,
- /*RightToLeft=*/true))
+ /*RightToLeft=*/true, &ObjectArg))
return false;
}
@@ -8373,20 +8385,20 @@ class ExprEvaluatorBase
if (Args.empty())
return Error(E);
- if (!EvaluateObjectArgument(Info, Args[0], ThisVal))
+ if (!EvaluateObjectArgument(Info, Args[0], ObjectArg))
return false;
// If we are calling a static operator, the 'this' argument needs to be
// ignored after being evaluated.
if (MD->isInstance())
- This = &ThisVal;
+ This = &ObjectArg;
// If this is syntactically a simple assignment using a trivial
// assignment operator, start the lifetimes of union members as needed,
// per C++20 [class.union]5.
if (Info.getLangOpts().CPlusPlus20 && OCE &&
OCE->getOperator() == OO_Equal && MD->isTrivial() &&
- !MaybeHandleUnionActiveMemberChange(Info, Args[0], ThisVal))
+ !MaybeHandleUnionActiveMemberChange(Info, Args[0], ObjectArg))
return false;
Args = Args.slice(1);
@@ -8441,7 +8453,8 @@ class ExprEvaluatorBase
// Evaluate the arguments now if we've not already done so.
if (!Call) {
Call = Info.CurrentCall->createCall(FD);
- if (!EvaluateArgs(Args, Call, Info, FD))
+ if (!EvaluateArgs(Args, Call, Info, FD, /*RightToLeft*/ false,
+ &ObjectArg))
return false;
}
@@ -8475,6 +8488,11 @@ class ExprEvaluatorBase
Stmt *Body = FD->getBody(Definition);
SourceLocation Loc = E->getExprLoc();
+ // Treat the object argument as `this` when evaluating defaulted
+ // special menmber functions
+ if (FD->hasCXXExplicitFunctionObjectParameter())
+ This = &ObjectArg;
+
if (!CheckConstexprFunction(Info, Loc, FD, Definition, Body) ||
!HandleFunctionCall(Loc, Definition, This, E, Args, Call, Body, Info,
Result, ResultSlot))
diff --git a/clang/test/SemaCXX/cxx2b-deducing-this-constexpr.cpp b/clang/test/SemaCXX/cxx2b-deducing-this-constexpr.cpp
index 191fb013e0316..5b06c7d1827a7 100644
--- a/clang/test/SemaCXX/cxx2b-deducing-this-constexpr.cpp
+++ b/clang/test/SemaCXX/cxx2b-deducing-this-constexpr.cpp
@@ -73,3 +73,32 @@ int test() {
}
}
+
+namespace GH142835 {
+struct MoveMe {
+ MoveMe& operator=(this MoveMe&, const MoveMe&) = default;
+ constexpr MoveMe& operator=(this MoveMe& self, MoveMe&& other) {
+ self.value = other.value;
+ other.value = 0;
+ return self;
+ }
+ int value = 4242;
+};
+
+struct S {
+ constexpr S& operator=(this S&, const S&) = default;
+ S& operator=(this S&, S&&) = default;
+
+ MoveMe move_me;
+};
+
+constexpr bool f() {
+ S s1{};
+ S s2{};
+ s2 = s1;
+ return true;
+}
+
+static_assert(f());
+
+}
More information about the cfe-commits
mailing list