Author: Eli Friedman Date: 2025-05-19T11:27:51-07:00 New Revision: 021443cd2a8127ab0432c5e48f9dbba30d9bc6ce
URL: https://github.com/llvm/llvm-project/commit/021443cd2a8127ab0432c5e48f9dbba30d9bc6ce DIFF: https://github.com/llvm/llvm-project/commit/021443cd2a8127ab0432c5e48f9dbba30d9bc6ce.diff LOG: [clang] fix constexpr-unknown handling of self-references. (#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 #131330 . Added: Modified: clang/docs/ReleaseNotes.rst clang/lib/AST/ExprConstant.cpp clang/test/SemaCXX/constant-expression-p2280r4.cpp Removed: ################################################################################ diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 472b70b46fcc0..ac9baf229b489 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -744,6 +744,8 @@ Bug Fixes to C++ Support - Fixed a function declaration mismatch that caused inconsistencies between concepts and variable template declarations. (#GH139476) - Clang no longer segfaults when there is a configuration mismatch between modules and their users (http://crbug.com/400353616). - Fix an incorrect deduction when calling an explicit object member function template through an overload set address. +- Fixed bug in constant evaluation that would allow using the value of a + reference in its own initializer in C++23 mode (#GH131330). Bug Fixes to AST Handling ^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index ca1fbdf7e652f..e9a269337404a 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -3492,11 +3492,33 @@ 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()) { + // 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 + // that is not usable in constant expressions, the reference is treated + // as binding to an unspecified object of the referenced type whose + // lifetime and that of all subobjects includes the entire constant + // evaluation and whose dynamic type is constexpr-unknown. + // + // Variables that are part of the current evaluation are not + // constexpr-unknown. + if (!AllowConstexprUnknown || IsLocalVariable) { + if (!Info.checkingPotentialConstantExpression()) + Info.FFDiag(E, diag::note_constexpr_use_uninit_reference); + return false; + } + Result = &Info.CurrentCall->createConstexprUnknownAPValues(VD, Base); + } + return true; + }; + // If this is a local variable, dig out its value. if (Frame) { Result = Frame->getTemporary(VD, Version); if (Result) - return true; + return CheckUninitReference(/*IsLocalVariable=*/true); if (!isa<ParmVarDecl>(VD)) { // Assume variables referenced within a lambda's call operator that were @@ -3521,7 +3543,7 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E, // in-flight value. if (Info.EvaluatingDecl == Base) { Result = Info.EvaluatingDeclValue; - return true; + return CheckUninitReference(/*IsLocalVariable=*/false); } // P2280R4 struck the restriction that variable of reference type lifetime @@ -3594,11 +3616,7 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E, // type so we can no longer assume we have an Init. // Used to be C++20 [expr.const]p5.12: // ... reference has a preceding initialization and either ... - if (Init && !VD->evaluateValue()) { - if (AllowConstexprUnknown) { - Result = &Info.CurrentCall->createConstexprUnknownAPValues(VD, Base); - return true; - } + if (Init && !VD->evaluateValue() && !AllowConstexprUnknown) { Info.FFDiag(E, diag::note_constexpr_var_init_non_constant, 1) << VD; NoteLValueLocation(Info, Base); return false; @@ -3636,18 +3654,14 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E, Result = VD->getEvaluatedValue(); - // 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 that - // is not usable in constant expressions, the reference is treated as binding - // to an unspecified object of the referenced type whose lifetime and that of - // all subobjects includes the entire constant evaluation and whose dynamic - // type is constexpr-unknown. - if (AllowConstexprUnknown) { - if (!Result) + if (!Result) { + if (AllowConstexprUnknown) Result = &Info.CurrentCall->createConstexprUnknownAPValues(VD, Base); + else + return false; } - return true; + + return CheckUninitReference(/*IsLocalVariable=*/false); } /// Get the base index of the given base class within an APValue representing @@ -8953,12 +8967,7 @@ bool LValueExprEvaluator::VisitDeclRefExpr(const DeclRefExpr *E) { return Error(E); } - bool LValueExprEvaluator::VisitVarDecl(const Expr *E, const VarDecl *VD) { - // C++23 [expr.const]p8 If we have a reference type allow unknown references - // and pointers. - bool AllowConstexprUnknown = - Info.getLangOpts().CPlusPlus23 && VD->getType()->isReferenceType(); // If we are within a lambda's call operator, check whether the 'VD' referred // to within 'E' actually represents a lambda-capture that maps to a // 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) { APValue *V; if (!evaluateVarDeclInit(Info, E, VD, Frame, Version, V)) return false; - if (!V->hasValue()) { - // FIXME: Is it possible for V to be indeterminate here? If so, we should - // adjust the diagnostic to say that. - // C++23 [expr.const]p8 If we have a variable that is unknown reference - // or pointer it may not have a value but still be usable later on so do not - // diagnose. - if (!Info.checkingPotentialConstantExpression() && !AllowConstexprUnknown) - Info.FFDiag(E, diag::note_constexpr_use_uninit_reference); - - // C++23 [expr.const]p8 If we have a variable that is unknown reference or - // pointer try to recover it from the frame and set the result accordingly. - if (VD->getType()->isReferenceType() && AllowConstexprUnknown) { - if (Frame) { - Result.set({VD, Frame->Index, Version}); - return true; - } - return Success(VD); - } - return false; - } return Success(*V, E); } diff --git a/clang/test/SemaCXX/constant-expression-p2280r4.cpp b/clang/test/SemaCXX/constant-expression-p2280r4.cpp index 88e0a8f153b10..c14250a268f6c 100644 --- a/clang/test/SemaCXX/constant-expression-p2280r4.cpp +++ b/clang/test/SemaCXX/constant-expression-p2280r4.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -std=c++23 -verify %s +// RUN: %clang_cc1 -std=c++23 -verify=expected,nointerpreter %s // RUN: %clang_cc1 -std=c++23 -verify=expected,interpreter %s -fexperimental-new-constant-interpreter using size_t = decltype(sizeof(0)); @@ -199,3 +199,54 @@ int f() { return !get_value<Dummy>(); // contextually convert the function call result to bool } } + +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); + 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}} + 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}} + return x; + } + constexpr int gg2 = g2(); // expected-error {{must be initialized by a constant expression}} \ + // expected-note {{in call to 'g2()'}} + 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}} + return x; + } + constexpr int &gg3 = g3(); // nointerpreter-error {{must be initialized by a constant expression}} \ + // nointerpreter-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}} + *(uintptr_t*)x = 10; + return 3; + } + constexpr uintptr_t gg4 = g4(); // expected-error {{must be initialized by a constant expression}} \ + // 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}} + return 3; + } + constexpr uintptr_t gg5 = g5(); // expected-error {{must be initialized by a constant expression}} \ + // expected-note {{in call to 'g5()'}} + +} _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits