Skip to content

Commit 0f6ff60

Browse files
committed
[Clang][CodeGen] Do not set inbounds flag for struct GEP with null base pointers
1 parent c048073 commit 0f6ff60

File tree

5 files changed

+105
-12
lines changed

5 files changed

+105
-12
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@ Potentially Breaking Changes
4242
C/C++ Language Potentially Breaking Changes
4343
-------------------------------------------
4444

45+
- New LLVM optimizations have been implemented that optimize pointer arithmetic on
46+
null pointers more aggressively. As part of this, clang has implemented a special
47+
case for old-style offsetof idioms like ``((int)(&(((struct S *)0)->field)))``, to
48+
ensure they are not caught by these optimizations. It is also possible to use
49+
``-fwrapv-pointer`` or ``-fno-delete-null-pointer-checks`` to make pointer arithmetic
50+
on null pointers well-defined. (#GH130734, #GH130742)
51+
4552
C++ Specific Potentially Breaking Changes
4653
-----------------------------------------
4754

clang/lib/CodeGen/CGExpr.cpp

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4785,6 +4785,16 @@ LValue CodeGenFunction::EmitMemberExpr(const MemberExpr *E) {
47854785
}
47864786

47874787
Expr *BaseExpr = E->getBase();
4788+
bool IsInBounds = !getLangOpts().PointerOverflowDefined;
4789+
if (IsInBounds) {
4790+
// Check whether the underlying base pointer is a constant null.
4791+
// If so, we do not set inbounds flag for GEP to avoid breaking some
4792+
// old-style offsetof idioms.
4793+
Expr *UnderlyingBaseExpr = BaseExpr->IgnoreParens();
4794+
while (auto *BaseMemberExpr = dyn_cast<MemberExpr>(UnderlyingBaseExpr))
4795+
UnderlyingBaseExpr = BaseMemberExpr->getBase()->IgnoreParens();
4796+
IsInBounds = !getContext().isSentinelNullExpr(UnderlyingBaseExpr);
4797+
}
47884798
// If this is s.x, emit s as an lvalue. If it is s->x, emit s as a scalar.
47894799
LValue BaseLV;
47904800
if (E->isArrow()) {
@@ -4806,7 +4816,7 @@ LValue CodeGenFunction::EmitMemberExpr(const MemberExpr *E) {
48064816

48074817
NamedDecl *ND = E->getMemberDecl();
48084818
if (auto *Field = dyn_cast<FieldDecl>(ND)) {
4809-
LValue LV = EmitLValueForField(BaseLV, Field);
4819+
LValue LV = EmitLValueForField(BaseLV, Field, IsInBounds);
48104820
setObjCGCLValueClass(getContext(), E, LV);
48114821
if (getLangOpts().OpenMP) {
48124822
// If the member was explicitly marked as nontemporal, mark it as
@@ -4892,12 +4902,15 @@ unsigned CodeGenFunction::getDebugInfoFIndex(const RecordDecl *Rec,
48924902
/// Get the address of a zero-sized field within a record. The resulting
48934903
/// address doesn't necessarily have the right type.
48944904
static Address emitAddrOfZeroSizeField(CodeGenFunction &CGF, Address Base,
4895-
const FieldDecl *Field) {
4905+
const FieldDecl *Field,
4906+
bool IsInBounds) {
48964907
CharUnits Offset = CGF.getContext().toCharUnitsFromBits(
48974908
CGF.getContext().getFieldOffset(Field));
48984909
if (Offset.isZero())
48994910
return Base;
49004911
Base = Base.withElementType(CGF.Int8Ty);
4912+
if (!IsInBounds)
4913+
return CGF.Builder.CreateConstByteGEP(Base, Offset);
49014914
return CGF.Builder.CreateConstInBoundsByteGEP(Base, Offset);
49024915
}
49034916

@@ -4906,16 +4919,16 @@ static Address emitAddrOfZeroSizeField(CodeGenFunction &CGF, Address Base,
49064919
///
49074920
/// The resulting address doesn't necessarily have the right type.
49084921
static Address emitAddrOfFieldStorage(CodeGenFunction &CGF, Address base,
4909-
const FieldDecl *field) {
4922+
const FieldDecl *field, bool IsInBounds) {
49104923
if (isEmptyFieldForLayout(CGF.getContext(), field))
4911-
return emitAddrOfZeroSizeField(CGF, base, field);
4924+
return emitAddrOfZeroSizeField(CGF, base, field, IsInBounds);
49124925

49134926
const RecordDecl *rec = field->getParent();
49144927

49154928
unsigned idx =
49164929
CGF.CGM.getTypes().getCGRecordLayout(rec).getLLVMFieldNo(field);
49174930

4918-
if (CGF.getLangOpts().PointerOverflowDefined)
4931+
if (!IsInBounds)
49194932
return CGF.Builder.CreateConstGEP2_32(base, 0, idx, field->getName());
49204933

49214934
return CGF.Builder.CreateStructGEP(base, idx, field->getName());
@@ -4953,8 +4966,8 @@ static bool hasAnyVptr(const QualType Type, const ASTContext &Context) {
49534966
return false;
49544967
}
49554968

4956-
LValue CodeGenFunction::EmitLValueForField(LValue base,
4957-
const FieldDecl *field) {
4969+
LValue CodeGenFunction::EmitLValueForField(LValue base, const FieldDecl *field,
4970+
bool IsInBounds) {
49584971
LValueBaseInfo BaseInfo = base.getBaseInfo();
49594972

49604973
if (field->isBitField()) {
@@ -5090,7 +5103,7 @@ LValue CodeGenFunction::EmitLValueForField(LValue base,
50905103
if (!IsInPreservedAIRegion &&
50915104
(!getDebugInfo() || !rec->hasAttr<BPFPreserveAccessIndexAttr>()))
50925105
// For structs, we GEP to the field that the record layout suggests.
5093-
addr = emitAddrOfFieldStorage(*this, addr, field);
5106+
addr = emitAddrOfFieldStorage(*this, addr, field, IsInBounds);
50945107
else
50955108
// Remember the original struct field index
50965109
addr = emitPreserveStructAccess(*this, base, addr, field);
@@ -5134,7 +5147,9 @@ CodeGenFunction::EmitLValueForFieldInitialization(LValue Base,
51345147
if (!FieldType->isReferenceType())
51355148
return EmitLValueForField(Base, Field);
51365149

5137-
Address V = emitAddrOfFieldStorage(*this, Base.getAddress(), Field);
5150+
Address V = emitAddrOfFieldStorage(
5151+
*this, Base.getAddress(), Field,
5152+
/*IsInBounds=*/!getLangOpts().PointerOverflowDefined);
51385153

51395154
// Make sure that the address is pointing to the right type.
51405155
llvm::Type *llvmType = ConvertTypeForMem(FieldType);

clang/lib/CodeGen/CodeGenFunction.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4459,7 +4459,8 @@ class CodeGenFunction : public CodeGenTypeCache {
44594459
const ObjCIvarDecl *Ivar);
44604460
llvm::Value *EmitIvarOffsetAsPointerDiff(const ObjCInterfaceDecl *Interface,
44614461
const ObjCIvarDecl *Ivar);
4462-
LValue EmitLValueForField(LValue Base, const FieldDecl *Field);
4462+
LValue EmitLValueForField(LValue Base, const FieldDecl *Field,
4463+
bool IsInBounds = false);
44634464
LValue EmitLValueForLambdaField(const FieldDecl *Field);
44644465
LValue EmitLValueForLambdaField(const FieldDecl *Field,
44654466
llvm::Value *ThisValue);

clang/test/CodeGen/catch-nullptr-and-nonzero-offset-in-offsetof-idiom.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ struct S {
1717

1818
// CHECK-LABEL: @get_offset_of_y_naively(
1919
// CHECK-NEXT: entry:
20-
// CHECK-NEXT: ret i64 ptrtoint (ptr getelementptr inbounds nuw ([[STRUCT_S:%.*]], ptr null, i32 0, i32 1) to i64)
20+
// CHECK-NEXT: ret i64 ptrtoint (ptr getelementptr ([[STRUCT_S:%.*]], ptr null, i32 0, i32 1) to i64)
2121
//
2222
uintptr_t get_offset_of_y_naively(void) {
2323
return ((uintptr_t)(&(((struct S *)0)->y)));

clang/test/CodeGenCXX/catch-nullptr-and-nonzero-offset-in-offsetof-idiom.cpp

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,82 @@ struct S {
1010

1111
// CHECK-LABEL: @_Z23get_offset_of_y_naivelyv(
1212
// CHECK-NEXT: entry:
13-
// CHECK-NEXT: ret i64 ptrtoint (ptr getelementptr inbounds nuw ([[STRUCT_S:%.*]], ptr null, i32 0, i32 1) to i64)
13+
// CHECK-NEXT: ret i64 ptrtoint (ptr getelementptr ([[STRUCT_S:%.*]], ptr null, i32 0, i32 1) to i64)
1414
//
1515
uintptr_t get_offset_of_y_naively() {
1616
return ((uintptr_t)(&(((S *)nullptr)->y)));
1717
}
1818

19+
struct Empty {};
20+
21+
struct T {
22+
int a;
23+
S s;
24+
[[no_unique_address]] Empty e1;
25+
int b;
26+
[[no_unique_address]] Empty e2;
27+
};
28+
29+
// CHECK-LABEL: @_Z30get_offset_of_y_naively_nestedv(
30+
// CHECK-NEXT: entry:
31+
// CHECK-NEXT: ret i64 ptrtoint (ptr getelementptr ([[STRUCT_S:%.*]], ptr getelementptr ([[STRUCT_T:%.*]], ptr null, i32 0, i32 1), i32 0, i32 1) to i64)
32+
//
33+
uintptr_t get_offset_of_y_naively_nested() {
34+
return ((uintptr_t)(&(((T *)nullptr)->s.y)));
35+
}
36+
37+
// CHECK-LABEL: @_Z42get_offset_of_y_naively_nested_with_parensv(
38+
// CHECK-NEXT: entry:
39+
// CHECK-NEXT: ret i64 ptrtoint (ptr getelementptr ([[STRUCT_S:%.*]], ptr getelementptr ([[STRUCT_T:%.*]], ptr null, i32 0, i32 1), i32 0, i32 1) to i64)
40+
//
41+
uintptr_t get_offset_of_y_naively_nested_with_parens() {
42+
return ((uintptr_t)(&((((T *)nullptr)->s).y)));
43+
}
44+
45+
// CHECK-LABEL: @_Z26get_offset_of_zero_storagev(
46+
// CHECK-NEXT: entry:
47+
// CHECK-NEXT: ret i64 ptrtoint (ptr getelementptr (i8, ptr null, i64 16) to i64)
48+
//
49+
uintptr_t get_offset_of_zero_storage() {
50+
return ((uintptr_t)(&(((T *)nullptr)->e2)));
51+
}
52+
53+
namespace std { typedef decltype(__nullptr) nullptr_t; }
54+
// CHECK-LABEL: @_Z29get_offset_of_y_integral_zerov(
55+
// CHECK-NEXT: entry:
56+
// CHECK-NEXT: ret i64 ptrtoint (ptr getelementptr ([[STRUCT_S:%.*]], ptr null, i32 0, i32 1) to i64)
57+
//
58+
uintptr_t get_offset_of_y_integral_zero() {
59+
return ((uintptr_t)(&(((S *)0)->y)));
60+
}
61+
62+
// CHECK-LABEL: @_Z37get_offset_of_y_integral_zero_voidptrv(
63+
// CHECK-NEXT: entry:
64+
// CHECK-NEXT: ret i64 ptrtoint (ptr getelementptr ([[STRUCT_S:%.*]], ptr null, i32 0, i32 1) to i64)
65+
//
66+
uintptr_t get_offset_of_y_integral_zero_voidptr() {
67+
return ((uintptr_t)(&(((S *)(void*)0)->y)));
68+
}
69+
70+
// CHECK-LABEL: @_Z25get_offset_of_y_nullptr_tv(
71+
// CHECK-NEXT: entry:
72+
// CHECK-NEXT: ret i64 ptrtoint (ptr getelementptr ([[STRUCT_S:%.*]], ptr null, i32 0, i32 1) to i64)
73+
//
74+
uintptr_t get_offset_of_y_nullptr_t() {
75+
return ((uintptr_t)(&(((S *)std::nullptr_t{})->y)));
76+
}
77+
78+
// CHECK-LABEL: @_Z32get_offset_of_y_nullptr_constantv(
79+
// CHECK-NEXT: entry:
80+
// CHECK-NEXT: [[NULL:%.*]] = alloca ptr, align 8
81+
// CHECK-NEXT: store ptr null, ptr [[NULL]], align 8
82+
// CHECK-NEXT: ret i64 ptrtoint (ptr getelementptr inbounds nuw ([[STRUCT_S:%.*]], ptr null, i32 0, i32 1) to i64)
83+
//
84+
uintptr_t get_offset_of_y_nullptr_constant() {
85+
constexpr void *null = nullptr;
86+
return ((uintptr_t)(&(((S *)null)->y)));
87+
}
88+
1989
// CHECK-LABEL: @_Z27get_offset_of_y_via_builtinv(
2090
// CHECK-NEXT: entry:
2191
// CHECK-NEXT: ret i64 4

0 commit comments

Comments
 (0)