From a3f85983a8ab50de52c1e4825f490120e8ecdac2 Mon Sep 17 00:00:00 2001 From: Corentin Jabot Date: Wed, 2 Jul 2025 15:55:41 +0200 Subject: [PATCH 1/7] Diagnose all dereferences of null pointers --- clang/docs/ReleaseNotes.rst | 3 +- .../include/clang/Basic/DiagnosticASTKinds.td | 9 +- clang/lib/AST/ByteCode/State.h | 1 + clang/lib/AST/ExprConstant.cpp | 87 +++++++++++++++---- clang/test/AST/ByteCode/complex.cpp | 5 +- clang/test/AST/ByteCode/const-eval.c | 2 + clang/test/AST/ByteCode/cxx11.cpp | 4 +- clang/test/AST/ByteCode/records.cpp | 10 ++- clang/test/CXX/drs/cwg14xx.cpp | 2 + clang/test/CXX/expr/expr.const/p2-0x.cpp | 8 +- clang/test/Sema/const-eval.c | 5 +- .../SemaCXX/constant-expression-cxx11.cpp | 4 +- .../SemaCXX/constant-expression-cxx14.cpp | 56 +++++++++++- .../SemaCXX/constant-expression-cxx2a.cpp | 2 +- .../SemaCXX/constexpr-backtrace-limit.cpp | 4 +- .../range.zip/iterator/increment.pass.cpp | 4 +- 16 files changed, 165 insertions(+), 41 deletions(-) diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 1eb3e369a302e..2f1705ba7db06 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -674,7 +674,7 @@ Improvements to Clang's diagnostics #GH142457, #GH139913, #GH138850, #GH137867, #GH137860, #GH107840, #GH93308, #GH69470, #GH59391, #GH58172, #GH46215, #GH45915, #GH45891, #GH44490, #GH36703, #GH32903, #GH23312, #GH69874. - + - Clang no longer emits a spurious -Wdangling-gsl warning in C++23 when iterating over an element of a temporary container in a range-based for loop.(#GH109793, #GH145164) @@ -970,6 +970,7 @@ Bug Fixes to C++ Support - Fixed a crash involving list-initialization of an empty class with a non-empty initializer list. (#GH147949) - Fixed constant evaluation of equality comparisons of constexpr-unknown references. (#GH147663) +- Diagnose binding a reference to ``*nullptr`` during constant evaluation. (#GH48665) Bug Fixes to AST Handling ^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td index a67b9995d3b54..e02075dba30b9 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -174,10 +174,11 @@ def note_constexpr_heap_alloc_limit_exceeded : Note< def note_constexpr_this : Note< "%select{|implicit }0use of 'this' pointer is only allowed within the " "evaluation of a call to a 'constexpr' member function">; -def access_kind : TextSubstitution< - "%select{read of|read of|assignment to|increment of|decrement of|" - "member call on|dynamic_cast of|typeid applied to|construction of|" - "destruction of|read of}0">; +def access_kind + : TextSubstitution< + "%select{read of|read of|assignment to|increment of|decrement of|" + "member call on|dynamic_cast of|typeid applied to|construction of|" + "destruction of|read of|read of}0">; def access_kind_subobject : TextSubstitution< "%select{read of|read of|assignment to|increment of|decrement of|" "member call on|dynamic_cast of|typeid applied to|" diff --git a/clang/lib/AST/ByteCode/State.h b/clang/lib/AST/ByteCode/State.h index 9a81fa6b7d220..6fc33222ac956 100644 --- a/clang/lib/AST/ByteCode/State.h +++ b/clang/lib/AST/ByteCode/State.h @@ -35,6 +35,7 @@ enum AccessKinds { AK_Construct, AK_Destroy, AK_IsWithinLifetime, + AK_Dereference }; /// The order of this enum is important for diagnostics. diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 1b33b6706e204..7bc772e1ef9bb 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -1529,7 +1529,7 @@ CallStackFrame::~CallStackFrame() { static bool isRead(AccessKinds AK) { return AK == AK_Read || AK == AK_ReadObjectRepresentation || - AK == AK_IsWithinLifetime; + AK == AK_IsWithinLifetime || AK == AK_Dereference; } static bool isModification(AccessKinds AK) { @@ -1540,6 +1540,7 @@ static bool isModification(AccessKinds AK) { case AK_DynamicCast: case AK_TypeId: case AK_IsWithinLifetime: + case AK_Dereference: return false; case AK_Assign: case AK_Increment: @@ -1558,7 +1559,7 @@ static bool isAnyAccess(AccessKinds AK) { /// Is this an access per the C++ definition? static bool isFormalAccess(AccessKinds AK) { return isAnyAccess(AK) && AK != AK_Construct && AK != AK_Destroy && - AK != AK_IsWithinLifetime; + AK != AK_IsWithinLifetime && AK != AK_Dereference; } /// Is this kind of axcess valid on an indeterminate object value? @@ -1571,6 +1572,7 @@ static bool isValidIndeterminateAccess(AccessKinds AK) { return false; case AK_IsWithinLifetime: + case AK_Dereference: case AK_ReadObjectRepresentation: case AK_Assign: case AK_Construct: @@ -4407,8 +4409,9 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, ConstexprVar = VD->isConstexpr(); // Unless we're looking at a local variable or argument in a constexpr call, - // the variable we're reading must be const. - if (!Frame) { + // the variable we're reading must be const (unless we are binding to a + // reference). + if (AK != clang::AK_Dereference && !Frame) { if (IsAccess && isa(VD)) { // Access of a parameter that's not associated with a frame isn't going // to work out, but we can leave it to evaluateVarDeclInit to provide a @@ -4472,7 +4475,11 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, } } - if (!evaluateVarDeclInit(Info, E, VD, Frame, LVal.getLValueVersion(), BaseVal)) + // When binding to a reference, the variable does not need to be constexpr + // or have constant initalization. + if (AK != clang::AK_Dereference && + !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: @@ -4491,7 +4498,10 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, } return CompleteObject(LVal.Base, &(*Alloc)->Value, LVal.Base.getDynamicAllocType()); - } else { + } + // When binding to a reference, the variable does not need to be + // within its lifetime. + else if (AK != clang::AK_Dereference) { const Expr *Base = LVal.Base.dyn_cast(); if (!Frame) { @@ -4572,7 +4582,7 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, NoteLValueLocation(Info, LVal.Base); return CompleteObject(); } - } else { + } else if (AK != clang::AK_Dereference) { BaseVal = Frame->getTemporary(Base, LVal.Base.getVersion()); assert(BaseVal && "missing value for temporary"); } @@ -5200,6 +5210,29 @@ enum EvalStmtResult { ESR_CaseNotFound }; } +/// Evaluates the initializer of a reference. +static bool EvaluateInitForDeclOfReferenceType(EvalInfo &Info, + const ValueDecl *D, + const Expr *Init, LValue &Result, + APValue &Val) { + assert(Init->isGLValue() && D->getType()->isReferenceType()); + // A reference is an lvalue + if (!EvaluateLValue(Init, Result, Info)) + return false; + // [C++26][decl.ref] + // The object designated by such a glvalue can be outside its lifetime + // Because a null pointer value or a pointer past the end of an object + // does not point to an object, a reference in a well-defined program cannot + // refer to such things; + if (!Result.Designator.Invalid && Result.Designator.isOnePastTheEnd()) { + Info.FFDiag(Init, diag::note_constexpr_access_past_end) << AK_Dereference; + return false; + } + + // save the result + Result.moveInto(Val); + return true; +} static bool EvaluateVarDecl(EvalInfo &Info, const VarDecl *VD) { if (VD->isInvalidDecl()) @@ -5221,7 +5254,10 @@ static bool EvaluateVarDecl(EvalInfo &Info, const VarDecl *VD) { if (InitE->isValueDependent()) return false; - if (!EvaluateInPlace(Val, Info, Result, InitE)) { + if (VD->getType()->isReferenceType() && + !VD->getType()->isFunctionReferenceType()) { + return EvaluateInitForDeclOfReferenceType(Info, VD, InitE, Result, Val); + } else if (!EvaluateInPlace(Val, Info, Result, InitE)) { // Wipe out any partially-computed value, to allow tracking that this // evaluation failed. Val = APValue(); @@ -6851,9 +6887,18 @@ static bool HandleConstructorCall(const Expr *E, const LValue &This, ThisOverrideRAII ThisOverride(*Info.CurrentCall, &SubobjectParent, isa(Init)); FullExpressionRAII InitScope(Info); - if (!EvaluateInPlace(*Value, Info, Subobject, Init) || - (FD && FD->isBitField() && - !truncateBitfieldValue(Info, Init, *Value, FD))) { + if (FD && FD->getType()->isReferenceType() && + !FD->getType()->isFunctionReferenceType()) { + LValue Result; + if (!EvaluateInitForDeclOfReferenceType(Info, FD, Init, Result, + *Value)) { + if (!Info.noteFailure()) + return false; + Success = false; + } + } else if (!EvaluateInPlace(*Value, Info, Subobject, Init) || + (FD && FD->isBitField() && + !truncateBitfieldValue(Info, Init, *Value, FD))) { // If we're checking for a potential constant expression, evaluate all // initializers even if some of them fail. if (!Info.noteFailure()) @@ -9287,7 +9332,10 @@ bool LValueExprEvaluator::VisitArraySubscriptExpr(const ArraySubscriptExpr *E) { } bool LValueExprEvaluator::VisitUnaryDeref(const UnaryOperator *E) { - return evaluatePointer(E->getSubExpr(), Result); + bool Success = evaluatePointer(E->getSubExpr(), Result); + return Success && + (!E->getType().getNonReferenceType()->isObjectType() || + findCompleteObject(Info, E, AK_Dereference, Result, E->getType())); } bool LValueExprEvaluator::VisitUnaryReal(const UnaryOperator *E) { @@ -10906,9 +10954,18 @@ bool RecordExprEvaluator::VisitCXXParenListOrInitListExpr( isa(Init)); APValue &FieldVal = Result.getStructField(Field->getFieldIndex()); - if (!EvaluateInPlace(FieldVal, Info, Subobject, Init) || - (Field->isBitField() && !truncateBitfieldValue(Info, Init, - FieldVal, Field))) { + if (Field->getType()->isReferenceType() && + !Field->getType()->isFunctionReferenceType()) { + LValue Result; + if (!EvaluateInitForDeclOfReferenceType(Info, Field, Init, Result, + FieldVal)) { + if (!Info.noteFailure()) + return false; + Success = false; + } + } else if (!EvaluateInPlace(FieldVal, Info, Subobject, Init) || + (Field->isBitField() && + !truncateBitfieldValue(Info, Init, FieldVal, Field))) { if (!Info.noteFailure()) return false; Success = false; diff --git a/clang/test/AST/ByteCode/complex.cpp b/clang/test/AST/ByteCode/complex.cpp index 2c0111c53d3bf..17f315b24eccb 100644 --- a/clang/test/AST/ByteCode/complex.cpp +++ b/clang/test/AST/ByteCode/complex.cpp @@ -396,10 +396,9 @@ namespace ComplexConstexpr { // both-note {{cannot refer to element 3 of array of 2 elements}} constexpr _Complex float *p = 0; constexpr float pr = __real *p; // both-error {{constant expr}} \ - // ref-note {{cannot access real component of null}} \ - // expected-note {{read of dereferenced null pointer}} + // both-note {{read of dereferenced null pointer}} constexpr float pi = __imag *p; // both-error {{constant expr}} \ - // ref-note {{cannot access imaginary component of null}} + // ref-note {{read of dereferenced null pointer}} constexpr const _Complex double *q = &test3 + 1; constexpr double qr = __real *q; // ref-error {{constant expr}} \ // ref-note {{cannot access real component of pointer past the end}} diff --git a/clang/test/AST/ByteCode/const-eval.c b/clang/test/AST/ByteCode/const-eval.c index eab14c08ec809..08f5f0adb3772 100644 --- a/clang/test/AST/ByteCode/const-eval.c +++ b/clang/test/AST/ByteCode/const-eval.c @@ -51,6 +51,8 @@ struct s { }; EVAL_EXPR(19, ((int)&*(char*)10 == 10 ? 1 : -1)); +// ref-error@-1 {{expression is not an integer constant expression}} \ +// ref-note@-1 {{read of dereferenced null pointer is not allowed in a constant expression}} #ifndef NEW_INTERP EVAL_EXPR(20, __builtin_constant_p(*((int*) 10))); diff --git a/clang/test/AST/ByteCode/cxx11.cpp b/clang/test/AST/ByteCode/cxx11.cpp index b34e7823220e2..58924a5fc48d9 100644 --- a/clang/test/AST/ByteCode/cxx11.cpp +++ b/clang/test/AST/ByteCode/cxx11.cpp @@ -39,7 +39,9 @@ struct S { constexpr S s = { 5 }; constexpr const int *p = &s.m + 1; -constexpr const int *np2 = &(*(int(*)[4])nullptr)[0]; // ok +constexpr const int *np2 = &(*(int(*)[4])nullptr)[0]; +// ref-error@-1 {{constexpr variable 'np2' must be initialized by a constant expression}} \ +// ref-note@-1 {{read of dereferenced null pointer is not allowed in a constant expression}} constexpr int preDec(int x) { // both-error {{never produces a constant expression}} return --x; // both-note {{subexpression}} diff --git a/clang/test/AST/ByteCode/records.cpp b/clang/test/AST/ByteCode/records.cpp index d369c64bc3904..ef8dc717bda78 100644 --- a/clang/test/AST/ByteCode/records.cpp +++ b/clang/test/AST/ByteCode/records.cpp @@ -413,7 +413,7 @@ namespace DeriveFailures { constexpr Derived(int i) : OtherVal(i) {} // ref-error {{never produces a constant expression}} \ // both-note {{non-constexpr constructor 'Base' cannot be used in a constant expression}} \ - // ref-note {{non-constexpr constructor 'Base' cannot be used in a constant expression}} + // ref-note {{non-constexpr constructor 'Base' cannot be used in a constant expression}} }; constexpr Derived D(12); // both-error {{must be initialized by a constant expression}} \ @@ -1660,9 +1660,11 @@ namespace NullptrCast { constexpr A *na = nullptr; constexpr B *nb = nullptr; constexpr A &ra = *nb; // both-error {{constant expression}} \ - // both-note {{cannot access base class of null pointer}} + // ref-note {{read of dereferenced null pointer}} \ + // expected-note {{cannot access base class of null pointer}} constexpr B &rb = (B&)*na; // both-error {{constant expression}} \ - // both-note {{cannot access derived class of null pointer}} + // ref-note {{read of dereferenced null pointer}} \ + // expected-note {{cannot access derived class of null pointer}} constexpr bool test() { auto a = (A*)(B*)nullptr; @@ -1740,7 +1742,7 @@ namespace CtorOfInvalidClass { #if __cplusplus >= 202002L template concept ReferenceOf = Q; - /// This calls a valid and constexpr copy constructor of InvalidCtor, + /// This calls a valid and constexpr copy constructor of InvalidCtor, /// but should still be rejected. template auto R, typename Rep> int F; // both-error {{non-type template argument is not a constant expression}} #endif diff --git a/clang/test/CXX/drs/cwg14xx.cpp b/clang/test/CXX/drs/cwg14xx.cpp index 17d5c2fc2e210..d1c4424ab5b7a 100644 --- a/clang/test/CXX/drs/cwg14xx.cpp +++ b/clang/test/CXX/drs/cwg14xx.cpp @@ -107,6 +107,8 @@ void f() { constexpr int p = &*a; // since-cxx11-error@-1 {{cannot initialize a variable of type 'const int' with an rvalue of type 'A *'}} constexpr A *p2 = &*a; + // since-cxx11-error@-1 {{constexpr variable 'p2' must be initialized by a constant expression}} \ + // since-cxx11-note@-1 {{read of dereferenced null pointer is not allowed in a constant expression}} } struct A { diff --git a/clang/test/CXX/expr/expr.const/p2-0x.cpp b/clang/test/CXX/expr/expr.const/p2-0x.cpp index c6c3381be5523..6309192162205 100644 --- a/clang/test/CXX/expr/expr.const/p2-0x.cpp +++ b/clang/test/CXX/expr/expr.const/p2-0x.cpp @@ -199,15 +199,15 @@ namespace UndefinedBehavior { constexpr A *na = nullptr; constexpr B *nb = nullptr; - constexpr A &ra = *nb; // expected-error {{constant expression}} expected-note {{cannot access base class of null pointer}} - constexpr B &rb = (B&)*na; // expected-error {{constant expression}} expected-note {{cannot access derived class of null pointer}} + constexpr A &ra = *nb; // expected-error {{constant expression}} expected-note {{read of dereferenced null pointer}} + constexpr B &rb = (B&)*na; // expected-error {{constant expression}} expected-note {{read of dereferenced null pointer}} static_assert((A*)nb == 0, ""); static_assert((B*)na == 0, ""); constexpr const int &nf = nb->n; // expected-error {{constant expression}} expected-note {{cannot access field of null pointer}} constexpr const int &mf = nb->m; // expected-error {{constant expression}} expected-note {{cannot access field of null pointer}} constexpr const int *np1 = (int*)nullptr + 0; // ok - constexpr const int *np2 = &(*(int(*)[4])nullptr)[0]; // ok - constexpr const int *np3 = &(*(int(*)[4])nullptr)[2]; // expected-error {{constant expression}} expected-note {{cannot perform pointer arithmetic on null pointer}} + constexpr const int *np2 = &(*(int(*)[4])nullptr)[0]; // expected-error {{constant expression}} expected-note {{read of dereferenced null pointer}} + constexpr const int *np3 = &(*(int(*)[4])nullptr)[2]; // expected-error {{constant expression}} expected-note {{read of dereferenced null pointer is not allowed in a constant expression}} struct C { constexpr int f() const { return 0; } diff --git a/clang/test/Sema/const-eval.c b/clang/test/Sema/const-eval.c index e358aceaad5a4..f3df072710491 100644 --- a/clang/test/Sema/const-eval.c +++ b/clang/test/Sema/const-eval.c @@ -32,7 +32,7 @@ void f(void) _Complex float g16 = (1.0f + 1.0fi); // ?: in constant expressions. -int g17[(3?:1) - 2]; +int g17[(3?:1) - 2]; EVAL_EXPR(18, ((int)((void*)10 + 10)) == 20 ? 1 : -1); @@ -41,6 +41,9 @@ struct s { }; EVAL_EXPR(19, ((int)&*(char*)10 == 10 ? 1 : -1)); +// expected-error@-1 {{not an integer constant expression}} \ +// expected-note@-1 {{read of dereferenced null pointer is not allowed in a constant expression}} + EVAL_EXPR(20, __builtin_constant_p(*((int*) 10))); diff --git a/clang/test/SemaCXX/constant-expression-cxx11.cpp b/clang/test/SemaCXX/constant-expression-cxx11.cpp index c390fee1c38d9..4b61354250102 100644 --- a/clang/test/SemaCXX/constant-expression-cxx11.cpp +++ b/clang/test/SemaCXX/constant-expression-cxx11.cpp @@ -1413,8 +1413,8 @@ namespace ComplexConstexpr { static_assert(t2p[2] == 0.0, ""); // expected-error {{constant expr}} expected-note {{one-past-the-end pointer}} static_assert(t2p[3] == 0.0, ""); // expected-error {{constant expr}} expected-note {{cannot refer to element 3 of array of 2 elements}} constexpr _Complex float *p = 0; // expected-warning {{'_Complex' is a C99 extension}} - constexpr float pr = __real *p; // expected-error {{constant expr}} expected-note {{cannot access real component of null}} - constexpr float pi = __imag *p; // expected-error {{constant expr}} expected-note {{cannot access imaginary component of null}} + constexpr float pr = __real *p; // expected-error {{constant expr}} expected-note {{read of dereferenced null pointer}} + constexpr float pi = __imag *p; // expected-error {{constant expr}} expected-note {{read of dereferenced null pointer}} constexpr const _Complex double *q = &test3 + 1; // expected-warning {{'_Complex' is a C99 extension}} constexpr double qr = __real *q; // expected-error {{constant expr}} expected-note {{cannot access real component of pointer past the end}} constexpr double qi = __imag *q; // expected-error {{constant expr}} expected-note {{cannot access imaginary component of pointer past the end}} diff --git a/clang/test/SemaCXX/constant-expression-cxx14.cpp b/clang/test/SemaCXX/constant-expression-cxx14.cpp index e93b98c185a82..5c7db44806d87 100644 --- a/clang/test/SemaCXX/constant-expression-cxx14.cpp +++ b/clang/test/SemaCXX/constant-expression-cxx14.cpp @@ -265,7 +265,7 @@ namespace const_modify { namespace null { constexpr int test(int *p) { - return *p = 123; // expected-note {{assignment to dereferenced null pointer}} + return *p = 123; // expected-note {{read of dereferenced null pointer}} } static_assert(test(0), ""); // expected-error {{constant expression}} expected-note {{in call}} } @@ -1335,4 +1335,58 @@ namespace comparison_dead_variable { } // FIXME: This should fail. static_assert(f(),""); + +} +namespace GH48665 { +constexpr bool foo(int *i) { + int &j = *i; + // expected-note@-1 {{read of dereferenced null pointer}} + return true; +} + +static_assert(foo(nullptr), ""); // expected-note {{in call to 'foo(nullptr)'}} +// expected-error@-1 {{static assertion expression is not an integral constant expression}} + +constexpr bool foo_rvalue(int *i) { + int &&j = (int&&)*i; + // expected-note@-1 {{read of dereferenced null pointer}} + return true; +} +static_assert(foo_rvalue(nullptr), ""); // expected-note {{in call to 'foo_rvalue(nullptr)'}} +// expected-error@-1 {{static assertion expression is not an integral constant expression}} + +int arr[3]; // expected-note {{declared here}} +constexpr bool f() { // cxx14_20-error {{constexpr function never produces a constant expression}} + int &r = arr[3]; // expected-note {{read of dereferenced one-past-the-end pointer}} \ + // cxx14_20-note {{read of dereferenced one-past-the-end pointer}} \ + // expected-warning {{array index 3 is past the end of the array}} + return true; +} +static_assert(f(), ""); // expected-note {{in call to 'f()'}} +// expected-error@-1 {{static assertion expression is not an integral constant expression}} + + +struct Aggregate { + int &r; +}; +constexpr bool test_agg(int *i) { + Aggregate a{*i}; //expected-note {{read of dereferenced null pointer}} + return true; +} +static_assert(test_agg(nullptr), ""); // expected-note {{in call to 'test_agg(nullptr)'}} +// expected-error@-1 {{static assertion expression is not an integral constant expression}} + +struct B { + constexpr B(int *p) : r{*p} {} // expected-note {{read of dereferenced null pointer}} + int &r; +}; + +constexpr bool test_ctr(int *i) { + B b(i); // expected-note {{in call to 'B(nullptr)'}} + return true; +} + +static_assert(test_ctr(nullptr), ""); // expected-note {{in call to 'test_ctr(nullptr)'}} +// expected-error@-1 {{static assertion expression is not an integral constant expression}} + } diff --git a/clang/test/SemaCXX/constant-expression-cxx2a.cpp b/clang/test/SemaCXX/constant-expression-cxx2a.cpp index 85720606fe9de..ffb7e633c2919 100644 --- a/clang/test/SemaCXX/constant-expression-cxx2a.cpp +++ b/clang/test/SemaCXX/constant-expression-cxx2a.cpp @@ -927,7 +927,7 @@ namespace dynamic_alloc { constexpr void use_after_free() { // expected-error {{never produces a constant expression}} int *p = new int; delete p; - *p = 1; // expected-note {{assignment to heap allocated object that has been deleted}} + *p = 1; // expected-note {{read of heap allocated object that has been deleted}} } constexpr void use_after_free_2() { // expected-error {{never produces a constant expression}} struct X { constexpr void f() {} }; diff --git a/clang/test/SemaCXX/constexpr-backtrace-limit.cpp b/clang/test/SemaCXX/constexpr-backtrace-limit.cpp index e867afdff5c3c..61991bf5d2931 100644 --- a/clang/test/SemaCXX/constexpr-backtrace-limit.cpp +++ b/clang/test/SemaCXX/constexpr-backtrace-limit.cpp @@ -15,14 +15,14 @@ // RUN: not %clang_cc1 -std=c++11 -fsyntax-only %s -fconstexpr-backtrace-limit=2 -fconstexpr-depth=8 -fno-caret-diagnostics 2>&1 | FileCheck %s -check-prefix=TEST3 // TEST3: constant expression -// TEST3-NEXT: reinterpret_cast +// TEST3-NEXT: read of dereferenced null pointe // TEST3-NEXT: in call to 'recurse(0)' // TEST3-NEXT: skipping 4 calls // TEST3-NEXT: in call to 'recurse(5)' // RUN: not %clang_cc1 -std=c++11 -fsyntax-only %s -fconstexpr-backtrace-limit=8 -fconstexpr-depth=8 -fno-caret-diagnostics 2>&1 | FileCheck %s -check-prefix=TEST4 // TEST4: constant expression -// TEST4-NEXT: reinterpret_cast +// TEST4-NEXT: read of dereferenced null pointe // TEST4-NEXT: in call to 'recurse(0)' // TEST4-NEXT: in call to 'recurse(1)' // TEST4-NEXT: in call to 'recurse(2)' diff --git a/libcxx/test/std/ranges/range.adaptors/range.zip/iterator/increment.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.zip/iterator/increment.pass.cpp index 0ca8d92800feb..94d2bd47e9806 100644 --- a/libcxx/test/std/ranges/range.adaptors/range.zip/iterator/increment.pass.cpp +++ b/libcxx/test/std/ranges/range.adaptors/range.zip/iterator/increment.pass.cpp @@ -59,7 +59,7 @@ constexpr bool test() { { // bidi - int buffer[2] = {1, 2}; + int buffer[3] = {1, 2, 3}; std::ranges::zip_view v(BidiCommonView{buffer}); auto it = v.begin(); @@ -81,7 +81,7 @@ constexpr bool test() { { // forward - int buffer[2] = {1, 2}; + int buffer[3] = {1, 2, 3}; std::ranges::zip_view v(ForwardSizedView{buffer}); auto it = v.begin(); From 07ae0de4a22eaf92d994ad4873ca39f5b68e9508 Mon Sep 17 00:00:00 2001 From: Corentin Jabot Date: Thu, 10 Jul 2025 14:13:37 +0200 Subject: [PATCH 2/7] address some feedback --- clang/lib/AST/ExprConstant.cpp | 8 ++++---- clang/test/CXX/drs/cwg14xx.cpp | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 7bc772e1ef9bb..274b288ea061d 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -1562,17 +1562,17 @@ static bool isFormalAccess(AccessKinds AK) { AK != AK_IsWithinLifetime && AK != AK_Dereference; } -/// Is this kind of axcess valid on an indeterminate object value? +/// Is this kind of access valid on an indeterminate object value? static bool isValidIndeterminateAccess(AccessKinds AK) { switch (AK) { case AK_Read: case AK_Increment: case AK_Decrement: + case AK_Dereference: // These need the object's value. return false; case AK_IsWithinLifetime: - case AK_Dereference: case AK_ReadObjectRepresentation: case AK_Assign: case AK_Construct: @@ -5216,7 +5216,7 @@ static bool EvaluateInitForDeclOfReferenceType(EvalInfo &Info, const Expr *Init, LValue &Result, APValue &Val) { assert(Init->isGLValue() && D->getType()->isReferenceType()); - // A reference is an lvalue + // A reference is an lvalue. if (!EvaluateLValue(Init, Result, Info)) return false; // [C++26][decl.ref] @@ -5229,7 +5229,7 @@ static bool EvaluateInitForDeclOfReferenceType(EvalInfo &Info, return false; } - // save the result + // Save the result. Result.moveInto(Val); return true; } diff --git a/clang/test/CXX/drs/cwg14xx.cpp b/clang/test/CXX/drs/cwg14xx.cpp index d1c4424ab5b7a..2b036f2adb48e 100644 --- a/clang/test/CXX/drs/cwg14xx.cpp +++ b/clang/test/CXX/drs/cwg14xx.cpp @@ -107,8 +107,8 @@ void f() { constexpr int p = &*a; // since-cxx11-error@-1 {{cannot initialize a variable of type 'const int' with an rvalue of type 'A *'}} constexpr A *p2 = &*a; - // since-cxx11-error@-1 {{constexpr variable 'p2' must be initialized by a constant expression}} \ - // since-cxx11-note@-1 {{read of dereferenced null pointer is not allowed in a constant expression}} + // since-cxx11-error@-1 {{constexpr variable 'p2' must be initialized by a constant expression}} + // since-cxx11-note@-2 {{read of dereferenced null pointer is not allowed in a constant expression}} } struct A { From 43cb1b40e14bcdd0f8c08c6602a2f46ee874576c Mon Sep 17 00:00:00 2001 From: Corentin Jabot Date: Thu, 10 Jul 2025 14:35:13 +0200 Subject: [PATCH 3/7] Add a disting diagnostic for dereferencing a null pointer > dereferencing a null pointer is not allowed in a constant expression --- clang/include/clang/Basic/DiagnosticASTKinds.td | 2 ++ clang/lib/AST/ExprConstant.cpp | 10 ++++++++-- clang/test/AST/ByteCode/complex.cpp | 5 +++-- clang/test/AST/ByteCode/const-eval.c | 2 +- clang/test/AST/ByteCode/cxx11.cpp | 2 +- clang/test/AST/ByteCode/records.cpp | 4 ++-- clang/test/CXX/drs/cwg14xx.cpp | 2 +- clang/test/CXX/expr/expr.const/p2-0x.cpp | 10 +++++----- clang/test/Sema/const-eval.c | 2 +- clang/test/SemaCXX/constant-expression-cxx11.cpp | 4 ++-- clang/test/SemaCXX/constant-expression-cxx14.cpp | 10 +++++----- clang/test/SemaCXX/constexpr-backtrace-limit.cpp | 4 ++-- 12 files changed, 33 insertions(+), 24 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td index e02075dba30b9..1d9f021f518e9 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -223,6 +223,8 @@ def note_constexpr_ltor_incomplete_type : Note< def note_constexpr_access_null : Note< "%sub{access_kind}0 " "dereferenced null pointer is not allowed in a constant expression">; +def note_constexpr_dereferencing_null : Note< + "dereferencing a null pointer is not allowed in a constant expression">; def note_constexpr_access_past_end : Note< "%sub{access_kind}0 dereferenced one-past-the-end pointer " "is not allowed in a constant expression">; diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 274b288ea061d..5d325c7f35ae3 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -1735,7 +1735,10 @@ namespace { bool checkNullPointerForFoldAccess(EvalInfo &Info, const Expr *E, AccessKinds AK) { return checkNullPointerDiagnosingWith([&Info, E, AK] { - Info.FFDiag(E, diag::note_constexpr_access_null) << AK; + if(AK == AccessKinds::AK_Dereference) + Info.FFDiag(E, diag::note_constexpr_dereferencing_null); + else + Info.FFDiag(E, diag::note_constexpr_access_null) << AK; }); } @@ -4307,7 +4310,10 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, } if (!LVal.Base) { - Info.FFDiag(E, diag::note_constexpr_access_null) << AK; + if(AK == AccessKinds::AK_Dereference) + Info.FFDiag(E, diag::note_constexpr_dereferencing_null); + else + Info.FFDiag(E, diag::note_constexpr_access_null) << AK; return CompleteObject(); } diff --git a/clang/test/AST/ByteCode/complex.cpp b/clang/test/AST/ByteCode/complex.cpp index 17f315b24eccb..959d759005ef4 100644 --- a/clang/test/AST/ByteCode/complex.cpp +++ b/clang/test/AST/ByteCode/complex.cpp @@ -396,9 +396,10 @@ namespace ComplexConstexpr { // both-note {{cannot refer to element 3 of array of 2 elements}} constexpr _Complex float *p = 0; constexpr float pr = __real *p; // both-error {{constant expr}} \ - // both-note {{read of dereferenced null pointer}} + // expected-note {{read of dereferenced null pointer}} \ + // ref-note {{dereferencing a null pointer}} constexpr float pi = __imag *p; // both-error {{constant expr}} \ - // ref-note {{read of dereferenced null pointer}} + // ref-note {{dereferencing a null pointer}} constexpr const _Complex double *q = &test3 + 1; constexpr double qr = __real *q; // ref-error {{constant expr}} \ // ref-note {{cannot access real component of pointer past the end}} diff --git a/clang/test/AST/ByteCode/const-eval.c b/clang/test/AST/ByteCode/const-eval.c index 08f5f0adb3772..c8651a744f969 100644 --- a/clang/test/AST/ByteCode/const-eval.c +++ b/clang/test/AST/ByteCode/const-eval.c @@ -52,7 +52,7 @@ struct s { EVAL_EXPR(19, ((int)&*(char*)10 == 10 ? 1 : -1)); // ref-error@-1 {{expression is not an integer constant expression}} \ -// ref-note@-1 {{read of dereferenced null pointer is not allowed in a constant expression}} +// ref-note@-1 {{dereferencing a null pointer}} #ifndef NEW_INTERP EVAL_EXPR(20, __builtin_constant_p(*((int*) 10))); diff --git a/clang/test/AST/ByteCode/cxx11.cpp b/clang/test/AST/ByteCode/cxx11.cpp index 58924a5fc48d9..55554220b0a8a 100644 --- a/clang/test/AST/ByteCode/cxx11.cpp +++ b/clang/test/AST/ByteCode/cxx11.cpp @@ -41,7 +41,7 @@ constexpr const int *p = &s.m + 1; constexpr const int *np2 = &(*(int(*)[4])nullptr)[0]; // ref-error@-1 {{constexpr variable 'np2' must be initialized by a constant expression}} \ -// ref-note@-1 {{read of dereferenced null pointer is not allowed in a constant expression}} +// ref-note@-1 {{dereferencing a null pointer is not allowed in a constant expression}} constexpr int preDec(int x) { // both-error {{never produces a constant expression}} return --x; // both-note {{subexpression}} diff --git a/clang/test/AST/ByteCode/records.cpp b/clang/test/AST/ByteCode/records.cpp index ef8dc717bda78..774fed6189d64 100644 --- a/clang/test/AST/ByteCode/records.cpp +++ b/clang/test/AST/ByteCode/records.cpp @@ -1660,10 +1660,10 @@ namespace NullptrCast { constexpr A *na = nullptr; constexpr B *nb = nullptr; constexpr A &ra = *nb; // both-error {{constant expression}} \ - // ref-note {{read of dereferenced null pointer}} \ + // ref-note {{dereferencing a null pointer}} \ // expected-note {{cannot access base class of null pointer}} constexpr B &rb = (B&)*na; // both-error {{constant expression}} \ - // ref-note {{read of dereferenced null pointer}} \ + // ref-note {{dereferencing a null pointer}} \ // expected-note {{cannot access derived class of null pointer}} constexpr bool test() { auto a = (A*)(B*)nullptr; diff --git a/clang/test/CXX/drs/cwg14xx.cpp b/clang/test/CXX/drs/cwg14xx.cpp index 2b036f2adb48e..8d39018d8926c 100644 --- a/clang/test/CXX/drs/cwg14xx.cpp +++ b/clang/test/CXX/drs/cwg14xx.cpp @@ -108,7 +108,7 @@ void f() { // since-cxx11-error@-1 {{cannot initialize a variable of type 'const int' with an rvalue of type 'A *'}} constexpr A *p2 = &*a; // since-cxx11-error@-1 {{constexpr variable 'p2' must be initialized by a constant expression}} - // since-cxx11-note@-2 {{read of dereferenced null pointer is not allowed in a constant expression}} + // since-cxx11-note@-2 {{dereferencing a null pointer}} } struct A { diff --git a/clang/test/CXX/expr/expr.const/p2-0x.cpp b/clang/test/CXX/expr/expr.const/p2-0x.cpp index 6309192162205..910c8635f7353 100644 --- a/clang/test/CXX/expr/expr.const/p2-0x.cpp +++ b/clang/test/CXX/expr/expr.const/p2-0x.cpp @@ -199,15 +199,15 @@ namespace UndefinedBehavior { constexpr A *na = nullptr; constexpr B *nb = nullptr; - constexpr A &ra = *nb; // expected-error {{constant expression}} expected-note {{read of dereferenced null pointer}} - constexpr B &rb = (B&)*na; // expected-error {{constant expression}} expected-note {{read of dereferenced null pointer}} + constexpr A &ra = *nb; // expected-error {{constant expression}} expected-note {{dereferencing a null pointer}} + constexpr B &rb = (B&)*na; // expected-error {{constant expression}} expected-note {{dereferencing a null pointer}} static_assert((A*)nb == 0, ""); static_assert((B*)na == 0, ""); constexpr const int &nf = nb->n; // expected-error {{constant expression}} expected-note {{cannot access field of null pointer}} constexpr const int &mf = nb->m; // expected-error {{constant expression}} expected-note {{cannot access field of null pointer}} constexpr const int *np1 = (int*)nullptr + 0; // ok - constexpr const int *np2 = &(*(int(*)[4])nullptr)[0]; // expected-error {{constant expression}} expected-note {{read of dereferenced null pointer}} - constexpr const int *np3 = &(*(int(*)[4])nullptr)[2]; // expected-error {{constant expression}} expected-note {{read of dereferenced null pointer is not allowed in a constant expression}} + constexpr const int *np2 = &(*(int(*)[4])nullptr)[0]; // expected-error {{constant expression}} expected-note {{dereferencing a null pointer}} + constexpr const int *np3 = &(*(int(*)[4])nullptr)[2]; // expected-error {{constant expression}} expected-note {{dereferencing a null pointer}} struct C { constexpr int f() const { return 0; } @@ -485,7 +485,7 @@ namespace std { namespace TypeId { struct S { virtual void f(); }; constexpr S *p = 0; - constexpr const std::type_info &ti1 = typeid(*p); // expected-error {{must be initialized by a constant expression}} cxx11-note {{typeid applied to expression of polymorphic type 'S'}} cxx20-note {{dereferenced null pointer}} + constexpr const std::type_info &ti1 = typeid(*p); // expected-error {{must be initialized by a constant expression}} cxx11-note {{typeid applied to expression of polymorphic type 'S'}} cxx20-note {{dereferencing a null pointer}} struct T {} t; constexpr const std::type_info &ti2 = typeid(t); diff --git a/clang/test/Sema/const-eval.c b/clang/test/Sema/const-eval.c index f3df072710491..87c21120e7c5d 100644 --- a/clang/test/Sema/const-eval.c +++ b/clang/test/Sema/const-eval.c @@ -42,7 +42,7 @@ struct s { EVAL_EXPR(19, ((int)&*(char*)10 == 10 ? 1 : -1)); // expected-error@-1 {{not an integer constant expression}} \ -// expected-note@-1 {{read of dereferenced null pointer is not allowed in a constant expression}} +// expected-note@-1 {{dereferencing a null pointer is not allowed in a constant expression}} EVAL_EXPR(20, __builtin_constant_p(*((int*) 10))); diff --git a/clang/test/SemaCXX/constant-expression-cxx11.cpp b/clang/test/SemaCXX/constant-expression-cxx11.cpp index 4b61354250102..5ecb8c607f59a 100644 --- a/clang/test/SemaCXX/constant-expression-cxx11.cpp +++ b/clang/test/SemaCXX/constant-expression-cxx11.cpp @@ -1413,8 +1413,8 @@ namespace ComplexConstexpr { static_assert(t2p[2] == 0.0, ""); // expected-error {{constant expr}} expected-note {{one-past-the-end pointer}} static_assert(t2p[3] == 0.0, ""); // expected-error {{constant expr}} expected-note {{cannot refer to element 3 of array of 2 elements}} constexpr _Complex float *p = 0; // expected-warning {{'_Complex' is a C99 extension}} - constexpr float pr = __real *p; // expected-error {{constant expr}} expected-note {{read of dereferenced null pointer}} - constexpr float pi = __imag *p; // expected-error {{constant expr}} expected-note {{read of dereferenced null pointer}} + constexpr float pr = __real *p; // expected-error {{constant expr}} expected-note {{dereferencing a null pointer}} + constexpr float pi = __imag *p; // expected-error {{constant expr}} expected-note {{dereferencing a null pointer}} constexpr const _Complex double *q = &test3 + 1; // expected-warning {{'_Complex' is a C99 extension}} constexpr double qr = __real *q; // expected-error {{constant expr}} expected-note {{cannot access real component of pointer past the end}} constexpr double qi = __imag *q; // expected-error {{constant expr}} expected-note {{cannot access imaginary component of pointer past the end}} diff --git a/clang/test/SemaCXX/constant-expression-cxx14.cpp b/clang/test/SemaCXX/constant-expression-cxx14.cpp index 5c7db44806d87..e5854aa2f2b87 100644 --- a/clang/test/SemaCXX/constant-expression-cxx14.cpp +++ b/clang/test/SemaCXX/constant-expression-cxx14.cpp @@ -265,7 +265,7 @@ namespace const_modify { namespace null { constexpr int test(int *p) { - return *p = 123; // expected-note {{read of dereferenced null pointer}} + return *p = 123; // expected-note {{dereferencing a null pointer}} } static_assert(test(0), ""); // expected-error {{constant expression}} expected-note {{in call}} } @@ -1340,7 +1340,7 @@ namespace comparison_dead_variable { namespace GH48665 { constexpr bool foo(int *i) { int &j = *i; - // expected-note@-1 {{read of dereferenced null pointer}} + // expected-note@-1 {{dereferencing a null pointer}} return true; } @@ -1349,7 +1349,7 @@ static_assert(foo(nullptr), ""); // expected-note {{in call to 'foo(nullptr)'}} constexpr bool foo_rvalue(int *i) { int &&j = (int&&)*i; - // expected-note@-1 {{read of dereferenced null pointer}} + // expected-note@-1 {{dereferencing a null pointer}} return true; } static_assert(foo_rvalue(nullptr), ""); // expected-note {{in call to 'foo_rvalue(nullptr)'}} @@ -1370,14 +1370,14 @@ struct Aggregate { int &r; }; constexpr bool test_agg(int *i) { - Aggregate a{*i}; //expected-note {{read of dereferenced null pointer}} + Aggregate a{*i}; //expected-note {{dereferencing a null pointer}} return true; } static_assert(test_agg(nullptr), ""); // expected-note {{in call to 'test_agg(nullptr)'}} // expected-error@-1 {{static assertion expression is not an integral constant expression}} struct B { - constexpr B(int *p) : r{*p} {} // expected-note {{read of dereferenced null pointer}} + constexpr B(int *p) : r{*p} {} // expected-note {{dereferencing a null pointer}} int &r; }; diff --git a/clang/test/SemaCXX/constexpr-backtrace-limit.cpp b/clang/test/SemaCXX/constexpr-backtrace-limit.cpp index 61991bf5d2931..f0c1206a4b8d3 100644 --- a/clang/test/SemaCXX/constexpr-backtrace-limit.cpp +++ b/clang/test/SemaCXX/constexpr-backtrace-limit.cpp @@ -15,14 +15,14 @@ // RUN: not %clang_cc1 -std=c++11 -fsyntax-only %s -fconstexpr-backtrace-limit=2 -fconstexpr-depth=8 -fno-caret-diagnostics 2>&1 | FileCheck %s -check-prefix=TEST3 // TEST3: constant expression -// TEST3-NEXT: read of dereferenced null pointe +// TEST3-NEXT: dereferencing a null pointer // TEST3-NEXT: in call to 'recurse(0)' // TEST3-NEXT: skipping 4 calls // TEST3-NEXT: in call to 'recurse(5)' // RUN: not %clang_cc1 -std=c++11 -fsyntax-only %s -fconstexpr-backtrace-limit=8 -fconstexpr-depth=8 -fno-caret-diagnostics 2>&1 | FileCheck %s -check-prefix=TEST4 // TEST4: constant expression -// TEST4-NEXT: read of dereferenced null pointe +// TEST4-NEXT: dereferencing a null pointer // TEST4-NEXT: in call to 'recurse(0)' // TEST4-NEXT: in call to 'recurse(1)' // TEST4-NEXT: in call to 'recurse(2)' From a8352d11039f18210706f592e63330dfb66c9bb8 Mon Sep 17 00:00:00 2001 From: Corentin Jabot Date: Thu, 10 Jul 2025 15:32:03 +0200 Subject: [PATCH 4/7] format --- clang/include/clang/Basic/DiagnosticASTKinds.td | 5 +++-- clang/lib/AST/ExprConstant.cpp | 12 ++++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td index 1d9f021f518e9..071a38f513911 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -223,8 +223,9 @@ def note_constexpr_ltor_incomplete_type : Note< def note_constexpr_access_null : Note< "%sub{access_kind}0 " "dereferenced null pointer is not allowed in a constant expression">; -def note_constexpr_dereferencing_null : Note< - "dereferencing a null pointer is not allowed in a constant expression">; +def note_constexpr_dereferencing_null + : Note<"dereferencing a null pointer is not allowed in a constant " + "expression">; def note_constexpr_access_past_end : Note< "%sub{access_kind}0 dereferenced one-past-the-end pointer " "is not allowed in a constant expression">; diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 5d325c7f35ae3..363c88753066f 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -1735,10 +1735,10 @@ namespace { bool checkNullPointerForFoldAccess(EvalInfo &Info, const Expr *E, AccessKinds AK) { return checkNullPointerDiagnosingWith([&Info, E, AK] { - if(AK == AccessKinds::AK_Dereference) - Info.FFDiag(E, diag::note_constexpr_dereferencing_null); + if (AK == AccessKinds::AK_Dereference) + Info.FFDiag(E, diag::note_constexpr_dereferencing_null); else - Info.FFDiag(E, diag::note_constexpr_access_null) << AK; + Info.FFDiag(E, diag::note_constexpr_access_null) << AK; }); } @@ -4310,10 +4310,10 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, } if (!LVal.Base) { - if(AK == AccessKinds::AK_Dereference) - Info.FFDiag(E, diag::note_constexpr_dereferencing_null); + if (AK == AccessKinds::AK_Dereference) + Info.FFDiag(E, diag::note_constexpr_dereferencing_null); else - Info.FFDiag(E, diag::note_constexpr_access_null) << AK; + Info.FFDiag(E, diag::note_constexpr_access_null) << AK; return CompleteObject(); } From 1d1a6352700b17b50f2a69affdf91a70cd3a8cf1 Mon Sep 17 00:00:00 2001 From: Corentin Jabot Date: Tue, 15 Jul 2025 22:56:55 +0200 Subject: [PATCH 5/7] add comment --- clang/lib/AST/ExprConstant.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 363c88753066f..e8c5cd516cdca 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -5260,6 +5260,8 @@ static bool EvaluateVarDecl(EvalInfo &Info, const VarDecl *VD) { if (InitE->isValueDependent()) return false; + // For references to objects, check they do not designate a one-past-the-end + // object. if (VD->getType()->isReferenceType() && !VD->getType()->isFunctionReferenceType()) { return EvaluateInitForDeclOfReferenceType(Info, VD, InitE, Result, Val); @@ -9339,6 +9341,9 @@ bool LValueExprEvaluator::VisitArraySubscriptExpr(const ArraySubscriptExpr *E) { bool LValueExprEvaluator::VisitUnaryDeref(const UnaryOperator *E) { bool Success = evaluatePointer(E->getSubExpr(), Result); + // [C++26][expr.unary.op] + // If the operand points to an object or function, the result + // denotes that object or function; otherwise, the behavior is undefined. return Success && (!E->getType().getNonReferenceType()->isObjectType() || findCompleteObject(Info, E, AK_Dereference, Result, E->getType())); From cd750c9f3bbfe05c8e257e5d3f09bff891c91794 Mon Sep 17 00:00:00 2001 From: Corentin Jabot Date: Wed, 16 Jul 2025 07:21:01 +0200 Subject: [PATCH 6/7] fix tests --- clang/lib/AST/ExprConstant.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index e8c5cd516cdca..fcc8f9f8478db 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -4490,7 +4490,7 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, // 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) { + if (AK != clang::AK_Dereference && !BaseVal) { Info.FFDiag(E, diag::note_constexpr_access_unknown_variable, 1) << AK << VD; Info.Note(VD->getLocation(), diag::note_declared_at); From aee78c5259db90dd6ffc31cb326a310d1feae29c Mon Sep 17 00:00:00 2001 From: Corentin Jabot Date: Wed, 16 Jul 2025 10:37:38 +0200 Subject: [PATCH 7/7] add function tests, simplify --- clang/lib/AST/ExprConstant.cpp | 6 +- .../SemaCXX/constant-expression-cxx14.cpp | 55 +++++++++++++++++++ 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index fcc8f9f8478db..767cc4c3b19eb 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -5262,8 +5262,7 @@ static bool EvaluateVarDecl(EvalInfo &Info, const VarDecl *VD) { // For references to objects, check they do not designate a one-past-the-end // object. - if (VD->getType()->isReferenceType() && - !VD->getType()->isFunctionReferenceType()) { + if (VD->getType()->isReferenceType()) { return EvaluateInitForDeclOfReferenceType(Info, VD, InitE, Result, Val); } else if (!EvaluateInPlace(Val, Info, Result, InitE)) { // Wipe out any partially-computed value, to allow tracking that this @@ -10965,8 +10964,7 @@ bool RecordExprEvaluator::VisitCXXParenListOrInitListExpr( isa(Init)); APValue &FieldVal = Result.getStructField(Field->getFieldIndex()); - if (Field->getType()->isReferenceType() && - !Field->getType()->isFunctionReferenceType()) { + if (Field->getType()->isReferenceType()) { LValue Result; if (!EvaluateInitForDeclOfReferenceType(Info, Field, Init, Result, FieldVal)) { diff --git a/clang/test/SemaCXX/constant-expression-cxx14.cpp b/clang/test/SemaCXX/constant-expression-cxx14.cpp index e5854aa2f2b87..182c0d01141ff 100644 --- a/clang/test/SemaCXX/constant-expression-cxx14.cpp +++ b/clang/test/SemaCXX/constant-expression-cxx14.cpp @@ -1389,4 +1389,59 @@ constexpr bool test_ctr(int *i) { static_assert(test_ctr(nullptr), ""); // expected-note {{in call to 'test_ctr(nullptr)'}} // expected-error@-1 {{static assertion expression is not an integral constant expression}} + +// verify that we can dereference function pointers +namespace functions { + +constexpr int f() {return 0;} +constexpr int(*f_ptr)() = &f; +constexpr int(*null_ptr)() = nullptr; + +constexpr int(&f_ref)() = f; +constexpr int test = (*f_ptr)(); +constexpr int test2 = (*f_ref)(); +constexpr int test3 = (*f_ref)(); +constexpr int test4 = (*null_ptr)(); +//expected-error@-1 {{constexpr variable 'test4' must be initialized by a constant expression}} \ +//expected-note@-1 {{'(*null_ptr)' evaluates to a null function pointer}} + +constexpr int(*f_ptr_arr[1])() = {&f}; +constexpr int test_array_ok = (f_ptr_arr[0])(); +constexpr int test_array_err = (f_ptr_arr[1])(); +// expected-error@-1 {{constexpr variable 'test_array_err' must be initialized by a constant expression}} \ +// expected-note@-1 {{read of dereferenced one-past-the-end pointer is not allowed in a constant expression}} + +struct S { + int(*f_ptr)() = &f; + int(*f_ptr_arr[1])() = {&f}; + int(&f_ref)() = f; + int(*null_ptr)() = nullptr; +}; + +constexpr int test_member() { + S s {}; + (*s.f_ptr)(); + (*s.f_ref)(); + (s.f_ref)(); + (s.f_ptr_arr[0])(); + (s.f_ptr_arr[1])(); + // expected-note@-1 {{read of dereferenced one-past-the-end pointer is not allowed in a constant expression}} + return 0; +} +constexpr int test_member_null() { // cxx14_20-error {{never produces a constant expression}} + S s {}; + (*s.null_ptr)(); // expected-note {{'(*s.null_ptr)' evaluates to a null function pointer}} \ + // cxx14_20-note {{'(*s.null_ptr)' evaluates to a null function pointer}} + return 0; +} + +static_assert(test_member(), ""); +// expected-error@-1 {{static assertion expression is not an integral constant expression}} \ +// expected-note@-1 {{in call to 'test_member()'}} + +static_assert(test_member_null(), ""); +// expected-error@-1 {{static assertion expression is not an integral constant expression}} \ +// expected-note@-1 {{in call to 'test_member_null()'}} + +} }