Skip to content

[Clang] Diagnose forming references to nullptr #143667

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -896,6 +896,7 @@ Bug Fixes to C++ Support
- Fixed a crash when constant evaluating some explicit object member assignment operators. (#GH142835)
- Fixed an access checking bug when substituting into concepts (#GH115838)
- Fix a bug where private access specifier of overloaded function not respected. (#GH107629)
- Diagnose binding a reference to ``*nullptr`` during constant evaluation. (#GH48665)

Bug Fixes to AST Handling
^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
9 changes: 5 additions & 4 deletions clang/include/clang/Basic/DiagnosticASTKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -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">;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I like "read of". Maybe we should explicitly mention reference binding?

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|"
Expand Down
1 change: 1 addition & 0 deletions clang/lib/AST/ByteCode/State.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ enum AccessKinds {
AK_Construct,
AK_Destroy,
AK_IsWithinLifetime,
AK_Dereference
};

/// The order of this enum is important for diagnostics.
Expand Down
87 changes: 72 additions & 15 deletions clang/lib/AST/ExprConstant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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:
Expand All @@ -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?
Expand All @@ -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:
Expand Down Expand Up @@ -4424,8 +4426,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<ParmVarDecl>(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
Expand Down Expand Up @@ -4489,7 +4492,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();
} else if (DynamicAllocLValue DA = LVal.Base.dyn_cast<DynamicAllocLValue>()) {
std::optional<DynAlloc *> Alloc = Info.lookupDynamicAlloc(DA);
Expand All @@ -4499,7 +4506,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<const Expr*>();

if (!Frame) {
Expand Down Expand Up @@ -4556,7 +4566,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");
}
Expand Down Expand Up @@ -5221,6 +5231,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())
Expand All @@ -5242,7 +5275,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();
Expand Down Expand Up @@ -6883,9 +6919,18 @@ static bool HandleConstructorCall(const Expr *E, const LValue &This,
ThisOverrideRAII ThisOverride(*Info.CurrentCall, &SubobjectParent,
isa<CXXDefaultInitExpr>(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())
Expand Down Expand Up @@ -9293,7 +9338,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) {
Expand Down Expand Up @@ -10912,9 +10960,18 @@ bool RecordExprEvaluator::VisitCXXParenListOrInitListExpr(
isa<CXXDefaultInitExpr>(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;
Expand Down
5 changes: 2 additions & 3 deletions clang/test/AST/ByteCode/complex.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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}}
Expand Down
2 changes: 2 additions & 0 deletions clang/test/AST/ByteCode/const-eval.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)));
Expand Down
4 changes: 3 additions & 1 deletion clang/test/AST/ByteCode/cxx11.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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}}
Expand Down
10 changes: 6 additions & 4 deletions clang/test/AST/ByteCode/records.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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}} \
Expand Down Expand Up @@ -1662,9 +1662,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}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of the new diagnostics seem like regressions.

constexpr bool test() {
auto a = (A*)(B*)nullptr;

Expand Down Expand Up @@ -1742,7 +1744,7 @@ namespace CtorOfInvalidClass {
#if __cplusplus >= 202002L
template <typename T, auto Q>
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<ReferenceOf<InvalidCtor> auto R, typename Rep> int F; // both-error {{non-type template argument is not a constant expression}}
#endif
Expand Down
2 changes: 2 additions & 0 deletions clang/test/CXX/drs/cwg14xx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// since-cxx11-note@-1 {{read of dereferenced null pointer is not allowed in a constant expression}}
// since-cxx11-note@-2 {{read of dereferenced null pointer is not allowed in a constant expression}}

As a consequence, you need to remove the backslash on the previous line.

}

struct A {
Expand Down
8 changes: 4 additions & 4 deletions clang/test/CXX/expr/expr.const/p2-0x.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
5 changes: 4 additions & 1 deletion clang/test/Sema/const-eval.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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)));

Expand Down
4 changes: 2 additions & 2 deletions clang/test/SemaCXX/constant-expression-cxx11.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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}}
Expand Down
Loading
Loading