Index: include/clang/AST/Expr.h =================================================================== --- include/clang/AST/Expr.h +++ include/clang/AST/Expr.h @@ -3124,6 +3124,12 @@ return isShiftAssignOp(getOpcode()); } + // Return true if a binary operator using the specified opcode and operands + // would match the 'p = (i8*)nullptr + n' idiom for casting a pointer-sized + // integer to a pointer. + static bool isNullPointerArithmeticExtension(ASTContext &Ctx, Opcode Opc, + Expr *LHS, Expr *RHS); + static bool classof(const Stmt *S) { return S->getStmtClass() >= firstBinaryOperatorConstant && S->getStmtClass() <= lastBinaryOperatorConstant; Index: include/clang/Basic/DiagnosticGroups.td =================================================================== --- include/clang/Basic/DiagnosticGroups.td +++ include/clang/Basic/DiagnosticGroups.td @@ -323,6 +323,7 @@ def ClassVarargs : DiagGroup<"class-varargs", [NonPODVarargs]>; def : DiagGroup<"nonportable-cfstrings">; def NonVirtualDtor : DiagGroup<"non-virtual-dtor">; +def NullPointerArithmetic : DiagGroup<"null-pointer-arithmetic">; def : DiagGroup<"effc++", [NonVirtualDtor]>; def OveralignedType : DiagGroup<"over-aligned">; def AlignedAllocationUnavailable : DiagGroup<"aligned-allocation-unavailable">; @@ -689,7 +690,8 @@ SemiBeforeMethodBody, MissingMethodReturnType, SignCompare, - UnusedParameter + UnusedParameter, + NullPointerArithmetic ]>; def Most : DiagGroup<"most", [ Index: include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- include/clang/Basic/DiagnosticSemaKinds.td +++ include/clang/Basic/DiagnosticSemaKinds.td @@ -5483,6 +5483,9 @@ def warn_sub_ptr_zero_size_types : Warning< "subtraction of pointers to type %0 of zero size has undefined behavior">, InGroup; +def warn_pointer_arith_null_ptr : Warning< + "performing pointer arithmetic on a null pointer has undefined behavior%select{| if the offset is nonzero}0">, + InGroup, DefaultIgnore; def warn_floatingpoint_eq : Warning< "comparing floating point with == or != is unsafe">, @@ -6025,6 +6028,9 @@ "arithmetic on%select{ a|}0 pointer%select{|s}0 to%select{ the|}2 function " "type%select{|s}2 %1%select{| and %3}2 is a GNU extension">, InGroup; +def ext_gnu_null_ptr_arith : Extension< + "arithmetic on a null pointer treated as a cast from integer to pointer is a GNU extension">, + InGroup; def err_readonly_message_assignment : Error< "assigning to 'readonly' return result of an Objective-C message not allowed">; def ext_integer_increment_complex : Extension< Index: lib/AST/Expr.cpp =================================================================== --- lib/AST/Expr.cpp +++ lib/AST/Expr.cpp @@ -1829,6 +1829,45 @@ return OverOps[Opc]; } +bool BinaryOperator::isNullPointerArithmeticExtension(ASTContext &Ctx, + Opcode Opc, + Expr *LHS, Expr *RHS) { + if (Opc != BO_Add) + return false; + + // Check that we have one pointer and one integer operand. + Expr *PExp; + Expr *IExp; + if (LHS->getType()->isPointerType()) { + if (!RHS->getType()->isIntegerType()) + return false; + PExp = LHS; + IExp = RHS; + } else if (RHS->getType()->isPointerType()) { + if (!LHS->getType()->isIntegerType()) + return false; + PExp = RHS; + IExp = LHS; + } else { + return false; + } + + // Check that the pointer is a nullptr. + if (!PExp->IgnoreParenCasts() + ->isNullPointerConstant(Ctx, Expr::NPC_ValueDependentIsNotNull)) + return false; + + // Check that the pointee type is char-sized. + const PointerType *PTy = PExp->getType()->getAs(); + if (!PTy || !PTy->getPointeeType()->isCharType()) + return false; + + // Check that the integer type is pointer-sized. + if (Ctx.getTypeSize(IExp->getType()) != Ctx.getTypeSize(PExp->getType())) + return false; + + return true; +} InitListExpr::InitListExpr(const ASTContext &C, SourceLocation lbraceloc, ArrayRef initExprs, SourceLocation rbraceloc) : Expr(InitListExprClass, QualType(), VK_RValue, OK_Ordinary, false, false, Index: lib/CodeGen/CGExprScalar.cpp =================================================================== --- lib/CodeGen/CGExprScalar.cpp +++ lib/CodeGen/CGExprScalar.cpp @@ -2671,6 +2671,30 @@ unsigned width = cast(index->getType())->getBitWidth(); auto &DL = CGF.CGM.getDataLayout(); auto PtrTy = cast(pointer->getType()); + + // Some versions of glibc and gcc use idioms (particularly in their malloc + // routines) that add a pointer-sized integer (known to be a pointer value) + // to a null pointer in order to cast the value back to an integer or as + // part of a pointer alignment algorithm. This is undefined behavior, but + // we'd like to be able to compile programs that use it. + // + // Normally, we'd generate a GEP with a null-pointer base here in response + // to that code, but it's also UB to dereference a pointer created that + // way. Instead (as an acknowledged hack to tolerate the idiom) we will + // generate a direct cast of the integer value to a pointer. + // + // The idiom (p = nullptr + N) is not met if any of the following are true: + // + // The operation is subtraction. + // The index is not pointer-sized. + // The pointer type is not byte-sized. + // + if (BinaryOperator::isNullPointerArithmeticExtension(CGF.getContext(), + op.Opcode, + expr->getLHS(), + expr->getRHS())) + return CGF.Builder.CreateIntToPtr(index, pointer->getType()); + if (width != DL.getTypeSizeInBits(PtrTy)) { // Zero-extend or sign-extend the pointer value according to // whether the index is signed or not. Index: lib/Sema/SemaExpr.cpp =================================================================== --- lib/Sema/SemaExpr.cpp +++ lib/Sema/SemaExpr.cpp @@ -8490,6 +8490,21 @@ << 0 /* one pointer */ << Pointer->getSourceRange(); } +/// \brief Diagnose invalid arithmetic on a null pointer. +/// +/// If \p IsGNUIdiom is true, the operation is using the 'p = (i8*)nullptr + n' +/// idiom, which we recognize as a GNU extension. +/// +static void diagnoseArithmeticOnNullPointer(Sema &S, SourceLocation Loc, + Expr *Pointer, bool IsGNUIdiom) { + if (IsGNUIdiom) + S.Diag(Loc, diag::ext_gnu_null_ptr_arith) + << Pointer->getSourceRange(); + else + S.Diag(Loc, diag::warn_pointer_arith_null_ptr) + << S.getLangOpts().CPlusPlus << Pointer->getSourceRange(); +} + /// \brief Diagnose invalid arithmetic on two function pointers. static void diagnoseArithmeticOnTwoFunctionPointers(Sema &S, SourceLocation Loc, Expr *LHS, Expr *RHS) { @@ -8784,6 +8799,21 @@ if (!IExp->getType()->isIntegerType()) return InvalidOperands(Loc, LHS, RHS); + // Adding to a null pointer results in undefined behavior. + if (PExp->IgnoreParenCasts()->isNullPointerConstant( + Context, Expr::NPC_ValueDependentIsNotNull)) { + // In C++ adding zero to a null pointer is defined. + llvm::APSInt KnownVal; + if (!getLangOpts().CPlusPlus || + (!IExp->isValueDependent() && + (!IExp->EvaluateAsInt(KnownVal, Context) || KnownVal != 0))) { + // Check the conditions to see if this is the 'p = nullptr + n' idiom. + bool IsGNUIdiom = BinaryOperator::isNullPointerArithmeticExtension( + Context, BO_Add, PExp, IExp); + diagnoseArithmeticOnNullPointer(*this, Loc, PExp, IsGNUIdiom); + } + } + if (!checkArithmeticOpPointerOperand(*this, Loc, PExp)) return QualType(); @@ -8845,6 +8875,20 @@ // The result type of a pointer-int computation is the pointer type. if (RHS.get()->getType()->isIntegerType()) { + // Subtracting from a null pointer should produce a warning. + // The last argument to the diagnose call says this doesn't match the + // GNU int-to-pointer idiom. + if (LHS.get()->IgnoreParenCasts()->isNullPointerConstant(Context, + Expr::NPC_ValueDependentIsNotNull)) { + // In C++ adding zero to a null pointer is defined. + llvm::APSInt KnownVal; + if (!getLangOpts().CPlusPlus || + (!RHS.get()->isValueDependent() && + (!RHS.get()->EvaluateAsInt(KnownVal, Context) || KnownVal != 0))) { + diagnoseArithmeticOnNullPointer(*this, Loc, LHS.get(), false); + } + } + if (!checkArithmeticOpPointerOperand(*this, Loc, LHS.get())) return QualType(); @@ -8880,6 +8924,8 @@ LHS.get(), RHS.get())) return QualType(); + // FIXME: Add warnings for nullptr - ptr. + // The pointee type may have zero size. As an extension, a structure or // union may have zero size or an array may have zero length. In this // case subtraction does not make sense. Index: test/CodeGen/nullptr-arithmetic.c =================================================================== --- test/CodeGen/nullptr-arithmetic.c +++ test/CodeGen/nullptr-arithmetic.c @@ -0,0 +1,42 @@ +// RUN: %clang_cc1 -S %s -emit-llvm -o - | FileCheck %s + +#include + +// This test is meant to verify code that handles the 'p = nullptr + n' idiom +// used by some versions of glibc and gcc. This is undefined behavior but +// it is intended there to act like a conversion from a pointer-sized integer +// to a pointer, and we would like to tolerate that. + +#define NULLPTRI8 ((int8_t*)0) + +// This should get the inttoptr instruction. +int8_t *test1(intptr_t n) { + return NULLPTRI8 + n; +} +// CHECK-LABEL: test1 +// CHECK: inttoptr +// CHECK-NOT: getelementptr + +// This doesn't meet the idiom because the offset type isn't pointer-sized. +int8_t *test2(int8_t n) { + return NULLPTRI8 + n; +} +// CHECK-LABEL: test2 +// CHECK: getelementptr +// CHECK-NOT: inttoptr + +// This doesn't meet the idiom because the element type is larger than a byte. +int16_t *test3(intptr_t n) { + return (int16_t*)0 + n; +} +// CHECK-LABEL: test3 +// CHECK: getelementptr +// CHECK-NOT: inttoptr + +// This doesn't meet the idiom because the offset is subtracted. +int8_t* test4(intptr_t n) { + return NULLPTRI8 - n; +} +// CHECK-LABEL: test4 +// CHECK: getelementptr +// CHECK-NOT: inttoptr Index: test/Sema/pointer-addition.c =================================================================== --- test/Sema/pointer-addition.c +++ test/Sema/pointer-addition.c @@ -1,4 +1,6 @@ -// RUN: %clang_cc1 %s -fsyntax-only -verify -pedantic -std=c11 +// RUN: %clang_cc1 %s -fsyntax-only -verify -pedantic -Wextra -std=c11 + +#include typedef struct S S; // expected-note 4 {{forward declaration of 'struct S'}} extern _Atomic(S*) e; @@ -20,4 +22,11 @@ d -= 1; // expected-warning {{arithmetic on a pointer to the function type 'void (S *, void *)' (aka 'void (struct S *, void *)') is a GNU extension}} (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}} e++; // expected-error {{arithmetic on a pointer to an incomplete type}} + intptr_t i = (intptr_t)b; + char *f = (char*)0 + i; // expected-warning {{arithmetic on a null pointer treated as a cast from integer to pointer is a GNU extension}} + // Cases that don't match the GNU inttoptr idiom get a different warning. + f = (char*)0 - i; // expected-warning {{performing pointer arithmetic on a null pointer has undefined behavior}} + int *g = (int*)0 + i; // expected-warning {{performing pointer arithmetic on a null pointer has undefined behavior}} + unsigned char j = (unsigned char)b; + f = (char*)0 + j; // expected-warning {{performing pointer arithmetic on a null pointer has undefined behavior}} } Index: test/SemaCXX/nullptr-arithmetic.cpp =================================================================== --- test/SemaCXX/nullptr-arithmetic.cpp +++ test/SemaCXX/nullptr-arithmetic.cpp @@ -0,0 +1,29 @@ +// RUN: %clang_cc1 %s -fsyntax-only -verify -pedantic -Wextra -std=c++11 + +#include + +void f(intptr_t offset, int8_t b) { + // A zero offset from a nullptr is OK. + char *f = (char*)nullptr + 0; + int *g = (int*)0 + 0; + f = (char*)nullptr - 0; + g = (int*)nullptr - 0; + // adding other values is undefined. + f = (char*)nullptr + offset; // expected-warning {{arithmetic on a null pointer treated as a cast from integer to pointer is a GNU extension}} + // Cases that don't match the GNU inttoptr idiom get a different warning. + f = (char*)0 - offset; // expected-warning {{performing pointer arithmetic on a null pointer has undefined behavior if the offset is nonzero}} + g = (int*)0 + offset; // expected-warning {{performing pointer arithmetic on a null pointer has undefined behavior if the offset is nonzero}} + f = (char*)0 + b; // expected-warning {{performing pointer arithmetic on a null pointer has undefined behavior if the offset is nonzero}} +} + +// Value-dependent pointer arithmetic should not produce a nullptr warning. +template +char* g(intptr_t offset) { + return P + offset; +} + +// Value-dependent offsets should not produce a nullptr warning. +template +char *h() { + return (char*)nullptr + N; +}