Skip to content

Commit b54f67a

Browse files
committed
Diagnose all dereferences of null pointers
1 parent a75587d commit b54f67a

File tree

16 files changed

+164
-40
lines changed

16 files changed

+164
-40
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -896,6 +896,7 @@ Bug Fixes to C++ Support
896896
- Fixed a crash when constant evaluating some explicit object member assignment operators. (#GH142835)
897897
- Fixed an access checking bug when substituting into concepts (#GH115838)
898898
- Fix a bug where private access specifier of overloaded function not respected. (#GH107629)
899+
- Diagnose binding a reference to ``*nullptr`` during constant evaluation. (#GH48665)
899900

900901
Bug Fixes to AST Handling
901902
^^^^^^^^^^^^^^^^^^^^^^^^^

clang/include/clang/Basic/DiagnosticASTKinds.td

Lines changed: 5 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|"

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: 72 additions & 15 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,7 +1559,7 @@ 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

15641565
/// Is this kind of axcess valid on an indeterminate object value?
@@ -1571,6 +1572,7 @@ static bool isValidIndeterminateAccess(AccessKinds AK) {
15711572
return false;
15721573

15731574
case AK_IsWithinLifetime:
1575+
case AK_Dereference:
15741576
case AK_ReadObjectRepresentation:
15751577
case AK_Assign:
15761578
case AK_Construct:
@@ -4424,8 +4426,9 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
44244426
ConstexprVar = VD->isConstexpr();
44254427

44264428
// Unless we're looking at a local variable or argument in a constexpr call,
4427-
// the variable we're reading must be const.
4428-
if (!Frame) {
4429+
// the variable we're reading must be const (unless we are binding to a
4430+
// reference).
4431+
if (AK != clang::AK_Dereference && !Frame) {
44294432
if (IsAccess && isa<ParmVarDecl>(VD)) {
44304433
// Access of a parameter that's not associated with a frame isn't going
44314434
// to work out, but we can leave it to evaluateVarDeclInit to provide a
@@ -4489,7 +4492,11 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
44894492
}
44904493
}
44914494

4492-
if (!evaluateVarDeclInit(Info, E, VD, Frame, LVal.getLValueVersion(), BaseVal))
4495+
// When binding to a reference, the variable does not need to be constexpr
4496+
// or have constant initalization.
4497+
if (AK != clang::AK_Dereference &&
4498+
!evaluateVarDeclInit(Info, E, VD, Frame, LVal.getLValueVersion(),
4499+
BaseVal))
44934500
return CompleteObject();
44944501
} else if (DynamicAllocLValue DA = LVal.Base.dyn_cast<DynamicAllocLValue>()) {
44954502
std::optional<DynAlloc *> Alloc = Info.lookupDynamicAlloc(DA);
@@ -4499,7 +4506,10 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
44994506
}
45004507
return CompleteObject(LVal.Base, &(*Alloc)->Value,
45014508
LVal.Base.getDynamicAllocType());
4502-
} else {
4509+
}
4510+
// When binding to a reference, the variable does not need to be
4511+
// within its lifetime.
4512+
else if (AK != clang::AK_Dereference) {
45034513
const Expr *Base = LVal.Base.dyn_cast<const Expr*>();
45044514

45054515
if (!Frame) {
@@ -4556,7 +4566,7 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
45564566
NoteLValueLocation(Info, LVal.Base);
45574567
return CompleteObject();
45584568
}
4559-
} else {
4569+
} else if (AK != clang::AK_Dereference) {
45604570
BaseVal = Frame->getTemporary(Base, LVal.Base.getVersion());
45614571
assert(BaseVal && "missing value for temporary");
45624572
}
@@ -5221,6 +5231,29 @@ enum EvalStmtResult {
52215231
ESR_CaseNotFound
52225232
};
52235233
}
5234+
/// Evaluates the initializer of a reference.
5235+
static bool EvaluateInitForDeclOfReferenceType(EvalInfo &Info,
5236+
const ValueDecl *D,
5237+
const Expr *Init, LValue &Result,
5238+
APValue &Val) {
5239+
assert(Init->isGLValue() && D->getType()->isReferenceType());
5240+
// A reference is an lvalue
5241+
if (!EvaluateLValue(Init, Result, Info))
5242+
return false;
5243+
// [C++26][decl.ref]
5244+
// The object designated by such a glvalue can be outside its lifetime
5245+
// Because a null pointer value or a pointer past the end of an object
5246+
// does not point to an object, a reference in a well-defined program cannot
5247+
// refer to such things;
5248+
if (!Result.Designator.Invalid && Result.Designator.isOnePastTheEnd()) {
5249+
Info.FFDiag(Init, diag::note_constexpr_access_past_end) << AK_Dereference;
5250+
return false;
5251+
}
5252+
5253+
// save the result
5254+
Result.moveInto(Val);
5255+
return true;
5256+
}
52245257

52255258
static bool EvaluateVarDecl(EvalInfo &Info, const VarDecl *VD) {
52265259
if (VD->isInvalidDecl())
@@ -5242,7 +5275,10 @@ static bool EvaluateVarDecl(EvalInfo &Info, const VarDecl *VD) {
52425275
if (InitE->isValueDependent())
52435276
return false;
52445277

5245-
if (!EvaluateInPlace(Val, Info, Result, InitE)) {
5278+
if (VD->getType()->isReferenceType() &&
5279+
!VD->getType()->isFunctionReferenceType()) {
5280+
return EvaluateInitForDeclOfReferenceType(Info, VD, InitE, Result, Val);
5281+
} else if (!EvaluateInPlace(Val, Info, Result, InitE)) {
52465282
// Wipe out any partially-computed value, to allow tracking that this
52475283
// evaluation failed.
52485284
Val = APValue();
@@ -6883,9 +6919,18 @@ static bool HandleConstructorCall(const Expr *E, const LValue &This,
68836919
ThisOverrideRAII ThisOverride(*Info.CurrentCall, &SubobjectParent,
68846920
isa<CXXDefaultInitExpr>(Init));
68856921
FullExpressionRAII InitScope(Info);
6886-
if (!EvaluateInPlace(*Value, Info, Subobject, Init) ||
6887-
(FD && FD->isBitField() &&
6888-
!truncateBitfieldValue(Info, Init, *Value, FD))) {
6922+
if (FD && FD->getType()->isReferenceType() &&
6923+
!FD->getType()->isFunctionReferenceType()) {
6924+
LValue Result;
6925+
if (!EvaluateInitForDeclOfReferenceType(Info, FD, Init, Result,
6926+
*Value)) {
6927+
if (!Info.noteFailure())
6928+
return false;
6929+
Success = false;
6930+
}
6931+
} else if (!EvaluateInPlace(*Value, Info, Subobject, Init) ||
6932+
(FD && FD->isBitField() &&
6933+
!truncateBitfieldValue(Info, Init, *Value, FD))) {
68896934
// If we're checking for a potential constant expression, evaluate all
68906935
// initializers even if some of them fail.
68916936
if (!Info.noteFailure())
@@ -9293,7 +9338,10 @@ bool LValueExprEvaluator::VisitArraySubscriptExpr(const ArraySubscriptExpr *E) {
92939338
}
92949339

92959340
bool LValueExprEvaluator::VisitUnaryDeref(const UnaryOperator *E) {
9296-
return evaluatePointer(E->getSubExpr(), Result);
9341+
bool Success = evaluatePointer(E->getSubExpr(), Result);
9342+
return Success &&
9343+
(!E->getType().getNonReferenceType()->isObjectType() ||
9344+
findCompleteObject(Info, E, AK_Dereference, Result, E->getType()));
92979345
}
92989346

92999347
bool LValueExprEvaluator::VisitUnaryReal(const UnaryOperator *E) {
@@ -10912,9 +10960,18 @@ bool RecordExprEvaluator::VisitCXXParenListOrInitListExpr(
1091210960
isa<CXXDefaultInitExpr>(Init));
1091310961

1091410962
APValue &FieldVal = Result.getStructField(Field->getFieldIndex());
10915-
if (!EvaluateInPlace(FieldVal, Info, Subobject, Init) ||
10916-
(Field->isBitField() && !truncateBitfieldValue(Info, Init,
10917-
FieldVal, Field))) {
10963+
if (Field->getType()->isReferenceType() &&
10964+
!Field->getType()->isFunctionReferenceType()) {
10965+
LValue Result;
10966+
if (!EvaluateInitForDeclOfReferenceType(Info, Field, Init, Result,
10967+
FieldVal)) {
10968+
if (!Info.noteFailure())
10969+
return false;
10970+
Success = false;
10971+
}
10972+
} else if (!EvaluateInPlace(FieldVal, Info, Subobject, Init) ||
10973+
(Field->isBitField() &&
10974+
!truncateBitfieldValue(Info, Init, FieldVal, Field))) {
1091810975
if (!Info.noteFailure())
1091910976
return false;
1092010977
Success = false;

clang/test/AST/ByteCode/complex.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -396,10 +396,9 @@ 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+
// both-note {{read of dereferenced null pointer}}
401400
constexpr float pi = __imag *p; // both-error {{constant expr}} \
402-
// ref-note {{cannot access imaginary component of null}}
401+
// ref-note {{read of dereferenced null pointer}}
403402
constexpr const _Complex double *q = &test3 + 1;
404403
constexpr double qr = __real *q; // ref-error {{constant expr}} \
405404
// 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 {{read of dereferenced null pointer is not allowed in a constant expression}}
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 {{read of dereferenced 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}} \
@@ -1662,9 +1662,11 @@ namespace NullptrCast {
16621662
constexpr A *na = nullptr;
16631663
constexpr B *nb = nullptr;
16641664
constexpr A &ra = *nb; // both-error {{constant expression}} \
1665-
// both-note {{cannot access base class of null pointer}}
1665+
// ref-note {{read of dereferenced null pointer}} \
1666+
// expected-note {{cannot access base class of null pointer}}
16661667
constexpr B &rb = (B&)*na; // both-error {{constant expression}} \
1667-
// both-note {{cannot access derived class of null pointer}}
1668+
// ref-note {{read of dereferenced null pointer}} \
1669+
// expected-note {{cannot access derived class of null pointer}}
16681670
constexpr bool test() {
16691671
auto a = (A*)(B*)nullptr;
16701672

@@ -1742,7 +1744,7 @@ namespace CtorOfInvalidClass {
17421744
#if __cplusplus >= 202002L
17431745
template <typename T, auto Q>
17441746
concept ReferenceOf = Q;
1745-
/// This calls a valid and constexpr copy constructor of InvalidCtor,
1747+
/// This calls a valid and constexpr copy constructor of InvalidCtor,
17461748
/// but should still be rejected.
17471749
template<ReferenceOf<InvalidCtor> auto R, typename Rep> int F; // both-error {{non-type template argument is not a constant expression}}
17481750
#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@-1 {{read of dereferenced null pointer is not allowed in a constant expression}}
110112
}
111113

112114
struct A {

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

Lines changed: 4 additions & 4 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 {{read of dereferenced null pointer}}
203+
constexpr B &rb = (B&)*na; // expected-error {{constant expression}} expected-note {{read of dereferenced 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 {{read of dereferenced null pointer}}
210+
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}}
211211

212212
struct C {
213213
constexpr int f() const { return 0; }

0 commit comments

Comments
 (0)