Skip to content

[clang][Interp] Only diagnose null field access in constant contexts #69223

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

Merged
merged 4 commits into from
Oct 26, 2023
Merged
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
2 changes: 1 addition & 1 deletion clang/lib/AST/Interp/Interp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ bool CheckLive(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
}

bool CheckDummy(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
return !Ptr.isDummy();
return !Ptr.isZero() && !Ptr.isDummy();
}

bool CheckNull(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/AST/Interp/Interp.h
Original file line number Diff line number Diff line change
Expand Up @@ -1155,7 +1155,7 @@ inline bool GetPtrGlobal(InterpState &S, CodePtr OpPC, uint32_t I) {
/// 2) Pushes Pointer.atField(Off) on the stack
inline bool GetPtrField(InterpState &S, CodePtr OpPC, uint32_t Off) {
const Pointer &Ptr = S.Stk.pop<Pointer>();
if (!CheckNull(S, OpPC, Ptr, CSK_Field))
if (S.inConstantContext() && !CheckNull(S, OpPC, Ptr, CSK_Field))
return false;
if (!CheckExtern(S, OpPC, Ptr))
return false;
Expand Down
28 changes: 23 additions & 5 deletions clang/lib/AST/Interp/Pointer.h
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,10 @@ class Pointer {
bool isField() const { return Base != 0 && Base != RootPtrMark; }

/// Accessor for information about the declaration site.
const Descriptor *getDeclDesc() const { return Pointee->Desc; }
const Descriptor *getDeclDesc() const {
assert(Pointee);
return Pointee->Desc;
}
SourceLocation getDeclLoc() const { return getDeclDesc()->getLocation(); }

/// Returns a pointer to the object of which this pointer is a field.
Expand Down Expand Up @@ -296,11 +299,17 @@ class Pointer {
bool isUnion() const;

/// Checks if the storage is extern.
bool isExtern() const { return Pointee->isExtern(); }
bool isExtern() const { return Pointee && Pointee->isExtern(); }
/// Checks if the storage is static.
bool isStatic() const { return Pointee->isStatic(); }
bool isStatic() const {
assert(Pointee);
return Pointee->isStatic();
}
/// Checks if the storage is temporary.
bool isTemporary() const { return Pointee->isTemporary(); }
bool isTemporary() const {
assert(Pointee);
return Pointee->isTemporary();
}
/// Checks if the storage is a static temporary.
bool isStaticTemporary() const { return isStatic() && isTemporary(); }

Expand All @@ -323,7 +332,10 @@ class Pointer {
}

/// Returns the declaration ID.
std::optional<unsigned> getDeclID() const { return Pointee->getDeclID(); }
std::optional<unsigned> getDeclID() const {
assert(Pointee);
return Pointee->getDeclID();
}

/// Returns the byte offset from the start.
unsigned getByteOffset() const {
Expand Down Expand Up @@ -351,6 +363,8 @@ class Pointer {

/// Checks if the index is one past end.
bool isOnePastEnd() const {
if (!Pointee)
return false;
return isElementPastEnd() || getSize() == getOffset();
}

Expand All @@ -360,6 +374,7 @@ class Pointer {
/// Dereferences the pointer, if it's live.
template <typename T> T &deref() const {
assert(isLive() && "Invalid pointer");
assert(Pointee);
if (isArrayRoot())
return *reinterpret_cast<T *>(Pointee->rawData() + Base +
sizeof(InitMapPtr));
Expand All @@ -370,6 +385,7 @@ class Pointer {
/// Dereferences a primitive element.
template <typename T> T &elem(unsigned I) const {
assert(I < getNumElems());
assert(Pointee);
return reinterpret_cast<T *>(Pointee->data() + sizeof(InitMapPtr))[I];
}

Expand Down Expand Up @@ -431,12 +447,14 @@ class Pointer {
/// Returns a descriptor at a given offset.
InlineDescriptor *getDescriptor(unsigned Offset) const {
assert(Offset != 0 && "Not a nested pointer");
assert(Pointee);
return reinterpret_cast<InlineDescriptor *>(Pointee->rawData() + Offset) -
1;
}

/// Returns a reference to the InitMapPtr which stores the initialization map.
InitMapPtr &getInitMap() const {
assert(Pointee);
return *reinterpret_cast<InitMapPtr *>(Pointee->rawData() + Base);
}

Expand Down
14 changes: 14 additions & 0 deletions clang/test/AST/Interp/c.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
// RUN: %clang_cc1 -verify=ref -std=c11 %s
// RUN: %clang_cc1 -pedantic -verify=pedantic-ref -std=c11 %s

typedef __INTPTR_TYPE__ intptr_t;

_Static_assert(1, "");
_Static_assert(0 != 1, "");
_Static_assert(1.0 == 1.0, ""); // pedantic-ref-warning {{not an integer constant expression}} \
Expand Down Expand Up @@ -67,3 +69,15 @@ _Static_assert(&Test50 != (void*)0, ""); // ref-warning {{always true}} \
// expected-warning {{always true}} \
// pedantic-expected-warning {{always true}} \
// pedantic-expected-warning {{is a GNU extension}}

struct y {int x,y;};
int a2[(intptr_t)&((struct y*)0)->y]; // expected-warning {{folded to constant array}} \
// pedantic-expected-warning {{folded to constant array}} \
// ref-warning {{folded to constant array}} \
// pedantic-ref-warning {{folded to constant array}}

const struct y *yy = (struct y*)0;
const intptr_t L = (intptr_t)(&(yy->y)); // expected-error {{not a compile-time constant}} \
// pedantic-expected-error {{not a compile-time constant}} \
// ref-error {{not a compile-time constant}} \
// pedantic-ref-error {{not a compile-time constant}}
33 changes: 33 additions & 0 deletions clang/test/AST/Interp/records.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1102,3 +1102,36 @@ namespace DelegatingConstructors {
static_assert(d4.a == 10, "");
static_assert(d4.b == 12, "");
}

namespace AccessOnNullptr {
struct F {
int a;
};

constexpr int a() { // expected-error {{never produces a constant expression}} \
// ref-error {{never produces a constant expression}}
F *f = nullptr;

f->a = 0; // expected-note 2{{cannot access field of null pointer}} \
// ref-note 2{{cannot access field of null pointer}}
return f->a;
}
static_assert(a() == 0, ""); // expected-error {{not an integral constant expression}} \
// expected-note {{in call to 'a()'}} \
// ref-error {{not an integral constant expression}} \
// ref-note {{in call to 'a()'}}

constexpr int a2() { // expected-error {{never produces a constant expression}} \
// ref-error {{never produces a constant expression}}
F *f = nullptr;


const int *a = &(f->a); // expected-note 2{{cannot access field of null pointer}} \
// ref-note 2{{cannot access field of null pointer}}
return f->a;
}
static_assert(a2() == 0, ""); // expected-error {{not an integral constant expression}} \
// expected-note {{in call to 'a2()'}} \
// ref-error {{not an integral constant expression}} \
// ref-note {{in call to 'a2()'}}
}