diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -56,7 +56,15 @@ Non-comprehensive list of changes in this release ------------------------------------------------- -- ... +* As per C++ Standard ``[expr.add]``, applying non-zero offset to ``nullptr`` + (or making non-``nullptr`` a ``nullptr``, by subtracting pointer's integral + value from the pointer itself) is undefined behaviour. + Since `r369789 `_ + (`D66608 `_ ``[InstCombine] icmp eq/ne (gep + inbounds P, Idx..), null -> icmp eq/ne P, null``) LLVM middle-end uses those + guarantees for transformations. If the source contains such UB's, said code + may now be miscompiled. Undefined Behaviour Sanitizer has gained + ``-fsanitize=nullptr-and-nonzero-offset`` check that will catch this UB. New Compiler Flags @@ -203,7 +211,41 @@ Undefined Behavior Sanitizer (UBSan) ------------------------------------ -- ... +- * ``nullptr-and-nonzero-offset`` check was added to catch the cases where + a non-zero offset being applied, either to a ``nullptr``, or the result + of applying of the offset is a ``nullptr``. + As per C++ Standard ``[expr.add]`` that is undefined behaviour. + + .. code-block:: c++ + + #include // for intptr_t + + static char *getelementpointer_inbounds(char *base, unsigned long offset) { + // Potentially UB. + return base + offset; + } + + char *getelementpointer_unsafe(char *base, unsigned long offset) { + // Always apply offset. UB if base is ``nullptr`` and ``offset`` is not + // zero, or if ``base`` is non-``nullptr`` and ``offset`` is + // ``-reinterpret_cast(base)``. + return getelementpointer_inbounds(base, offset); + } + + char *getelementpointer_safe(char *base, unsigned long offset) { + // Cast pointer to integer, perform usual arithmetic addition, + // and cast to pointer. This is legal. + char *computed = + reinterpret_cast(reinterpret_cast(base) + offset); + // If either the pointer becomes non-``nullptr``, or becomes + // ``nullptr``, we must use ``computed`` result. + if (((base == nullptr) && (computed != nullptr)) || + ((base != nullptr) && (computed == nullptr))) + return computed; + // Else we can use ``getelementpointer_inbounds()``. + return getelementpointer_inbounds(base, offset); + } + Core Analysis Improvements ========================== diff --git a/clang/docs/UndefinedBehaviorSanitizer.rst b/clang/docs/UndefinedBehaviorSanitizer.rst --- a/clang/docs/UndefinedBehaviorSanitizer.rst +++ b/clang/docs/UndefinedBehaviorSanitizer.rst @@ -129,6 +129,9 @@ invalid pointers. These checks are made in terms of ``__builtin_object_size``, and consequently may be able to detect more problems at higher optimization levels. + - ``-fsanitize=nullptr-and-nonzero-offset``: Applying non-zero offset to a + pointer, where either the original pointer is ``nullptr``, or the computed + pointer is ``nullptr``. - ``-fsanitize=pointer-overflow``: Performing pointer arithmetic which overflows. - ``-fsanitize=return``: In C++, reaching the end of a @@ -171,6 +174,8 @@ ``implicit-conversion``, and the ``nullability-*`` group of checks. - ``-fsanitize=undefined-trap``: Deprecated alias of ``-fsanitize=undefined``. + - ``-fsanitize=pointer-offsetting``: Enables ``nullptr-and-nonzero-offset`` + and ``pointer-overflow``. - ``-fsanitize=implicit-integer-truncation``: Catches lossy integral conversions. Enables ``implicit-signed-integer-truncation`` and ``implicit-unsigned-integer-truncation``. diff --git a/clang/include/clang/Basic/Sanitizers.def b/clang/include/clang/Basic/Sanitizers.def --- a/clang/include/clang/Basic/Sanitizers.def +++ b/clang/include/clang/Basic/Sanitizers.def @@ -94,7 +94,10 @@ SANITIZER_GROUP("nullability", Nullability, NullabilityArg | NullabilityAssign | NullabilityReturn) SANITIZER("object-size", ObjectSize) +SANITIZER("nullptr-and-nonzero-offset", NullptrAndNonZeroOffset) SANITIZER("pointer-overflow", PointerOverflow) +SANITIZER_GROUP("pointer-offsetting", PointerOffsetting, + NullptrAndNonZeroOffset | PointerOverflow) SANITIZER("return", Return) SANITIZER("returns-nonnull-attribute", ReturnsNonnullAttribute) SANITIZER("shift-base", ShiftBase) @@ -135,9 +138,9 @@ Alignment | Bool | Builtin | ArrayBounds | Enum | FloatCastOverflow | IntegerDivideByZero | NonnullAttribute | Null | ObjectSize | - PointerOverflow | Return | ReturnsNonnullAttribute | Shift | - SignedIntegerOverflow | Unreachable | VLABound | Function | - Vptr) + PointerOffsetting | Return | ReturnsNonnullAttribute | + Shift | SignedIntegerOverflow | Unreachable | VLABound | + Function | Vptr) // -fsanitize=undefined-trap is an alias for -fsanitize=undefined. SANITIZER_GROUP("undefined-trap", UndefinedTrap, Undefined) diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp --- a/clang/lib/CodeGen/CGExprScalar.cpp +++ b/clang/lib/CodeGen/CGExprScalar.cpp @@ -27,6 +27,7 @@ #include "clang/Basic/FixedPoint.h" #include "clang/Basic/TargetInfo.h" #include "llvm/ADT/Optional.h" +#include "llvm/Analysis/ValueTracking.h" #include "llvm/IR/CFG.h" #include "llvm/IR/Constants.h" #include "llvm/IR/DataLayout.h" @@ -4533,33 +4534,32 @@ llvm_unreachable("Unhandled compound assignment operator"); } -Value *CodeGenFunction::EmitCheckedInBoundsGEP(Value *Ptr, - ArrayRef IdxList, - bool SignedIndices, - bool IsSubtraction, - SourceLocation Loc, - const Twine &Name) { - Value *GEPVal = Builder.CreateInBoundsGEP(Ptr, IdxList, Name); +std::pair +CodeGenFunction::EmitGEPOffsetInBytes(Value *BasePtr, Value *GEPVal) { + const auto &DL = CGM.getDataLayout(); - // If the pointer overflow sanitizer isn't enabled, do nothing. - if (!SanOpts.has(SanitizerKind::PointerOverflow)) - return GEPVal; + // The total (signed) byte offset for the GEP. + llvm::Value *TotalOffset = nullptr; - // If the GEP has already been reduced to a constant, leave it be. - if (isa(GEPVal)) - return GEPVal; - - // Only check for overflows in the default address space. - if (GEPVal->getType()->getPointerAddressSpace()) - return GEPVal; + // Was the GEP has already been reduced to a constant? + if (isa(GEPVal)) { + // Compute the offset by casting both pointers to integers and subtracting: + // GEPVal = BasePtr + ptr(Offset) <--> Offset = int(GEPVal) - int(BasePtr) + Value *BasePtr_int = + Builder.CreatePtrToInt(BasePtr, DL.getIntPtrType(BasePtr->getType())); + Value *GEPVal_int = + Builder.CreatePtrToInt(GEPVal, DL.getIntPtrType(GEPVal->getType())); + TotalOffset = Builder.CreateSub(GEPVal_int, BasePtr_int); + return {TotalOffset, /*OffsetOverflows=*/Builder.getFalse()}; + } auto *GEP = cast(GEPVal); + assert(GEP->getPointerOperand() == BasePtr && + "BasePtr must be the the base of the GEP."); assert(GEP->isInBounds() && "Expected inbounds GEP"); - SanitizerScope SanScope(this); - auto &VMContext = getLLVMContext(); - const auto &DL = CGM.getDataLayout(); auto *IntPtrTy = DL.getIntPtrType(GEP->getPointerOperandType()); + auto &VMContext = getLLVMContext(); // Grab references to the signed add/mul overflow intrinsics for intptr_t. auto *Zero = llvm::ConstantInt::getNullValue(IntPtrTy); @@ -4568,8 +4568,6 @@ auto *SMulIntrinsic = CGM.getIntrinsic(llvm::Intrinsic::smul_with_overflow, IntPtrTy); - // The total (signed) byte offset for the GEP. - llvm::Value *TotalOffset = nullptr; // The offset overflow flag - true if the total offset overflows. llvm::Value *OffsetOverflows = Builder.getFalse(); @@ -4627,41 +4625,101 @@ TotalOffset = eval(BO_Add, TotalOffset, LocalOffset); } - // Common case: if the total offset is zero, don't emit a check. - if (TotalOffset == Zero) + return {TotalOffset, OffsetOverflows}; +} + +Value * +CodeGenFunction::EmitCheckedInBoundsGEP(Value *Ptr, ArrayRef IdxList, + bool SignedIndices, bool IsSubtraction, + SourceLocation Loc, const Twine &Name) { + Value *GEPVal = Builder.CreateInBoundsGEP(Ptr, IdxList, Name); + + // If none of the appropriate sanitizers are enabled, do nothing. + if (!SanOpts.hasOneOf(SanitizerKind::PointerOffsetting)) return GEPVal; + const auto &DL = CGM.getDataLayout(); + + SanitizerScope SanScope(this); + llvm::Type *PtrTy = Ptr->getType(); + llvm::Type *IntPtrTy = DL.getIntPtrType(PtrTy); + + // The total (signed) byte offset for the GEP. + llvm::Value *TotalOffset; + // The offset overflow flag - true if the total offset overflows. + llvm::Value *OffsetOverflows; + + std::tie(TotalOffset, OffsetOverflows) = EmitGEPOffsetInBytes(Ptr, GEPVal); + + llvm::Value *Zero = llvm::ConstantInt::getNullValue(IntPtrTy); + + // Common case: if the total offset is zero, don't emit a check. + if (TotalOffset == Zero) { + assert(OffsetOverflows == Builder.getFalse() && + "Assuming that whereever we constant-fold the offset to zero, " + "we also *know* we had no overflow along the way."); + return GEPVal; + } + // Now that we've computed the total offset, add it to the base pointer (with // wrapping semantics). - auto *IntPtr = Builder.CreatePtrToInt(GEP->getPointerOperand(), IntPtrTy); + auto *IntPtr = Builder.CreatePtrToInt(Ptr, IntPtrTy); auto *ComputedGEP = Builder.CreateAdd(IntPtr, TotalOffset); - // The GEP is valid if: - // 1) The total offset doesn't overflow, and - // 2) The sign of the difference between the computed address and the base - // pointer matches the sign of the total offset. - llvm::Value *ValidGEP; - auto *NoOffsetOverflow = Builder.CreateNot(OffsetOverflows); - if (SignedIndices) { - auto *PosOrZeroValid = Builder.CreateICmpUGE(ComputedGEP, IntPtr); - auto *PosOrZeroOffset = Builder.CreateICmpSGE(TotalOffset, Zero); - llvm::Value *NegValid = Builder.CreateICmpULT(ComputedGEP, IntPtr); - ValidGEP = Builder.CreateAnd( - Builder.CreateSelect(PosOrZeroOffset, PosOrZeroValid, NegValid), - NoOffsetOverflow); - } else if (!SignedIndices && !IsSubtraction) { - auto *PosOrZeroValid = Builder.CreateICmpUGE(ComputedGEP, IntPtr); - ValidGEP = Builder.CreateAnd(PosOrZeroValid, NoOffsetOverflow); - } else { - auto *NegOrZeroValid = Builder.CreateICmpULE(ComputedGEP, IntPtr); - ValidGEP = Builder.CreateAnd(NegOrZeroValid, NoOffsetOverflow); + llvm::SmallVector, 2> Checks; + + // Perform nullptr-and-offset check unless the nullptr is defined. + if (SanOpts.has(SanitizerKind::NullptrAndNonZeroOffset) && + !NullPointerIsDefined(Builder.GetInsertBlock()->getParent(), + PtrTy->getPointerAddressSpace())) { + // If the base pointer evaluates to a null pointer value, the only valid + // pointer this inbounds GEP can produce is also a null pointer, so the + // offset must also evaluate to zero. + // Likewise, if we have non-zero base pointer, we can not get null pointer + // as a result, so the offset can not be -intptr_t(BasePtr). + // In other words, both bointers are either null, or both are non-null, + // or the behaviour is undefined. + auto *BaseIsNullptr = Builder.CreateIsNull(Ptr); + auto *ResultIsNullptr = Builder.CreateIsNull(ComputedGEP); + Checks.emplace_back(Builder.CreateICmpEQ(BaseIsNullptr, ResultIsNullptr), + SanitizerKind::NullptrAndNonZeroOffset); } + // Check for overflows unless the GEP got constant-folded, + // and only in the default address space + if (SanOpts.has(SanitizerKind::PointerOverflow) && + !isa(GEPVal) && PtrTy->getPointerAddressSpace() == 0) { + // The GEP is valid if: + // 1) The total offset doesn't overflow, and + // 2) The sign of the difference between the computed address and the base + // pointer matches the sign of the total offset. + llvm::Value *ValidGEP; + auto *NoOffsetOverflow = Builder.CreateNot(OffsetOverflows); + if (SignedIndices) { + auto *PosOrZeroValid = Builder.CreateICmpUGE(ComputedGEP, IntPtr); + auto *PosOrZeroOffset = Builder.CreateICmpSGE(TotalOffset, Zero); + llvm::Value *NegValid = Builder.CreateICmpULT(ComputedGEP, IntPtr); + ValidGEP = Builder.CreateAnd( + Builder.CreateSelect(PosOrZeroOffset, PosOrZeroValid, NegValid), + NoOffsetOverflow); + } else if (!SignedIndices && !IsSubtraction) { + auto *PosOrZeroValid = Builder.CreateICmpUGE(ComputedGEP, IntPtr); + ValidGEP = Builder.CreateAnd(PosOrZeroValid, NoOffsetOverflow); + } else { + auto *NegOrZeroValid = Builder.CreateICmpULE(ComputedGEP, IntPtr); + ValidGEP = Builder.CreateAnd(NegOrZeroValid, NoOffsetOverflow); + } + Checks.emplace_back(ValidGEP, SanitizerKind::PointerOverflow); + } + + // Did we end up producing any checks? + if (Checks.empty()) + return GEPVal; + llvm::Constant *StaticArgs[] = {EmitCheckSourceLocation(Loc)}; // Pass the computed GEP to the runtime to avoid emitting poisoned arguments. llvm::Value *DynamicArgs[] = {IntPtr, ComputedGEP}; - EmitCheck(std::make_pair(ValidGEP, SanitizerKind::PointerOverflow), - SanitizerHandler::PointerOverflow, StaticArgs, DynamicArgs); + EmitCheck(Checks, SanitizerHandler::PointerOverflow, StaticArgs, DynamicArgs); return GEPVal; } diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -4064,6 +4064,13 @@ /// operation is a subtraction. enum { NotSubtraction = false, IsSubtraction = true }; + /// Evaluate given GEPVal, which is either an inbounds GEP, or a constant, + /// and compute the total offset it applies from it's base pointer BasePtr. + /// Returns offset in bytes and a boolean flag whether an overflow happened + /// during evaluation. + std::pair + EmitGEPOffsetInBytes(llvm::Value *BasePtr, llvm::Value *GEPVal); + /// Same as IRBuilder::CreateInBoundsGEP, but additionally emits a check to /// detect undefined behavior when the pointer overflow sanitizer is enabled. /// \p SignedIndices indicates whether any of the GEP indices are signed. diff --git a/clang/test/CodeGen/catch-nullptr-and-nonzero-offset-blacklist.c b/clang/test/CodeGen/catch-nullptr-and-nonzero-offset-blacklist.c new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/catch-nullptr-and-nonzero-offset-blacklist.c @@ -0,0 +1,40 @@ +// RUN: %clang_cc1 -fsanitize=nullptr-and-nonzero-offset -fsanitize-recover=nullptr-and-nonzero-offset -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s -implicit-check-not="call void @__ubsan_handle_pointer_overflow" --check-prefixes=CHECK,CHECK-NULL-IS-INVALID-PTR +// RUN: %clang_cc1 -fno-delete-null-pointer-checks -fsanitize=nullptr-and-nonzero-offset -fsanitize-recover=nullptr-and-nonzero-offset -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s -implicit-check-not="call void @__ubsan_handle_pointer_overflow" --check-prefixes=CHECK,CHECK-NULL-IS-VALID-PTR + +// CHECK-LABEL: @baseline +char *baseline(char *base, unsigned long offset) { + // CHECK-NULL-IS-INVALID-PTR: call void @__ubsan_handle_pointer_overflow( + return base + offset; +} + +// CHECK-LABEL: @blacklist_0 +__attribute__((no_sanitize("undefined"))) char *blacklist_0(char *base, unsigned long offset) { + return base + offset; +} + +// CHECK-LABEL: @blacklist_1 +__attribute__((no_sanitize("pointer-offsetting"))) char *blacklist_1(char *base, unsigned long offset) { + return base + offset; +} + +// CHECK-LABEL: @blacklist_2 +__attribute__((no_sanitize("nullptr-and-nonzero-offset"))) char *blacklist_2(char *base, unsigned long offset) { + return base + offset; +} + +// CHECK-LABEL: @dont_ignore_volatile_ptrs +char *volatile dont_ignore_volatile_ptrs(char *volatile base, unsigned long offset) { + // CHECK-NULL-IS-INVALID-PTR: call void @__ubsan_handle_pointer_overflow( + return base + offset; +} + +// CHECK-LABEL: @dont_ignore_volatiles +volatile char *dont_ignore_volatiles(volatile char *base, unsigned long offset) { + // CHECK-NULL-IS-INVALID-PTR: call void @__ubsan_handle_pointer_overflow( + return base + offset; +} + +// CHECK-LABEL: @ignore_non_default_address_space +__attribute__((address_space(1))) char *ignore_non_default_address_space(__attribute__((address_space(1))) char *base, unsigned long offset) { + return base + offset; +} diff --git a/clang/test/CodeGen/catch-nullptr-and-nonzero-offset.c b/clang/test/CodeGen/catch-nullptr-and-nonzero-offset.c new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/catch-nullptr-and-nonzero-offset.c @@ -0,0 +1,351 @@ +// RUN: %clang_cc1 -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefixes=CHECK,CHECK-NOSANITIZE +// RUN: %clang_cc1 -fsanitize=nullptr-and-nonzero-offset -fno-sanitize-recover=nullptr-and-nonzero-offset -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s -implicit-check-not="call void @__ubsan_handle_pointer_overflow" --check-prefixes=CHECK,CHECK-SANITIZE,CHECK-SANITIZE-ANYRECOVER,CHECK-SANITIZE-NORECOVER,CHECK-SANITIZE-UNREACHABLE +// RUN: %clang_cc1 -fsanitize=nullptr-and-nonzero-offset -fsanitize-recover=nullptr-and-nonzero-offset -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s -implicit-check-not="call void @__ubsan_handle_pointer_overflow" --check-prefixes=CHECK,CHECK-SANITIZE,CHECK-SANITIZE-ANYRECOVER,CHECK-SANITIZE-RECOVER +// RUN: %clang_cc1 -fsanitize=nullptr-and-nonzero-offset -fsanitize-trap=nullptr-and-nonzero-offset -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s -implicit-check-not="call void @__ubsan_handle_pointer_overflow" --check-prefixes=CHECK,CHECK-SANITIZE,CHECK-SANITIZE-TRAP,CHECK-SANITIZE-UNREACHABLE + +// If the base pointer evaluates to a null pointer value, the only valid +// pointer this inbounds GEP can produce is also a null pointer. +// Likewise, if we have non-zero base pointer, we can not get null pointer +// as a result, so the offset can not be -int(BasePtr). + +// So in other words, the offset can not change "null status" of the pointer, +// as in if the pointer was null, it can not become non-null, and vice verse, +// if it was non-null, it can not become null. + +// CHECK-SANITIZE-ANYRECOVER-DAG: @[[LINE_100:.*]] = {{.*}}, i32 100, i32 15 } } +// CHECK-SANITIZE-ANYRECOVER-DAG: @[[LINE_300:.*]] = {{.*}}, i32 300, i32 15 } } +// CHECK-SANITIZE-ANYRECOVER-DAG: @[[LINE_400:.*]] = {{.*}}, i32 400, i32 15 } } +// CHECK-SANITIZE-ANYRECOVER-DAG: @[[LINE_500:.*]] = {{.*}}, i32 500, i32 15 } } +// CHECK-SANITIZE-ANYRECOVER-DAG: @[[LINE_700:.*]] = {{.*}}, i32 700, i32 15 } } +// CHECK-SANITIZE-ANYRECOVER-DAG: @[[LINE_800:.*]] = {{.*}}, i32 800, i32 15 } } +// CHECK-SANITIZE-ANYRECOVER-DAG: @[[LINE_900:.*]] = {{.*}}, i32 900, i32 15 } } +// CHECK-SANITIZE-ANYRECOVER-DAG: @[[LINE_1100:.*]] = {{.*}}, i32 1100, i32 15 } } +// CHECK-SANITIZE-ANYRECOVER-DAG: @[[LINE_1200:.*]] = {{.*}}, i32 1200, i32 15 } } +// CHECK-SANITIZE-ANYRECOVER-DAG: @[[LINE_1300:.*]] = {{.*}}, i32 1300, i32 15 } } +// CHECK-SANITIZE-ANYRECOVER-DAG: @[[LINE_1500:.*]] = {{.*}}, i32 1500, i32 15 } } +// CHECK-SANITIZE-ANYRECOVER-DAG: @[[LINE_1600:.*]] = {{.*}}, i32 1600, i32 15 } } + +// We just don't know, can be bad, can be ok. +char *var_var(char *base, unsigned long offset) { + // CHECK: define i8* @var_var(i8* %[[BASE:.*]], i64 %[[OFFSET:.*]]) + // CHECK-NEXT: [[ENTRY:.*]]: + // CHECK-NEXT: %[[BASE_ADDR:.*]] = alloca i8*, align 8 + // CHECK-NEXT: %[[OFFSET_ADDR:.*]] = alloca i64, align 8 + // CHECK-NEXT: store i8* %[[BASE]], i8** %[[BASE_ADDR]], align 8 + // CHECK-NEXT: store i64 %[[OFFSET]], i64* %[[OFFSET_ADDR]], align 8 + // CHECK-NEXT: %[[BASE_RELOADED:.*]] = load i8*, i8** %[[BASE_ADDR]], align 8 + // CHECK-NEXT: %[[OFFSET_RELOADED:.*]] = load i64, i64* %[[OFFSET_ADDR]], align 8 + // CHECK-NEXT: %[[ADD_PTR:.*]] = getelementptr inbounds i8, i8* %[[BASE_RELOADED]], i64 %[[OFFSET_RELOADED]] + // CHECK-SANITIZE-NEXT: %[[COMPUTED_OFFSET_AGGREGATE:.*]] = call { i64, i1 } @llvm.smul.with.overflow.i64(i64 1, i64 %[[OFFSET_RELOADED]]), !nosanitize + // CHECK-SANITIZE-NEXT: %[[COMPUTED_OFFSET_OVERFLOWED:.*]] = extractvalue { i64, i1 } %[[COMPUTED_OFFSET_AGGREGATE]], 1, !nosanitize + // CHECK-SANITIZE-NEXT: %[[COMPUTED_OFFSET:.*]] = extractvalue { i64, i1 } %[[COMPUTED_OFFSET_AGGREGATE]], 0, !nosanitize + // CHECK-SANITIZE-NEXT: %[[BASE_RELOADED_INT:.*]] = ptrtoint i8* %[[BASE_RELOADED]] to i64, !nosanitize + // CHECK-SANITIZE-NEXT: %[[COMPUTED_GEP:.*]] = add i64 %[[BASE_RELOADED_INT]], %[[COMPUTED_OFFSET]], !nosanitize + // CHECK-SANITIZE-NEXT: %[[BASE_IS_NULLPTR:.*]] = icmp eq i8* %[[BASE_RELOADED]], null, !nosanitize + // CHECK-SANITIZE-NEXT: %[[COMPUTED_GEP_IS_NULL:.*]] = icmp eq i64 %[[COMPUTED_GEP]], 0, !nosanitize + // CHECK-SANITIZE-NEXT: %[[BOTH_POINTERS_ARE_NULL_OR_BOTH_ARE_NONNULL:.*]] = icmp eq i1 %[[BASE_IS_NULLPTR]], %[[COMPUTED_GEP_IS_NULL]], !nosanitize + // CHECK-SANITIZE-NEXT: br i1 %[[BOTH_POINTERS_ARE_NULL_OR_BOTH_ARE_NONNULL]], label %[[CONT:.*]], label %[[HANDLER_POINTER_OVERFLOW:[^,]+]],{{.*}} !nosanitize + // CHECK-SANITIZE: [[HANDLER_POINTER_OVERFLOW]]: + // CHECK-SANITIZE-NORECOVER-NEXT: call void @__ubsan_handle_pointer_overflow_abort(i8* bitcast ({ {{{.*}}} }* @[[LINE_100]] to i8*), i64 %[[BASE_RELOADED_INT]], i64 %[[COMPUTED_GEP]]) + // CHECK-SANITIZE-RECOVER-NEXT: call void @__ubsan_handle_pointer_overflow(i8* bitcast ({ {{{.*}}} }* @[[LINE_100]] to i8*), i64 %[[BASE_RELOADED_INT]], i64 %[[COMPUTED_GEP]]) + // CHECK-SANITIZE-TRAP-NEXT: call void @llvm.trap(){{.*}}, !nosanitize + // CHECK-SANITIZE-UNREACHABLE-NEXT: unreachable, !nosanitize + // CHECK-SANITIZE: [[CONT]]: + // CHECK-NEXT: ret i8* %[[ADD_PTR]] +#line 100 + return base + offset; +} + +// Offset is zero, so okay regardless of the pointer. +char *var_zero_OK(char *base) { + // CHECK: define i8* @var_zero_OK(i8* %[[BASE:.*]]) + // CHECK-NEXT: [[ENTRY:.*]]: + // CHECK-NEXT: %[[BASE_ADDR:.*]] = alloca i8*, align 8 + // CHECK-NEXT: store i8* %[[BASE]], i8** %[[BASE_ADDR]], align 8 + // CHECK-NEXT: %[[BASE_RELOADED:.*]] = load i8*, i8** %[[BASE_ADDR]], align 8 + // CHECK-NEXT: %[[ADD_PTR:.*]] = getelementptr inbounds i8, i8* %[[BASE_RELOADED]], i64 0 + // CHECK-NEXT: ret i8* %[[ADD_PTR]] + static const unsigned long offset = 0; +#line 200 + return base + offset; +} + +// Bad if pointer is non-null. +char *var_one(char *base) { + // CHECK: define i8* @var_one(i8* %[[BASE:.*]]) + // CHECK-NEXT: [[ENTRY:.*]]: + // CHECK-NEXT: %[[BASE_ADDR:.*]] = alloca i8*, align 8 + // CHECK-NEXT: store i8* %[[BASE]], i8** %[[BASE_ADDR]], align 8 + // CHECK-NEXT: %[[BASE_RELOADED:.*]] = load i8*, i8** %[[BASE_ADDR]], align 8 + // CHECK-NEXT: %[[ADD_PTR:.*]] = getelementptr inbounds i8, i8* %[[BASE_RELOADED]], i64 1 + // CHECK-SANITIZE-NEXT: %[[BASE_RELOADED_INT:.*]] = ptrtoint i8* %[[BASE_RELOADED]] to i64, !nosanitize + // CHECK-SANITIZE-NEXT: %[[COMPUTED_GEP:.*]] = add i64 %[[BASE_RELOADED_INT]], 1, !nosanitize + // CHECK-SANITIZE-NEXT: %[[BASE_IS_NULLPTR:.*]] = icmp eq i8* %[[BASE_RELOADED]], null, !nosanitize + // CHECK-SANITIZE-NEXT: %[[COMPUTED_GEP_IS_NULL:.*]] = icmp eq i64 %[[COMPUTED_GEP]], 0, !nosanitize + // CHECK-SANITIZE-NEXT: %[[BOTH_POINTERS_ARE_NULL_OR_BOTH_ARE_NONNULL:.*]] = icmp eq i1 %[[BASE_IS_NULLPTR]], %[[COMPUTED_GEP_IS_NULL]], !nosanitize + // CHECK-SANITIZE-NEXT: br i1 %[[BOTH_POINTERS_ARE_NULL_OR_BOTH_ARE_NONNULL]], label %[[CONT:.*]], label %[[HANDLER_POINTER_OVERFLOW:[^,]+]],{{.*}} !nosanitize + // CHECK-SANITIZE: [[HANDLER_POINTER_OVERFLOW]]: + // CHECK-SANITIZE-NORECOVER-NEXT: call void @__ubsan_handle_pointer_overflow_abort(i8* bitcast ({ {{{.*}}} }* @[[LINE_300]] to i8*), i64 %[[BASE_RELOADED_INT]], i64 %[[COMPUTED_GEP]]) + // CHECK-SANITIZE-RECOVER-NEXT: call void @__ubsan_handle_pointer_overflow(i8* bitcast ({ {{{.*}}} }* @[[LINE_300]] to i8*), i64 %[[BASE_RELOADED_INT]], i64 %[[COMPUTED_GEP]]) + // CHECK-SANITIZE-TRAP-NEXT: call void @llvm.trap(){{.*}}, !nosanitize + // CHECK-SANITIZE-UNREACHABLE-NEXT: unreachable, !nosanitize + // CHECK-SANITIZE: [[CONT]]: + // CHECK-NEXT: ret i8* %[[ADD_PTR]] + static const unsigned long offset = 1; +#line 300 + return base + offset; +} + +// Bad if pointer is one. +char *var_allones(char *base) { + // CHECK: define i8* @var_allones(i8* %[[BASE:.*]]) + // CHECK-NEXT: [[ENTRY:.*]]: + // CHECK-NEXT: %[[BASE_ADDR:.*]] = alloca i8*, align 8 + // CHECK-NEXT: store i8* %[[BASE]], i8** %[[BASE_ADDR]], align 8 + // CHECK-NEXT: %[[BASE_RELOADED:.*]] = load i8*, i8** %[[BASE_ADDR]], align 8 + // CHECK-NEXT: %[[ADD_PTR:.*]] = getelementptr inbounds i8, i8* %[[BASE_RELOADED]], i64 -1 + // CHECK-SANITIZE-NEXT: %[[BASE_RELOADED_INT:.*]] = ptrtoint i8* %[[BASE_RELOADED]] to i64, !nosanitize + // CHECK-SANITIZE-NEXT: %[[COMPUTED_GEP:.*]] = add i64 %[[BASE_RELOADED_INT]], -1, !nosanitize + // CHECK-SANITIZE-NEXT: %[[BASE_IS_NULLPTR:.*]] = icmp eq i8* %[[BASE_RELOADED]], null, !nosanitize + // CHECK-SANITIZE-NEXT: %[[COMPUTED_GEP_IS_NULL:.*]] = icmp eq i64 %[[COMPUTED_GEP]], 0, !nosanitize + // CHECK-SANITIZE-NEXT: %[[BOTH_POINTERS_ARE_NULL_OR_BOTH_ARE_NONNULL:.*]] = icmp eq i1 %[[BASE_IS_NULLPTR]], %[[COMPUTED_GEP_IS_NULL]], !nosanitize + // CHECK-SANITIZE-NEXT: br i1 %[[BOTH_POINTERS_ARE_NULL_OR_BOTH_ARE_NONNULL]], label %[[CONT:.*]], label %[[HANDLER_POINTER_OVERFLOW:[^,]+]],{{.*}} !nosanitize + // CHECK-SANITIZE: [[HANDLER_POINTER_OVERFLOW]]: + // CHECK-SANITIZE-NORECOVER-NEXT: call void @__ubsan_handle_pointer_overflow_abort(i8* bitcast ({ {{{.*}}} }* @[[LINE_400]] to i8*), i64 %[[BASE_RELOADED_INT]], i64 %[[COMPUTED_GEP]]) + // CHECK-SANITIZE-RECOVER-NEXT: call void @__ubsan_handle_pointer_overflow(i8* bitcast ({ {{{.*}}} }* @[[LINE_400]] to i8*), i64 %[[BASE_RELOADED_INT]], i64 %[[COMPUTED_GEP]]) + // CHECK-SANITIZE-TRAP-NEXT: call void @llvm.trap(){{.*}}, !nosanitize + // CHECK-SANITIZE-UNREACHABLE-NEXT: unreachable, !nosanitize + // CHECK-SANITIZE: [[CONT]]: + // CHECK-NEXT: ret i8* %[[ADD_PTR]] + static const unsigned long offset = -1; +#line 400 + return base + offset; +} + +//------------------------------------------------------------------------------ + +// Bad if offset is non-zero. +char *nullptr_var(unsigned long offset) { + // CHECK: define i8* @nullptr_var(i64 %[[OFFSET:.*]]) + // CHECK-NEXT: [[ENTRY:.*]]: + // CHECK-NEXT: %[[OFFSET_ADDR:.*]] = alloca i64, align 8 + // CHECK-NEXT: store i64 %[[OFFSET]], i64* %[[OFFSET_ADDR]], align 8 + // CHECK-NEXT: %[[OFFSET_RELOADED:.*]] = load i64, i64* %[[OFFSET_ADDR]], align 8 + // CHECK-NEXT: %[[ADD_PTR:.*]] = getelementptr inbounds i8, i8* null, i64 %[[OFFSET_RELOADED]] + // CHECK-SANITIZE-NEXT: %[[COMPUTED_OFFSET_AGGREGATE:.*]] = call { i64, i1 } @llvm.smul.with.overflow.i64(i64 1, i64 %[[OFFSET_RELOADED]]), !nosanitize + // CHECK-SANITIZE-NEXT: %[[COMPUTED_OFFSET_OVERFLOWED:.*]] = extractvalue { i64, i1 } %[[COMPUTED_OFFSET_AGGREGATE]], 1, !nosanitize + // CHECK-SANITIZE-NEXT: %[[COMPUTED_OFFSET:.*]] = extractvalue { i64, i1 } %[[COMPUTED_OFFSET_AGGREGATE]], 0, !nosanitize + // CHECK-SANITIZE-NEXT: %[[COMPUTED_GEP:.*]] = add i64 0, %[[COMPUTED_OFFSET]], !nosanitize + // CHECK-SANITIZE-NEXT: %[[COMPUTED_GEP_IS_NULL:.*]] = icmp eq i64 %[[COMPUTED_GEP]], 0, !nosanitize + // CHECK-SANITIZE-NEXT: %[[BOTH_POINTERS_ARE_NULL_OR_BOTH_ARE_NONNULL:.*]] = icmp eq i1 true, %[[COMPUTED_GEP_IS_NULL]], !nosanitize + // CHECK-SANITIZE-NEXT: br i1 %[[BOTH_POINTERS_ARE_NULL_OR_BOTH_ARE_NONNULL]], label %[[CONT:.*]], label %[[HANDLER_POINTER_OVERFLOW:[^,]+]],{{.*}} !nosanitize + // CHECK-SANITIZE: [[HANDLER_POINTER_OVERFLOW]]: + // CHECK-SANITIZE-NORECOVER-NEXT: call void @__ubsan_handle_pointer_overflow_abort(i8* bitcast ({ {{{.*}}} }* @[[LINE_500]] to i8*), i64 0, i64 %[[COMPUTED_GEP]]) + // CHECK-SANITIZE-RECOVER-NEXT: call void @__ubsan_handle_pointer_overflow(i8* bitcast ({ {{{.*}}} }* @[[LINE_500]] to i8*), i64 0, i64 %[[COMPUTED_GEP]]) + // CHECK-SANITIZE-TRAP-NEXT: call void @llvm.trap(){{.*}}, !nosanitize + // CHECK-SANITIZE-UNREACHABLE-NEXT: unreachable, !nosanitize + // CHECK-SANITIZE: [[CONT]]: + // CHECK-NEXT: ret i8* %[[ADD_PTR]] + static char *const base = (char *)0; +#line 500 + return base + offset; +} + +// Pointer and offset are both zero, so all good. +char *nullptr_zero_OK() { + // CHECK: define i8* @nullptr_zero_OK() + // CHECK-NEXT: [[ENTRY:.*]]: + // CHECK-NEXT: ret i8* null + static char *const base = (char *)0; + static const unsigned long offset = 0; +#line 600 + return base + offset; +} + +// Null pointer and non-zero offset. Bad. +char *nullptr_one_BAD() { + // CHECK: define i8* @nullptr_one_BAD() + // CHECK-NEXT: [[ENTRY:.*]]: + // CHECK-SANITIZE-NEXT: br i1 icmp eq (i64 ptrtoint (i8* getelementptr inbounds (i8, i8* null, i64 1) to i64), i64 0), label %[[CONT:.*]], label %[[HANDLER_POINTER_OVERFLOW:[^,]+]],{{.*}} !nosanitize + // CHECK-SANITIZE: [[HANDLER_POINTER_OVERFLOW]]: + // CHECK-SANITIZE-NORECOVER-NEXT: call void @__ubsan_handle_pointer_overflow_abort(i8* bitcast ({ {{{.*}}} }* @[[LINE_700]] to i8*), i64 0, i64 ptrtoint (i8* getelementptr inbounds (i8, i8* null, i64 1) to i64)) + // CHECK-SANITIZE-RECOVER-NEXT: call void @__ubsan_handle_pointer_overflow(i8* bitcast ({ {{{.*}}} }* @[[LINE_700]] to i8*), i64 0, i64 ptrtoint (i8* getelementptr inbounds (i8, i8* null, i64 1) to i64)) + // CHECK-SANITIZE-TRAP-NEXT: call void @llvm.trap(){{.*}}, !nosanitize + // CHECK-SANITIZE-UNREACHABLE-NEXT: unreachable, !nosanitize + // CHECK-SANITIZE: [[CONT]]: + // CHECK-NEXT: ret i8* getelementptr inbounds (i8, i8* null, i64 1) + static char *const base = (char *)0; + static const unsigned long offset = 1; +#line 700 + return base + offset; +} + +// Null pointer and non-zero offset. Bad. +char *nullptr_allones_BAD() { + // CHECK: define i8* @nullptr_allones_BAD() + // CHECK-NEXT: [[ENTRY:.*]]: + // CHECK-SANITIZE-NEXT: br i1 icmp eq (i64 mul (i64 ptrtoint (i8* getelementptr (i8, i8* null, i32 1) to i64), i64 -1), i64 0), label %[[CONT:.*]], label %[[HANDLER_POINTER_OVERFLOW:[^,]+]],{{.*}} !nosanitize + // CHECK-SANITIZE: [[HANDLER_POINTER_OVERFLOW]]: + // CHECK-SANITIZE-NORECOVER-NEXT: call void @__ubsan_handle_pointer_overflow_abort(i8* bitcast ({ {{{.*}}} }* @[[LINE_800]] to i8*), i64 0, i64 mul (i64 ptrtoint (i8* getelementptr (i8, i8* null, i32 1) to i64), i64 -1)) + // CHECK-SANITIZE-RECOVER-NEXT: call void @__ubsan_handle_pointer_overflow(i8* bitcast ({ {{{.*}}} }* @[[LINE_800]] to i8*), i64 0, i64 mul (i64 ptrtoint (i8* getelementptr (i8, i8* null, i32 1) to i64), i64 -1)) + // CHECK-SANITIZE-TRAP-NEXT: call void @llvm.trap(){{.*}}, !nosanitize + // CHECK-SANITIZE-UNREACHABLE-NEXT: unreachable, !nosanitize + // CHECK-SANITIZE: [[CONT]]: + // CHECK-NEXT: ret i8* getelementptr inbounds (i8, i8* null, i64 -1) + static char *const base = (char *)0; + static const unsigned long offset = -1; +#line 800 + return base + offset; +} + +//------------------------------------------------------------------------------ + +// Pointer is one, bad if offset is all-ones. +char *one_var(unsigned long offset) { + // CHECK: define i8* @one_var(i64 %[[OFFSET:.*]]) + // CHECK-NEXT: [[ENTRY:.*]]: + // CHECK-NEXT: %[[OFFSET_ADDR:.*]] = alloca i64, align 8 + // CHECK-NEXT: store i64 %[[OFFSET]], i64* %[[OFFSET_ADDR]], align 8 + // CHECK-NEXT: %[[OFFSET_RELOADED:.*]] = load i64, i64* %[[OFFSET_ADDR]], align 8 + // CHECK-NEXT: %[[ADD_PTR:.*]] = getelementptr inbounds i8, i8* inttoptr (i64 1 to i8*), i64 %[[OFFSET_RELOADED]] + // CHECK-SANITIZE-NEXT: %[[COMPUTED_OFFSET_AGGREGATE:.*]] = call { i64, i1 } @llvm.smul.with.overflow.i64(i64 1, i64 %[[OFFSET_RELOADED]]), !nosanitize + // CHECK-SANITIZE-NEXT: %[[COMPUTED_OFFSET_OVERFLOWED:.*]] = extractvalue { i64, i1 } %[[COMPUTED_OFFSET_AGGREGATE]], 1, !nosanitize + // CHECK-SANITIZE-NEXT: %[[COMPUTED_OFFSET:.*]] = extractvalue { i64, i1 } %[[COMPUTED_OFFSET_AGGREGATE]], 0, !nosanitize + // CHECK-SANITIZE-NEXT: %[[COMPUTED_GEP:.*]] = add i64 1, %[[COMPUTED_OFFSET]], !nosanitize + // CHECK-SANITIZE-NEXT: %[[COMPUTED_GEP_IS_NULL:.*]] = icmp eq i64 %[[COMPUTED_GEP]], 0, !nosanitize + // CHECK-SANITIZE-NEXT: %[[BOTH_POINTERS_ARE_NULL_OR_BOTH_ARE_NONNULL:.*]] = icmp eq i1 icmp eq (i8* inttoptr (i64 1 to i8*), i8* null), %[[COMPUTED_GEP_IS_NULL]], !nosanitize + // CHECK-SANITIZE-NEXT: br i1 %[[BOTH_POINTERS_ARE_NULL_OR_BOTH_ARE_NONNULL]], label %[[CONT:.*]], label %[[HANDLER_POINTER_OVERFLOW:[^,]+]],{{.*}} !nosanitize + // CHECK-SANITIZE: [[HANDLER_POINTER_OVERFLOW]]: + // CHECK-SANITIZE-NORECOVER-NEXT: call void @__ubsan_handle_pointer_overflow_abort(i8* bitcast ({ {{{.*}}} }* @[[LINE_900]] to i8*), i64 1, i64 %[[COMPUTED_GEP]]) + // CHECK-SANITIZE-RECOVER-NEXT: call void @__ubsan_handle_pointer_overflow(i8* bitcast ({ {{{.*}}} }* @[[LINE_900]] to i8*), i64 1, i64 %[[COMPUTED_GEP]]) + // CHECK-SANITIZE-TRAP-NEXT: call void @llvm.trap(){{.*}}, !nosanitize + // CHECK-SANITIZE-UNREACHABLE-NEXT: unreachable, !nosanitize + // CHECK-SANITIZE: [[CONT]]: + // CHECK-NEXT: ret i8* %[[ADD_PTR]] + static char *const base = (char *)1; +#line 900 + return base + offset; +} + +// Zero offset, all good. +char *one_zero_OK() { + // CHECK: define i8* @one_zero_OK() + // CHECK-NEXT: [[ENTRY:.*]]: + // CHECK-NEXT: ret i8* inttoptr (i64 1 to i8*) + static char *const base = (char *)1; + static const unsigned long offset = 0; +#line 1000 + return base + offset; +} + +// Both pointer and offset are one, OK, but we can't prove that at codegen. +char *one_one_OK() { + // CHECK: define i8* @one_one_OK() + // CHECK-NEXT: [[ENTRY:.*]]: + // CHECK-SANITIZE-NEXT: br i1 xor (i1 icmp ne (i8* inttoptr (i64 1 to i8*), i8* null), i1 icmp eq (i64 add (i64 sub (i64 ptrtoint (i8* getelementptr inbounds (i8, i8* inttoptr (i64 1 to i8*), i64 1) to i64), i64 1), i64 1), i64 0)), label %[[CONT:.*]], label %[[HANDLER_POINTER_OVERFLOW:[^,]+]],{{.*}} !nosanitize + // CHECK-SANITIZE: [[HANDLER_POINTER_OVERFLOW]]: + // CHECK-SANITIZE-NORECOVER-NEXT: call void @__ubsan_handle_pointer_overflow_abort(i8* bitcast ({ {{{.*}}} }* @[[LINE_1100]] to i8*), i64 1, i64 add (i64 sub (i64 ptrtoint (i8* getelementptr inbounds (i8, i8* inttoptr (i64 1 to i8*), i64 1) to i64), i64 1), i64 1)) + // CHECK-SANITIZE-RECOVER-NEXT: call void @__ubsan_handle_pointer_overflow(i8* bitcast ({ {{{.*}}} }* @[[LINE_1100]] to i8*), i64 1, i64 add (i64 sub (i64 ptrtoint (i8* getelementptr inbounds (i8, i8* inttoptr (i64 1 to i8*), i64 1) to i64), i64 1), i64 1)) + // CHECK-SANITIZE-TRAP-NEXT: call void @llvm.trap(){{.*}}, !nosanitize + // CHECK-SANITIZE-UNREACHABLE-NEXT: unreachable, !nosanitize + // CHECK-SANITIZE: [[CONT]]: + // CHECK-NEXT: ret i8* getelementptr inbounds (i8, i8* inttoptr (i64 1 to i8*), i64 1) + static char *const base = (char *)1; + static const unsigned long offset = 1; +#line 1100 + return base + offset; +} + +// Pointer is one, offset is all-ones. Bad. +char *one_allones_BAD() { + // CHECK: define i8* @one_allones_BAD() + // CHECK-NEXT: [[ENTRY:.*]]: + // CHECK-SANITIZE-NEXT: br i1 xor (i1 icmp ne (i8* inttoptr (i64 1 to i8*), i8* null), i1 icmp eq (i64 add (i64 sub (i64 ptrtoint (i8* getelementptr inbounds (i8, i8* inttoptr (i64 1 to i8*), i64 -1) to i64), i64 1), i64 1), i64 0)), label %[[CONT:.*]], label %[[HANDLER_POINTER_OVERFLOW:[^,]+]],{{.*}} !nosanitize + // CHECK-SANITIZE: [[HANDLER_POINTER_OVERFLOW]]: + // CHECK-SANITIZE-NORECOVER-NEXT: call void @__ubsan_handle_pointer_overflow_abort(i8* bitcast ({ {{{.*}}} }* @[[LINE_1200]] to i8*), i64 1, i64 add (i64 sub (i64 ptrtoint (i8* getelementptr inbounds (i8, i8* inttoptr (i64 1 to i8*), i64 -1) to i64), i64 1), i64 1)) + // CHECK-SANITIZE-RECOVER-NEXT: call void @__ubsan_handle_pointer_overflow(i8* bitcast ({ {{{.*}}} }* @[[LINE_1200]] to i8*), i64 1, i64 add (i64 sub (i64 ptrtoint (i8* getelementptr inbounds (i8, i8* inttoptr (i64 1 to i8*), i64 -1) to i64), i64 1), i64 1)) + // CHECK-SANITIZE-TRAP-NEXT: call void @llvm.trap(){{.*}}, !nosanitize + // CHECK-SANITIZE-UNREACHABLE-NEXT: unreachable, !nosanitize + // CHECK-SANITIZE: [[CONT]]: + // CHECK-NEXT: ret i8* getelementptr inbounds (i8, i8* inttoptr (i64 1 to i8*), i64 -1) + static char *const base = (char *)1; + static const unsigned long offset = -1; +#line 1200 + return base + offset; +} + +//------------------------------------------------------------------------------ + +// Pointer is all-ones. Bad if offset is one. +char *allones_var(unsigned long offset) { + // CHECK: define i8* @allones_var(i64 %[[OFFSET:.*]]) + // CHECK-NEXT: [[ENTRY:.*]]: + // CHECK-NEXT: %[[OFFSET_ADDR:.*]] = alloca i64, align 8 + // CHECK-NEXT: store i64 %[[OFFSET]], i64* %[[OFFSET_ADDR]], align 8 + // CHECK-NEXT: %[[OFFSET_RELOADED:.*]] = load i64, i64* %[[OFFSET_ADDR]], align 8 + // CHECK-NEXT: %[[ADD_PTR:.*]] = getelementptr inbounds i8, i8* inttoptr (i64 -1 to i8*), i64 %[[OFFSET_RELOADED]] + // CHECK-SANITIZE-NEXT: %[[COMPUTED_OFFSET_AGGREGATE:.*]] = call { i64, i1 } @llvm.smul.with.overflow.i64(i64 1, i64 %[[OFFSET_RELOADED]]), !nosanitize + // CHECK-SANITIZE-NEXT: %[[COMPUTED_OFFSET_OVERFLOWED:.*]] = extractvalue { i64, i1 } %[[COMPUTED_OFFSET_AGGREGATE]], 1, !nosanitize + // CHECK-SANITIZE-NEXT: %[[COMPUTED_OFFSET:.*]] = extractvalue { i64, i1 } %[[COMPUTED_OFFSET_AGGREGATE]], 0, !nosanitize + // CHECK-SANITIZE-NEXT: %[[COMPUTED_GEP:.*]] = add i64 -1, %[[COMPUTED_OFFSET]], !nosanitize + // CHECK-SANITIZE-NEXT: %[[COMPUTED_GEP_IS_NULL:.*]] = icmp eq i64 %[[COMPUTED_GEP]], 0, !nosanitize + // CHECK-SANITIZE-NEXT: %[[BOTH_POINTERS_ARE_NULL_OR_BOTH_ARE_NONNULL:.*]] = icmp eq i1 icmp eq (i8* inttoptr (i64 -1 to i8*), i8* null), %[[COMPUTED_GEP_IS_NULL]], !nosanitize + // CHECK-SANITIZE-NEXT: br i1 %[[BOTH_POINTERS_ARE_NULL_OR_BOTH_ARE_NONNULL]], label %[[CONT:.*]], label %[[HANDLER_POINTER_OVERFLOW:[^,]+]],{{.*}} !nosanitize + // CHECK-SANITIZE: [[HANDLER_POINTER_OVERFLOW]]: + // CHECK-SANITIZE-NORECOVER-NEXT: call void @__ubsan_handle_pointer_overflow_abort(i8* bitcast ({ {{{.*}}} }* @[[LINE_1300]] to i8*), i64 -1, i64 %[[COMPUTED_GEP]]) + // CHECK-SANITIZE-RECOVER-NEXT: call void @__ubsan_handle_pointer_overflow(i8* bitcast ({ {{{.*}}} }* @[[LINE_1300]] to i8*), i64 -1, i64 %[[COMPUTED_GEP]]) + // CHECK-SANITIZE-TRAP-NEXT: call void @llvm.trap(){{.*}}, !nosanitize + // CHECK-SANITIZE-UNREACHABLE-NEXT: unreachable, !nosanitize + // CHECK-SANITIZE: [[CONT]]: + // CHECK-NEXT: ret i8* %[[ADD_PTR]] + static char *const base = (char *)-1; +#line 1300 + return base + offset; +} + +// Offset is zero, all good. +char *allones_zero_OK() { + // CHECK: define i8* @allones_zero_OK() + // CHECK-NEXT: [[ENTRY:.*]]: + // CHECK-NEXT: ret i8* inttoptr (i64 -1 to i8*) + static char *const base = (char *)-1; + static const unsigned long offset = 0; +#line 1400 + return base + offset; +} + +// Pointer is all-ones, offset is one. Bad. +char *allones_one_BAD() { + // CHECK: define i8* @allones_one_BAD() + // CHECK-NEXT: [[ENTRY:.*]]: + // CHECK-SANITIZE-NEXT: br i1 xor (i1 icmp ne (i8* inttoptr (i64 -1 to i8*), i8* null), i1 icmp eq (i64 add (i64 sub (i64 ptrtoint (i8* getelementptr inbounds (i8, i8* inttoptr (i64 -1 to i8*), i64 1) to i64), i64 -1), i64 -1), i64 0)), label %[[CONT:.*]], label %[[HANDLER_POINTER_OVERFLOW:[^,]+]],{{.*}} !nosanitize + // CHECK-SANITIZE: [[HANDLER_POINTER_OVERFLOW]]: + // CHECK-SANITIZE-NORECOVER-NEXT: call void @__ubsan_handle_pointer_overflow_abort(i8* bitcast ({ {{{.*}}} }* @[[LINE_1500]] to i8*), i64 -1, i64 add (i64 sub (i64 ptrtoint (i8* getelementptr inbounds (i8, i8* inttoptr (i64 -1 to i8*), i64 1) to i64), i64 -1), i64 -1)) + // CHECK-SANITIZE-RECOVER-NEXT: call void @__ubsan_handle_pointer_overflow(i8* bitcast ({ {{{.*}}} }* @[[LINE_1500]] to i8*), i64 -1, i64 add (i64 sub (i64 ptrtoint (i8* getelementptr inbounds (i8, i8* inttoptr (i64 -1 to i8*), i64 1) to i64), i64 -1), i64 -1)) + // CHECK-SANITIZE-TRAP-NEXT: call void @llvm.trap(){{.*}}, !nosanitize + // CHECK-SANITIZE-UNREACHABLE-NEXT: unreachable, !nosanitize + // CHECK-SANITIZE: [[CONT]]: + // CHECK-NEXT: ret i8* getelementptr inbounds (i8, i8* inttoptr (i64 -1 to i8*), i64 1) + static char *const base = (char *)-1; + static const unsigned long offset = 1; +#line 1500 + return base + offset; +} + +// All-ones pointer and offset. OK, but we can't prove that at codegen. +char *allones_allones_OK() { + // CHECK: define i8* @allones_allones_OK() + // CHECK-NEXT: [[ENTRY:.*]]: + // CHECK-SANITIZE-NEXT: br i1 xor (i1 icmp ne (i8* inttoptr (i64 -1 to i8*), i8* null), i1 icmp eq (i64 add (i64 sub (i64 ptrtoint (i8* getelementptr inbounds (i8, i8* inttoptr (i64 -1 to i8*), i64 -1) to i64), i64 -1), i64 -1), i64 0)), label %[[CONT:.*]], label %[[HANDLER_POINTER_OVERFLOW:[^,]+]],{{.*}} !nosanitize + // CHECK-SANITIZE: [[HANDLER_POINTER_OVERFLOW]]: + // CHECK-SANITIZE-NORECOVER-NEXT: call void @__ubsan_handle_pointer_overflow_abort(i8* bitcast ({ {{{.*}}} }* @[[LINE_1600]] to i8*), i64 -1, i64 add (i64 sub (i64 ptrtoint (i8* getelementptr inbounds (i8, i8* inttoptr (i64 -1 to i8*), i64 -1) to i64), i64 -1), i64 -1)) + // CHECK-SANITIZE-RECOVER-NEXT: call void @__ubsan_handle_pointer_overflow(i8* bitcast ({ {{{.*}}} }* @[[LINE_1600]] to i8*), i64 -1, i64 add (i64 sub (i64 ptrtoint (i8* getelementptr inbounds (i8, i8* inttoptr (i64 -1 to i8*), i64 -1) to i64), i64 -1), i64 -1)) + // CHECK-SANITIZE-TRAP-NEXT: call void @llvm.trap(){{.*}}, !nosanitize + // CHECK-SANITIZE-UNREACHABLE-NEXT: unreachable, !nosanitize + // CHECK-SANITIZE: [[CONT]]: + // CHECK-NEXT: ret i8* getelementptr inbounds (i8, i8* inttoptr (i64 -1 to i8*), i64 -1) + static char *const base = (char *)-1; + static const unsigned long offset = -1; +#line 1600 + return base + offset; +} diff --git a/clang/test/CodeGen/catch-pointer-offsetting.c b/clang/test/CodeGen/catch-pointer-offsetting.c new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/catch-pointer-offsetting.c @@ -0,0 +1,41 @@ +// RUN: %clang_cc1 -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefixes=CHECK,CHECK-NOSANITIZE +// RUN: %clang_cc1 -fsanitize=nullptr-and-nonzero-offset,pointer-overflow -fno-sanitize-recover=nullptr-and-nonzero-offset,pointer-overflow -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s -implicit-check-not="call void @__ubsan_handle_pointer_overflow" --check-prefixes=CHECK,CHECK-SANITIZE,CHECK-SANITIZE-ANYRECOVER,CHECK-SANITIZE-NORECOVER,CHECK-SANITIZE-UNREACHABLE +// RUN: %clang_cc1 -fsanitize=nullptr-and-nonzero-offset,pointer-overflow -fsanitize-recover=nullptr-and-nonzero-offset,pointer-overflow -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s -implicit-check-not="call void @__ubsan_handle_pointer_overflow" --check-prefixes=CHECK,CHECK-SANITIZE,CHECK-SANITIZE-ANYRECOVER,CHECK-SANITIZE-RECOVER +// RUN: %clang_cc1 -fsanitize=nullptr-and-nonzero-offset,pointer-overflow -fsanitize-trap=nullptr-and-nonzero-offset,pointer-overflow -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s -implicit-check-not="call void @__ubsan_handle_pointer_overflow" --check-prefixes=CHECK,CHECK-SANITIZE,CHECK-SANITIZE-TRAP,CHECK-SANITIZE-UNREACHABLE + +// CHECK-SANITIZE-ANYRECOVER-DAG: @[[LINE_100:.*]] = {{.*}}, i32 100, i32 15 } } + +// We just don't know, can be bad, can be ok. +char *var_var(char *base, unsigned long offset) { + // CHECK: define i8* @var_var(i8* %[[BASE:.*]], i64 %[[OFFSET:.*]]) + // CHECK-NEXT: [[ENTRY:.*]]: + // CHECK-NEXT: %[[BASE_ADDR:.*]] = alloca i8*, align 8 + // CHECK-NEXT: %[[OFFSET_ADDR:.*]] = alloca i64, align 8 + // CHECK-NEXT: store i8* %[[BASE]], i8** %[[BASE_ADDR]], align 8 + // CHECK-NEXT: store i64 %[[OFFSET]], i64* %[[OFFSET_ADDR]], align 8 + // CHECK-NEXT: %[[BASE_RELOADED:.*]] = load i8*, i8** %[[BASE_ADDR]], align 8 + // CHECK-NEXT: %[[OFFSET_RELOADED:.*]] = load i64, i64* %[[OFFSET_ADDR]], align 8 + // CHECK-NEXT: %[[ADD_PTR:.*]] = getelementptr inbounds i8, i8* %[[BASE_RELOADED]], i64 %[[OFFSET_RELOADED]] + // CHECK-SANITIZE-NEXT: %[[COMPUTED_OFFSET_AGGREGATE:.*]] = call { i64, i1 } @llvm.smul.with.overflow.i64(i64 1, i64 %[[OFFSET_RELOADED]]), !nosanitize + // CHECK-SANITIZE-NEXT: %[[COMPUTED_OFFSET_OVERFLOWED:.*]] = extractvalue { i64, i1 } %[[COMPUTED_OFFSET_AGGREGATE]], 1, !nosanitize + // CHECK-SANITIZE-NEXT: %[[COMPUTED_OFFSET:.*]] = extractvalue { i64, i1 } %[[COMPUTED_OFFSET_AGGREGATE]], 0, !nosanitize + // CHECK-SANITIZE-NEXT: %[[BASE_RELOADED_INT:.*]] = ptrtoint i8* %[[BASE_RELOADED]] to i64, !nosanitize + // CHECK-SANITIZE-NEXT: %[[COMPUTED_GEP:.*]] = add i64 %[[BASE_RELOADED_INT]], %[[COMPUTED_OFFSET]], !nosanitize + // CHECK-SANITIZE-NEXT: %[[BASE_IS_NULLPTR:.*]] = icmp eq i8* %[[BASE_RELOADED]], null, !nosanitize + // CHECK-SANITIZE-NEXT: %[[COMPUTED_GEP_IS_NULL:.*]] = icmp eq i64 %[[COMPUTED_GEP]], 0, !nosanitize + // CHECK-SANITIZE-NEXT: %[[BOTH_POINTERS_ARE_NULL_OR_BOTH_ARE_NONNULL:.*]] = icmp eq i1 %[[BASE_IS_NULLPTR]], %[[COMPUTED_GEP_IS_NULL]], !nosanitize + // CHECK-SANITIZE-NEXT: %[[COMPUTED_OFFSET_DID_NOT_OVERFLOW:.*]] = xor i1 %[[COMPUTED_OFFSET_OVERFLOWED]], true, !nosanitize + // CHECK-SANITIZE-NEXT: %[[COMPUTED_GEP_IS_UGE_BASE:.*]] = icmp uge i64 %[[COMPUTED_GEP]], %[[BASE_RELOADED_INT]], !nosanitize + // CHECK-SANITIZE-NEXT: %[[GEP_DID_NOT_OVERFLOW:.*]] = and i1 %[[COMPUTED_GEP_IS_UGE_BASE]], %[[COMPUTED_OFFSET_DID_NOT_OVERFLOW]], !nosanitize + // CHECK-SANITIZE-NEXT: %[[GEP_IS_OKAY:.*]] = and i1 %[[BOTH_POINTERS_ARE_NULL_OR_BOTH_ARE_NONNULL]], %[[GEP_DID_NOT_OVERFLOW]], !nosanitize + // CHECK-SANITIZE-NEXT: br i1 %[[GEP_IS_OKAY]], label %[[CONT:.*]], label %[[HANDLER_POINTER_OVERFLOW:[^,]+]],{{.*}} !nosanitize + // CHECK-SANITIZE: [[HANDLER_POINTER_OVERFLOW]]: + // CHECK-SANITIZE-NORECOVER-NEXT: call void @__ubsan_handle_pointer_overflow_abort(i8* bitcast ({ {{{.*}}} }* @[[LINE_100]] to i8*), i64 %[[BASE_RELOADED_INT]], i64 %[[COMPUTED_GEP]]) + // CHECK-SANITIZE-RECOVER-NEXT: call void @__ubsan_handle_pointer_overflow(i8* bitcast ({ {{{.*}}} }* @[[LINE_100]] to i8*), i64 %[[BASE_RELOADED_INT]], i64 %[[COMPUTED_GEP]]) + // CHECK-SANITIZE-TRAP-NEXT: call void @llvm.trap(){{.*}}, !nosanitize + // CHECK-SANITIZE-UNREACHABLE-NEXT: unreachable, !nosanitize + // CHECK-SANITIZE: [[CONT]]: + // CHECK-NEXT: ret i8* %[[ADD_PTR]] +#line 100 + return base + offset; +} diff --git a/clang/test/CodeGen/catch-pointer-overflow.c b/clang/test/CodeGen/catch-pointer-overflow.c new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/catch-pointer-overflow.c @@ -0,0 +1,37 @@ +// RUN: %clang_cc1 -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefixes=CHECK,CHECK-NOSANITIZE +// RUN: %clang_cc1 -fsanitize=pointer-overflow -fno-sanitize-recover=pointer-overflow -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s -implicit-check-not="call void @__ubsan_handle_pointer_overflow" --check-prefixes=CHECK,CHECK-SANITIZE,CHECK-SANITIZE-ANYRECOVER,CHECK-SANITIZE-NORECOVER,CHECK-SANITIZE-UNREACHABLE +// RUN: %clang_cc1 -fsanitize=pointer-overflow -fsanitize-recover=pointer-overflow -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s -implicit-check-not="call void @__ubsan_handle_pointer_overflow" --check-prefixes=CHECK,CHECK-SANITIZE,CHECK-SANITIZE-ANYRECOVER,CHECK-SANITIZE-RECOVER +// RUN: %clang_cc1 -fsanitize=pointer-overflow -fsanitize-trap=pointer-overflow -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s -implicit-check-not="call void @__ubsan_handle_pointer_overflow" --check-prefixes=CHECK,CHECK-SANITIZE,CHECK-SANITIZE-TRAP,CHECK-SANITIZE-UNREACHABLE + +// CHECK-SANITIZE-ANYRECOVER-DAG: @[[LINE_100:.*]] = {{.*}}, i32 100, i32 15 } } + +// We just don't know, can be bad, can be ok. +char *var_var(char *base, unsigned long offset) { + // CHECK: define i8* @var_var(i8* %[[BASE:.*]], i64 %[[OFFSET:.*]]) + // CHECK-NEXT: [[ENTRY:.*]]: + // CHECK-NEXT: %[[BASE_ADDR:.*]] = alloca i8*, align 8 + // CHECK-NEXT: %[[OFFSET_ADDR:.*]] = alloca i64, align 8 + // CHECK-NEXT: store i8* %[[BASE]], i8** %[[BASE_ADDR]], align 8 + // CHECK-NEXT: store i64 %[[OFFSET]], i64* %[[OFFSET_ADDR]], align 8 + // CHECK-NEXT: %[[BASE_RELOADED:.*]] = load i8*, i8** %[[BASE_ADDR]], align 8 + // CHECK-NEXT: %[[OFFSET_RELOADED:.*]] = load i64, i64* %[[OFFSET_ADDR]], align 8 + // CHECK-NEXT: %[[ADD_PTR:.*]] = getelementptr inbounds i8, i8* %[[BASE_RELOADED]], i64 %[[OFFSET_RELOADED]] + // CHECK-SANITIZE-NEXT: %[[COMPUTED_OFFSET_AGGREGATE:.*]] = call { i64, i1 } @llvm.smul.with.overflow.i64(i64 1, i64 %[[OFFSET_RELOADED]]), !nosanitize + // CHECK-SANITIZE-NEXT: %[[COMPUTED_OFFSET_OVERFLOWED:.*]] = extractvalue { i64, i1 } %[[COMPUTED_OFFSET_AGGREGATE]], 1, !nosanitize + // CHECK-SANITIZE-NEXT: %[[COMPUTED_OFFSET:.*]] = extractvalue { i64, i1 } %[[COMPUTED_OFFSET_AGGREGATE]], 0, !nosanitize + // CHECK-SANITIZE-NEXT: %[[BASE_RELOADED_INT:.*]] = ptrtoint i8* %[[BASE_RELOADED]] to i64, !nosanitize + // CHECK-SANITIZE-NEXT: %[[COMPUTED_GEP:.*]] = add i64 %[[BASE_RELOADED_INT]], %[[COMPUTED_OFFSET]], !nosanitize + // CHECK-SANITIZE-NEXT: %[[COMPUTED_OFFSET_DID_NOT_OVERFLOW:.*]] = xor i1 %[[COMPUTED_OFFSET_OVERFLOWED]], true, !nosanitize + // CHECK-SANITIZE-NEXT: %[[COMPUTED_GEP_IS_UGE_BASE:.*]] = icmp uge i64 %[[COMPUTED_GEP]], %[[BASE_RELOADED_INT]], !nosanitize + // CHECK-SANITIZE-NEXT: %[[GEP_DID_NOT_OVERFLOW:.*]] = and i1 %[[COMPUTED_GEP_IS_UGE_BASE]], %[[COMPUTED_OFFSET_DID_NOT_OVERFLOW]], !nosanitize + // CHECK-SANITIZE-NEXT: br i1 %[[GEP_DID_NOT_OVERFLOW]], label %[[CONT:.*]], label %[[HANDLER_POINTER_OVERFLOW:[^,]+]],{{.*}} !nosanitize + // CHECK-SANITIZE: [[HANDLER_POINTER_OVERFLOW]]: + // CHECK-SANITIZE-NORECOVER-NEXT: call void @__ubsan_handle_pointer_overflow_abort(i8* bitcast ({ {{{.*}}} }* @[[LINE_100]] to i8*), i64 %[[BASE_RELOADED_INT]], i64 %[[COMPUTED_GEP]]) + // CHECK-SANITIZE-RECOVER-NEXT: call void @__ubsan_handle_pointer_overflow(i8* bitcast ({ {{{.*}}} }* @[[LINE_100]] to i8*), i64 %[[BASE_RELOADED_INT]], i64 %[[COMPUTED_GEP]]) + // CHECK-SANITIZE-TRAP-NEXT: call void @llvm.trap(){{.*}}, !nosanitize + // CHECK-SANITIZE-UNREACHABLE-NEXT: unreachable, !nosanitize + // CHECK-SANITIZE: [[CONT]]: + // CHECK-NEXT: ret i8* %[[ADD_PTR]] +#line 100 + return base + offset; +} diff --git a/clang/test/Driver/fsanitize.c b/clang/test/Driver/fsanitize.c --- a/clang/test/Driver/fsanitize.c +++ b/clang/test/Driver/fsanitize.c @@ -4,15 +4,15 @@ // RUN: %clang -target x86_64-linux-gnu -fsanitize=undefined-trap -fsanitize-undefined-trap-on-error %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-UNDEFINED-TRAP // RUN: %clang -target x86_64-linux-gnu -fsanitize-undefined-trap-on-error -fsanitize=undefined-trap %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-UNDEFINED-TRAP // CHECK-UNDEFINED-TRAP-NOT: -fsanitize-recover -// CHECK-UNDEFINED-TRAP: "-fsanitize={{((signed-integer-overflow|integer-divide-by-zero|shift-base|shift-exponent|unreachable|return|vla-bound|alignment|null|pointer-overflow|float-cast-overflow|array-bounds|enum|bool|builtin|returns-nonnull-attribute|nonnull-attribute|function),?){18}"}} -// CHECK-UNDEFINED-TRAP: "-fsanitize-trap=alignment,array-bounds,bool,builtin,enum,float-cast-overflow,function,integer-divide-by-zero,nonnull-attribute,null,pointer-overflow,return,returns-nonnull-attribute,shift-base,shift-exponent,signed-integer-overflow,unreachable,vla-bound" -// CHECK-UNDEFINED-TRAP2: "-fsanitize-trap=alignment,array-bounds,bool,builtin,enum,float-cast-overflow,function,integer-divide-by-zero,nonnull-attribute,null,pointer-overflow,return,returns-nonnull-attribute,shift-base,shift-exponent,unreachable,vla-bound" +// CHECK-UNDEFINED-TRAP: "-fsanitize={{((signed-integer-overflow|integer-divide-by-zero|shift-base|shift-exponent|unreachable|return|vla-bound|alignment|null|nullptr-and-nonzero-offset|pointer-overflow|float-cast-overflow|array-bounds|enum|bool|builtin|returns-nonnull-attribute|nonnull-attribute|function),?){19}"}} +// CHECK-UNDEFINED-TRAP: "-fsanitize-trap=alignment,array-bounds,bool,builtin,enum,float-cast-overflow,function,integer-divide-by-zero,nonnull-attribute,null,nullptr-and-nonzero-offset,pointer-overflow,return,returns-nonnull-attribute,shift-base,shift-exponent,signed-integer-overflow,unreachable,vla-bound" +// CHECK-UNDEFINED-TRAP2: "-fsanitize-trap=alignment,array-bounds,bool,builtin,enum,float-cast-overflow,function,integer-divide-by-zero,nonnull-attribute,null,nullptr-and-nonzero-offset,pointer-overflow,return,returns-nonnull-attribute,shift-base,shift-exponent,unreachable,vla-bound" // RUN: %clang -target x86_64-linux-gnu -fsanitize=undefined %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-UNDEFINED -// CHECK-UNDEFINED: "-fsanitize={{((signed-integer-overflow|integer-divide-by-zero|function|shift-base|shift-exponent|unreachable|return|vla-bound|alignment|null|vptr|pointer-overflow|float-cast-overflow|array-bounds|enum|bool|builtin|returns-nonnull-attribute|nonnull-attribute),?){19}"}} +// CHECK-UNDEFINED: "-fsanitize={{((signed-integer-overflow|integer-divide-by-zero|function|shift-base|shift-exponent|unreachable|return|vla-bound|alignment|null|vptr|nullptr-and-nonzero-offset|pointer-overflow|float-cast-overflow|array-bounds|enum|bool|builtin|returns-nonnull-attribute|nonnull-attribute),?){20}"}} // RUN: %clang -target x86_64-apple-darwin10 -fsanitize=undefined %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-UNDEFINED-DARWIN -// CHECK-UNDEFINED-DARWIN: "-fsanitize={{((signed-integer-overflow|integer-divide-by-zero|function|shift-base|shift-exponent|unreachable|return|vla-bound|alignment|null|pointer-overflow|float-cast-overflow|array-bounds|enum|bool|builtin|returns-nonnull-attribute|nonnull-attribute),?){18}"}} +// CHECK-UNDEFINED-DARWIN: "-fsanitize={{((signed-integer-overflow|integer-divide-by-zero|function|shift-base|shift-exponent|unreachable|return|vla-bound|alignment|null|nullptr-and-nonzero-offset|pointer-overflow|float-cast-overflow|array-bounds|enum|bool|builtin|returns-nonnull-attribute|nonnull-attribute),?){19}"}} // RUN: %clang -target i386-pc-win32 -fsanitize=undefined %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-UNDEFINED-WIN --check-prefix=CHECK-UNDEFINED-WIN32 // RUN: %clang -target i386-pc-win32 -fsanitize=undefined -x c++ %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-UNDEFINED-WIN --check-prefix=CHECK-UNDEFINED-WIN32 --check-prefix=CHECK-UNDEFINED-WIN-CXX @@ -23,7 +23,7 @@ // CHECK-UNDEFINED-WIN64: "--dependent-lib={{[^"]*}}ubsan_standalone-x86_64.lib" // CHECK-UNDEFINED-WIN64-MINGW: "--dependent-lib={{[^"]*}}libclang_rt.ubsan_standalone-x86_64.a" // CHECK-UNDEFINED-WIN-CXX: "--dependent-lib={{[^"]*}}ubsan_standalone_cxx{{[^"]*}}.lib" -// CHECK-UNDEFINED-WIN-SAME: "-fsanitize={{((signed-integer-overflow|integer-divide-by-zero|shift-base|shift-exponent|unreachable|return|vla-bound|alignment|null|pointer-overflow|float-cast-overflow|array-bounds|enum|bool|builtin|returns-nonnull-attribute|nonnull-attribute),?){17}"}} +// CHECK-UNDEFINED-WIN-SAME: "-fsanitize={{((signed-integer-overflow|integer-divide-by-zero|shift-base|shift-exponent|unreachable|return|vla-bound|alignment|null|nullptr-and-nonzero-offset|pointer-overflow|float-cast-overflow|array-bounds|enum|bool|builtin|returns-nonnull-attribute|nonnull-attribute),?){18}"}} // RUN: %clang -target i386-pc-win32 -fsanitize-coverage=bb %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-COVERAGE-WIN32 // CHECK-COVERAGE-WIN32: "--dependent-lib={{[^"]*}}ubsan_standalone-i386.lib" @@ -33,6 +33,21 @@ // RUN: %clang -target %itanium_abi_triple -fsanitize=integer %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-INTEGER -implicit-check-not="-fsanitize-address-use-after-scope" // CHECK-INTEGER: "-fsanitize={{((signed-integer-overflow|unsigned-integer-overflow|integer-divide-by-zero|shift-base|shift-exponent|implicit-unsigned-integer-truncation|implicit-signed-integer-truncation|implicit-integer-sign-change),?){8}"}} +// RUN: %clang -fsanitize=pointer-offsetting %s -### 2>&1 | FileCheck %s --check-prefixes=CHECK-pointer-offsetting,CHECK-pointer-offsetting-RECOVER +// RUN: %clang -fsanitize=pointer-offsetting -fsanitize-recover=pointer-offsetting %s -### 2>&1 | FileCheck %s --check-prefixes=CHECK-pointer-offsetting,CHECK-pointer-offsetting-RECOVER +// RUN: %clang -fsanitize=pointer-offsetting -fno-sanitize-recover=pointer-offsetting %s -### 2>&1 | FileCheck %s --check-prefixes=CHECK-pointer-offsetting,CHECK-pointer-offsetting-NORECOVER +// RUN: %clang -fsanitize=pointer-offsetting -fsanitize-trap=pointer-offsetting %s -### 2>&1 | FileCheck %s --check-prefixes=CHECK-pointer-offsetting,CHECK-pointer-offsetting-TRAP +// CHECK-pointer-offsetting: "-fsanitize={{((nullptr-and-nonzero-offset|pointer-overflow),?){2}"}} +// CHECK-pointer-offsetting-RECOVER: "-fsanitize-recover={{((nullptr-and-nonzero-offset|pointer-overflow),?){2}"}} +// CHECK-pointer-offsetting-RECOVER-NOT: "-fno-sanitize-recover={{((nullptr-and-nonzero-offset|pointer-overflow),?){2}"}} +// CHECK-pointer-offsetting-RECOVER-NOT: "-fsanitize-trap={{((nullptr-and-nonzero-offset|pointer-overflow),?){2}"}} +// CHECK-pointer-offsetting-NORECOVER-NOT: "-fno-sanitize-recover={{((nullptr-and-nonzero-offset|pointer-overflow),?){2}"}} // ??? +// CHECK-pointer-offsetting-NORECOVER-NOT: "-fsanitize-recover={{((nullptr-and-nonzero-offset|pointer-overflow),?){2}"}} +// CHECK-pointer-offsetting-NORECOVER-NOT: "-fsanitize-trap={{((nullptr-and-nonzero-offset|pointer-overflow),?){2}"}} +// CHECK-pointer-offsetting-TRAP: "-fsanitize-trap={{((nullptr-and-nonzero-offset|pointer-overflow),?){2}"}} +// CHECK-pointer-offsetting-TRAP-NOT: "-fsanitize-recover={{((nullptr-and-nonzero-offset|pointer-overflow),?){2}"}} +// CHECK-pointer-offsetting-TRAP-NOT: "-fno-sanitize-recover={{((nullptr-and-nonzero-offset|pointer-overflow),?){2}"}} + // RUN: %clang -fsanitize=implicit-conversion %s -### 2>&1 | FileCheck %s --check-prefixes=CHECK-implicit-conversion,CHECK-implicit-conversion-RECOVER // RUN: %clang -fsanitize=implicit-conversion -fsanitize-recover=implicit-conversion %s -### 2>&1 | FileCheck %s --check-prefixes=CHECK-implicit-conversion,CHECK-implicit-conversion-RECOVER // RUN: %clang -fsanitize=implicit-conversion -fno-sanitize-recover=implicit-conversion %s -### 2>&1 | FileCheck %s --check-prefixes=CHECK-implicit-conversion,CHECK-implicit-conversion-NORECOVER @@ -88,7 +103,7 @@ // CHECK-FNO-SANITIZE-ALL: "-fsanitize=thread" // RUN: %clang -target x86_64-linux-gnu -fsanitize=thread,undefined -fno-sanitize=thread -fno-sanitize=float-cast-overflow,vptr,bool,builtin,enum %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-PARTIAL-UNDEFINED -// CHECK-PARTIAL-UNDEFINED: "-fsanitize={{((signed-integer-overflow|integer-divide-by-zero|function|shift-base|shift-exponent|unreachable|return|vla-bound|alignment|null|pointer-overflow|array-bounds|returns-nonnull-attribute|nonnull-attribute),?){14}"}} +// CHECK-PARTIAL-UNDEFINED: "-fsanitize={{((signed-integer-overflow|integer-divide-by-zero|function|shift-base|shift-exponent|unreachable|return|vla-bound|alignment|null|nullptr-and-nonzero-offset|pointer-overflow|array-bounds|returns-nonnull-attribute|nonnull-attribute),?){15}"}} // RUN: %clang -fsanitize=shift -fno-sanitize=shift-base %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-FSANITIZE-SHIFT-PARTIAL // CHECK-FSANITIZE-SHIFT-PARTIAL: "-fsanitize=shift-exponent" @@ -359,7 +374,7 @@ // RUN: %clang -target x86_64-linux-gnu %s -fsanitize=undefined -fno-sanitize-recover=undefined -### 2>&1 | FileCheck %s --check-prefix=CHECK-NO-RECOVER-UBSAN // RUN: %clang -target x86_64-linux-gnu %s -fsanitize=undefined -fno-sanitize-recover=all -fsanitize-recover=thread -### 2>&1 | FileCheck %s --check-prefix=CHECK-NO-RECOVER-UBSAN // RUN: %clang -target x86_64-linux-gnu %s -fsanitize=undefined -fsanitize-recover=all -fno-sanitize-recover=undefined -### 2>&1 | FileCheck %s --check-prefix=CHECK-NO-RECOVER-UBSAN -// CHECK-RECOVER-UBSAN: "-fsanitize-recover={{((signed-integer-overflow|integer-divide-by-zero|function|shift-base|shift-exponent|vla-bound|alignment|null|vptr|pointer-overflow|float-cast-overflow|array-bounds|enum|bool|builtin|returns-nonnull-attribute|nonnull-attribute),?){17}"}} +// CHECK-RECOVER-UBSAN: "-fsanitize-recover={{((signed-integer-overflow|integer-divide-by-zero|function|shift-base|shift-exponent|vla-bound|alignment|null|vptr|nullptr-and-nonzero-offset|pointer-overflow|float-cast-overflow|array-bounds|enum|bool|builtin|returns-nonnull-attribute|nonnull-attribute),?){18}"}} // CHECK-NO-RECOVER-UBSAN-NOT: sanitize-recover // RUN: %clang -target x86_64-linux-gnu %s -fsanitize=undefined -fno-sanitize-recover=all -fsanitize-recover=object-size,shift-base -### 2>&1 | FileCheck %s --check-prefix=CHECK-PARTIAL-RECOVER @@ -778,7 +793,7 @@ // CHECK-TSAN-MINIMAL: error: invalid argument '-fsanitize-minimal-runtime' not allowed with '-fsanitize=thread' // RUN: %clang -target x86_64-linux-gnu -fsanitize=undefined -fsanitize-minimal-runtime %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-UBSAN-MINIMAL -// CHECK-UBSAN-MINIMAL: "-fsanitize={{((signed-integer-overflow|integer-divide-by-zero|shift-base|shift-exponent|unreachable|return|vla-bound|alignment|null|pointer-overflow|float-cast-overflow|array-bounds|enum|bool|builtin|returns-nonnull-attribute|nonnull-attribute),?){17}"}} +// CHECK-UBSAN-MINIMAL: "-fsanitize={{((signed-integer-overflow|integer-divide-by-zero|shift-base|shift-exponent|unreachable|return|vla-bound|alignment|null|nullptr-and-nonzero-offset|pointer-overflow|float-cast-overflow|array-bounds|enum|bool|builtin|returns-nonnull-attribute|nonnull-attribute),?){18}"}} // CHECK-UBSAN-MINIMAL: "-fsanitize-minimal-runtime" // RUN: %clang -target x86_64-linux-gnu -fsanitize=undefined -fsanitize=function -fsanitize-minimal-runtime %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-UBSAN-FUNCTION-MINIMAL diff --git a/compiler-rt/lib/ubsan/ubsan_checks.inc b/compiler-rt/lib/ubsan/ubsan_checks.inc --- a/compiler-rt/lib/ubsan/ubsan_checks.inc +++ b/compiler-rt/lib/ubsan/ubsan_checks.inc @@ -18,6 +18,8 @@ UBSAN_CHECK(GenericUB, "undefined-behavior", "undefined") UBSAN_CHECK(NullPointerUse, "null-pointer-use", "null") +UBSAN_CHECK(NullptrWithNonZeroOffset, "nullptr-with-nonzero-offset", "nullptr-and-nonzero-offset") +UBSAN_CHECK(NullptrAfterNonZeroOffset, "nullptr-after-nonzero-offset", "nullptr-and-nonzero-offset") UBSAN_CHECK(PointerOverflow, "pointer-overflow", "pointer-overflow") UBSAN_CHECK(MisalignedPointerUse, "misaligned-pointer-use", "alignment") UBSAN_CHECK(AlignmentAssumption, "alignment-assumption", "alignment") diff --git a/compiler-rt/lib/ubsan/ubsan_handlers.cpp b/compiler-rt/lib/ubsan/ubsan_handlers.cpp --- a/compiler-rt/lib/ubsan/ubsan_handlers.cpp +++ b/compiler-rt/lib/ubsan/ubsan_handlers.cpp @@ -691,14 +691,30 @@ ValueHandle Result, ReportOptions Opts) { SourceLocation Loc = Data->Loc.acquire(); - ErrorType ET = ErrorType::PointerOverflow; + ErrorType ET; + + if (Base == 0 && Result != 0) + ET = ErrorType::NullptrWithNonZeroOffset; + else if (Base != 0 && Result == 0) + ET = ErrorType::NullptrAfterNonZeroOffset; + else + ET = ErrorType::PointerOverflow; if (ignoreReport(Loc, Opts, ET)) return; ScopedReport R(Opts, Loc, ET); - if ((sptr(Base) >= 0) == (sptr(Result) >= 0)) { + if (ET == ErrorType::NullptrWithNonZeroOffset) { + Diag(Loc, DL_Error, ET, + "applying non-zero offset %0 to null pointer is undefined") + << Result; + } else if (ET == ErrorType::NullptrAfterNonZeroOffset) { + Diag(Loc, DL_Error, ET, + "subtracting integral value of non-null pointer %0 from pointer " + "itself is undefined") + << (void *)Base; + } else if ((sptr(Base) >= 0) == (sptr(Result) >= 0)) { if (Base > Result) Diag(Loc, DL_Error, ET, "addition of unsigned offset to %0 overflowed to %1") diff --git a/compiler-rt/test/ubsan/TestCases/Pointer/index-overflow.cpp b/compiler-rt/test/ubsan/TestCases/Pointer/index-overflow.cpp --- a/compiler-rt/test/ubsan/TestCases/Pointer/index-overflow.cpp +++ b/compiler-rt/test/ubsan/TestCases/Pointer/index-overflow.cpp @@ -1,7 +1,9 @@ // RUN: %clangxx -fsanitize=pointer-overflow %s -o %t -// RUN: %run %t 1 2>&1 | FileCheck %s --check-prefix=ERR +// RUN: %run %t 2 2>&1 | FileCheck %s --check-prefix=ERR2 +// RUN: %run %t 1 2>&1 | FileCheck %s --check-prefix=ERR1 // RUN: %run %t 0 2>&1 | FileCheck %s --check-prefix=SAFE // RUN: %run %t -1 2>&1 | FileCheck %s --check-prefix=SAFE +// RUN: %run %t -2 2>&1 | FileCheck %s --check-prefix=SAFE #include #include @@ -9,7 +11,8 @@ int main(int argc, char *argv[]) { // SAFE-NOT: runtime error - // ERR: runtime error: pointer index expression with base {{.*}} overflowed to + // ERR2: runtime error: pointer index expression with base {{.*}} overflowed to + // ERR1: runtime error: subtracting integral value of non-null pointer 0x{{.*}} from pointer itself is undefined char *p = (char *)(UINTPTR_MAX); diff --git a/compiler-rt/test/ubsan/TestCases/Pointer/nullptr-and-nonzero-offset-constants.cpp b/compiler-rt/test/ubsan/TestCases/Pointer/nullptr-and-nonzero-offset-constants.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/test/ubsan/TestCases/Pointer/nullptr-and-nonzero-offset-constants.cpp @@ -0,0 +1,25 @@ +// RUN: %clang -x c -fsanitize=nullptr-and-nonzero-offset -O0 %s -o %t && %run %t 2>&1 | FileCheck %s --implicit-check-not="error:" +// RUN: %clang -x c -fsanitize=nullptr-and-nonzero-offset -O1 %s -o %t && %run %t 2>&1 | FileCheck %s --implicit-check-not="error:" +// RUN: %clang -x c -fsanitize=nullptr-and-nonzero-offset -O2 %s -o %t && %run %t 2>&1 | FileCheck %s --implicit-check-not="error:" +// RUN: %clang -x c -fsanitize=nullptr-and-nonzero-offset -O3 %s -o %t && %run %t 2>&1 | FileCheck %s --implicit-check-not="error:" + +// RUN: %clang -x c++ -fsanitize=nullptr-and-nonzero-offset -O0 %s -o %t && %run %t 2>&1 | FileCheck %s --implicit-check-not="error:" +// RUN: %clang -x c++ -fsanitize=nullptr-and-nonzero-offset -O1 %s -o %t && %run %t 2>&1 | FileCheck %s --implicit-check-not="error:" +// RUN: %clang -x c++ -fsanitize=nullptr-and-nonzero-offset -O2 %s -o %t && %run %t 2>&1 | FileCheck %s --implicit-check-not="error:" +// RUN: %clang -x c++ -fsanitize=nullptr-and-nonzero-offset -O3 %s -o %t && %run %t 2>&1 | FileCheck %s --implicit-check-not="error:" + +#include + +int main(int argc, char *argv[]) { + char *base, *result; + + base = (char *)0; + result = base + 1; + // CHECK: {{.*}}.cpp:[[@LINE-1]]:17: runtime error: applying non-zero offset 1 to null pointer is undefined + + base = (char *)1; + result = base - 1; + // CHECK: {{.*}}.cpp:[[@LINE-1]]:17: runtime error: subtracting integral value of non-null pointer 0x{{[0]*}}1 from pointer itself is undefined + + return 0; +} diff --git a/compiler-rt/test/ubsan/TestCases/Pointer/nullptr-and-nonzero-offset-summary.cpp b/compiler-rt/test/ubsan/TestCases/Pointer/nullptr-and-nonzero-offset-summary.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/test/ubsan/TestCases/Pointer/nullptr-and-nonzero-offset-summary.cpp @@ -0,0 +1,22 @@ +// RUN: %clangxx -fsanitize=nullptr-and-nonzero-offset %s -o %t +// RUN: %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-NOTYPE +// RUN: %env_ubsan_opts=report_error_type=1 %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-TYPE +// REQUIRES: !ubsan-standalone && !ubsan-standalone-static + +#include + +int main(int argc, char *argv[]) { + char *base, *result; + + base = (char *)0; + result = base + 1; + // CHECK-NOTYPE: SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior {{.*}}summary.cpp:[[@LINE-1]]:17 + // CHECK-TYPE: SUMMARY: UndefinedBehaviorSanitizer: nullptr-with-nonzero-offset {{.*}}summary.cpp:[[@LINE-2]]:17 + + base = (char *)1; + result = base - 1; + // CHECK-NOTYPE: SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior {{.*}}summary.cpp:[[@LINE-1]]:17 + // CHECK-TYPE: SUMMARY: UndefinedBehaviorSanitizer: nullptr-after-nonzero-offset {{.*}}summary.cpp:[[@LINE-2]]:17 + + return 0; +} diff --git a/compiler-rt/test/ubsan/TestCases/Pointer/nullptr-and-nonzero-offset-variable.cpp b/compiler-rt/test/ubsan/TestCases/Pointer/nullptr-and-nonzero-offset-variable.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/test/ubsan/TestCases/Pointer/nullptr-and-nonzero-offset-variable.cpp @@ -0,0 +1,52 @@ +// RUN: %clang -x c -fsanitize=nullptr-and-nonzero-offset -O0 %s -o %t && %run %t 2>&1 | FileCheck %s --implicit-check-not="error:" --check-prefix=CHECK-OK +// RUN: %clang -x c -fsanitize=nullptr-and-nonzero-offset -O1 %s -o %t && %run %t 2>&1 | FileCheck %s --implicit-check-not="error:" --check-prefix=CHECK-OK +// RUN: %clang -x c -fsanitize=nullptr-and-nonzero-offset -O2 %s -o %t && %run %t 2>&1 | FileCheck %s --implicit-check-not="error:" --check-prefix=CHECK-OK +// RUN: %clang -x c -fsanitize=nullptr-and-nonzero-offset -O3 %s -o %t && %run %t 2>&1 | FileCheck %s --implicit-check-not="error:" --check-prefix=CHECK-OK + +// RUN: %clang -x c++ -fsanitize=nullptr-and-nonzero-offset -O0 %s -o %t && %run %t 2>&1 | FileCheck %s --implicit-check-not="error:" --check-prefix=CHECK-OK +// RUN: %clang -x c++ -fsanitize=nullptr-and-nonzero-offset -O1 %s -o %t && %run %t 2>&1 | FileCheck %s --implicit-check-not="error:" --check-prefix=CHECK-OK +// RUN: %clang -x c++ -fsanitize=nullptr-and-nonzero-offset -O2 %s -o %t && %run %t 2>&1 | FileCheck %s --implicit-check-not="error:" --check-prefix=CHECK-OK +// RUN: %clang -x c++ -fsanitize=nullptr-and-nonzero-offset -O3 %s -o %t && %run %t 2>&1 | FileCheck %s --implicit-check-not="error:" --check-prefix=CHECK-OK + +// RUN: %clang -x c -fsanitize=nullptr-and-nonzero-offset -O0 %s -o %t && %run %t I_AM_UB 2>&1 | FileCheck %s --implicit-check-not="error:" --check-prefix=CHECK-UB +// RUN: %clang -x c -fsanitize=nullptr-and-nonzero-offset -O1 %s -o %t && %run %t I_AM_UB 2>&1 | FileCheck %s --implicit-check-not="error:" --check-prefix=CHECK-UB +// RUN: %clang -x c -fsanitize=nullptr-and-nonzero-offset -O2 %s -o %t && %run %t I_AM_UB 2>&1 | FileCheck %s --implicit-check-not="error:" --check-prefix=CHECK-UB +// RUN: %clang -x c -fsanitize=nullptr-and-nonzero-offset -O3 %s -o %t && %run %t I_AM_UB 2>&1 | FileCheck %s --implicit-check-not="error:" --check-prefix=CHECK-UB + +// RUN: %clang -x c++ -fsanitize=nullptr-and-nonzero-offset -O0 %s -o %t && %run %t I_AM_UB 2>&1 | FileCheck %s --implicit-check-not="error:" --check-prefix=CHECK-UB +// RUN: %clang -x c++ -fsanitize=nullptr-and-nonzero-offset -O1 %s -o %t && %run %t I_AM_UB 2>&1 | FileCheck %s --implicit-check-not="error:" --check-prefix=CHECK-UB +// RUN: %clang -x c++ -fsanitize=nullptr-and-nonzero-offset -O2 %s -o %t && %run %t I_AM_UB 2>&1 | FileCheck %s --implicit-check-not="error:" --check-prefix=CHECK-UB +// RUN: %clang -x c++ -fsanitize=nullptr-and-nonzero-offset -O3 %s -o %t && %run %t I_AM_UB 2>&1 | FileCheck %s --implicit-check-not="error:" --check-prefix=CHECK-UB + +#include +#include + +// Just so deduplication doesn't do anything. +static char *getelementpointer_inbounds_v0(char *base, unsigned long offset) { + // Potentially UB. + return base + offset; +} +static char *getelementpointer_inbounds_v1(char *base, unsigned long offset) { + // Potentially UB. + return base + offset; +} + +int main(int argc, char *argv[]) { + char *base; + unsigned long offset; + + printf("Dummy\n"); + // CHECK: Dummy + + base = (char *)0; + offset = argc - 1; + (void)getelementpointer_inbounds_v0(base, offset); + // CHECK-UB: {{.*}}.cpp:[[@LINE-17]]:15: runtime error: applying non-zero offset 1 to null pointer is undefined + + base = (char *)(intptr_t)(argc - 1); + offset = argc == 1 ? 0 : -(argc - 1); + (void)getelementpointer_inbounds_v1(base, offset); + // CHECK-UB: {{.*}}.cpp:[[@LINE-18]]:15: runtime error: subtracting integral value of non-null pointer 0x{{[0]*}}1 from pointer itself is undefined + + return 0; +} diff --git a/compiler-rt/test/ubsan/TestCases/Pointer/unsigned-index-expression.cpp b/compiler-rt/test/ubsan/TestCases/Pointer/unsigned-index-expression.cpp --- a/compiler-rt/test/ubsan/TestCases/Pointer/unsigned-index-expression.cpp +++ b/compiler-rt/test/ubsan/TestCases/Pointer/unsigned-index-expression.cpp @@ -12,7 +12,7 @@ // CHECK: unsigned-index-expression.cpp:[[@LINE+1]]:16: runtime error: subtraction of unsigned offset from 0x{{.*}} overflowed to 0x{{.*}} char *q1 = p - neg_1; - // CHECK: unsigned-index-expression.cpp:[[@LINE+2]]:16: runtime error: pointer index expression with base 0x{{0*}} overflowed to 0x{{.*}} + // CHECK: unsigned-index-expression.cpp:[[@LINE+2]]:16: runtime error: applying non-zero offset {{.*}} to null pointer is undefined char *n = nullptr; char *q2 = n - 1ULL; diff --git a/compiler-rt/test/ubsan_minimal/TestCases/nullptr-and-nonzero-offset.c b/compiler-rt/test/ubsan_minimal/TestCases/nullptr-and-nonzero-offset.c new file mode 100644 --- /dev/null +++ b/compiler-rt/test/ubsan_minimal/TestCases/nullptr-and-nonzero-offset.c @@ -0,0 +1,23 @@ +// RUN: %clang -fsanitize=nullptr-and-nonzero-offset %s -o %t && %run %t 2>&1 | FileCheck %s --check-prefixes=CHECK + +#include + +int main(int argc, char *argv[]) { + char *base, *result; + + // CHECK-NOT: pointer-overflow + + base = (char *)0; + result = base + 1; + // CHECK: pointer-overflow + + // CHECK-NOT: pointer-overflow + + base = (char *)1; + result = base - 1; + // CHECK: pointer-overflow + + // CHECK-NOT: pointer-overflow + + return 0; +} diff --git a/llvm/docs/ReleaseNotes.rst b/llvm/docs/ReleaseNotes.rst --- a/llvm/docs/ReleaseNotes.rst +++ b/llvm/docs/ReleaseNotes.rst @@ -54,6 +54,20 @@ ``bcmp`` pattern, and convert it into a call to ``bcmp`` (or ``memcmp``) function. +* As per :ref:`LLVM Language Reference Manual `, + ``getelementptr inbounds`` can not change the null status of a pointer, + meaning it can not produce non-null pointer given null base pointer, and + likewise given non-null base pointer it can not produce null pointer; if it + does, the result is a :ref:`poison value `. + Since `r369789 `_ + (`D66608 `_ ``[InstCombine] icmp eq/ne (gep + inbounds P, Idx..), null -> icmp eq/ne P, null``) LLVM uses that for + transformations. If the original source violates these requirements this + may result in code being miscompiled. If you are using clang front-end, + Undefined Behaviour Sanitizer ``-fsanitize=nullptr-and-nonzero-offset`` check + will catch such cases. + + Changes to the LLVM IR ----------------------