Skip to content

Commit

Permalink
[ubsan] Add a nullability sanitizer
Browse files Browse the repository at this point in the history
Teach UBSan to detect when a value with the _Nonnull type annotation
assumes a null value. Call expressions, initializers, assignments, and
return statements are all checked.

Because _Nonnull does not affect IRGen, the new checks are disabled by
default. The new driver flags are:

  -fsanitize=nullability-arg      (_Nonnull violation in call)
  -fsanitize=nullability-assign   (_Nonnull violation in assignment)
  -fsanitize=nullability-return   (_Nonnull violation in return stmt)
  -fsanitize=nullability          (all of the above)

This patch builds on top of UBSan's existing support for detecting
violations of the nonnull attributes ('nonnull' and 'returns_nonnull'),
and relies on the compiler-rt support for those checks. Eventually we
will need to update the diagnostic messages in compiler-rt (there are
FIXME's for this, which will be addressed in a follow-up).

One point of note is that the nullability-return check is only allowed
to kick in if all arguments to the function satisfy their nullability
preconditions. This makes it necessary to emit some null checks in the
function body itself.

Testing: check-clang and check-ubsan. I also built some Apple ObjC
frameworks with an asserts-enabled compiler, and verified that we get
valid reports.

Differential Revision: https://reviews.llvm.org/D30762

llvm-svn: 297700
  • Loading branch information
vedantk committed Mar 14, 2017
1 parent 620f86f commit 42c17ec
Show file tree
Hide file tree
Showing 11 changed files with 398 additions and 29 deletions.
12 changes: 11 additions & 1 deletion clang/docs/UndefinedBehaviorSanitizer.rst
Expand Up @@ -92,6 +92,12 @@ Available checks are:
parameter which is declared to never be null.
- ``-fsanitize=null``: Use of a null pointer or creation of a null
reference.
- ``-fsanitize=nullability-arg``: Passing null as a function parameter
which is annotated with ``_Nonnull``.
- ``-fsanitize=nullability-assign``: Assigning null to an lvalue which
is annotated with ``_Nonnull``.
- ``-fsanitize=nullability-return``: Returning null from a function with
a return type annotated with ``_Nonnull``.
- ``-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
Expand Down Expand Up @@ -130,11 +136,15 @@ Available checks are:

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 the ``nullability-*`` checks.
- ``-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 ``nullability-arg``,
``nullability-assign``, and ``nullability-return``. While violating
nullability does not have undefined behavior, it is often unintentional,
so UBSan offers to catch it.

Stack traces and report symbolization
=====================================
Expand Down
5 changes: 5 additions & 0 deletions clang/include/clang/Basic/Sanitizers.def
Expand Up @@ -64,6 +64,11 @@ SANITIZER("function", Function)
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)
Expand Down
110 changes: 90 additions & 20 deletions clang/lib/CodeGen/CGCall.cpp
Expand Up @@ -2912,19 +2912,7 @@ void CodeGenFunction::EmitFunctionEpilog(const CGFunctionInfo &FI,

llvm::Instruction *Ret;
if (RV) {
if (CurCodeDecl && SanOpts.has(SanitizerKind::ReturnsNonnullAttribute)) {
if (auto RetNNAttr = CurCodeDecl->getAttr<ReturnsNonNullAttr>()) {
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();
Expand All @@ -2934,6 +2922,62 @@ void CodeGenFunction::EmitFunctionEpilog(const CGFunctionInfo &FI,
Ret->setDebugLoc(std::move(RetDbgLoc));
}

void CodeGenFunction::EmitReturnValueCheck(llvm::Value *RV,
SourceLocation EndLoc) {
// A current decl may not be available when emitting vtable thunks.
if (!CurCodeDecl)
return;

ReturnsNonNullAttr *RetNNAttr = nullptr;
if (SanOpts.has(SanitizerKind::ReturnsNonnullAttribute))
RetNNAttr = CurCodeDecl->getAttr<ReturnsNonNullAttr>();

if (!RetNNAttr && !requiresReturnValueNullabilityCheck())
return;

// Prefer the returns_nonnull attribute if it's present.
SourceLocation AttrLoc;
SanitizerMask CheckKind;
if (RetNNAttr) {
assert(!requiresReturnValueNullabilityCheck() &&
"Cannot check nullability and the nonnull attribute");
AttrLoc = RetNNAttr->getLocation();
CheckKind = SanitizerKind::ReturnsNonnullAttribute;
} else {
// FIXME: The runtime shouldn't refer to the 'returns_nonnull' attribute.
if (auto *DD = dyn_cast<DeclaratorDecl>(CurCodeDecl))
if (auto *TSI = DD->getTypeSourceInfo())
if (auto FTL = TSI->getTypeLoc().castAs<FunctionTypeLoc>())
AttrLoc = FTL.getReturnLoc().findNullabilityLoc();
CheckKind = SanitizerKind::NullabilityReturn;
}

SanitizerScope SanScope(this);

llvm::BasicBlock *Check = nullptr;
llvm::BasicBlock *NoCheck = nullptr;
if (requiresReturnValueNullabilityCheck()) {
// Before doing the nullability check, make sure that the preconditions for
// the check are met.
Check = createBasicBlock("nullcheck");
NoCheck = createBasicBlock("no.nullcheck");
Builder.CreateCondBr(RetValNullabilityPrecondition, Check, NoCheck);
EmitBlock(Check);
}

// Now do the null check. If the returns_nonnull attribute is present, this
// is done unconditionally.
llvm::Value *Cond = Builder.CreateIsNotNull(RV);
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;
Expand Down Expand Up @@ -3244,25 +3288,51 @@ void CodeGenFunction::EmitNonNullArgCheck(RValue RV, QualType ArgType,
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;

// The param decl may be missing in a variadic function.
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)

// Prefer the nonnull attribute if it's present.
const NonNullAttr *NNAttr = nullptr;
if (SanOpts.has(SanitizerKind::NonnullAttribute))
NNAttr = getNonNullAttr(AC.getDecl(), PVD, ArgType, ArgNo);

bool CanCheckNullability = false;
if (SanOpts.has(SanitizerKind::NullabilityArg) && !NNAttr && PVD) {
auto Nullability = PVD->getType()->getNullability(getContext());
CanCheckNullability = Nullability &&
*Nullability == NullabilityKind::NonNull &&
PVD->getTypeSourceInfo();
}

if (!NNAttr && !CanCheckNullability)
return;

SourceLocation AttrLoc;
SanitizerMask CheckKind;
if (NNAttr) {
AttrLoc = NNAttr->getLocation();
CheckKind = SanitizerKind::NonnullAttribute;
} else {
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(
Expand Down
38 changes: 38 additions & 0 deletions clang/lib/CodeGen/CGDecl.cpp
Expand Up @@ -672,13 +672,36 @@ static void drillIntoBlockVariable(CodeGenFunction &CGF,
lvalue.setAddress(CGF.emitBlockByrefAddress(lvalue.getAddress(), var));
}

void CodeGenFunction::EmitNullabilityCheck(LValue LHS, llvm::Value *RHS,
SourceLocation Loc) {
if (!SanOpts.has(SanitizerKind::NullabilityAssign))
return;

auto Nullability = LHS.getType()->getNullability(getContext());
if (!Nullability || *Nullability != NullabilityKind::NonNull)
return;

// Check if the right hand side of the assignment is nonnull, if the left
// hand side must be nonnull.
SanitizerScope SanScope(this);
llvm::Value *IsNotNull = Builder.CreateIsNotNull(RHS);
// FIXME: The runtime shouldn't refer to a 'reference'.
llvm::Constant *StaticData[] = {
EmitCheckSourceLocation(Loc), EmitCheckTypeDescriptor(LHS.getType()),
llvm::ConstantInt::get(Int8Ty, 1),
llvm::ConstantInt::get(Int8Ty, TCK_ReferenceBinding)};
EmitCheck({{IsNotNull, SanitizerKind::NullabilityAssign}},
SanitizerHandler::TypeMismatch, StaticData, RHS);
}

void CodeGenFunction::EmitScalarInit(const Expr *init, const ValueDecl *D,
LValue lvalue, bool capturedByInit) {
Qualifiers::ObjCLifetime lifetime = lvalue.getObjCLifetime();
if (!lifetime) {
llvm::Value *value = EmitScalarExpr(init);
if (capturedByInit)
drillIntoBlockVariable(*this, lvalue, cast<VarDecl>(D));
EmitNullabilityCheck(lvalue, value, init->getExprLoc());
EmitStoreThroughLValue(RValue::get(value), lvalue, true);
return;
}
Expand Down Expand Up @@ -767,6 +790,8 @@ void CodeGenFunction::EmitScalarInit(const Expr *init, const ValueDecl *D,

if (capturedByInit) drillIntoBlockVariable(*this, lvalue, cast<VarDecl>(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.
Expand Down Expand Up @@ -1880,6 +1905,19 @@ void CodeGenFunction::EmitParmDecl(const VarDecl &D, ParamValue Arg,

if (D.hasAttr<AnnotateAttr>())
EmitVarAnnotations(&D, DeclPtr.getPointer());

// We can only check return value nullability if all arguments to the
// function satisfy their nullability preconditions. This makes it necessary
// to emit null checks for args in the function body itself.
if (requiresReturnValueNullabilityCheck()) {
auto Nullability = Ty->getNullability(getContext());
if (Nullability && *Nullability == NullabilityKind::NonNull) {
SanitizerScope SanScope(this);
RetValNullabilityPrecondition =
Builder.CreateAnd(RetValNullabilityPrecondition,
Builder.CreateIsNotNull(Arg.getAnyValue()));
}
}
}

void CodeGenModule::EmitOMPDeclareReduction(const OMPDeclareReductionDecl *D,
Expand Down
6 changes: 4 additions & 2 deletions clang/lib/CodeGen/CGExprScalar.cpp
Expand Up @@ -3132,10 +3132,12 @@ Value *ScalarExprEmitter::VisitBinAssign(const BinaryOperator *E) {
// 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.
Expand Down
12 changes: 12 additions & 0 deletions clang/lib/CodeGen/CodeGenFunction.cpp
Expand Up @@ -822,6 +822,18 @@ void CodeGenFunction::StartFunction(GlobalDecl GD,
}
}

// 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)) {
auto Nullability = FnRetTy->getNullability(getContext());
if (Nullability && *Nullability == NullabilityKind::NonNull) {
if (!(SanOpts.has(SanitizerKind::ReturnsNonnullAttribute) &&
CurCodeDecl && CurCodeDecl->getAttr<ReturnsNonNullAttr>()))
RetValNullabilityPrecondition =
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").
Expand Down
17 changes: 17 additions & 0 deletions clang/lib/CodeGen/CodeGenFunction.h
Expand Up @@ -1375,6 +1375,16 @@ class CodeGenFunction : public CodeGenTypeCache {
/// information about the layout of the variable.
llvm::DenseMap<const ValueDecl *, BlockByrefInfo> BlockByrefInfos;

/// Used by -fsanitize=nullability-return to determine whether the return
/// value can be checked.
llvm::Value *RetValNullabilityPrecondition = nullptr;

/// Check if -fsanitize=nullability-return instrumentation is required for
/// this function.
bool requiresReturnValueNullabilityCheck() const {
return RetValNullabilityPrecondition;
}

llvm::BasicBlock *TerminateLandingPad;
llvm::BasicBlock *TerminateHandler;
llvm::BasicBlock *TrapBB;
Expand Down Expand Up @@ -1753,6 +1763,9 @@ class CodeGenFunction : public CodeGenTypeCache {
void EmitFunctionEpilog(const CGFunctionInfo &FI, bool EmitRetDbgLoc,
SourceLocation EndLoc);

/// 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);

Expand Down Expand Up @@ -3458,6 +3471,10 @@ class CodeGenFunction : public CodeGenTypeCache {
void EmitBranchOnBoolExpr(const Expr *Cond, llvm::BasicBlock *TrueBlock,
llvm::BasicBlock *FalseBlock, uint64_t TrueCount);

/// Given an assignment `*LHS = RHS`, emit a test that checks if \p RHS is
/// nonnull, if \p LHS is marked _Nonnull.
void EmitNullabilityCheck(LValue LHS, llvm::Value *RHS, 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);
Expand Down
11 changes: 6 additions & 5 deletions clang/lib/Driver/SanitizerArgs.cpp
Expand Up @@ -26,18 +26,19 @@ using namespace clang::driver;
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,
};
Expand Down
3 changes: 2 additions & 1 deletion clang/lib/Driver/ToolChain.cpp
Expand Up @@ -697,7 +697,8 @@ SanitizerMask ToolChain::getSupportedSanitizers() const {
// 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 ||
Expand Down
31 changes: 31 additions & 0 deletions clang/test/CodeGenObjC/ubsan-nonnull-and-nullability.m
@@ -0,0 +1,31 @@
// REQUIRES: asserts
// RUN: %clang_cc1 -x objective-c -emit-llvm -triple x86_64-apple-macosx10.10.0 -fsanitize=nullability-return,returns-nonnull-attribute,nullability-arg,nonnull-attribute %s -o - -w | 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;
}

// CHECK-LABEL: define void @f2
void f2(int *_Nonnull __attribute__((nonnull)) p) {}

// CHECK-LABEL: define void @call_f2
void call_f2() {
// CHECK: call void @__ubsan_handle_nonnull_arg_abort
// CHECK-NOT: call void @__ubsan_handle_nonnull_arg_abort
f2((void *)0);
}

0 comments on commit 42c17ec

Please sign in to comment.