Skip to content

Commit 3d0a540

Browse files
author
Andrew Kaylor
committed
Teach clang to tolerate the 'p = nullptr + n' idiom used by glibc
Differential Revision: https://reviews.llvm.org/D37042 llvm-svn: 313666
1 parent 3177821 commit 3d0a540

File tree

9 files changed

+205
-2
lines changed

9 files changed

+205
-2
lines changed

clang/include/clang/AST/Expr.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3126,6 +3126,12 @@ class BinaryOperator : public Expr {
31263126
return isShiftAssignOp(getOpcode());
31273127
}
31283128

3129+
// Return true if a binary operator using the specified opcode and operands
3130+
// would match the 'p = (i8*)nullptr + n' idiom for casting a pointer-sized
3131+
// integer to a pointer.
3132+
static bool isNullPointerArithmeticExtension(ASTContext &Ctx, Opcode Opc,
3133+
Expr *LHS, Expr *RHS);
3134+
31293135
static bool classof(const Stmt *S) {
31303136
return S->getStmtClass() >= firstBinaryOperatorConstant &&
31313137
S->getStmtClass() <= lastBinaryOperatorConstant;

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ def NonPODVarargs : DiagGroup<"non-pod-varargs">;
338338
def ClassVarargs : DiagGroup<"class-varargs", [NonPODVarargs]>;
339339
def : DiagGroup<"nonportable-cfstrings">;
340340
def NonVirtualDtor : DiagGroup<"non-virtual-dtor">;
341+
def NullPointerArithmetic : DiagGroup<"null-pointer-arithmetic">;
341342
def : DiagGroup<"effc++", [NonVirtualDtor]>;
342343
def OveralignedType : DiagGroup<"over-aligned">;
343344
def AlignedAllocationUnavailable : DiagGroup<"aligned-allocation-unavailable">;
@@ -706,7 +707,8 @@ def Extra : DiagGroup<"extra", [
706707
SemiBeforeMethodBody,
707708
MissingMethodReturnType,
708709
SignCompare,
709-
UnusedParameter
710+
UnusedParameter,
711+
NullPointerArithmetic
710712
]>;
711713

712714
def Most : DiagGroup<"most", [

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5501,6 +5501,12 @@ def err_offsetof_field_of_virtual_base : Error<
55015501
def warn_sub_ptr_zero_size_types : Warning<
55025502
"subtraction of pointers to type %0 of zero size has undefined behavior">,
55035503
InGroup<PointerArith>;
5504+
def warn_pointer_arith_null_ptr : Warning<
5505+
"performing pointer arithmetic on a null pointer has undefined behavior%select{| if the offset is nonzero}0">,
5506+
InGroup<NullPointerArithmetic>, DefaultIgnore;
5507+
def warn_gnu_null_ptr_arith : Warning<
5508+
"arithmetic on a null pointer treated as a cast from integer to pointer is a GNU extension">,
5509+
InGroup<NullPointerArithmetic>, DefaultIgnore;
55045510

55055511
def warn_floatingpoint_eq : Warning<
55065512
"comparing floating point with == or != is unsafe">,

clang/lib/AST/Expr.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1829,6 +1829,45 @@ OverloadedOperatorKind BinaryOperator::getOverloadedOperator(Opcode Opc) {
18291829
return OverOps[Opc];
18301830
}
18311831

1832+
bool BinaryOperator::isNullPointerArithmeticExtension(ASTContext &Ctx,
1833+
Opcode Opc,
1834+
Expr *LHS, Expr *RHS) {
1835+
if (Opc != BO_Add)
1836+
return false;
1837+
1838+
// Check that we have one pointer and one integer operand.
1839+
Expr *PExp;
1840+
Expr *IExp;
1841+
if (LHS->getType()->isPointerType()) {
1842+
if (!RHS->getType()->isIntegerType())
1843+
return false;
1844+
PExp = LHS;
1845+
IExp = RHS;
1846+
} else if (RHS->getType()->isPointerType()) {
1847+
if (!LHS->getType()->isIntegerType())
1848+
return false;
1849+
PExp = RHS;
1850+
IExp = LHS;
1851+
} else {
1852+
return false;
1853+
}
1854+
1855+
// Check that the pointer is a nullptr.
1856+
if (!PExp->IgnoreParenCasts()
1857+
->isNullPointerConstant(Ctx, Expr::NPC_ValueDependentIsNotNull))
1858+
return false;
1859+
1860+
// Check that the pointee type is char-sized.
1861+
const PointerType *PTy = PExp->getType()->getAs<PointerType>();
1862+
if (!PTy || !PTy->getPointeeType()->isCharType())
1863+
return false;
1864+
1865+
// Check that the integer type is pointer-sized.
1866+
if (Ctx.getTypeSize(IExp->getType()) != Ctx.getTypeSize(PExp->getType()))
1867+
return false;
1868+
1869+
return true;
1870+
}
18321871
InitListExpr::InitListExpr(const ASTContext &C, SourceLocation lbraceloc,
18331872
ArrayRef<Expr*> initExprs, SourceLocation rbraceloc)
18341873
: Expr(InitListExprClass, QualType(), VK_RValue, OK_Ordinary, false, false,

clang/lib/CodeGen/CGExprScalar.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2678,6 +2678,30 @@ static Value *emitPointerArithmetic(CodeGenFunction &CGF,
26782678
unsigned width = cast<llvm::IntegerType>(index->getType())->getBitWidth();
26792679
auto &DL = CGF.CGM.getDataLayout();
26802680
auto PtrTy = cast<llvm::PointerType>(pointer->getType());
2681+
2682+
// Some versions of glibc and gcc use idioms (particularly in their malloc
2683+
// routines) that add a pointer-sized integer (known to be a pointer value)
2684+
// to a null pointer in order to cast the value back to an integer or as
2685+
// part of a pointer alignment algorithm. This is undefined behavior, but
2686+
// we'd like to be able to compile programs that use it.
2687+
//
2688+
// Normally, we'd generate a GEP with a null-pointer base here in response
2689+
// to that code, but it's also UB to dereference a pointer created that
2690+
// way. Instead (as an acknowledged hack to tolerate the idiom) we will
2691+
// generate a direct cast of the integer value to a pointer.
2692+
//
2693+
// The idiom (p = nullptr + N) is not met if any of the following are true:
2694+
//
2695+
// The operation is subtraction.
2696+
// The index is not pointer-sized.
2697+
// The pointer type is not byte-sized.
2698+
//
2699+
if (BinaryOperator::isNullPointerArithmeticExtension(CGF.getContext(),
2700+
op.Opcode,
2701+
expr->getLHS(),
2702+
expr->getRHS()))
2703+
return CGF.Builder.CreateIntToPtr(index, pointer->getType());
2704+
26812705
if (width != DL.getTypeSizeInBits(PtrTy)) {
26822706
// Zero-extend or sign-extend the pointer value according to
26832707
// whether the index is signed or not.

clang/lib/Sema/SemaExpr.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8572,6 +8572,21 @@ static void diagnoseArithmeticOnVoidPointer(Sema &S, SourceLocation Loc,
85728572
<< 0 /* one pointer */ << Pointer->getSourceRange();
85738573
}
85748574

8575+
/// \brief Diagnose invalid arithmetic on a null pointer.
8576+
///
8577+
/// If \p IsGNUIdiom is true, the operation is using the 'p = (i8*)nullptr + n'
8578+
/// idiom, which we recognize as a GNU extension.
8579+
///
8580+
static void diagnoseArithmeticOnNullPointer(Sema &S, SourceLocation Loc,
8581+
Expr *Pointer, bool IsGNUIdiom) {
8582+
if (IsGNUIdiom)
8583+
S.Diag(Loc, diag::warn_gnu_null_ptr_arith)
8584+
<< Pointer->getSourceRange();
8585+
else
8586+
S.Diag(Loc, diag::warn_pointer_arith_null_ptr)
8587+
<< S.getLangOpts().CPlusPlus << Pointer->getSourceRange();
8588+
}
8589+
85758590
/// \brief Diagnose invalid arithmetic on two function pointers.
85768591
static void diagnoseArithmeticOnTwoFunctionPointers(Sema &S, SourceLocation Loc,
85778592
Expr *LHS, Expr *RHS) {
@@ -8866,6 +8881,21 @@ QualType Sema::CheckAdditionOperands(ExprResult &LHS, ExprResult &RHS,
88668881
if (!IExp->getType()->isIntegerType())
88678882
return InvalidOperands(Loc, LHS, RHS);
88688883

8884+
// Adding to a null pointer results in undefined behavior.
8885+
if (PExp->IgnoreParenCasts()->isNullPointerConstant(
8886+
Context, Expr::NPC_ValueDependentIsNotNull)) {
8887+
// In C++ adding zero to a null pointer is defined.
8888+
llvm::APSInt KnownVal;
8889+
if (!getLangOpts().CPlusPlus ||
8890+
(!IExp->isValueDependent() &&
8891+
(!IExp->EvaluateAsInt(KnownVal, Context) || KnownVal != 0))) {
8892+
// Check the conditions to see if this is the 'p = nullptr + n' idiom.
8893+
bool IsGNUIdiom = BinaryOperator::isNullPointerArithmeticExtension(
8894+
Context, BO_Add, PExp, IExp);
8895+
diagnoseArithmeticOnNullPointer(*this, Loc, PExp, IsGNUIdiom);
8896+
}
8897+
}
8898+
88698899
if (!checkArithmeticOpPointerOperand(*this, Loc, PExp))
88708900
return QualType();
88718901

@@ -8927,6 +8957,20 @@ QualType Sema::CheckSubtractionOperands(ExprResult &LHS, ExprResult &RHS,
89278957

89288958
// The result type of a pointer-int computation is the pointer type.
89298959
if (RHS.get()->getType()->isIntegerType()) {
8960+
// Subtracting from a null pointer should produce a warning.
8961+
// The last argument to the diagnose call says this doesn't match the
8962+
// GNU int-to-pointer idiom.
8963+
if (LHS.get()->IgnoreParenCasts()->isNullPointerConstant(Context,
8964+
Expr::NPC_ValueDependentIsNotNull)) {
8965+
// In C++ adding zero to a null pointer is defined.
8966+
llvm::APSInt KnownVal;
8967+
if (!getLangOpts().CPlusPlus ||
8968+
(!RHS.get()->isValueDependent() &&
8969+
(!RHS.get()->EvaluateAsInt(KnownVal, Context) || KnownVal != 0))) {
8970+
diagnoseArithmeticOnNullPointer(*this, Loc, LHS.get(), false);
8971+
}
8972+
}
8973+
89308974
if (!checkArithmeticOpPointerOperand(*this, Loc, LHS.get()))
89318975
return QualType();
89328976

@@ -8962,6 +9006,8 @@ QualType Sema::CheckSubtractionOperands(ExprResult &LHS, ExprResult &RHS,
89629006
LHS.get(), RHS.get()))
89639007
return QualType();
89649008

9009+
// FIXME: Add warnings for nullptr - ptr.
9010+
89659011
// The pointee type may have zero size. As an extension, a structure or
89669012
// union may have zero size or an array may have zero length. In this
89679013
// case subtraction does not make sense.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// RUN: %clang_cc1 -S %s -emit-llvm -o - | FileCheck %s
2+
3+
#include <stdint.h>
4+
5+
// This test is meant to verify code that handles the 'p = nullptr + n' idiom
6+
// used by some versions of glibc and gcc. This is undefined behavior but
7+
// it is intended there to act like a conversion from a pointer-sized integer
8+
// to a pointer, and we would like to tolerate that.
9+
10+
#define NULLPTRI8 ((int8_t*)0)
11+
12+
// This should get the inttoptr instruction.
13+
int8_t *test1(intptr_t n) {
14+
return NULLPTRI8 + n;
15+
}
16+
// CHECK-LABEL: test1
17+
// CHECK: inttoptr
18+
// CHECK-NOT: getelementptr
19+
20+
// This doesn't meet the idiom because the offset type isn't pointer-sized.
21+
int8_t *test2(int8_t n) {
22+
return NULLPTRI8 + n;
23+
}
24+
// CHECK-LABEL: test2
25+
// CHECK: getelementptr
26+
// CHECK-NOT: inttoptr
27+
28+
// This doesn't meet the idiom because the element type is larger than a byte.
29+
int16_t *test3(intptr_t n) {
30+
return (int16_t*)0 + n;
31+
}
32+
// CHECK-LABEL: test3
33+
// CHECK: getelementptr
34+
// CHECK-NOT: inttoptr
35+
36+
// This doesn't meet the idiom because the offset is subtracted.
37+
int8_t* test4(intptr_t n) {
38+
return NULLPTRI8 - n;
39+
}
40+
// CHECK-LABEL: test4
41+
// CHECK: getelementptr
42+
// CHECK-NOT: inttoptr

clang/test/Sema/pointer-addition.c

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
// RUN: %clang_cc1 %s -fsyntax-only -verify -pedantic -std=c11
1+
// RUN: %clang_cc1 %s -fsyntax-only -verify -pedantic -Wextra -std=c11
2+
3+
#include <stdint.h>
24

35
typedef struct S S; // expected-note 4 {{forward declaration of 'struct S'}}
46
extern _Atomic(S*) e;
@@ -20,4 +22,11 @@ void a(S* b, void* c) {
2022
d -= 1; // expected-warning {{arithmetic on a pointer to the function type 'void (S *, void *)' (aka 'void (struct S *, void *)') is a GNU extension}}
2123
(void)(1 + d); // expected-warning {{arithmetic on a pointer to the function type 'void (S *, void *)' (aka 'void (struct S *, void *)') is a GNU extension}}
2224
e++; // expected-error {{arithmetic on a pointer to an incomplete type}}
25+
intptr_t i = (intptr_t)b;
26+
char *f = (char*)0 + i; // expected-warning {{arithmetic on a null pointer treated as a cast from integer to pointer is a GNU extension}}
27+
// Cases that don't match the GNU inttoptr idiom get a different warning.
28+
f = (char*)0 - i; // expected-warning {{performing pointer arithmetic on a null pointer has undefined behavior}}
29+
int *g = (int*)0 + i; // expected-warning {{performing pointer arithmetic on a null pointer has undefined behavior}}
30+
unsigned char j = (unsigned char)b;
31+
f = (char*)0 + j; // expected-warning {{performing pointer arithmetic on a null pointer has undefined behavior}}
2332
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// RUN: %clang_cc1 %s -fsyntax-only -verify -pedantic -Wextra -std=c++11
2+
3+
#include <stdint.h>
4+
5+
void f(intptr_t offset, int8_t b) {
6+
// A zero offset from a nullptr is OK.
7+
char *f = (char*)nullptr + 0;
8+
int *g = (int*)0 + 0;
9+
f = (char*)nullptr - 0;
10+
g = (int*)nullptr - 0;
11+
// adding other values is undefined.
12+
f = (char*)nullptr + offset; // expected-warning {{arithmetic on a null pointer treated as a cast from integer to pointer is a GNU extension}}
13+
// Cases that don't match the GNU inttoptr idiom get a different warning.
14+
f = (char*)0 - offset; // expected-warning {{performing pointer arithmetic on a null pointer has undefined behavior if the offset is nonzero}}
15+
g = (int*)0 + offset; // expected-warning {{performing pointer arithmetic on a null pointer has undefined behavior if the offset is nonzero}}
16+
f = (char*)0 + b; // expected-warning {{performing pointer arithmetic on a null pointer has undefined behavior if the offset is nonzero}}
17+
}
18+
19+
// Value-dependent pointer arithmetic should not produce a nullptr warning.
20+
template<char *P>
21+
char* g(intptr_t offset) {
22+
return P + offset;
23+
}
24+
25+
// Value-dependent offsets should not produce a nullptr warning.
26+
template<intptr_t N>
27+
char *h() {
28+
return (char*)nullptr + N;
29+
}

0 commit comments

Comments
 (0)