Skip to content

Commit 021443c

Browse files
[clang] fix constexpr-unknown handling of self-references. (llvm#132990)
Usually, in constant evaluation, references which are local to the evaluation have to be initialized before they're accessed. However, there's one funny special case: the initializer of a reference can refer to itself. This generally ends up being undefined behavior if it's used in an evaluated context, but it isn't otherwise forbidden. In constant evaluation, this splits into two cases: global variables, and local variables in constexpr functions. This patch handles both of those cases. (Local variables tends to trip other errors in most cases, but if you try hard enough, you can get an accepts-invalid.) Fixes llvm#131330 .
1 parent 99720bb commit 021443c

File tree

3 files changed

+85
-43
lines changed

3 files changed

+85
-43
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -744,6 +744,8 @@ Bug Fixes to C++ Support
744744
- Fixed a function declaration mismatch that caused inconsistencies between concepts and variable template declarations. (#GH139476)
745745
- Clang no longer segfaults when there is a configuration mismatch between modules and their users (http://crbug.com/400353616).
746746
- Fix an incorrect deduction when calling an explicit object member function template through an overload set address.
747+
- Fixed bug in constant evaluation that would allow using the value of a
748+
reference in its own initializer in C++23 mode (#GH131330).
747749

748750
Bug Fixes to AST Handling
749751
^^^^^^^^^^^^^^^^^^^^^^^^^

clang/lib/AST/ExprConstant.cpp

Lines changed: 31 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3492,11 +3492,33 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E,
34923492

34933493
APValue::LValueBase Base(VD, Frame ? Frame->Index : 0, Version);
34943494

3495+
auto CheckUninitReference = [&](bool IsLocalVariable) {
3496+
if (!Result->hasValue() && VD->getType()->isReferenceType()) {
3497+
// C++23 [expr.const]p8
3498+
// ... For such an object that is not usable in constant expressions, the
3499+
// dynamic type of the object is constexpr-unknown. For such a reference
3500+
// that is not usable in constant expressions, the reference is treated
3501+
// as binding to an unspecified object of the referenced type whose
3502+
// lifetime and that of all subobjects includes the entire constant
3503+
// evaluation and whose dynamic type is constexpr-unknown.
3504+
//
3505+
// Variables that are part of the current evaluation are not
3506+
// constexpr-unknown.
3507+
if (!AllowConstexprUnknown || IsLocalVariable) {
3508+
if (!Info.checkingPotentialConstantExpression())
3509+
Info.FFDiag(E, diag::note_constexpr_use_uninit_reference);
3510+
return false;
3511+
}
3512+
Result = &Info.CurrentCall->createConstexprUnknownAPValues(VD, Base);
3513+
}
3514+
return true;
3515+
};
3516+
34953517
// If this is a local variable, dig out its value.
34963518
if (Frame) {
34973519
Result = Frame->getTemporary(VD, Version);
34983520
if (Result)
3499-
return true;
3521+
return CheckUninitReference(/*IsLocalVariable=*/true);
35003522

35013523
if (!isa<ParmVarDecl>(VD)) {
35023524
// Assume variables referenced within a lambda's call operator that were
@@ -3521,7 +3543,7 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E,
35213543
// in-flight value.
35223544
if (Info.EvaluatingDecl == Base) {
35233545
Result = Info.EvaluatingDeclValue;
3524-
return true;
3546+
return CheckUninitReference(/*IsLocalVariable=*/false);
35253547
}
35263548

35273549
// P2280R4 struck the restriction that variable of reference type lifetime
@@ -3594,11 +3616,7 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E,
35943616
// type so we can no longer assume we have an Init.
35953617
// Used to be C++20 [expr.const]p5.12:
35963618
// ... reference has a preceding initialization and either ...
3597-
if (Init && !VD->evaluateValue()) {
3598-
if (AllowConstexprUnknown) {
3599-
Result = &Info.CurrentCall->createConstexprUnknownAPValues(VD, Base);
3600-
return true;
3601-
}
3619+
if (Init && !VD->evaluateValue() && !AllowConstexprUnknown) {
36023620
Info.FFDiag(E, diag::note_constexpr_var_init_non_constant, 1) << VD;
36033621
NoteLValueLocation(Info, Base);
36043622
return false;
@@ -3636,18 +3654,14 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E,
36363654

36373655
Result = VD->getEvaluatedValue();
36383656

3639-
// C++23 [expr.const]p8
3640-
// ... For such an object that is not usable in constant expressions, the
3641-
// dynamic type of the object is constexpr-unknown. For such a reference that
3642-
// is not usable in constant expressions, the reference is treated as binding
3643-
// to an unspecified object of the referenced type whose lifetime and that of
3644-
// all subobjects includes the entire constant evaluation and whose dynamic
3645-
// type is constexpr-unknown.
3646-
if (AllowConstexprUnknown) {
3647-
if (!Result)
3657+
if (!Result) {
3658+
if (AllowConstexprUnknown)
36483659
Result = &Info.CurrentCall->createConstexprUnknownAPValues(VD, Base);
3660+
else
3661+
return false;
36493662
}
3650-
return true;
3663+
3664+
return CheckUninitReference(/*IsLocalVariable=*/false);
36513665
}
36523666

36533667
/// Get the base index of the given base class within an APValue representing
@@ -8953,12 +8967,7 @@ bool LValueExprEvaluator::VisitDeclRefExpr(const DeclRefExpr *E) {
89538967
return Error(E);
89548968
}
89558969

8956-
89578970
bool LValueExprEvaluator::VisitVarDecl(const Expr *E, const VarDecl *VD) {
8958-
// C++23 [expr.const]p8 If we have a reference type allow unknown references
8959-
// and pointers.
8960-
bool AllowConstexprUnknown =
8961-
Info.getLangOpts().CPlusPlus23 && VD->getType()->isReferenceType();
89628971
// If we are within a lambda's call operator, check whether the 'VD' referred
89638972
// to within 'E' actually represents a lambda-capture that maps to a
89648973
// data-member/field within the closure object, and if so, evaluate to the
@@ -9025,26 +9034,6 @@ bool LValueExprEvaluator::VisitVarDecl(const Expr *E, const VarDecl *VD) {
90259034
APValue *V;
90269035
if (!evaluateVarDeclInit(Info, E, VD, Frame, Version, V))
90279036
return false;
9028-
if (!V->hasValue()) {
9029-
// FIXME: Is it possible for V to be indeterminate here? If so, we should
9030-
// adjust the diagnostic to say that.
9031-
// C++23 [expr.const]p8 If we have a variable that is unknown reference
9032-
// or pointer it may not have a value but still be usable later on so do not
9033-
// diagnose.
9034-
if (!Info.checkingPotentialConstantExpression() && !AllowConstexprUnknown)
9035-
Info.FFDiag(E, diag::note_constexpr_use_uninit_reference);
9036-
9037-
// C++23 [expr.const]p8 If we have a variable that is unknown reference or
9038-
// pointer try to recover it from the frame and set the result accordingly.
9039-
if (VD->getType()->isReferenceType() && AllowConstexprUnknown) {
9040-
if (Frame) {
9041-
Result.set({VD, Frame->Index, Version});
9042-
return true;
9043-
}
9044-
return Success(VD);
9045-
}
9046-
return false;
9047-
}
90489037

90499038
return Success(*V, E);
90509039
}

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

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %clang_cc1 -std=c++23 -verify %s
1+
// RUN: %clang_cc1 -std=c++23 -verify=expected,nointerpreter %s
22
// RUN: %clang_cc1 -std=c++23 -verify=expected,interpreter %s -fexperimental-new-constant-interpreter
33

44
using size_t = decltype(sizeof(0));
@@ -199,3 +199,54 @@ int f() {
199199
return !get_value<Dummy>(); // contextually convert the function call result to bool
200200
}
201201
}
202+
203+
namespace uninit_reference_used {
204+
int y;
205+
constexpr int &r = r; // expected-error {{must be initialized by a constant expression}} \
206+
// expected-note {{initializer of 'r' is not a constant expression}} \
207+
// expected-note {{declared here}}
208+
constexpr int &rr = (rr, y);
209+
constexpr int &g() {
210+
int &x = x; // expected-warning {{reference 'x' is not yet bound to a value when used within its own initialization}} \
211+
// nointerpreter-note {{use of reference outside its lifetime is not allowed in a constant expression}} \
212+
// interpreter-note {{read of uninitialized object is not allowed in a constant expression}}
213+
return x;
214+
}
215+
constexpr int &gg = g(); // expected-error {{must be initialized by a constant expression}} \
216+
// expected-note {{in call to 'g()'}}
217+
constexpr int g2() {
218+
int &x = x; // expected-warning {{reference 'x' is not yet bound to a value when used within its own initialization}} \
219+
// nointerpreter-note {{use of reference outside its lifetime is not allowed in a constant expression}} \
220+
// interpreter-note {{read of uninitialized object is not allowed in a constant expression}}
221+
return x;
222+
}
223+
constexpr int gg2 = g2(); // expected-error {{must be initialized by a constant expression}} \
224+
// expected-note {{in call to 'g2()'}}
225+
constexpr int &g3() {
226+
int &x = (x,y); // expected-warning{{left operand of comma operator has no effect}} \
227+
// expected-warning {{reference 'x' is not yet bound to a value when used within its own initialization}} \
228+
// nointerpreter-note {{use of reference outside its lifetime is not allowed in a constant expression}}
229+
return x;
230+
}
231+
constexpr int &gg3 = g3(); // nointerpreter-error {{must be initialized by a constant expression}} \
232+
// nointerpreter-note {{in call to 'g3()'}}
233+
typedef decltype(sizeof(1)) uintptr_t;
234+
constexpr uintptr_t g4() {
235+
uintptr_t * &x = x; // expected-warning {{reference 'x' is not yet bound to a value when used within its own initialization}} \
236+
// nointerpreter-note {{use of reference outside its lifetime is not allowed in a constant expression}} \
237+
// interpreter-note {{read of uninitialized object is not allowed in a constant expression}}
238+
*(uintptr_t*)x = 10;
239+
return 3;
240+
}
241+
constexpr uintptr_t gg4 = g4(); // expected-error {{must be initialized by a constant expression}} \
242+
// expected-note {{in call to 'g4()'}}
243+
constexpr int g5() {
244+
int &x = x; // expected-warning {{reference 'x' is not yet bound to a value when used within its own initialization}} \
245+
// nointerpreter-note {{use of reference outside its lifetime is not allowed in a constant expression}} \
246+
// interpreter-note {{read of uninitialized object is not allowed in a constant expression}}
247+
return 3;
248+
}
249+
constexpr uintptr_t gg5 = g5(); // expected-error {{must be initialized by a constant expression}} \
250+
// expected-note {{in call to 'g5()'}}
251+
252+
}

0 commit comments

Comments
 (0)