llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clang Author: Yanzuo Liu (zwuis) <details> <summary>Changes</summary> Evaluating uninitialized reference is undefined behaviour. So rejecting it in constant evaluation. Note that discarded-value expression IS evaluated. There are diagnostic regressions for bytecode interpreter because [P2280R4](https://wg21.link/p2280r4) is not implemented. Fixes #<!-- -->157082 AI usage: Changes in Compiler.cpp is generated by AI and modified by me afterwards. Assisted-by: GPT-5.2 --- Full diff: https://github.com/llvm/llvm-project/pull/180923.diff 7 Files Affected: - (modified) clang/docs/ReleaseNotes.rst (+1) - (modified) clang/lib/AST/ByteCode/Compiler.cpp (+14-2) - (modified) clang/lib/AST/ByteCode/Interp.cpp (+19-9) - (modified) clang/lib/AST/ExprConstant.cpp (+8-5) - (modified) clang/test/AST/ByteCode/cxx23.cpp (+1-4) - (modified) clang/test/SemaCXX/constant-expression-cxx11.cpp (+4-2) - (modified) clang/test/SemaCXX/constant-expression-p2280r4.cpp (+21-20) ``````````diff diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 59fdbc80e8bed..047f2c3fa3d97 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -263,6 +263,7 @@ Bug Fixes to C++ Support - Fixed a crash when instantiating ``requires`` expressions involving substitution failures in C++ concepts. (#GH176402) - Fixed a crash when a default argument is passed to an explicit object parameter. (#GH176639) - Fixed a crash when diagnosing an invalid static member function with an explicit object parameter (#GH177741) +- Using uninitialized reference in constant evaluation is now correctly rejected. Bug Fixes to AST Handling ^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp index 7aa7a8d75c8e1..f0162a87d6729 100644 --- a/clang/lib/AST/ByteCode/Compiler.cpp +++ b/clang/lib/AST/ByteCode/Compiler.cpp @@ -7150,8 +7150,20 @@ bool Compiler<Emitter>::VisitVectorUnaryOperator(const UnaryOperator *E) { template <class Emitter> bool Compiler<Emitter>::visitDeclRef(const ValueDecl *D, const Expr *E) { - if (DiscardResult) - return true; + if (DiscardResult) { + if (!(isa<VarDecl>(D) && D->getType()->isReferenceType())) + return true; + + // C++26 [dcl.ref]p6 + // ... The behavior of an evaluation of a reference that does not happen + // after the initialization of the reference is undefined. + // + // Visit the variable to check if it is initialized. + llvm::SaveAndRestore _(DiscardResult, /*NewValue=*/false); + if (!this->visitDeclRef(D, E)) + return false; + return this->emitPopPtr(E); + } if (const auto *ECD = dyn_cast<EnumConstantDecl>(D)) return this->emitConst(ECD->getInitVal(), E); diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp index d6939c023f43b..c2bdccedad3a1 100644 --- a/clang/lib/AST/ByteCode/Interp.cpp +++ b/clang/lib/AST/ByteCode/Interp.cpp @@ -703,17 +703,19 @@ bool DiagnoseUninitialized(InterpState &S, CodePtr OpPC, bool Extern, if (Extern && S.checkingPotentialConstantExpression()) return false; - if (const auto *VD = Desc->asVarDecl(); - VD && (VD->isConstexpr() || VD->hasGlobalStorage())) { + const auto *VD = Desc->asVarDecl(); - if (VD == S.EvaluatingDecl && - !(S.getLangOpts().CPlusPlus23 && VD->getType()->isReferenceType())) { + if (VD && (VD->isConstexpr() || VD->hasGlobalStorage())) { + + if (VD == S.EvaluatingDecl) { if (!S.getLangOpts().CPlusPlus14 && !VD->getType().isConstant(S.getASTContext())) { // Diagnose as non-const read. diagnoseNonConstVariable(S, OpPC, VD); + } else if (const SourceInfo &Loc = S.Current->getSource(OpPC); + VD->getType()->isReferenceType()) { + S.FFDiag(Loc, diag::note_constexpr_use_uninit_reference); } else { - const SourceInfo &Loc = S.Current->getSource(OpPC); // Diagnose as "read of object outside its lifetime". S.FFDiag(Loc, diag::note_constexpr_access_uninit) << AK << /*IsIndeterminate=*/false; @@ -723,8 +725,12 @@ bool DiagnoseUninitialized(InterpState &S, CodePtr OpPC, bool Extern, if (VD->getAnyInitializer()) { const SourceInfo &Loc = S.Current->getSource(OpPC); - S.FFDiag(Loc, diag::note_constexpr_var_init_non_constant, 1) << VD; - S.Note(VD->getLocation(), diag::note_declared_at); + if (VD->getType()->isReferenceType()) { + S.FFDiag(Loc, diag::note_constexpr_use_uninit_reference); + } else { + S.FFDiag(Loc, diag::note_constexpr_var_init_non_constant, 1) << VD; + S.Note(VD->getLocation(), diag::note_declared_at); + } } else { diagnoseMissingInitializer(S, OpPC, VD); } @@ -732,8 +738,12 @@ bool DiagnoseUninitialized(InterpState &S, CodePtr OpPC, bool Extern, } if (!S.checkingPotentialConstantExpression()) { - S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_access_uninit) - << AK << /*uninitialized=*/true << S.Current->getRange(OpPC); + const SourceInfo &Loc = S.Current->getSource(OpPC); + if (VD && VD->getType()->isReferenceType()) + S.FFDiag(Loc, diag::note_constexpr_use_uninit_reference); + else + S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_access_uninit) + << AK << /*uninitialized=*/true << S.Current->getRange(OpPC); } return false; } diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 44629b8bae194..ef1d106bb0a4c 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -3335,7 +3335,10 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E, APValue::LValueBase Base(VD, Frame ? Frame->Index : 0, Version); - auto CheckUninitReference = [&](bool IsLocalVariable) { + // C++26 [dcl.ref]p6 + // ... The behavior of an evaluation of a reference that does not happen after + // the initialization of the reference is undefined. + auto CheckUnknownReference = [&](bool UnknownRefIsUninit) { if (!Result || (!Result->hasValue() && VD->getType()->isReferenceType())) { // C++23 [expr.const]p8 // ... For such an object that is not usable in constant expressions, the @@ -3347,7 +3350,7 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E, // // Variables that are part of the current evaluation are not // constexpr-unknown. - if (!AllowConstexprUnknown || IsLocalVariable) { + if (!AllowConstexprUnknown || UnknownRefIsUninit) { if (!Info.checkingPotentialConstantExpression()) Info.FFDiag(E, diag::note_constexpr_use_uninit_reference); return false; @@ -3361,7 +3364,7 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E, if (Frame) { Result = Frame->getTemporary(VD, Version); if (Result) - return CheckUninitReference(/*IsLocalVariable=*/true); + return CheckUnknownReference(/*UnknownRefIsUninit=*/true); if (!isa<ParmVarDecl>(VD)) { // Assume variables referenced within a lambda's call operator that were @@ -3386,7 +3389,7 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E, // in-flight value. if (Info.EvaluatingDecl == Base) { Result = Info.EvaluatingDeclValue; - return CheckUninitReference(/*IsLocalVariable=*/false); + return CheckUnknownReference(/*UnknownRefIsUninit=*/true); } // P2280R4 struck the restriction that variable of reference type lifetime @@ -3505,7 +3508,7 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E, if (!Result && !AllowConstexprUnknown) return false; - return CheckUninitReference(/*IsLocalVariable=*/false); + return CheckUnknownReference(/*UnknownRefIsUninit=*/false); } /// Get the base index of the given base class within an APValue representing diff --git a/clang/test/AST/ByteCode/cxx23.cpp b/clang/test/AST/ByteCode/cxx23.cpp index e3be7887c357e..6a0a92aaa8189 100644 --- a/clang/test/AST/ByteCode/cxx23.cpp +++ b/clang/test/AST/ByteCode/cxx23.cpp @@ -90,11 +90,8 @@ namespace ThreadLocalStore { void store() { a = 42; } } -#if __cplusplus >= 202302L constexpr int &b = b; // all-error {{must be initialized by a constant expression}} \ - // all-note {{initializer of 'b' is not a constant expression}} \ - // all-note {{declared here}} -#endif + // all-note {{use of reference outside its lifetime is not allowed in a constant expression}} namespace StaticLambdas { constexpr auto static_capture_constexpr() { diff --git a/clang/test/SemaCXX/constant-expression-cxx11.cpp b/clang/test/SemaCXX/constant-expression-cxx11.cpp index 91c4ff1cb520d..d992ec5b24ae4 100644 --- a/clang/test/SemaCXX/constant-expression-cxx11.cpp +++ b/clang/test/SemaCXX/constant-expression-cxx11.cpp @@ -2012,8 +2012,7 @@ namespace ConstexprConstructorRecovery { namespace Lifetime { void f() { - constexpr int &n = n; // expected-error {{constant expression}} cxx23-note {{reference to 'n' is not a constant expression}} cxx23-note {{address of non-static constexpr variable 'n' may differ}} expected-warning {{not yet bound to a value}} - // cxx11_20-note@-1 {{use of reference outside its lifetime is not allowed in a constant expression}} + constexpr int &n = n; // expected-error {{constant expression}} expected-note {{use of reference outside its lifetime is not allowed in a constant expression}} expected-warning {{not yet bound to a value}} constexpr int m = m; // expected-error {{constant expression}} expected-note {{read of object outside its lifetime}} } @@ -2074,6 +2073,9 @@ namespace Lifetime { void rf() { constexpr R r; // expected-error {{constant expression}} expected-note {{in call}} } + + constexpr int k5 = 0; + constexpr const int &ref = (ref, k5); // expected-error {{constant expression}} expected-note {{use of reference outside its lifetime is not allowed in a constant expression}} } namespace Bitfields { diff --git a/clang/test/SemaCXX/constant-expression-p2280r4.cpp b/clang/test/SemaCXX/constant-expression-p2280r4.cpp index 7c8e3e975f091..c6097d2ef897a 100644 --- a/clang/test/SemaCXX/constant-expression-p2280r4.cpp +++ b/clang/test/SemaCXX/constant-expression-p2280r4.cpp @@ -161,25 +161,30 @@ int g() { namespace GH128409 { int &ff(); - int &x = ff(); // expected-note {{declared here}} + int &x = ff(); // nointerpreter-note {{declared here}} constinit int &z = x; // expected-error {{variable does not have a constant initializer}} \ // expected-note {{required by 'constinit' specifier here}} \ - // expected-note {{initializer of 'x' is not a constant expression}} + // nointerpreter-note {{initializer of 'x' is not a constant expression}} \ + // interpreter-note {{use of reference outside its lifetime is not allowed in a constant expression}} } namespace GH129845 { int &ff(); - int &x = ff(); // expected-note {{declared here}} + int &x = ff(); // nointerpreter-note {{declared here}} struct A { int& x; }; constexpr A g = {x}; // expected-error {{constexpr variable 'g' must be initialized by a constant expression}} \ - // expected-note {{initializer of 'x' is not a constant expression}} + // nointerpreter-note {{initializer of 'x' is not a constant expression}} \ + // interpreter-note {{use of reference outside its lifetime is not allowed in a constant expression}} const A* gg = &g; } namespace extern_reference_used_as_unknown { - extern int &x; + extern int &x; // interpreter-note {{declared here}} int y; - constinit int& g = (x,y); // expected-warning {{left operand of comma operator has no effect}} + constinit int& g = (x,y); // expected-warning {{left operand of comma operator has no effect}} \ + // interpreter-error {{variable does not have a constant initializer}} \ + // interpreter-note {{required by 'constinit' specifier here}} \ + // interpreter-note {{initializer of 'x' is unknown}} } namespace GH139452 { @@ -206,21 +211,19 @@ int f() { namespace uninit_reference_used { int y; constexpr int &r = r; // expected-error {{must be initialized by a constant expression}} \ - // expected-note {{initializer of 'r' is not a constant expression}} \ - // expected-note {{declared here}} - constexpr int &rr = (rr, y); + // expected-note {{use of reference outside its lifetime is not allowed in a constant expression}} + constexpr int &rr = (rr, y); // expected-error {{constexpr variable 'rr' must be initialized by a constant expression}} \ + // expected-note {{use of reference outside its lifetime is not allowed in a constant expression}} constexpr int &g() { int &x = x; // expected-warning {{reference 'x' is not yet bound to a value when used within its own initialization}} \ - // nointerpreter-note {{use of reference outside its lifetime is not allowed in a constant expression}} \ - // interpreter-note {{read of uninitialized object is not allowed in a constant expression}} + // expected-note {{use of reference outside its lifetime is not allowed in a constant expression}} \ return x; } constexpr int &gg = g(); // expected-error {{must be initialized by a constant expression}} \ // expected-note {{in call to 'g()'}} constexpr int g2() { int &x = x; // expected-warning {{reference 'x' is not yet bound to a value when used within its own initialization}} \ - // nointerpreter-note {{use of reference outside its lifetime is not allowed in a constant expression}} \ - // interpreter-note {{read of uninitialized object is not allowed in a constant expression}} + // expected-note {{use of reference outside its lifetime is not allowed in a constant expression}} \ return x; } constexpr int gg2 = g2(); // expected-error {{must be initialized by a constant expression}} \ @@ -228,16 +231,15 @@ namespace uninit_reference_used { constexpr int &g3() { int &x = (x,y); // expected-warning{{left operand of comma operator has no effect}} \ // expected-warning {{reference 'x' is not yet bound to a value when used within its own initialization}} \ - // nointerpreter-note {{use of reference outside its lifetime is not allowed in a constant expression}} + // expected-note {{use of reference outside its lifetime is not allowed in a constant expression}} return x; } - constexpr int &gg3 = g3(); // nointerpreter-error {{must be initialized by a constant expression}} \ - // nointerpreter-note {{in call to 'g3()'}} + constexpr int &gg3 = g3(); // expected-error {{must be initialized by a constant expression}} \ + // expected-note {{in call to 'g3()'}} typedef decltype(sizeof(1)) uintptr_t; constexpr uintptr_t g4() { uintptr_t * &x = x; // expected-warning {{reference 'x' is not yet bound to a value when used within its own initialization}} \ - // nointerpreter-note {{use of reference outside its lifetime is not allowed in a constant expression}} \ - // interpreter-note {{read of uninitialized object is not allowed in a constant expression}} + // expected-note {{use of reference outside its lifetime is not allowed in a constant expression}} *(uintptr_t*)x = 10; return 3; } @@ -245,8 +247,7 @@ namespace uninit_reference_used { // expected-note {{in call to 'g4()'}} constexpr int g5() { int &x = x; // expected-warning {{reference 'x' is not yet bound to a value when used within its own initialization}} \ - // nointerpreter-note {{use of reference outside its lifetime is not allowed in a constant expression}} \ - // interpreter-note {{read of uninitialized object is not allowed in a constant expression}} + // expected-note {{use of reference outside its lifetime is not allowed in a constant expression}} \ return 3; } constexpr uintptr_t gg5 = g5(); // expected-error {{must be initialized by a constant expression}} \ `````````` </details> https://github.com/llvm/llvm-project/pull/180923 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
