Index: docs/UndefinedBehaviorSanitizer.rst =================================================================== --- docs/UndefinedBehaviorSanitizer.rst +++ docs/UndefinedBehaviorSanitizer.rst @@ -92,6 +92,15 @@ parameter which is declared to never be null. - ``-fsanitize=null``: Use of a null pointer or creation of a null reference. + - ``-fsanitize=nullability``: Passing null to a ``_Nonnull`` function + parameter, assigning null to to a lvalue marked ``_Nonnull``, or + returning null from a function with a ``_Nonnull`` return type. + You can enable just the return value check with + ``-fsanitize=nullability-return``, the assignment check with + ``-fsanitize=nullability-assign``, and the argument check with + ``-fsanitize=nullability-arg``. While violating nullability rules does + not result in undefined behavior, it is often unintentional, so UBSan + offers to catch it. - ``-fsanitize=object-size``: An attempt to potentially use bytes which the optimizer can determine are not part of the object being accessed. This will also detect some types of undefined behavior that may not @@ -130,11 +139,13 @@ You can also use the following check groups: - ``-fsanitize=undefined``: All of the checks listed above other than - ``unsigned-integer-overflow``. + ``unsigned-integer-overflow`` and ``nullability``. - ``-fsanitize=undefined-trap``: Deprecated alias of ``-fsanitize=undefined``. - ``-fsanitize=integer``: Checks for undefined or suspicious integer behavior (e.g. unsigned integer overflow). + - ``-fsanitize=nullability``: Enables ``-fsanitize=nullability-arg``, + ``-fsanitize=nullability-assign``, and ``-fsanitize=nullability-return``. Stack traces and report symbolization ===================================== Index: include/clang/Basic/Sanitizers.def =================================================================== --- include/clang/Basic/Sanitizers.def +++ include/clang/Basic/Sanitizers.def @@ -64,6 +64,11 @@ SANITIZER("integer-divide-by-zero", IntegerDivideByZero) SANITIZER("nonnull-attribute", NonnullAttribute) SANITIZER("null", Null) +SANITIZER("nullability-arg", NullabilityArg) +SANITIZER("nullability-assign", NullabilityAssign) +SANITIZER("nullability-return", NullabilityReturn) +SANITIZER_GROUP("nullability", Nullability, + NullabilityArg | NullabilityAssign | NullabilityReturn) SANITIZER("object-size", ObjectSize) SANITIZER("return", Return) SANITIZER("returns-nonnull-attribute", ReturnsNonnullAttribute) Index: lib/CodeGen/CGCall.cpp =================================================================== --- lib/CodeGen/CGCall.cpp +++ lib/CodeGen/CGCall.cpp @@ -2912,19 +2912,7 @@ llvm::Instruction *Ret; if (RV) { - if (CurCodeDecl && SanOpts.has(SanitizerKind::ReturnsNonnullAttribute)) { - if (auto RetNNAttr = CurCodeDecl->getAttr()) { - SanitizerScope SanScope(this); - llvm::Value *Cond = Builder.CreateICmpNE( - RV, llvm::Constant::getNullValue(RV->getType())); - llvm::Constant *StaticData[] = { - EmitCheckSourceLocation(EndLoc), - EmitCheckSourceLocation(RetNNAttr->getLocation()), - }; - EmitCheck(std::make_pair(Cond, SanitizerKind::ReturnsNonnullAttribute), - SanitizerHandler::NonnullReturn, StaticData, None); - } - } + EmitReturnValueCheck(RV, EndLoc); Ret = Builder.CreateRet(RV); } else { Ret = Builder.CreateRetVoid(); @@ -2934,6 +2922,57 @@ Ret->setDebugLoc(std::move(RetDbgLoc)); } +void CodeGenFunction::EmitReturnValueCheck(llvm::Value *RV, + SourceLocation EndLoc) { + if (!CurCodeDecl) + return; + + ReturnsNonNullAttr *RetNNAttr = nullptr; + if (SanOpts.has(SanitizerKind::ReturnsNonnullAttribute)) + RetNNAttr = CurCodeDecl->getAttr(); + + if (!RetNNAttr && !requiresReturnValueNullabilityCheck()) + return; + + SourceLocation AttrLoc; + SanitizerMask CheckKind; + if (RetNNAttr) { + assert(!requiresReturnValueNullabilityCheck() && + "Cannot check both the nonnull attribute and the annotation"); + AttrLoc = RetNNAttr->getLocation(); + CheckKind = SanitizerKind::ReturnsNonnullAttribute; + } else { + // FIXME: The runtime shouldn't refer to the 'returns_nonnull' attribute. + if (auto *DD = dyn_cast(CurCodeDecl)) + if (auto FTL = + DD->getTypeSourceInfo()->getTypeLoc().castAs()) + AttrLoc = FTL.getReturnLoc().findNullabilityLoc(); + CheckKind = SanitizerKind::NullabilityReturn; + } + + SanitizerScope SanScope(this); + + llvm::BasicBlock *Check = nullptr; + llvm::BasicBlock *NoCheck = nullptr; + if (requiresReturnValueNullabilityCheck()) { + Check = createBasicBlock("nullcheck"); + NoCheck = createBasicBlock("no.nullcheck"); + Builder.CreateCondBr(CanCheckRetValNullability, Check, NoCheck); + EmitBlock(Check); + } + + llvm::Value *Cond = + Builder.CreateICmpNE(RV, llvm::Constant::getNullValue(RV->getType())); + llvm::Constant *StaticData[] = { + EmitCheckSourceLocation(EndLoc), EmitCheckSourceLocation(AttrLoc), + }; + EmitCheck(std::make_pair(Cond, CheckKind), SanitizerHandler::NonnullReturn, + StaticData, None); + + if (requiresReturnValueNullabilityCheck()) + EmitBlock(NoCheck); +} + static bool isInAllocaArgument(CGCXXABI &ABI, QualType type) { const CXXRecordDecl *RD = type->getAsCXXRecordDecl(); return RD && ABI.getRecordArgABI(RD) == CGCXXABI::RAA_DirectInMemory; @@ -3244,25 +3283,43 @@ SourceLocation ArgLoc, AbstractCallee AC, unsigned ParmNum) { - if (!SanOpts.has(SanitizerKind::NonnullAttribute) || !AC.getDecl()) + if (!AC.getDecl() || !(SanOpts.has(SanitizerKind::NonnullAttribute) || + SanOpts.has(SanitizerKind::NullabilityArg))) return; + + SourceLocation AttrLoc; + SanitizerMask CheckKind; auto PVD = ParmNum < AC.getNumParams() ? AC.getParamDecl(ParmNum) : nullptr; unsigned ArgNo = PVD ? PVD->getFunctionScopeIndex() : ParmNum; - auto NNAttr = getNonNullAttr(AC.getDecl(), PVD, ArgType, ArgNo); - if (!NNAttr) - return; + if (SanOpts.has(SanitizerKind::NonnullAttribute)) { + auto NNAttr = getNonNullAttr(AC.getDecl(), PVD, ArgType, ArgNo); + if (!NNAttr) + return; + + AttrLoc = NNAttr->getLocation(); + CheckKind = SanitizerKind::NonnullAttribute; + } else { + if (!PVD) + return; + auto Nullability = PVD->getType()->getNullability(getContext()); + if (!Nullability || *Nullability != NullabilityKind::NonNull) + return; + + AttrLoc = PVD->getTypeSourceInfo()->getTypeLoc().findNullabilityLoc(); + CheckKind = SanitizerKind::NullabilityArg; + } + SanitizerScope SanScope(this); assert(RV.isScalar()); llvm::Value *V = RV.getScalarVal(); llvm::Value *Cond = Builder.CreateICmpNE(V, llvm::Constant::getNullValue(V->getType())); llvm::Constant *StaticData[] = { - EmitCheckSourceLocation(ArgLoc), - EmitCheckSourceLocation(NNAttr->getLocation()), + EmitCheckSourceLocation(ArgLoc), EmitCheckSourceLocation(AttrLoc), llvm::ConstantInt::get(Int32Ty, ArgNo + 1), }; - EmitCheck(std::make_pair(Cond, SanitizerKind::NonnullAttribute), - SanitizerHandler::NonnullArg, StaticData, None); + EmitCheck(std::make_pair(Cond, CheckKind), SanitizerHandler::NonnullArg, + StaticData, None); } void CodeGenFunction::EmitCallArgs( Index: lib/CodeGen/CGDecl.cpp =================================================================== --- lib/CodeGen/CGDecl.cpp +++ lib/CodeGen/CGDecl.cpp @@ -672,6 +672,26 @@ lvalue.setAddress(CGF.emitBlockByrefAddress(lvalue.getAddress(), var)); } +void CodeGenFunction::EmitNullabilityCheck(LValue Dst, llvm::Value *Src, + SourceLocation Loc) { + if (!SanOpts.has(SanitizerKind::NullabilityAssign)) + return; + + auto Nullability = Dst.getType()->getNullability(getContext()); + if (!Nullability || *Nullability != NullabilityKind::NonNull) + return; + + SanitizerScope SanScope(this); + llvm::Value *IsNotNull = Builder.CreateIsNotNull(Src); + // FIXME: The runtime shouldn't refer to a 'reference'. + llvm::Constant *StaticData[] = { + EmitCheckSourceLocation(Loc), EmitCheckTypeDescriptor(Dst.getType()), + llvm::ConstantInt::get(Int8Ty, 1), + llvm::ConstantInt::get(Int8Ty, TCK_ReferenceBinding)}; + EmitCheck({{IsNotNull, SanitizerKind::NullabilityAssign}}, + SanitizerHandler::TypeMismatch, StaticData, Src); +} + void CodeGenFunction::EmitScalarInit(const Expr *init, const ValueDecl *D, LValue lvalue, bool capturedByInit) { Qualifiers::ObjCLifetime lifetime = lvalue.getObjCLifetime(); @@ -679,6 +699,7 @@ llvm::Value *value = EmitScalarExpr(init); if (capturedByInit) drillIntoBlockVariable(*this, lvalue, cast(D)); + EmitNullabilityCheck(lvalue, value, init->getExprLoc()); EmitStoreThroughLValue(RValue::get(value), lvalue, true); return; } @@ -767,6 +788,8 @@ if (capturedByInit) drillIntoBlockVariable(*this, lvalue, cast(D)); + EmitNullabilityCheck(lvalue, value, init->getExprLoc()); + // If the variable might have been accessed by its initializer, we // might have to initialize with a barrier. We have to do this for // both __weak and __strong, but __weak got filtered out above. @@ -1880,6 +1903,18 @@ if (D.hasAttr()) EmitVarAnnotations(&D, DeclPtr.getPointer()); + + // Skip the return value nullability check if the nullability preconditions + // are broken. + if (requiresReturnValueNullabilityCheck()) { + auto Nullability = Ty->getNullability(getContext()); + if (Nullability && *Nullability == NullabilityKind::NonNull) { + SanitizerScope SanScope(this); + CanCheckRetValNullability = + Builder.CreateAnd(CanCheckRetValNullability, + Builder.CreateIsNotNull(Arg.getAnyValue())); + } + } } void CodeGenModule::EmitOMPDeclareReduction(const OMPDeclareReductionDecl *D, Index: lib/CodeGen/CGExprScalar.cpp =================================================================== --- lib/CodeGen/CGExprScalar.cpp +++ lib/CodeGen/CGExprScalar.cpp @@ -3132,10 +3132,12 @@ // because the result is altered by the store, i.e., [C99 6.5.16p1] // 'An assignment expression has the value of the left operand after // the assignment...'. - if (LHS.isBitField()) + if (LHS.isBitField()) { CGF.EmitStoreThroughBitfieldLValue(RValue::get(RHS), LHS, &RHS); - else + } else { + CGF.EmitNullabilityCheck(LHS, RHS, E->getExprLoc()); CGF.EmitStoreThroughLValue(RValue::get(RHS), LHS); + } } // If the result is clearly ignored, return now. Index: lib/CodeGen/CodeGenFunction.h =================================================================== --- lib/CodeGen/CodeGenFunction.h +++ lib/CodeGen/CodeGenFunction.h @@ -1375,6 +1375,16 @@ /// information about the layout of the variable. llvm::DenseMap BlockByrefInfos; + /// Used by -fsanitize=nullability-return to determine whether the return + /// value can be checked. + llvm::Value *CanCheckRetValNullability = nullptr; + + /// Check if -fsanitize=nullability-return instrumentation is required for + /// this function. + bool requiresReturnValueNullabilityCheck() const { + return CanCheckRetValNullability; + } + llvm::BasicBlock *TerminateLandingPad; llvm::BasicBlock *TerminateHandler; llvm::BasicBlock *TrapBB; @@ -1753,6 +1763,9 @@ void EmitFunctionEpilog(const CGFunctionInfo &FI, bool EmitRetDbgLoc, SourceLocation EndLoc); + /// \brief Emit a test that checks if the return value \p RV is nonnull. + void EmitReturnValueCheck(llvm::Value *RV, SourceLocation EndLoc); + /// EmitStartEHSpec - Emit the start of the exception spec. void EmitStartEHSpec(const Decl *D); @@ -3458,6 +3471,10 @@ void EmitBranchOnBoolExpr(const Expr *Cond, llvm::BasicBlock *TrueBlock, llvm::BasicBlock *FalseBlock, uint64_t TrueCount); + /// \brief Emit a test that checks if \p Src is nonnull if \p Dst is + /// annotated as nonnull. + void EmitNullabilityCheck(LValue Dst, llvm::Value *Src, SourceLocation Loc); + /// \brief Emit a description of a type in a format suitable for passing to /// a runtime sanitizer handler. llvm::Constant *EmitCheckTypeDescriptor(QualType T); Index: lib/CodeGen/CodeGenFunction.cpp =================================================================== --- lib/CodeGen/CodeGenFunction.cpp +++ lib/CodeGen/CodeGenFunction.cpp @@ -822,6 +822,16 @@ } } + // If we're checking nullability, we need to know whether we can check the + // return value. Initialize the flag to 'true' and refine it in EmitParmDecl. + if (SanOpts.has(SanitizerKind::NullabilityReturn)) + if (auto Nullability = FnRetTy->getNullability(getContext())) + if (Nullability && *Nullability == NullabilityKind::NonNull) + if (!(SanOpts.has(SanitizerKind::ReturnsNonnullAttribute) && + CurCodeDecl && CurCodeDecl->getAttr())) + CanCheckRetValNullability = + llvm::ConstantInt::getTrue(getLLVMContext()); + // If we're in C++ mode and the function name is "main", it is guaranteed // to be norecurse by the standard (3.6.1.3 "The function main shall not be // used within a program"). Index: lib/Driver/SanitizerArgs.cpp =================================================================== --- lib/Driver/SanitizerArgs.cpp +++ lib/Driver/SanitizerArgs.cpp @@ -26,18 +26,19 @@ using namespace llvm::opt; enum : SanitizerMask { - NeedsUbsanRt = Undefined | Integer | CFI, + NeedsUbsanRt = Undefined | Integer | Nullability | CFI, NeedsUbsanCxxRt = Vptr | CFI, NotAllowedWithTrap = Vptr, RequiresPIE = DataFlow, NeedsUnwindTables = Address | Thread | Memory | DataFlow, - SupportsCoverage = Address | Memory | Leak | Undefined | Integer | DataFlow, - RecoverableByDefault = Undefined | Integer, + SupportsCoverage = + Address | Memory | Leak | Undefined | Integer | Nullability | DataFlow, + RecoverableByDefault = Undefined | Integer | Nullability, Unrecoverable = Unreachable | Return, LegacyFsanitizeRecoverMask = Undefined | Integer, NeedsLTO = CFI, - TrappingSupported = - (Undefined & ~Vptr) | UnsignedIntegerOverflow | LocalBounds | CFI, + TrappingSupported = (Undefined & ~Vptr) | UnsignedIntegerOverflow | + Nullability | LocalBounds | CFI, TrappingDefault = CFI, CFIClasses = CFIVCall | CFINVCall | CFIDerivedCast | CFIUnrelatedCast, }; Index: lib/Driver/ToolChain.cpp =================================================================== --- lib/Driver/ToolChain.cpp +++ lib/Driver/ToolChain.cpp @@ -697,7 +697,8 @@ // platform dependent. using namespace SanitizerKind; SanitizerMask Res = (Undefined & ~Vptr & ~Function) | (CFI & ~CFIICall) | - CFICastStrict | UnsignedIntegerOverflow | LocalBounds; + CFICastStrict | UnsignedIntegerOverflow | Nullability | + LocalBounds; if (getTriple().getArch() == llvm::Triple::x86 || getTriple().getArch() == llvm::Triple::x86_64 || getTriple().getArch() == llvm::Triple::arm || Index: test/CodeGenObjC/ubsan-null-retval.m =================================================================== --- /dev/null +++ test/CodeGenObjC/ubsan-null-retval.m @@ -0,0 +1,21 @@ +// REQUIRES: asserts +// RUN: %clang_cc1 -x objective-c -emit-llvm -triple x86_64-apple-macosx10.10.0 -fsanitize=nullability-return,returns-nonnull-attribute %s -o - | FileCheck %s + +// If both the annotation and the attribute are present, prefer the attribute, +// since it actually affects IRGen. +// +// CHECK-LABEL: define nonnull i32* @f1 +__attribute__((returns_nonnull)) int *_Nonnull f1(int *_Nonnull p) { + // CHECK: entry: + // CHECK-NEXT: [[ADDR:%.*]] = alloca i32* + // CHECK-NEXT: store i32* [[P:%.*]], i32** [[ADDR]] + // CHECK-NEXT: [[ARG:%.*]] = load i32*, i32** [[ADDR]] + // CHECK-NEXT: [[ICMP:%.*]] = icmp ne i32* [[ARG]], null, !nosanitize + // CHECK-NEXT: br i1 [[ICMP]], label %[[CONT:.+]], label %[[HANDLE:[^,]+]] + // CHECK: [[HANDLE]]: + // CHECK-NEXT: call void @__ubsan_handle_nonnull_return_abort + // CHECK-NEXT: unreachable, !nosanitize + // CHECK: [[CONT]]: + // CHECK-NEXT: ret i32* + return p; +} Index: test/CodeGenObjC/ubsan-nullability.m =================================================================== --- /dev/null +++ test/CodeGenObjC/ubsan-nullability.m @@ -0,0 +1,182 @@ +// REQUIRES: asserts +// RUN: %clang_cc1 -x objective-c -emit-llvm -triple x86_64-apple-macosx10.10.0 -fsanitize=nullability-arg,nullability-assign,nullability-return -w %s -o - | FileCheck %s + +// CHECK: [[NONNULL_RV_LOC1:@.*]] = private unnamed_addr global {{.*}} i32 109, i32 1 {{.*}} i32 100, i32 6 +// CHECK: [[NONNULL_ARG_LOC:@.*]] = private unnamed_addr global {{.*}} i32 204, i32 15 {{.*}} i32 190, i32 23 +// CHECK: [[NONNULL_ASSIGN1_LOC:@.*]] = private unnamed_addr global {{.*}} i32 305, i32 9 +// CHECK: [[NONNULL_ASSIGN2_LOC:@.*]] = private unnamed_addr global {{.*}} i32 405, i32 10 +// CHECK: [[NONNULL_ASSIGN3_LOC:@.*]] = private unnamed_addr global {{.*}} i32 505, i32 10 +// CHECK: [[NONNULL_INIT1_LOC:@.*]] = private unnamed_addr global {{.*}} i32 604, i32 25 +// CHECK: [[NONNULL_INIT2_LOC1:@.*]] = private unnamed_addr global {{.*}} i32 707, i32 26 +// CHECK: [[NONNULL_INIT2_LOC2:@.*]] = private unnamed_addr global {{.*}} i32 707, i32 29 +// CHECK: [[NONNULL_RV_LOC2:@.*]] = private unnamed_addr global {{.*}} i32 817, i32 1 {{.*}} i32 800, i32 6 + +#define NULL ((void *)0) + +// CHECK-LABEL: define i32* @nonnull_retval1 +#line 100 +int *_Nonnull nonnull_retval1(int *p) { + // CHECK: br i1 true, label %[[NULL:.*]], label %[[NONULL:.*]], !nosanitize + // CHECK: [[NULL]]: + // CHECK: [[ICMP:%.*]] = icmp ne i32* {{.*}}, null, !nosanitize + // CHECK-NEXT: br i1 [[ICMP]], {{.*}}, !nosanitize + // CHECK: call void @__ubsan_handle_nonnull_return{{.*}}[[NONNULL_RV_LOC1]] + return p; + // CHECK: [[NONULL]]: + // CHECK: ret i32* +} + +#line 190 +void nonnull_arg(int *_Nonnull p) {} + +// CHECK-LABEL: define void @call_func_with_nonnull_arg +#line 200 +void call_func_with_nonnull_arg(int *_Nonnull p) { + // CHECK: [[ICMP:%.*]] = icmp ne i32* {{.*}}, null, !nosanitize + // CHECK-NEXT: br i1 [[ICMP]], {{.*}}, !nosanitize + // CHECK: call void @__ubsan_handle_nonnull_arg{{.*}}[[NONNULL_ARG_LOC]] + nonnull_arg(p); +} + +// CHECK-LABEL: define void @nonnull_assign1 +#line 300 +void nonnull_assign1(int *p) { + // CHECK: [[ICMP:%.*]] = icmp ne i32* {{.*}}, null, !nosanitize + // CHECK-NEXT: br i1 [[ICMP]], {{.*}}, !nosanitize + // CHECK: call void @__ubsan_handle_type_mismatch{{.*}}[[NONNULL_ASSIGN1_LOC]] + int *_Nonnull local; + local = p; +} + +// CHECK-LABEL: define void @nonnull_assign2 +#line 400 +void nonnull_assign2(int *p) { + // CHECK: [[ICMP:%.*]] = icmp ne i32* %{{.*}}, null, !nosanitize + // CHECK-NEXT: br i1 [[ICMP]], {{.*}}, !nosanitize + // CHECK: call void @__ubsan_handle_type_mismatch{{.*}}[[NONNULL_ASSIGN2_LOC]] + int *_Nonnull arr[1]; + arr[0] = p; +} + +struct S1 { + int *_Nonnull mptr; +}; + +// CHECK-LABEL: define void @nonnull_assign3 +#line 500 +void nonnull_assign3(int *p) { + // CHECK: [[ICMP:%.*]] = icmp ne i32* %{{.*}}, null, !nosanitize + // CHECK-NEXT: br i1 [[ICMP]], {{.*}}, !nosanitize + // CHECK: call void @__ubsan_handle_type_mismatch{{.*}}[[NONNULL_ASSIGN3_LOC]] + struct S1 s; + s.mptr = p; +} + +// CHECK-LABEL: define void @nonnull_init1 +#line 600 +void nonnull_init1(int *p) { + // CHECK: [[ICMP:%.*]] = icmp ne i32* %{{.*}}, null, !nosanitize + // CHECK-NEXT: br i1 [[ICMP]], {{.*}}, !nosanitize + // CHECK: call void @__ubsan_handle_type_mismatch{{.*}}[[NONNULL_INIT1_LOC]] + int *_Nonnull local = p; +} + +// CHECK-LABEL: define void @nonnull_init2 +#line 700 +void nonnull_init2(int *p) { + // CHECK: [[ICMP:%.*]] = icmp ne i32* %{{.*}}, null, !nosanitize + // CHECK-NEXT: br i1 [[ICMP]], {{.*}}, !nosanitize + // CHECK: call void @__ubsan_handle_type_mismatch{{.*}}[[NONNULL_INIT2_LOC1]] + // CHECK: [[ICMP:%.*]] = icmp ne i32* %{{.*}}, null, !nosanitize + // CHECK-NEXT: br i1 [[ICMP]], {{.*}}, !nosanitize + // CHECK: call void @__ubsan_handle_type_mismatch{{.*}}[[NONNULL_INIT2_LOC2]] + int *_Nonnull arr[] = {p, p}; +} + +// CHECK-LABEL: define i32* @nonnull_retval2 +#line 800 +int *_Nonnull nonnull_retval2(int *_Nonnull arg1, //< Test this. + int *_Nonnull arg2, //< Test this. + int *_Nullable arg3, //< Don't test the rest. + int *arg4, + int arg5, ...) { + // CHECK: [[ARG1CMP:%.*]] = icmp ne i32* %arg1, null, !nosanitize + // CHECK: [[DO_RV_CHECK_1:%.*]] = and i1 true, [[ARG1CMP]], !nosanitize + // CHECK: [[ARG2CMP:%.*]] = icmp ne i32* %arg2, null, !nosanitize + // CHECK: [[DO_RV_CHECK_2:%.*]] = and i1 [[DO_RV_CHECK_1]], [[ARG2CMP]] + // CHECK: br i1 [[DO_RV_CHECK_2]], label %[[NULL:.*]], label %[[NONULL:.*]], !nosanitize + // CHECK: [[NULL]]: + // CHECK: [[ICMP:%.*]] = icmp ne i32* {{.*}}, null, !nosanitize + // CHECK-NEXT: br i1 [[ICMP]], {{.*}}, !nosanitize + // CHECK: call void @__ubsan_handle_nonnull_return{{.*}}[[NONNULL_RV_LOC2]] + return arg1; + // CHECK: [[NONULL]]: + // CHECK: ret i32* +} + +@interface A ++(int *_Nonnull) objc_clsmethod: (int *_Nonnull) arg1; +-(int *_Nonnull) objc_method: (int *_Nonnull) arg1; +@end + +@implementation A + +// CHECK-LABEL: define internal i32* @"\01+[A objc_clsmethod:]" ++(int *_Nonnull) objc_clsmethod: (int *_Nonnull) arg1 { + // CHECK: [[ARG1CMP:%.*]] = icmp ne i32* %arg1, null, !nosanitize + // CHECK: [[DO_RV_CHECK:%.*]] = and i1 true, [[ARG1CMP]] + // CHECK: br i1 [[DO_RV_CHECK]], label %[[NULL:.*]], label %[[NONULL:.*]], !nosanitize + // CHECK: [[NULL]]: + // CHECK: [[ICMP:%.*]] = icmp ne i32* {{.*}}, null, !nosanitize + // CHECK-NEXT: br i1 [[ICMP]], {{.*}}, !nosanitize + // CHECK: call void @__ubsan_handle_nonnull_return{{.*}} + return arg1; + // CHECK: [[NONULL]]: + // CHECK: ret i32* +} + +// CHECK-LABEL: define internal i32* @"\01-[A objc_method:]" +-(int *_Nonnull) objc_method: (int *_Nonnull) arg1 { + // CHECK: [[ARG1CMP:%.*]] = icmp ne i32* %arg1, null, !nosanitize + // CHECK: [[DO_RV_CHECK:%.*]] = and i1 true, [[ARG1CMP]] + // CHECK: br i1 [[DO_RV_CHECK]], label %[[NULL:.*]], label %[[NONULL:.*]], !nosanitize + // CHECK: [[NULL]]: + // CHECK: [[ICMP:%.*]] = icmp ne i32* {{.*}}, null, !nosanitize + // CHECK-NEXT: br i1 [[ICMP]], {{.*}}, !nosanitize + // CHECK: call void @__ubsan_handle_nonnull_return{{.*}} + return arg1; + // CHECK: [[NONULL]]: + // CHECK: ret i32* +} +@end + +// CHECK-LABEL: define void @call_A +void call_A(A *a, int *p) { + // CHECK: [[ICMP:%.*]] = icmp ne i32* [[P1:%.*]], null, !nosanitize + // CHECK: br i1 [[ICMP]], {{.*}}, !nosanitize + // CHECK: call void @__ubsan_handle_nonnull_arg{{.*}} !nosanitize + // CHECK: call i32* {{.*}} @objc_msgSend to i32* {{.*}}({{.*}}, i32* [[P1]]) + [a objc_method: p]; + + // CHECK: [[ICMP:%.*]] = icmp ne i32* [[P2:%.*]], null, !nosanitize + // CHECK: br i1 [[ICMP]], {{.*}}, !nosanitize + // CHECK: call void @__ubsan_handle_nonnull_arg{{.*}} !nosanitize + // CHECK: call i32* {{.*}} @objc_msgSend to i32* {{.*}}({{.*}}, i32* [[P2]]) + [A objc_clsmethod: p]; +} + +void dont_crash(int *_Nonnull p, ...) {} + +int main() { + nonnull_retval1(NULL); + nonnull_retval2(NULL, NULL, NULL, NULL, 0, 0, 0, 0); + call_func_with_nonnull_arg(NULL); + nonnull_assign1(NULL); + nonnull_assign2(NULL); + nonnull_assign3(NULL); + nonnull_init1(NULL); + nonnull_init2(NULL); + call_A(NULL, NULL); + dont_crash(NULL, NULL); + return 0; +}