[clang] [clang] Followup for constexpr-unknown potential constant expressions. (PR #151053)
Eli Friedman via cfe-commits
cfe-commits at lists.llvm.org
Thu Aug 7 14:45:46 PDT 2025
https://github.com/efriedma-quic updated https://github.com/llvm/llvm-project/pull/151053
>From 43e441c0559294f0e4d85cf67bea480140b2e9d1 Mon Sep 17 00:00:00 2001
From: Eli Friedman <efriedma at quicinc.com>
Date: Mon, 28 Jul 2025 15:47:33 -0700
Subject: [PATCH 1/2] [clang] Followup for constexpr-unknown potential constant
expressions.
6a60f18997d62b0e2842a921fcb6beb3e52ed823 fixed the primary issue of
dereferences, but there are some expressions that depend on the
identity of the pointed-to object without actually accessing it. Handle
those cases.
Also, while I'm here, fix a crash in interpreter mode comparing typeid
to nullptr.
---
clang/lib/AST/ByteCode/Pointer.cpp | 5 +-
clang/lib/AST/ExprConstant.cpp | 141 ++++++++++--------
.../SemaCXX/constant-expression-p2280r4.cpp | 38 ++++-
3 files changed, 121 insertions(+), 63 deletions(-)
diff --git a/clang/lib/AST/ByteCode/Pointer.cpp b/clang/lib/AST/ByteCode/Pointer.cpp
index 4019b74669282..d94251996ba16 100644
--- a/clang/lib/AST/ByteCode/Pointer.cpp
+++ b/clang/lib/AST/ByteCode/Pointer.cpp
@@ -563,8 +563,11 @@ bool Pointer::hasSameBase(const Pointer &A, const Pointer &B) {
if (A.isTypeidPointer() && B.isTypeidPointer())
return true;
- if (A.isIntegralPointer() || B.isIntegralPointer())
+ if (A.isIntegralPointer() || B.isIntegralPointer()) {
+ if (A.isTypeidPointer() || B.isTypeidPointer())
+ return false;
return A.getSource() == B.getSource();
+ }
if (A.StorageKind != B.StorageKind)
return false;
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 9808298a1b1d0..373cf14859bbd 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -465,49 +465,7 @@ namespace {
void diagnosePointerArithmetic(EvalInfo &Info, const Expr *E,
const APSInt &N);
/// Add N to the address of this subobject.
- void adjustIndex(EvalInfo &Info, const Expr *E, APSInt N) {
- if (Invalid || !N) return;
- uint64_t TruncatedN = N.extOrTrunc(64).getZExtValue();
- if (isMostDerivedAnUnsizedArray()) {
- diagnoseUnsizedArrayPointerArithmetic(Info, E);
- // Can't verify -- trust that the user is doing the right thing (or if
- // not, trust that the caller will catch the bad behavior).
- // FIXME: Should we reject if this overflows, at least?
- Entries.back() = PathEntry::ArrayIndex(
- Entries.back().getAsArrayIndex() + TruncatedN);
- return;
- }
-
- // [expr.add]p4: For the purposes of these operators, a pointer to a
- // nonarray object behaves the same as a pointer to the first element of
- // an array of length one with the type of the object as its element type.
- bool IsArray = MostDerivedPathLength == Entries.size() &&
- MostDerivedIsArrayElement;
- uint64_t ArrayIndex = IsArray ? Entries.back().getAsArrayIndex()
- : (uint64_t)IsOnePastTheEnd;
- uint64_t ArraySize =
- IsArray ? getMostDerivedArraySize() : (uint64_t)1;
-
- if (N < -(int64_t)ArrayIndex || N > ArraySize - ArrayIndex) {
- // Calculate the actual index in a wide enough type, so we can include
- // it in the note.
- N = N.extend(std::max<unsigned>(N.getBitWidth() + 1, 65));
- (llvm::APInt&)N += ArrayIndex;
- assert(N.ugt(ArraySize) && "bounds check failed for in-bounds index");
- diagnosePointerArithmetic(Info, E, N);
- setInvalid();
- return;
- }
-
- ArrayIndex += TruncatedN;
- assert(ArrayIndex <= ArraySize &&
- "bounds check succeeded for out-of-bounds index");
-
- if (IsArray)
- Entries.back() = PathEntry::ArrayIndex(ArrayIndex);
- else
- IsOnePastTheEnd = (ArrayIndex != 0);
- }
+ void adjustIndex(EvalInfo &Info, const Expr *E, APSInt N, LValue &LV);
};
/// A scope at the end of which an object can need to be destroyed.
@@ -1799,7 +1757,7 @@ namespace {
Offset = CharUnits::fromQuantity(Offset64 + ElemSize64 * Index64);
if (checkNullPointer(Info, E, CSK_ArrayIndex))
- Designator.adjustIndex(Info, E, Index);
+ Designator.adjustIndex(Info, E, Index, *this);
clearIsNullPointer();
}
void adjustOffset(CharUnits N) {
@@ -1907,6 +1865,54 @@ namespace {
}
}
+void SubobjectDesignator::adjustIndex(EvalInfo &Info, const Expr *E, APSInt N,
+ LValue &LV) {
+ if (Invalid || !N)
+ return;
+ uint64_t TruncatedN = N.extOrTrunc(64).getZExtValue();
+ if (isMostDerivedAnUnsizedArray()) {
+ diagnoseUnsizedArrayPointerArithmetic(Info, E);
+ // Can't verify -- trust that the user is doing the right thing (or if
+ // not, trust that the caller will catch the bad behavior).
+ // FIXME: Should we reject if this overflows, at least?
+ Entries.back() =
+ PathEntry::ArrayIndex(Entries.back().getAsArrayIndex() + TruncatedN);
+ return;
+ }
+
+ // [expr.add]p4: For the purposes of these operators, a pointer to a
+ // nonarray object behaves the same as a pointer to the first element of
+ // an array of length one with the type of the object as its element type.
+ bool IsArray =
+ MostDerivedPathLength == Entries.size() && MostDerivedIsArrayElement;
+ uint64_t ArrayIndex =
+ IsArray ? Entries.back().getAsArrayIndex() : (uint64_t)IsOnePastTheEnd;
+ uint64_t ArraySize = IsArray ? getMostDerivedArraySize() : (uint64_t)1;
+
+ if (N < -(int64_t)ArrayIndex || N > ArraySize - ArrayIndex) {
+ if (!Info.checkingPotentialConstantExpression() ||
+ !LV.AllowConstexprUnknown) {
+ // Calculate the actual index in a wide enough type, so we can include
+ // it in the note.
+ N = N.extend(std::max<unsigned>(N.getBitWidth() + 1, 65));
+ (llvm::APInt &)N += ArrayIndex;
+ assert(N.ugt(ArraySize) && "bounds check failed for in-bounds index");
+ diagnosePointerArithmetic(Info, E, N);
+ }
+ setInvalid();
+ return;
+ }
+
+ ArrayIndex += TruncatedN;
+ assert(ArrayIndex <= ArraySize &&
+ "bounds check succeeded for out-of-bounds index");
+
+ if (IsArray)
+ Entries.back() = PathEntry::ArrayIndex(ArrayIndex);
+ else
+ IsOnePastTheEnd = (ArrayIndex != 0);
+}
+
static bool Evaluate(APValue &Result, EvalInfo &Info, const Expr *E);
static bool EvaluateInPlace(APValue &Result, EvalInfo &Info,
const LValue &This, const Expr *E,
@@ -5126,12 +5132,18 @@ static bool HandleBaseToDerivedCast(EvalInfo &Info, const CastExpr *E,
if (const PointerType *PT = TargetQT->getAs<PointerType>())
TargetQT = PT->getPointeeType();
- // Check this cast lands within the final derived-to-base subobject path.
- if (D.MostDerivedPathLength + E->path_size() > D.Entries.size()) {
- Info.CCEDiag(E, diag::note_constexpr_invalid_downcast)
- << D.MostDerivedType << TargetQT;
+ auto InvalidCast = [&]() {
+ if (!Info.checkingPotentialConstantExpression() ||
+ !Result.AllowConstexprUnknown) {
+ Info.CCEDiag(E, diag::note_constexpr_invalid_downcast)
+ << D.MostDerivedType << TargetQT;
+ }
return false;
- }
+ };
+
+ // Check this cast lands within the final derived-to-base subobject path.
+ if (D.MostDerivedPathLength + E->path_size() > D.Entries.size())
+ return InvalidCast();
// Check the type of the final cast. We don't need to check the path,
// since a cast can only be formed if the path is unique.
@@ -5142,11 +5154,8 @@ static bool HandleBaseToDerivedCast(EvalInfo &Info, const CastExpr *E,
FinalType = D.MostDerivedType->getAsCXXRecordDecl();
else
FinalType = getAsBaseClass(D.Entries[NewEntriesSize - 1]);
- if (FinalType->getCanonicalDecl() != TargetType->getCanonicalDecl()) {
- Info.CCEDiag(E, diag::note_constexpr_invalid_downcast)
- << D.MostDerivedType << TargetQT;
- return false;
- }
+ if (FinalType->getCanonicalDecl() != TargetType->getCanonicalDecl())
+ return InvalidCast();
// Truncate the lvalue to the appropriate derived class.
return CastToDerivedClass(Info, E, Result, TargetType, NewEntriesSize);
@@ -6104,12 +6113,15 @@ static bool checkDynamicType(EvalInfo &Info, const Expr *E, const LValue &This,
} else if (Polymorphic) {
// Conservatively refuse to perform a polymorphic operation if we would
// not be able to read a notional 'vptr' value.
- APValue Val;
- This.moveInto(Val);
- QualType StarThisType =
- Info.Ctx.getLValueReferenceType(This.Designator.getType(Info.Ctx));
- Info.FFDiag(E, diag::note_constexpr_polymorphic_unknown_dynamic_type)
- << AK << Val.getAsString(Info.Ctx, StarThisType);
+ if (!Info.checkingPotentialConstantExpression() ||
+ !This.AllowConstexprUnknown) {
+ APValue Val;
+ This.moveInto(Val);
+ QualType StarThisType =
+ Info.Ctx.getLValueReferenceType(This.Designator.getType(Info.Ctx));
+ Info.FFDiag(E, diag::note_constexpr_polymorphic_unknown_dynamic_type)
+ << AK << Val.getAsString(Info.Ctx, StarThisType);
+ }
return false;
}
return true;
@@ -14554,6 +14566,11 @@ EvaluateComparisonBinaryOperator(EvalInfo &Info, const BinaryOperator *E,
// Reject differing bases from the normal codepath; we special-case
// comparisons to null.
if (!HasSameBase(LHSValue, RHSValue)) {
+ // Bail out early if we're checking potential constant expression.
+ // Otherwise, prefer to diagnose other issues.
+ if (Info.checkingPotentialConstantExpression() &&
+ (LHSValue.AllowConstexprUnknown || RHSValue.AllowConstexprUnknown))
+ return false;
auto DiagComparison = [&] (unsigned DiagID, bool Reversed = false) {
std::string LHS = LHSValue.toString(Info.Ctx, E->getLHS()->getType());
std::string RHS = RHSValue.toString(Info.Ctx, E->getRHS()->getType());
@@ -14874,6 +14891,10 @@ bool IntExprEvaluator::VisitBinaryOperator(const BinaryOperator *E) {
// Reject differing bases from the normal codepath; we special-case
// comparisons to null.
if (!HasSameBase(LHSValue, RHSValue)) {
+ if (Info.checkingPotentialConstantExpression() &&
+ (LHSValue.AllowConstexprUnknown || RHSValue.AllowConstexprUnknown))
+ return false;
+
const Expr *LHSExpr = LHSValue.Base.dyn_cast<const Expr *>();
const Expr *RHSExpr = RHSValue.Base.dyn_cast<const Expr *>();
diff --git a/clang/test/SemaCXX/constant-expression-p2280r4.cpp b/clang/test/SemaCXX/constant-expression-p2280r4.cpp
index 312a77830420b..78e2e17016280 100644
--- a/clang/test/SemaCXX/constant-expression-p2280r4.cpp
+++ b/clang/test/SemaCXX/constant-expression-p2280r4.cpp
@@ -1,5 +1,5 @@
-// RUN: %clang_cc1 -std=c++23 -verify=expected,nointerpreter %s
-// RUN: %clang_cc1 -std=c++23 -verify=expected,interpreter %s -fexperimental-new-constant-interpreter
+// RUN: %clang_cc1 -std=c++23 -verify=expected,nointerpreter -Winvalid-constexpr %s
+// RUN: %clang_cc1 -std=c++23 -verify=expected,interpreter %s -fexperimental-new-constant-interpreter -Winvalid-constexpr
using size_t = decltype(sizeof(0));
@@ -397,3 +397,37 @@ namespace GH150015 {
// nointerpreter-note {{comparison of addresses of subobjects of different base classes has unspecified value}} \
// interpreter-note {{initializer of 'z' is unknown}}
}
+
+namespace InvalidConstexprFn {
+ // Make sure we don't trigger -Winvalid-constexpr incorrectly.
+ constexpr bool same_address(const int &a, const int &b) { return &a == &b; }
+ constexpr int next_element(const int &p) { return (&p)[2]; }
+
+ struct Base {};
+ struct Derived : Base { int n; };
+ constexpr int get_derived_member(const Base& b) { return static_cast<const Derived&>(b).n; }
+
+ struct PolyBase {
+ constexpr virtual int get() const { return 0; }
+ };
+ struct PolyDerived : PolyBase {
+ constexpr int get() const override { return 1; }
+ };
+ constexpr int virtual_call(const PolyBase& b) { return b.get(); }
+ constexpr auto* type(const PolyBase& b) { return &typeid(b); }
+ // FIXME: Intepreter doesn't support constexpr dynamic_cast yet.
+ constexpr const void* dyncast(const PolyBase& b) { return dynamic_cast<const void*>(&b); } // interpreter-error {{constexpr function never produces a constant expression}} \
+ // interpreter-note 2 {{subexpression not valid in a constant expression}}
+ constexpr int sub(const int (&a)[], const int (&b)[]) { return a-b; }
+ constexpr const int* add(const int &a) { return &a+3; }
+
+ constexpr int arr[3]{0, 1, 2};
+ static_assert(same_address(arr[1], arr[1]));
+ static_assert(next_element(arr[0]) == 2);
+ static_assert(get_derived_member(Derived{}) == 0);
+ static_assert(virtual_call(PolyDerived{}) == 1);
+ static_assert(type(PolyDerived{}) != nullptr);
+ static_assert(dyncast(PolyDerived{}) != nullptr); // interpreter-error {{static assertion expression is not an integral constant expression}} interpreter-note {{in call}}
+ static_assert(sub(arr, arr) == 0);
+ static_assert(add(arr[0]) == &arr[3]);
+}
>From 804289cbd3693aa006e979a92bbb387ca6686d63 Mon Sep 17 00:00:00 2001
From: Eli Friedman <efriedma at quicinc.com>
Date: Thu, 7 Aug 2025 14:45:25 -0700
Subject: [PATCH 2/2] Address review comments.
---
clang/lib/AST/ByteCode/Pointer.cpp | 6 ------
clang/lib/AST/ExprConstant.cpp | 4 ++--
2 files changed, 2 insertions(+), 8 deletions(-)
diff --git a/clang/lib/AST/ByteCode/Pointer.cpp b/clang/lib/AST/ByteCode/Pointer.cpp
index d94251996ba16..04a7524fb208a 100644
--- a/clang/lib/AST/ByteCode/Pointer.cpp
+++ b/clang/lib/AST/ByteCode/Pointer.cpp
@@ -563,12 +563,6 @@ bool Pointer::hasSameBase(const Pointer &A, const Pointer &B) {
if (A.isTypeidPointer() && B.isTypeidPointer())
return true;
- if (A.isIntegralPointer() || B.isIntegralPointer()) {
- if (A.isTypeidPointer() || B.isTypeidPointer())
- return false;
- return A.getSource() == B.getSource();
- }
-
if (A.StorageKind != B.StorageKind)
return false;
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 373cf14859bbd..f9eb1b169c3fa 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -465,7 +465,7 @@ namespace {
void diagnosePointerArithmetic(EvalInfo &Info, const Expr *E,
const APSInt &N);
/// Add N to the address of this subobject.
- void adjustIndex(EvalInfo &Info, const Expr *E, APSInt N, LValue &LV);
+ void adjustIndex(EvalInfo &Info, const Expr *E, APSInt N, const LValue &LV);
};
/// A scope at the end of which an object can need to be destroyed.
@@ -1866,7 +1866,7 @@ namespace {
}
void SubobjectDesignator::adjustIndex(EvalInfo &Info, const Expr *E, APSInt N,
- LValue &LV) {
+ const LValue &LV) {
if (Invalid || !N)
return;
uint64_t TruncatedN = N.extOrTrunc(64).getZExtValue();
More information about the cfe-commits
mailing list