Skip to content

Commit 9e5470e

Browse files
authored
[Clang] Diagnose forming references to nullptr (#143667)
Per [decl.ref], > 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. Note this does not fixes the new bytecode interpreter. Fixes #48665
1 parent 5abdce4 commit 9e5470e

File tree

16 files changed

+238
-46
lines changed

16 files changed

+238
-46
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -674,7 +674,7 @@ Improvements to Clang's diagnostics
674674
#GH142457, #GH139913, #GH138850, #GH137867, #GH137860, #GH107840, #GH93308,
675675
#GH69470, #GH59391, #GH58172, #GH46215, #GH45915, #GH45891, #GH44490,
676676
#GH36703, #GH32903, #GH23312, #GH69874.
677-
677+
678678
- Clang no longer emits a spurious -Wdangling-gsl warning in C++23 when
679679
iterating over an element of a temporary container in a range-based
680680
for loop.(#GH109793, #GH145164)
@@ -970,6 +970,7 @@ Bug Fixes to C++ Support
970970
- Fixed a crash involving list-initialization of an empty class with a
971971
non-empty initializer list. (#GH147949)
972972
- Fixed constant evaluation of equality comparisons of constexpr-unknown references. (#GH147663)
973+
- Diagnose binding a reference to ``*nullptr`` during constant evaluation. (#GH48665)
973974

974975
Bug Fixes to AST Handling
975976
^^^^^^^^^^^^^^^^^^^^^^^^^

clang/include/clang/Basic/DiagnosticASTKinds.td

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -174,10 +174,11 @@ def note_constexpr_heap_alloc_limit_exceeded : Note<
174174
def note_constexpr_this : Note<
175175
"%select{|implicit }0use of 'this' pointer is only allowed within the "
176176
"evaluation of a call to a 'constexpr' member function">;
177-
def access_kind : TextSubstitution<
178-
"%select{read of|read of|assignment to|increment of|decrement of|"
179-
"member call on|dynamic_cast of|typeid applied to|construction of|"
180-
"destruction of|read of}0">;
177+
def access_kind
178+
: TextSubstitution<
179+
"%select{read of|read of|assignment to|increment of|decrement of|"
180+
"member call on|dynamic_cast of|typeid applied to|construction of|"
181+
"destruction of|read of|read of}0">;
181182
def access_kind_subobject : TextSubstitution<
182183
"%select{read of|read of|assignment to|increment of|decrement of|"
183184
"member call on|dynamic_cast of|typeid applied to|"
@@ -222,6 +223,9 @@ def note_constexpr_ltor_incomplete_type : Note<
222223
def note_constexpr_access_null : Note<
223224
"%sub{access_kind}0 "
224225
"dereferenced null pointer is not allowed in a constant expression">;
226+
def note_constexpr_dereferencing_null
227+
: Note<"dereferencing a null pointer is not allowed in a constant "
228+
"expression">;
225229
def note_constexpr_access_past_end : Note<
226230
"%sub{access_kind}0 dereferenced one-past-the-end pointer "
227231
"is not allowed in a constant expression">;

clang/lib/AST/ByteCode/State.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ enum AccessKinds {
3535
AK_Construct,
3636
AK_Destroy,
3737
AK_IsWithinLifetime,
38+
AK_Dereference
3839
};
3940

4041
/// The order of this enum is important for diagnostics.

clang/lib/AST/ExprConstant.cpp

Lines changed: 85 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1529,7 +1529,7 @@ CallStackFrame::~CallStackFrame() {
15291529

15301530
static bool isRead(AccessKinds AK) {
15311531
return AK == AK_Read || AK == AK_ReadObjectRepresentation ||
1532-
AK == AK_IsWithinLifetime;
1532+
AK == AK_IsWithinLifetime || AK == AK_Dereference;
15331533
}
15341534

15351535
static bool isModification(AccessKinds AK) {
@@ -1540,6 +1540,7 @@ static bool isModification(AccessKinds AK) {
15401540
case AK_DynamicCast:
15411541
case AK_TypeId:
15421542
case AK_IsWithinLifetime:
1543+
case AK_Dereference:
15431544
return false;
15441545
case AK_Assign:
15451546
case AK_Increment:
@@ -1558,15 +1559,16 @@ static bool isAnyAccess(AccessKinds AK) {
15581559
/// Is this an access per the C++ definition?
15591560
static bool isFormalAccess(AccessKinds AK) {
15601561
return isAnyAccess(AK) && AK != AK_Construct && AK != AK_Destroy &&
1561-
AK != AK_IsWithinLifetime;
1562+
AK != AK_IsWithinLifetime && AK != AK_Dereference;
15621563
}
15631564

1564-
/// Is this kind of axcess valid on an indeterminate object value?
1565+
/// Is this kind of access valid on an indeterminate object value?
15651566
static bool isValidIndeterminateAccess(AccessKinds AK) {
15661567
switch (AK) {
15671568
case AK_Read:
15681569
case AK_Increment:
15691570
case AK_Decrement:
1571+
case AK_Dereference:
15701572
// These need the object's value.
15711573
return false;
15721574

@@ -1733,7 +1735,10 @@ namespace {
17331735
bool checkNullPointerForFoldAccess(EvalInfo &Info, const Expr *E,
17341736
AccessKinds AK) {
17351737
return checkNullPointerDiagnosingWith([&Info, E, AK] {
1736-
Info.FFDiag(E, diag::note_constexpr_access_null) << AK;
1738+
if (AK == AccessKinds::AK_Dereference)
1739+
Info.FFDiag(E, diag::note_constexpr_dereferencing_null);
1740+
else
1741+
Info.FFDiag(E, diag::note_constexpr_access_null) << AK;
17371742
});
17381743
}
17391744

@@ -4305,7 +4310,10 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
43054310
}
43064311

43074312
if (!LVal.Base) {
4308-
Info.FFDiag(E, diag::note_constexpr_access_null) << AK;
4313+
if (AK == AccessKinds::AK_Dereference)
4314+
Info.FFDiag(E, diag::note_constexpr_dereferencing_null);
4315+
else
4316+
Info.FFDiag(E, diag::note_constexpr_access_null) << AK;
43094317
return CompleteObject();
43104318
}
43114319

@@ -4407,8 +4415,9 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
44074415
ConstexprVar = VD->isConstexpr();
44084416

44094417
// Unless we're looking at a local variable or argument in a constexpr call,
4410-
// the variable we're reading must be const.
4411-
if (!Frame) {
4418+
// the variable we're reading must be const (unless we are binding to a
4419+
// reference).
4420+
if (AK != clang::AK_Dereference && !Frame) {
44124421
if (IsAccess && isa<ParmVarDecl>(VD)) {
44134422
// Access of a parameter that's not associated with a frame isn't going
44144423
// to work out, but we can leave it to evaluateVarDeclInit to provide a
@@ -4472,12 +4481,16 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
44724481
}
44734482
}
44744483

4475-
if (!evaluateVarDeclInit(Info, E, VD, Frame, LVal.getLValueVersion(), BaseVal))
4484+
// When binding to a reference, the variable does not need to be constexpr
4485+
// or have constant initalization.
4486+
if (AK != clang::AK_Dereference &&
4487+
!evaluateVarDeclInit(Info, E, VD, Frame, LVal.getLValueVersion(),
4488+
BaseVal))
44764489
return CompleteObject();
44774490
// If evaluateVarDeclInit sees a constexpr-unknown variable, it returns
44784491
// a null BaseVal. Any constexpr-unknown variable seen here is an error:
44794492
// we can't access a constexpr-unknown object.
4480-
if (!BaseVal) {
4493+
if (AK != clang::AK_Dereference && !BaseVal) {
44814494
Info.FFDiag(E, diag::note_constexpr_access_unknown_variable, 1)
44824495
<< AK << VD;
44834496
Info.Note(VD->getLocation(), diag::note_declared_at);
@@ -4491,7 +4504,10 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
44914504
}
44924505
return CompleteObject(LVal.Base, &(*Alloc)->Value,
44934506
LVal.Base.getDynamicAllocType());
4494-
} else {
4507+
}
4508+
// When binding to a reference, the variable does not need to be
4509+
// within its lifetime.
4510+
else if (AK != clang::AK_Dereference) {
44954511
const Expr *Base = LVal.Base.dyn_cast<const Expr*>();
44964512

44974513
if (!Frame) {
@@ -4572,7 +4588,7 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
45724588
NoteLValueLocation(Info, LVal.Base);
45734589
return CompleteObject();
45744590
}
4575-
} else {
4591+
} else if (AK != clang::AK_Dereference) {
45764592
BaseVal = Frame->getTemporary(Base, LVal.Base.getVersion());
45774593
assert(BaseVal && "missing value for temporary");
45784594
}
@@ -5200,6 +5216,29 @@ enum EvalStmtResult {
52005216
ESR_CaseNotFound
52015217
};
52025218
}
5219+
/// Evaluates the initializer of a reference.
5220+
static bool EvaluateInitForDeclOfReferenceType(EvalInfo &Info,
5221+
const ValueDecl *D,
5222+
const Expr *Init, LValue &Result,
5223+
APValue &Val) {
5224+
assert(Init->isGLValue() && D->getType()->isReferenceType());
5225+
// A reference is an lvalue.
5226+
if (!EvaluateLValue(Init, Result, Info))
5227+
return false;
5228+
// [C++26][decl.ref]
5229+
// The object designated by such a glvalue can be outside its lifetime
5230+
// Because a null pointer value or a pointer past the end of an object
5231+
// does not point to an object, a reference in a well-defined program cannot
5232+
// refer to such things;
5233+
if (!Result.Designator.Invalid && Result.Designator.isOnePastTheEnd()) {
5234+
Info.FFDiag(Init, diag::note_constexpr_access_past_end) << AK_Dereference;
5235+
return false;
5236+
}
5237+
5238+
// Save the result.
5239+
Result.moveInto(Val);
5240+
return true;
5241+
}
52035242

52045243
static bool EvaluateVarDecl(EvalInfo &Info, const VarDecl *VD) {
52055244
if (VD->isInvalidDecl())
@@ -5221,7 +5260,11 @@ static bool EvaluateVarDecl(EvalInfo &Info, const VarDecl *VD) {
52215260
if (InitE->isValueDependent())
52225261
return false;
52235262

5224-
if (!EvaluateInPlace(Val, Info, Result, InitE)) {
5263+
// For references to objects, check they do not designate a one-past-the-end
5264+
// object.
5265+
if (VD->getType()->isReferenceType()) {
5266+
return EvaluateInitForDeclOfReferenceType(Info, VD, InitE, Result, Val);
5267+
} else if (!EvaluateInPlace(Val, Info, Result, InitE)) {
52255268
// Wipe out any partially-computed value, to allow tracking that this
52265269
// evaluation failed.
52275270
Val = APValue();
@@ -6851,9 +6894,18 @@ static bool HandleConstructorCall(const Expr *E, const LValue &This,
68516894
ThisOverrideRAII ThisOverride(*Info.CurrentCall, &SubobjectParent,
68526895
isa<CXXDefaultInitExpr>(Init));
68536896
FullExpressionRAII InitScope(Info);
6854-
if (!EvaluateInPlace(*Value, Info, Subobject, Init) ||
6855-
(FD && FD->isBitField() &&
6856-
!truncateBitfieldValue(Info, Init, *Value, FD))) {
6897+
if (FD && FD->getType()->isReferenceType() &&
6898+
!FD->getType()->isFunctionReferenceType()) {
6899+
LValue Result;
6900+
if (!EvaluateInitForDeclOfReferenceType(Info, FD, Init, Result,
6901+
*Value)) {
6902+
if (!Info.noteFailure())
6903+
return false;
6904+
Success = false;
6905+
}
6906+
} else if (!EvaluateInPlace(*Value, Info, Subobject, Init) ||
6907+
(FD && FD->isBitField() &&
6908+
!truncateBitfieldValue(Info, Init, *Value, FD))) {
68576909
// If we're checking for a potential constant expression, evaluate all
68586910
// initializers even if some of them fail.
68596911
if (!Info.noteFailure())
@@ -9287,7 +9339,13 @@ bool LValueExprEvaluator::VisitArraySubscriptExpr(const ArraySubscriptExpr *E) {
92879339
}
92889340

92899341
bool LValueExprEvaluator::VisitUnaryDeref(const UnaryOperator *E) {
9290-
return evaluatePointer(E->getSubExpr(), Result);
9342+
bool Success = evaluatePointer(E->getSubExpr(), Result);
9343+
// [C++26][expr.unary.op]
9344+
// If the operand points to an object or function, the result
9345+
// denotes that object or function; otherwise, the behavior is undefined.
9346+
return Success &&
9347+
(!E->getType().getNonReferenceType()->isObjectType() ||
9348+
findCompleteObject(Info, E, AK_Dereference, Result, E->getType()));
92919349
}
92929350

92939351
bool LValueExprEvaluator::VisitUnaryReal(const UnaryOperator *E) {
@@ -10906,9 +10964,17 @@ bool RecordExprEvaluator::VisitCXXParenListOrInitListExpr(
1090610964
isa<CXXDefaultInitExpr>(Init));
1090710965

1090810966
APValue &FieldVal = Result.getStructField(Field->getFieldIndex());
10909-
if (!EvaluateInPlace(FieldVal, Info, Subobject, Init) ||
10910-
(Field->isBitField() && !truncateBitfieldValue(Info, Init,
10911-
FieldVal, Field))) {
10967+
if (Field->getType()->isReferenceType()) {
10968+
LValue Result;
10969+
if (!EvaluateInitForDeclOfReferenceType(Info, Field, Init, Result,
10970+
FieldVal)) {
10971+
if (!Info.noteFailure())
10972+
return false;
10973+
Success = false;
10974+
}
10975+
} else if (!EvaluateInPlace(FieldVal, Info, Subobject, Init) ||
10976+
(Field->isBitField() &&
10977+
!truncateBitfieldValue(Info, Init, FieldVal, Field))) {
1091210978
if (!Info.noteFailure())
1091310979
return false;
1091410980
Success = false;

clang/test/AST/ByteCode/complex.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -396,10 +396,10 @@ namespace ComplexConstexpr {
396396
// both-note {{cannot refer to element 3 of array of 2 elements}}
397397
constexpr _Complex float *p = 0;
398398
constexpr float pr = __real *p; // both-error {{constant expr}} \
399-
// ref-note {{cannot access real component of null}} \
400-
// expected-note {{read of dereferenced null pointer}}
399+
// expected-note {{read of dereferenced null pointer}} \
400+
// ref-note {{dereferencing a null pointer}}
401401
constexpr float pi = __imag *p; // both-error {{constant expr}} \
402-
// ref-note {{cannot access imaginary component of null}}
402+
// ref-note {{dereferencing a null pointer}}
403403
constexpr const _Complex double *q = &test3 + 1;
404404
constexpr double qr = __real *q; // ref-error {{constant expr}} \
405405
// ref-note {{cannot access real component of pointer past the end}}

clang/test/AST/ByteCode/const-eval.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ struct s {
5151
};
5252

5353
EVAL_EXPR(19, ((int)&*(char*)10 == 10 ? 1 : -1));
54+
// ref-error@-1 {{expression is not an integer constant expression}} \
55+
// ref-note@-1 {{dereferencing a null pointer}}
5456

5557
#ifndef NEW_INTERP
5658
EVAL_EXPR(20, __builtin_constant_p(*((int*) 10)));

clang/test/AST/ByteCode/cxx11.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ struct S {
3939
constexpr S s = { 5 };
4040
constexpr const int *p = &s.m + 1;
4141

42-
constexpr const int *np2 = &(*(int(*)[4])nullptr)[0]; // ok
42+
constexpr const int *np2 = &(*(int(*)[4])nullptr)[0];
43+
// ref-error@-1 {{constexpr variable 'np2' must be initialized by a constant expression}} \
44+
// ref-note@-1 {{dereferencing a null pointer is not allowed in a constant expression}}
4345

4446
constexpr int preDec(int x) { // both-error {{never produces a constant expression}}
4547
return --x; // both-note {{subexpression}}

clang/test/AST/ByteCode/records.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,7 @@ namespace DeriveFailures {
413413

414414
constexpr Derived(int i) : OtherVal(i) {} // ref-error {{never produces a constant expression}} \
415415
// both-note {{non-constexpr constructor 'Base' cannot be used in a constant expression}} \
416-
// ref-note {{non-constexpr constructor 'Base' cannot be used in a constant expression}}
416+
// ref-note {{non-constexpr constructor 'Base' cannot be used in a constant expression}}
417417
};
418418

419419
constexpr Derived D(12); // both-error {{must be initialized by a constant expression}} \
@@ -1660,9 +1660,11 @@ namespace NullptrCast {
16601660
constexpr A *na = nullptr;
16611661
constexpr B *nb = nullptr;
16621662
constexpr A &ra = *nb; // both-error {{constant expression}} \
1663-
// both-note {{cannot access base class of null pointer}}
1663+
// ref-note {{dereferencing a null pointer}} \
1664+
// expected-note {{cannot access base class of null pointer}}
16641665
constexpr B &rb = (B&)*na; // both-error {{constant expression}} \
1665-
// both-note {{cannot access derived class of null pointer}}
1666+
// ref-note {{dereferencing a null pointer}} \
1667+
// expected-note {{cannot access derived class of null pointer}}
16661668
constexpr bool test() {
16671669
auto a = (A*)(B*)nullptr;
16681670

@@ -1740,7 +1742,7 @@ namespace CtorOfInvalidClass {
17401742
#if __cplusplus >= 202002L
17411743
template <typename T, auto Q>
17421744
concept ReferenceOf = Q;
1743-
/// This calls a valid and constexpr copy constructor of InvalidCtor,
1745+
/// This calls a valid and constexpr copy constructor of InvalidCtor,
17441746
/// but should still be rejected.
17451747
template<ReferenceOf<InvalidCtor> auto R, typename Rep> int F; // both-error {{non-type template argument is not a constant expression}}
17461748
#endif

clang/test/CXX/drs/cwg14xx.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ void f() {
107107
constexpr int p = &*a;
108108
// since-cxx11-error@-1 {{cannot initialize a variable of type 'const int' with an rvalue of type 'A *'}}
109109
constexpr A *p2 = &*a;
110+
// since-cxx11-error@-1 {{constexpr variable 'p2' must be initialized by a constant expression}}
111+
// since-cxx11-note@-2 {{dereferencing a null pointer}}
110112
}
111113

112114
struct A {

clang/test/CXX/expr/expr.const/p2-0x.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -199,15 +199,15 @@ namespace UndefinedBehavior {
199199

200200
constexpr A *na = nullptr;
201201
constexpr B *nb = nullptr;
202-
constexpr A &ra = *nb; // expected-error {{constant expression}} expected-note {{cannot access base class of null pointer}}
203-
constexpr B &rb = (B&)*na; // expected-error {{constant expression}} expected-note {{cannot access derived class of null pointer}}
202+
constexpr A &ra = *nb; // expected-error {{constant expression}} expected-note {{dereferencing a null pointer}}
203+
constexpr B &rb = (B&)*na; // expected-error {{constant expression}} expected-note {{dereferencing a null pointer}}
204204
static_assert((A*)nb == 0, "");
205205
static_assert((B*)na == 0, "");
206206
constexpr const int &nf = nb->n; // expected-error {{constant expression}} expected-note {{cannot access field of null pointer}}
207207
constexpr const int &mf = nb->m; // expected-error {{constant expression}} expected-note {{cannot access field of null pointer}}
208208
constexpr const int *np1 = (int*)nullptr + 0; // ok
209-
constexpr const int *np2 = &(*(int(*)[4])nullptr)[0]; // ok
210-
constexpr const int *np3 = &(*(int(*)[4])nullptr)[2]; // expected-error {{constant expression}} expected-note {{cannot perform pointer arithmetic on null pointer}}
209+
constexpr const int *np2 = &(*(int(*)[4])nullptr)[0]; // expected-error {{constant expression}} expected-note {{dereferencing a null pointer}}
210+
constexpr const int *np3 = &(*(int(*)[4])nullptr)[2]; // expected-error {{constant expression}} expected-note {{dereferencing a null pointer}}
211211

212212
struct C {
213213
constexpr int f() const { return 0; }
@@ -485,7 +485,7 @@ namespace std {
485485
namespace TypeId {
486486
struct S { virtual void f(); };
487487
constexpr S *p = 0;
488-
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}}
488+
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}}
489489

490490
struct T {} t;
491491
constexpr const std::type_info &ti2 = typeid(t);

0 commit comments

Comments
 (0)