Skip to content

Commit 43e441c

Browse files
committed
[clang] Followup for constexpr-unknown potential constant expressions.
6a60f18 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.
1 parent 936ee35 commit 43e441c

File tree

3 files changed

+121
-63
lines changed

3 files changed

+121
-63
lines changed

clang/lib/AST/ByteCode/Pointer.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -563,8 +563,11 @@ bool Pointer::hasSameBase(const Pointer &A, const Pointer &B) {
563563
if (A.isTypeidPointer() && B.isTypeidPointer())
564564
return true;
565565

566-
if (A.isIntegralPointer() || B.isIntegralPointer())
566+
if (A.isIntegralPointer() || B.isIntegralPointer()) {
567+
if (A.isTypeidPointer() || B.isTypeidPointer())
568+
return false;
567569
return A.getSource() == B.getSource();
570+
}
568571

569572
if (A.StorageKind != B.StorageKind)
570573
return false;

clang/lib/AST/ExprConstant.cpp

Lines changed: 81 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -465,49 +465,7 @@ namespace {
465465
void diagnosePointerArithmetic(EvalInfo &Info, const Expr *E,
466466
const APSInt &N);
467467
/// Add N to the address of this subobject.
468-
void adjustIndex(EvalInfo &Info, const Expr *E, APSInt N) {
469-
if (Invalid || !N) return;
470-
uint64_t TruncatedN = N.extOrTrunc(64).getZExtValue();
471-
if (isMostDerivedAnUnsizedArray()) {
472-
diagnoseUnsizedArrayPointerArithmetic(Info, E);
473-
// Can't verify -- trust that the user is doing the right thing (or if
474-
// not, trust that the caller will catch the bad behavior).
475-
// FIXME: Should we reject if this overflows, at least?
476-
Entries.back() = PathEntry::ArrayIndex(
477-
Entries.back().getAsArrayIndex() + TruncatedN);
478-
return;
479-
}
480-
481-
// [expr.add]p4: For the purposes of these operators, a pointer to a
482-
// nonarray object behaves the same as a pointer to the first element of
483-
// an array of length one with the type of the object as its element type.
484-
bool IsArray = MostDerivedPathLength == Entries.size() &&
485-
MostDerivedIsArrayElement;
486-
uint64_t ArrayIndex = IsArray ? Entries.back().getAsArrayIndex()
487-
: (uint64_t)IsOnePastTheEnd;
488-
uint64_t ArraySize =
489-
IsArray ? getMostDerivedArraySize() : (uint64_t)1;
490-
491-
if (N < -(int64_t)ArrayIndex || N > ArraySize - ArrayIndex) {
492-
// Calculate the actual index in a wide enough type, so we can include
493-
// it in the note.
494-
N = N.extend(std::max<unsigned>(N.getBitWidth() + 1, 65));
495-
(llvm::APInt&)N += ArrayIndex;
496-
assert(N.ugt(ArraySize) && "bounds check failed for in-bounds index");
497-
diagnosePointerArithmetic(Info, E, N);
498-
setInvalid();
499-
return;
500-
}
501-
502-
ArrayIndex += TruncatedN;
503-
assert(ArrayIndex <= ArraySize &&
504-
"bounds check succeeded for out-of-bounds index");
505-
506-
if (IsArray)
507-
Entries.back() = PathEntry::ArrayIndex(ArrayIndex);
508-
else
509-
IsOnePastTheEnd = (ArrayIndex != 0);
510-
}
468+
void adjustIndex(EvalInfo &Info, const Expr *E, APSInt N, LValue &LV);
511469
};
512470

513471
/// A scope at the end of which an object can need to be destroyed.
@@ -1799,7 +1757,7 @@ namespace {
17991757
Offset = CharUnits::fromQuantity(Offset64 + ElemSize64 * Index64);
18001758

18011759
if (checkNullPointer(Info, E, CSK_ArrayIndex))
1802-
Designator.adjustIndex(Info, E, Index);
1760+
Designator.adjustIndex(Info, E, Index, *this);
18031761
clearIsNullPointer();
18041762
}
18051763
void adjustOffset(CharUnits N) {
@@ -1907,6 +1865,54 @@ namespace {
19071865
}
19081866
}
19091867

1868+
void SubobjectDesignator::adjustIndex(EvalInfo &Info, const Expr *E, APSInt N,
1869+
LValue &LV) {
1870+
if (Invalid || !N)
1871+
return;
1872+
uint64_t TruncatedN = N.extOrTrunc(64).getZExtValue();
1873+
if (isMostDerivedAnUnsizedArray()) {
1874+
diagnoseUnsizedArrayPointerArithmetic(Info, E);
1875+
// Can't verify -- trust that the user is doing the right thing (or if
1876+
// not, trust that the caller will catch the bad behavior).
1877+
// FIXME: Should we reject if this overflows, at least?
1878+
Entries.back() =
1879+
PathEntry::ArrayIndex(Entries.back().getAsArrayIndex() + TruncatedN);
1880+
return;
1881+
}
1882+
1883+
// [expr.add]p4: For the purposes of these operators, a pointer to a
1884+
// nonarray object behaves the same as a pointer to the first element of
1885+
// an array of length one with the type of the object as its element type.
1886+
bool IsArray =
1887+
MostDerivedPathLength == Entries.size() && MostDerivedIsArrayElement;
1888+
uint64_t ArrayIndex =
1889+
IsArray ? Entries.back().getAsArrayIndex() : (uint64_t)IsOnePastTheEnd;
1890+
uint64_t ArraySize = IsArray ? getMostDerivedArraySize() : (uint64_t)1;
1891+
1892+
if (N < -(int64_t)ArrayIndex || N > ArraySize - ArrayIndex) {
1893+
if (!Info.checkingPotentialConstantExpression() ||
1894+
!LV.AllowConstexprUnknown) {
1895+
// Calculate the actual index in a wide enough type, so we can include
1896+
// it in the note.
1897+
N = N.extend(std::max<unsigned>(N.getBitWidth() + 1, 65));
1898+
(llvm::APInt &)N += ArrayIndex;
1899+
assert(N.ugt(ArraySize) && "bounds check failed for in-bounds index");
1900+
diagnosePointerArithmetic(Info, E, N);
1901+
}
1902+
setInvalid();
1903+
return;
1904+
}
1905+
1906+
ArrayIndex += TruncatedN;
1907+
assert(ArrayIndex <= ArraySize &&
1908+
"bounds check succeeded for out-of-bounds index");
1909+
1910+
if (IsArray)
1911+
Entries.back() = PathEntry::ArrayIndex(ArrayIndex);
1912+
else
1913+
IsOnePastTheEnd = (ArrayIndex != 0);
1914+
}
1915+
19101916
static bool Evaluate(APValue &Result, EvalInfo &Info, const Expr *E);
19111917
static bool EvaluateInPlace(APValue &Result, EvalInfo &Info,
19121918
const LValue &This, const Expr *E,
@@ -5126,12 +5132,18 @@ static bool HandleBaseToDerivedCast(EvalInfo &Info, const CastExpr *E,
51265132
if (const PointerType *PT = TargetQT->getAs<PointerType>())
51275133
TargetQT = PT->getPointeeType();
51285134

5129-
// Check this cast lands within the final derived-to-base subobject path.
5130-
if (D.MostDerivedPathLength + E->path_size() > D.Entries.size()) {
5131-
Info.CCEDiag(E, diag::note_constexpr_invalid_downcast)
5132-
<< D.MostDerivedType << TargetQT;
5135+
auto InvalidCast = [&]() {
5136+
if (!Info.checkingPotentialConstantExpression() ||
5137+
!Result.AllowConstexprUnknown) {
5138+
Info.CCEDiag(E, diag::note_constexpr_invalid_downcast)
5139+
<< D.MostDerivedType << TargetQT;
5140+
}
51335141
return false;
5134-
}
5142+
};
5143+
5144+
// Check this cast lands within the final derived-to-base subobject path.
5145+
if (D.MostDerivedPathLength + E->path_size() > D.Entries.size())
5146+
return InvalidCast();
51355147

51365148
// Check the type of the final cast. We don't need to check the path,
51375149
// since a cast can only be formed if the path is unique.
@@ -5142,11 +5154,8 @@ static bool HandleBaseToDerivedCast(EvalInfo &Info, const CastExpr *E,
51425154
FinalType = D.MostDerivedType->getAsCXXRecordDecl();
51435155
else
51445156
FinalType = getAsBaseClass(D.Entries[NewEntriesSize - 1]);
5145-
if (FinalType->getCanonicalDecl() != TargetType->getCanonicalDecl()) {
5146-
Info.CCEDiag(E, diag::note_constexpr_invalid_downcast)
5147-
<< D.MostDerivedType << TargetQT;
5148-
return false;
5149-
}
5157+
if (FinalType->getCanonicalDecl() != TargetType->getCanonicalDecl())
5158+
return InvalidCast();
51505159

51515160
// Truncate the lvalue to the appropriate derived class.
51525161
return CastToDerivedClass(Info, E, Result, TargetType, NewEntriesSize);
@@ -6104,12 +6113,15 @@ static bool checkDynamicType(EvalInfo &Info, const Expr *E, const LValue &This,
61046113
} else if (Polymorphic) {
61056114
// Conservatively refuse to perform a polymorphic operation if we would
61066115
// not be able to read a notional 'vptr' value.
6107-
APValue Val;
6108-
This.moveInto(Val);
6109-
QualType StarThisType =
6110-
Info.Ctx.getLValueReferenceType(This.Designator.getType(Info.Ctx));
6111-
Info.FFDiag(E, diag::note_constexpr_polymorphic_unknown_dynamic_type)
6112-
<< AK << Val.getAsString(Info.Ctx, StarThisType);
6116+
if (!Info.checkingPotentialConstantExpression() ||
6117+
!This.AllowConstexprUnknown) {
6118+
APValue Val;
6119+
This.moveInto(Val);
6120+
QualType StarThisType =
6121+
Info.Ctx.getLValueReferenceType(This.Designator.getType(Info.Ctx));
6122+
Info.FFDiag(E, diag::note_constexpr_polymorphic_unknown_dynamic_type)
6123+
<< AK << Val.getAsString(Info.Ctx, StarThisType);
6124+
}
61136125
return false;
61146126
}
61156127
return true;
@@ -14554,6 +14566,11 @@ EvaluateComparisonBinaryOperator(EvalInfo &Info, const BinaryOperator *E,
1455414566
// Reject differing bases from the normal codepath; we special-case
1455514567
// comparisons to null.
1455614568
if (!HasSameBase(LHSValue, RHSValue)) {
14569+
// Bail out early if we're checking potential constant expression.
14570+
// Otherwise, prefer to diagnose other issues.
14571+
if (Info.checkingPotentialConstantExpression() &&
14572+
(LHSValue.AllowConstexprUnknown || RHSValue.AllowConstexprUnknown))
14573+
return false;
1455714574
auto DiagComparison = [&] (unsigned DiagID, bool Reversed = false) {
1455814575
std::string LHS = LHSValue.toString(Info.Ctx, E->getLHS()->getType());
1455914576
std::string RHS = RHSValue.toString(Info.Ctx, E->getRHS()->getType());
@@ -14874,6 +14891,10 @@ bool IntExprEvaluator::VisitBinaryOperator(const BinaryOperator *E) {
1487414891
// Reject differing bases from the normal codepath; we special-case
1487514892
// comparisons to null.
1487614893
if (!HasSameBase(LHSValue, RHSValue)) {
14894+
if (Info.checkingPotentialConstantExpression() &&
14895+
(LHSValue.AllowConstexprUnknown || RHSValue.AllowConstexprUnknown))
14896+
return false;
14897+
1487714898
const Expr *LHSExpr = LHSValue.Base.dyn_cast<const Expr *>();
1487814899
const Expr *RHSExpr = RHSValue.Base.dyn_cast<const Expr *>();
1487914900

clang/test/SemaCXX/constant-expression-p2280r4.cpp

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
// RUN: %clang_cc1 -std=c++23 -verify=expected,nointerpreter %s
2-
// RUN: %clang_cc1 -std=c++23 -verify=expected,interpreter %s -fexperimental-new-constant-interpreter
1+
// RUN: %clang_cc1 -std=c++23 -verify=expected,nointerpreter -Winvalid-constexpr %s
2+
// RUN: %clang_cc1 -std=c++23 -verify=expected,interpreter %s -fexperimental-new-constant-interpreter -Winvalid-constexpr
33

44
using size_t = decltype(sizeof(0));
55

@@ -397,3 +397,37 @@ namespace GH150015 {
397397
// nointerpreter-note {{comparison of addresses of subobjects of different base classes has unspecified value}} \
398398
// interpreter-note {{initializer of 'z' is unknown}}
399399
}
400+
401+
namespace InvalidConstexprFn {
402+
// Make sure we don't trigger -Winvalid-constexpr incorrectly.
403+
constexpr bool same_address(const int &a, const int &b) { return &a == &b; }
404+
constexpr int next_element(const int &p) { return (&p)[2]; }
405+
406+
struct Base {};
407+
struct Derived : Base { int n; };
408+
constexpr int get_derived_member(const Base& b) { return static_cast<const Derived&>(b).n; }
409+
410+
struct PolyBase {
411+
constexpr virtual int get() const { return 0; }
412+
};
413+
struct PolyDerived : PolyBase {
414+
constexpr int get() const override { return 1; }
415+
};
416+
constexpr int virtual_call(const PolyBase& b) { return b.get(); }
417+
constexpr auto* type(const PolyBase& b) { return &typeid(b); }
418+
// FIXME: Intepreter doesn't support constexpr dynamic_cast yet.
419+
constexpr const void* dyncast(const PolyBase& b) { return dynamic_cast<const void*>(&b); } // interpreter-error {{constexpr function never produces a constant expression}} \
420+
// interpreter-note 2 {{subexpression not valid in a constant expression}}
421+
constexpr int sub(const int (&a)[], const int (&b)[]) { return a-b; }
422+
constexpr const int* add(const int &a) { return &a+3; }
423+
424+
constexpr int arr[3]{0, 1, 2};
425+
static_assert(same_address(arr[1], arr[1]));
426+
static_assert(next_element(arr[0]) == 2);
427+
static_assert(get_derived_member(Derived{}) == 0);
428+
static_assert(virtual_call(PolyDerived{}) == 1);
429+
static_assert(type(PolyDerived{}) != nullptr);
430+
static_assert(dyncast(PolyDerived{}) != nullptr); // interpreter-error {{static assertion expression is not an integral constant expression}} interpreter-note {{in call}}
431+
static_assert(sub(arr, arr) == 0);
432+
static_assert(add(arr[0]) == &arr[3]);
433+
}

0 commit comments

Comments
 (0)