Skip to content

[clang] Improve constexpr-unknown diagnostics. #146288

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions clang/include/clang/Basic/DiagnosticASTKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,8 @@ def note_constexpr_access_static_temporary : Note<
"outside the expression that created the temporary">;
def note_constexpr_access_unreadable_object : Note<
"%sub{access_kind}0 object '%1' whose value is not known">;
def note_constexpr_access_unknown_variable : Note<
"%sub{access_kind}0 variable %1 whose value is not known">;
def note_constexpr_access_deleted_object : Note<
"%sub{access_kind}0 heap allocated object that has been deleted">;
def note_constexpr_modify_global : Note<
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/AST/APValue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -821,7 +821,7 @@ void APValue::printPretty(raw_ostream &Out, const PrintingPolicy &Policy,
else if (isLValueOnePastTheEnd())
Out << "*(&";

QualType ElemTy = Base.getType();
QualType ElemTy = Base.getType().getNonReferenceType();
if (const ValueDecl *VD = Base.dyn_cast<const ValueDecl*>()) {
Out << *VD;
} else if (TypeInfoLValue TI = Base.dyn_cast<TypeInfoLValue>()) {
Expand Down
69 changes: 27 additions & 42 deletions clang/lib/AST/ExprConstant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,9 @@ namespace {
assert(!isBaseAnAllocSizeCall(Base) &&
"Unsized arrays shouldn't appear here");
unsigned MostDerivedLength = 0;
Type = getType(Base);
// The type of Base is a reference type if the base is a constexpr-unknown
// variable. In that case, look through the reference type.
Type = getType(Base).getNonReferenceType();

for (unsigned I = 0, N = Path.size(); I != N; ++I) {
if (Type->isArrayType()) {
Expand Down Expand Up @@ -289,7 +291,7 @@ namespace {
: Invalid(false), IsOnePastTheEnd(false),
FirstEntryIsAnUnsizedArray(false), MostDerivedIsArrayElement(false),
MostDerivedPathLength(0), MostDerivedArraySize(0),
MostDerivedType(T) {}
MostDerivedType(T.isNull() ? QualType() : T.getNonReferenceType()) {}

SubobjectDesignator(ASTContext &Ctx, const APValue &V)
: Invalid(!V.isLValue() || !V.hasLValuePath()), IsOnePastTheEnd(false),
Expand Down Expand Up @@ -571,7 +573,6 @@ namespace {
typedef std::map<MapKeyTy, APValue> MapTy;
/// Temporaries - Temporary lvalues materialized within this stack frame.
MapTy Temporaries;
MapTy ConstexprUnknownAPValues;

/// CallRange - The source range of the call expression for this call.
SourceRange CallRange;
Expand Down Expand Up @@ -646,9 +647,6 @@ namespace {
APValue &createTemporary(const KeyT *Key, QualType T,
ScopeKind Scope, LValue &LV);

APValue &createConstexprUnknownAPValues(const VarDecl *Key,
APValue::LValueBase Base);

/// Allocate storage for a parameter of a function call made in this frame.
APValue &createParam(CallRef Args, const ParmVarDecl *PVD, LValue &LV);

Expand Down Expand Up @@ -1756,7 +1754,8 @@ namespace {
return;
}
if (checkSubobject(Info, E, CSK_ArrayToPointer)) {
assert(getType(Base)->isPointerType() || getType(Base)->isArrayType());
assert(getType(Base).getNonReferenceType()->isPointerType() ||
getType(Base).getNonReferenceType()->isArrayType());
Designator.FirstEntryIsAnUnsizedArray = true;
Designator.addUnsizedArrayUnchecked(ElemTy);
}
Expand Down Expand Up @@ -1955,15 +1954,6 @@ APValue &CallStackFrame::createTemporary(const KeyT *Key, QualType T,
return createLocal(Base, Key, T, Scope);
}

APValue &
CallStackFrame::createConstexprUnknownAPValues(const VarDecl *Key,
APValue::LValueBase Base) {
APValue &Result = ConstexprUnknownAPValues[MapKeyTy(Key, Base.getVersion())];
Result = APValue(Base, CharUnits::Zero(), APValue::ConstexprUnknown{});

return Result;
}

/// Allocate storage for a parameter of a function call made in this frame.
APValue &CallStackFrame::createParam(CallRef Args, const ParmVarDecl *PVD,
LValue &LV) {
Expand Down Expand Up @@ -3493,7 +3483,7 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E,
APValue::LValueBase Base(VD, Frame ? Frame->Index : 0, Version);

auto CheckUninitReference = [&](bool IsLocalVariable) {
if (!Result->hasValue() && VD->getType()->isReferenceType()) {
if (!Result || (!Result->hasValue() && VD->getType()->isReferenceType())) {
// C++23 [expr.const]p8
// ... For such an object that is not usable in constant expressions, the
// dynamic type of the object is constexpr-unknown. For such a reference
Expand All @@ -3509,7 +3499,7 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E,
Info.FFDiag(E, diag::note_constexpr_use_uninit_reference);
return false;
}
Result = &Info.CurrentCall->createConstexprUnknownAPValues(VD, Base);
Result = nullptr;
}
return true;
};
Expand Down Expand Up @@ -3552,7 +3542,7 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E,
// ... its lifetime began within the evaluation of E;
if (isa<ParmVarDecl>(VD)) {
if (AllowConstexprUnknown) {
Result = &Info.CurrentCall->createConstexprUnknownAPValues(VD, Base);
Result = nullptr;
return true;
}

Expand Down Expand Up @@ -3659,12 +3649,8 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E,

Result = VD->getEvaluatedValue();

if (!Result) {
if (AllowConstexprUnknown)
Result = &Info.CurrentCall->createConstexprUnknownAPValues(VD, Base);
else
return false;
}
if (!Result && !AllowConstexprUnknown)
return false;

return CheckUninitReference(/*IsLocalVariable=*/false);
}
Expand Down Expand Up @@ -3947,11 +3933,6 @@ findSubobject(EvalInfo &Info, const Expr *E, const CompleteObject &Obj,
const FieldDecl *LastField = nullptr;
const FieldDecl *VolatileField = nullptr;

// C++23 [expr.const]p8 If we have an unknown reference or pointers and it
// does not have a value then bail out.
if (O->allowConstexprUnknown() && !O->hasValue())
return false;

// Walk the designator's path to find the subobject.
for (unsigned I = 0, N = Sub.Entries.size(); /**/; ++I) {
// Reading an indeterminate value is undefined, but assigning over one is OK.
Expand Down Expand Up @@ -4491,6 +4472,15 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,

if (!evaluateVarDeclInit(Info, E, VD, Frame, LVal.getLValueVersion(), BaseVal))
return CompleteObject();
// If evaluateVarDeclInit sees a constexpr-unknown variable, it returns
// a null BaseVal. Any constexpr-unknown variable seen here is an error:
// we can't access a constexpr-unknown object.
if (!BaseVal) {
Info.FFDiag(E, diag::note_constexpr_access_unknown_variable, 1)
<< AK << VD;
Info.Note(VD->getLocation(), diag::note_declared_at);
return CompleteObject();
}
} else if (DynamicAllocLValue DA = LVal.Base.dyn_cast<DynamicAllocLValue>()) {
std::optional<DynAlloc *> Alloc = Info.lookupDynamicAlloc(DA);
if (!Alloc) {
Expand Down Expand Up @@ -6057,15 +6047,6 @@ struct CheckDynamicTypeHandler {
/// dynamic type.
static bool checkDynamicType(EvalInfo &Info, const Expr *E, const LValue &This,
AccessKinds AK, bool Polymorphic) {
// We are not allowed to invoke a virtual function whose dynamic type
// is constexpr-unknown, so stop early and let this fail later on if we
// attempt to do so.
// C++23 [expr.const]p5.6
// an invocation of a virtual function ([class.virtual]) for an object whose
// dynamic type is constexpr-unknown;
if (This.allowConstexprUnknown())
return true;

if (This.Designator.Invalid)
return false;

Expand Down Expand Up @@ -6139,9 +6120,7 @@ static std::optional<DynamicType> ComputeDynamicType(EvalInfo &Info,
// meaningful dynamic type. (We consider objects of non-class type to have no
// dynamic type.)
if (!checkDynamicType(Info, E, This, AK,
(AK == AK_TypeId
? (E->getType()->isReferenceType() ? true : false)
: true)))
AK != AK_TypeId || This.AllowConstexprUnknown))
return std::nullopt;

if (This.Designator.Invalid)
Expand Down Expand Up @@ -9063,6 +9042,12 @@ bool LValueExprEvaluator::VisitVarDecl(const Expr *E, const VarDecl *VD) {
if (!evaluateVarDeclInit(Info, E, VD, Frame, Version, V))
return false;

if (!V) {
Result.set(VD);
Result.AllowConstexprUnknown = true;
return true;
}

return Success(*V, E);
}

Expand Down
6 changes: 3 additions & 3 deletions clang/test/SemaCXX/constant-expression-cxx11.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1466,7 +1466,7 @@ namespace InstantiateCaseStmt {

namespace ConvertedConstantExpr {
extern int &m;
extern int &n; // pre-cxx23-note 2{{declared here}}
extern int &n; // expected-note 2{{declared here}}

constexpr int k = 4;
int &m = const_cast<int&>(k);
Expand All @@ -1475,9 +1475,9 @@ namespace ConvertedConstantExpr {
// useless note and instead just point to the non-constant subexpression.
enum class E {
em = m,
en = n, // expected-error {{enumerator value is not a constant expression}} cxx11_20-note {{initializer of 'n' is unknown}}
en = n, // expected-error {{enumerator value is not a constant expression}} cxx11_20-note {{initializer of 'n' is unknown}} cxx23-note {{read of non-constexpr variable 'n'}}
eo = (m + // expected-error {{not a constant expression}}
n // cxx11_20-note {{initializer of 'n' is unknown}}
n // cxx11_20-note {{initializer of 'n' is unknown}} cxx23-note {{read of non-constexpr variable 'n'}}
),
eq = reinterpret_cast<long>((int*)0) // expected-error {{not a constant expression}} expected-note {{reinterpret_cast}}
};
Expand Down
91 changes: 85 additions & 6 deletions clang/test/SemaCXX/constant-expression-p2280r4.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,23 +35,26 @@ constexpr int how_many(Swim& swam) {
return (p + 1 - 1)->phelps();
}

void splash(Swim& swam) {
void splash(Swim& swam) { // nointerpreter-note {{declared here}}
static_assert(swam.phelps() == 28); // ok
static_assert((&swam)->phelps() == 28); // ok
Swim* pswam = &swam; // expected-note {{declared here}}
static_assert(pswam->phelps() == 28); // expected-error {{static assertion expression is not an integral constant expression}} \
// expected-note {{read of non-constexpr variable 'pswam' is not allowed in a constant expression}}
static_assert(how_many(swam) == 28); // ok
static_assert(Swim().lochte() == 12); // ok
static_assert(swam.lochte() == 12); // expected-error {{static assertion expression is not an integral constant expression}}
static_assert(swam.coughlin == 12); // expected-error {{static assertion expression is not an integral constant expression}}
static_assert(swam.lochte() == 12); // expected-error {{static assertion expression is not an integral constant expression}} \
// nointerpreter-note {{virtual function called on object 'swam' whose dynamic type is not constant}}
static_assert(swam.coughlin == 12); // expected-error {{static assertion expression is not an integral constant expression}} \
// nointerpreter-note {{read of variable 'swam' whose value is not known}}
}

extern Swim dc;
extern Swim& trident; // interpreter-note {{declared here}}

constexpr auto& sandeno = typeid(dc); // ok: can only be typeid(Swim)
constexpr auto& gallagher = typeid(trident); // expected-error {{constexpr variable 'gallagher' must be initialized by a constant expression}} \
// nointerpreter-note {{typeid applied to object 'trident' whose dynamic type is not constant}} \
// interpreter-note {{initializer of 'trident' is unknown}}

namespace explicitThis {
Expand Down Expand Up @@ -253,12 +256,88 @@ namespace uninit_reference_used {

namespace param_reference {
constexpr int arbitrary = -12345;
constexpr void f(const int &x = arbitrary) { // expected-note {{declared here}}
constexpr void f(const int &x = arbitrary) { // nointerpreter-note 3 {{declared here}} interpreter-note {{declared here}}
constexpr const int &v1 = x; // expected-error {{must be initialized by a constant expression}} \
// expected-note {{reference to 'x' is not a constant expression}}
constexpr const int &v2 = (x, arbitrary); // expected-warning {{left operand of comma operator has no effect}}
constexpr int v3 = x; // expected-error {{must be initialized by a constant expression}}
static_assert(x==arbitrary); // expected-error {{static assertion expression is not an integral constant expression}}
constexpr int v3 = x; // expected-error {{must be initialized by a constant expression}} \
// nointerpreter-note {{read of variable 'x' whose value is not known}}
static_assert(x==arbitrary); // expected-error {{static assertion expression is not an integral constant expression}} \
// nointerpreter-note {{read of variable 'x' whose value is not known}}
static_assert(&x - &x == 0);
}
}

namespace dropped_note {
extern int &x; // expected-note {{declared here}}
constexpr int f() { return x; } // nointerpreter-note {{read of non-constexpr variable 'x'}} \
// interpreter-note {{initializer of 'x' is unknown}}
constexpr int y = f(); // expected-error {{constexpr variable 'y' must be initialized by a constant expression}} expected-note {{in call to 'f()'}}
}

namespace dynamic {
struct A {virtual ~A();};
struct B : A {};
void f(A& a) {
constexpr B* b = dynamic_cast<B*>(&a); // expected-error {{must be initialized by a constant expression}} \
// nointerpreter-note {{dynamic_cast applied to object 'a' whose dynamic type is not constant}}
constexpr void* b2 = dynamic_cast<void*>(&a); // expected-error {{must be initialized by a constant expression}} \
// nointerpreter-note {{dynamic_cast applied to object 'a' whose dynamic type is not constant}}
}
}

namespace unsized_array {
void f(int (&a)[], int (&b)[], int (&c)[4]) {
constexpr int t1 = a - a;
constexpr int t2 = a - b; // expected-error {{constexpr variable 't2' must be initialized by a constant expression}} \
// nointerpreter-note {{arithmetic involving unrelated objects '&a[0]' and '&b[0]' has unspecified value}} \
// interpreter-note {{arithmetic involving unrelated objects 'a' and 'b' has unspecified value}}
constexpr int t3 = a - &c[2]; // expected-error {{constexpr variable 't3' must be initialized by a constant expression}} \
// nointerpreter-note {{arithmetic involving unrelated objects '&a[0]' and '&c[2]' has unspecified value}} \
// interpreter-note {{arithmetic involving unrelated objects 'a' and '*((char*)&c + 8)' has unspecified value}}
}
}

namespace casting {
struct A {};
struct B : A {};
struct C : A {};
extern A &a; // interpreter-note {{declared here}}
extern B &b; // expected-note {{declared here}} interpreter-note 2 {{declared here}}
constexpr B &t1 = (B&)a; // expected-error {{must be initialized by a constant expression}} \
// nointerpreter-note {{cannot cast object of dynamic type 'A' to type 'B'}} \
// interpreter-note {{initializer of 'a' is unknown}}
constexpr B &t2 = (B&)(A&)b; // expected-error {{must be initialized by a constant expression}} \
// nointerpreter-note {{initializer of 'b' is not a constant expression}} \
// interpreter-note {{initializer of 'b' is unknown}}
// FIXME: interpreter incorrectly rejects.
constexpr bool t3 = &b + 1 == &(B&)(A&)b; // interpreter-error {{must be initialized by a constant expression}} \
// interpreter-note {{initializer of 'b' is unknown}}
constexpr C &t4 = (C&)(A&)b; // expected-error {{must be initialized by a constant expression}} \
// nointerpreter-note {{cannot cast object of dynamic type 'B' to type 'C'}} \
// interpreter-note {{initializer of 'b' is unknown}}
}

namespace pointer_comparisons {
extern int &extern_n; // interpreter-note 2 {{declared here}}
extern int &extern_n2;
constexpr int f1(bool b, int& n) {
if (b) {
return &extern_n == &n;
}
return f1(true, n);
}
// FIXME: interpreter incorrectly rejects; both sides are the same constexpr-unknown value.
static_assert(f1(false, extern_n)); // interpreter-error {{static assertion expression is not an integral constant expression}} \
// interpreter-note {{initializer of 'extern_n' is unknown}}
// FIXME: We should diagnose this: we don't know if the references bind
// to the same object.
static_assert(&extern_n != &extern_n2); // interpreter-error {{static assertion expression is not an integral constant expression}} \
// interpreter-note {{initializer of 'extern_n' is unknown}}
void f2(const int &n) {
// FIXME: We should not diagnose this: the two objects provably have
// different addresses because the lifetime of "n" extends across
// the initialization.
constexpr int x = &x == &n; // nointerpreter-error {{must be initialized by a constant expression}}
}
}