Index: llvm/trunk/include/llvm/Transforms/Instrumentation/PoisonChecking.h =================================================================== --- llvm/trunk/include/llvm/Transforms/Instrumentation/PoisonChecking.h +++ llvm/trunk/include/llvm/Transforms/Instrumentation/PoisonChecking.h @@ -0,0 +1,25 @@ +//===- PoisonChecking.h - ---------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + + +#ifndef LLVM_TRANSFORMS_INSTRUMENTATION_POISON_CHECKING_H +#define LLVM_TRANSFORMS_INSTRUMENTATION_POISON_CHECKING_H + +#include "llvm/IR/PassManager.h" + +namespace llvm { + +struct PoisonCheckingPass : public PassInfoMixin { + PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM); + PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM); +}; + +} + + +#endif // LLVM_TRANSFORMS_INSTRUMENTATION_POISON_CHECKING_H Index: llvm/trunk/lib/IR/Instruction.cpp =================================================================== --- llvm/trunk/lib/IR/Instruction.cpp +++ llvm/trunk/lib/IR/Instruction.cpp @@ -138,8 +138,10 @@ cast(this)->setIsInBounds(false); break; } + // TODO: FastMathFlags! } + bool Instruction::isExact() const { return cast(this)->isExact(); } Index: llvm/trunk/lib/Passes/PassBuilder.cpp =================================================================== --- llvm/trunk/lib/Passes/PassBuilder.cpp +++ llvm/trunk/lib/Passes/PassBuilder.cpp @@ -100,6 +100,7 @@ #include "llvm/Transforms/Instrumentation/InstrProfiling.h" #include "llvm/Transforms/Instrumentation/MemorySanitizer.h" #include "llvm/Transforms/Instrumentation/PGOInstrumentation.h" +#include "llvm/Transforms/Instrumentation/PoisonChecking.h" #include "llvm/Transforms/Instrumentation/ThreadSanitizer.h" #include "llvm/Transforms/Scalar/ADCE.h" #include "llvm/Transforms/Scalar/AlignmentFromAssumptions.h" Index: llvm/trunk/lib/Passes/PassRegistry.def =================================================================== --- llvm/trunk/lib/Passes/PassRegistry.def +++ llvm/trunk/lib/Passes/PassRegistry.def @@ -86,6 +86,7 @@ MODULE_PASS("verify", VerifierPass()) MODULE_PASS("asan-module", ModuleAddressSanitizerPass(/*CompileKernel=*/false, false, true, false)) MODULE_PASS("kasan-module", ModuleAddressSanitizerPass(/*CompileKernel=*/true, false, true, false)) +MODULE_PASS("poison-checking", PoisonCheckingPass()) #undef MODULE_PASS #ifndef CGSCC_ANALYSIS Index: llvm/trunk/lib/Transforms/Instrumentation/CMakeLists.txt =================================================================== --- llvm/trunk/lib/Transforms/Instrumentation/CMakeLists.txt +++ llvm/trunk/lib/Transforms/Instrumentation/CMakeLists.txt @@ -12,6 +12,7 @@ InstrProfiling.cpp PGOInstrumentation.cpp PGOMemOPSizeOpt.cpp + PoisonChecking.cpp SanitizerCoverage.cpp ThreadSanitizer.cpp HWAddressSanitizer.cpp Index: llvm/trunk/lib/Transforms/Instrumentation/PoisonChecking.cpp =================================================================== --- llvm/trunk/lib/Transforms/Instrumentation/PoisonChecking.cpp +++ llvm/trunk/lib/Transforms/Instrumentation/PoisonChecking.cpp @@ -0,0 +1,283 @@ +//===- PoisonChecking.cpp - -----------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Implements a transform pass which instruments IR such that poison semantics +// are made explicit. That is, it provides a (possibly partial) executable +// semantics for every instruction w.r.t. poison as specified in the LLVM +// LangRef. There are obvious parallels to the sanitizer tools, but this pass +// is focused purely on the semantics of LLVM IR, not any particular source +// language. If you're looking for something to see if your C/C++ contains +// UB, this is not it. +// +// The rewritten semantics of each instruction will include the following +// components: +// +// 1) The original instruction, unmodified. +// 2) A propagation rule which translates dynamic information about the poison +// state of each input to whether the dynamic output of the instruction +// produces poison. +// 3) A flag validation rule which validates any poison producing flags on the +// instruction itself (e.g. checks for overflow on nsw). +// 4) A check rule which traps (to a handler function) if this instruction must +// execute undefined behavior given the poison state of it's inputs. +// +// At the moment, the UB detection is done in a best effort manner; that is, +// the resulting code may produce a false negative result (not report UB when +// it actually exists according to the LangRef spec), but should never produce +// a false positive (report UB where it doesn't exist). The intention is to +// eventually support a "strict" mode which never dynamically reports a false +// negative at the cost of rejecting some valid inputs to translation. +// +// Use cases for this pass include: +// - Understanding (and testing!) the implications of the definition of poison +// from the LangRef. +// - Validating the output of a IR fuzzer to ensure that all programs produced +// are well defined on the specific input used. +// - Finding/confirming poison specific miscompiles by checking the poison +// status of an input/IR pair is the same before and after an optimization +// transform. +// - Checking that a bugpoint reduction does not introduce UB which didn't +// exist in the original program being reduced. +// +// The major sources of inaccuracy are currently: +// - Most validation rules not yet implemented for instructions with poison +// relavant flags. At the moment, only nsw/nuw on add/sub are supported. +// - UB which is control dependent on a branch on poison is not yet +// reported. Currently, only data flow dependence is modeled. +// - Poison which is propagated through memory is not modeled. As such, +// storing poison to memory and then reloading it will cause a false negative +// as we consider the reloaded value to not be poisoned. +// - Poison propagation across function boundaries is not modeled. At the +// moment, all arguments and return values are assumed not to be poison. +// - Undef is not modeled. In particular, the optimizer's freedom to pick +// concrete values for undef bits so as to maximize potential for producing +// poison is not modeled. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Transforms/Instrumentation/PoisonChecking.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/Statistic.h" +#include "llvm/Analysis/MemoryBuiltins.h" +#include "llvm/Analysis/ValueTracking.h" +#include "llvm/IR/InstVisitor.h" +#include "llvm/IR/IntrinsicInst.h" +#include "llvm/IR/IRBuilder.h" +#include "llvm/IR/PatternMatch.h" +#include "llvm/Support/Debug.h" + +using namespace llvm; + +#define DEBUG_TYPE "poison-checking" + +static cl::opt +LocalCheck("poison-checking-function-local", + cl::init(false), + cl::desc("Check that returns are non-poison (for testing)")); + + +static bool isConstantFalse(Value* V) { + assert(V->getType()->isIntegerTy(1)); + if (auto *CI = dyn_cast(V)) + return CI->isZero(); + return false; +} + +static Value *buildOrChain(IRBuilder<> &B, ArrayRef Ops) { + if (Ops.size() == 0) + return B.getFalse(); + unsigned i = 0; + for (; i < Ops.size() && isConstantFalse(Ops[i]); i++) {} + if (i == Ops.size()) + return B.getFalse(); + Value *Accum = Ops[i++]; + for (; i < Ops.size(); i++) + if (!isConstantFalse(Ops[i])) + Accum = B.CreateOr(Accum, Ops[i]); + return Accum; +} + +static void generatePoisonChecksForBinOp(Instruction &I, + SmallVector &Checks) { + assert(isa(I)); + + IRBuilder<> B(&I); + Value *LHS = I.getOperand(0); + Value *RHS = I.getOperand(1); + switch (I.getOpcode()) { + default: + return; + case Instruction::Add: { + if (I.hasNoSignedWrap()) { + auto *OverflowOp = + B.CreateBinaryIntrinsic(Intrinsic::sadd_with_overflow, LHS, RHS); + Checks.push_back(B.CreateExtractValue(OverflowOp, 1)); + } + if (I.hasNoUnsignedWrap()) { + auto *OverflowOp = + B.CreateBinaryIntrinsic(Intrinsic::uadd_with_overflow, LHS, RHS); + Checks.push_back(B.CreateExtractValue(OverflowOp, 1)); + } + break; + } + case Instruction::Sub: { + if (I.hasNoSignedWrap()) { + auto *OverflowOp = + B.CreateBinaryIntrinsic(Intrinsic::ssub_with_overflow, LHS, RHS); + Checks.push_back(B.CreateExtractValue(OverflowOp, 1)); + } + if (I.hasNoUnsignedWrap()) { + auto *OverflowOp = + B.CreateBinaryIntrinsic(Intrinsic::usub_with_overflow, LHS, RHS); + Checks.push_back(B.CreateExtractValue(OverflowOp, 1)); + } + break; + } + case Instruction::Mul: { + if (I.hasNoSignedWrap()) { + auto *OverflowOp = + B.CreateBinaryIntrinsic(Intrinsic::smul_with_overflow, LHS, RHS); + Checks.push_back(B.CreateExtractValue(OverflowOp, 1)); + } + if (I.hasNoUnsignedWrap()) { + auto *OverflowOp = + B.CreateBinaryIntrinsic(Intrinsic::umul_with_overflow, LHS, RHS); + Checks.push_back(B.CreateExtractValue(OverflowOp, 1)); + } + break; + } + }; +} + +static Value* generatePoisonChecks(Instruction &I) { + IRBuilder<> B(&I); + SmallVector Checks; + if (isa(I)) + generatePoisonChecksForBinOp(I, Checks); + return buildOrChain(B, Checks); +} + +static Value *getPoisonFor(DenseMap &ValToPoison, Value *V) { + auto Itr = ValToPoison.find(V); + if (Itr != ValToPoison.end()) + return Itr->second; + if (isa(V)) { + return ConstantInt::getFalse(V->getContext()); + } + // Return false for unknwon values - this implements a non-strict mode where + // unhandled IR constructs are simply considered to never produce poison. At + // some point in the future, we probably want a "strict mode" for testing if + // nothing else. + return ConstantInt::getFalse(V->getContext()); +} + +static void CreateAssert(IRBuilder<> &B, Value *Cond) { + assert(Cond->getType()->isIntegerTy(1)); + if (auto *CI = dyn_cast(Cond)) + if (CI->isAllOnesValue()) + return; + + Module *M = B.GetInsertBlock()->getModule(); + M->getOrInsertFunction("__poison_checker_assert", + Type::getVoidTy(M->getContext()), + Type::getInt1Ty(M->getContext())); + Function *TrapFunc = M->getFunction("__poison_checker_assert"); + B.CreateCall(TrapFunc, Cond); +} + +static void CreateAssertNot(IRBuilder<> &B, Value *Cond) { + assert(Cond->getType()->isIntegerTy(1)); + CreateAssert(B, B.CreateNot(Cond)); +} + +static bool rewrite(Function &F) { + auto * const Int1Ty = Type::getInt1Ty(F.getContext()); + + DenseMap ValToPoison; + + for (BasicBlock &BB : F) + for (auto I = BB.begin(); isa(&*I); I++) { + auto *OldPHI = cast(&*I); + auto *NewPHI = PHINode::Create(Int1Ty, + OldPHI->getNumIncomingValues()); + for (unsigned i = 0; i < OldPHI->getNumIncomingValues(); i++) + NewPHI->addIncoming(UndefValue::get(Int1Ty), + OldPHI->getIncomingBlock(i)); + NewPHI->insertBefore(OldPHI); + ValToPoison[OldPHI] = NewPHI; + } + + for (BasicBlock &BB : F) + for (Instruction &I : BB) { + if (isa(I)) continue; + + IRBuilder<> B(cast(&I)); + if (Value *Op = const_cast(getGuaranteedNonFullPoisonOp(&I))) + CreateAssertNot(B, getPoisonFor(ValToPoison, Op)); + + if (LocalCheck) + if (auto *RI = dyn_cast(&I)) + if (RI->getNumOperands() != 0) { + Value *Op = RI->getOperand(0); + CreateAssertNot(B, getPoisonFor(ValToPoison, Op)); + } + + SmallVector Checks; + if (propagatesFullPoison(&I)) + for (Value *V : I.operands()) + Checks.push_back(getPoisonFor(ValToPoison, V)); + + if (auto *Check = generatePoisonChecks(I)) + Checks.push_back(Check); + ValToPoison[&I] = buildOrChain(B, Checks); + } + + for (BasicBlock &BB : F) + for (auto I = BB.begin(); isa(&*I); I++) { + auto *OldPHI = cast(&*I); + if (!ValToPoison.count(OldPHI)) + continue; // skip the newly inserted phis + auto *NewPHI = cast(ValToPoison[OldPHI]); + for (unsigned i = 0; i < OldPHI->getNumIncomingValues(); i++) { + auto *OldVal = OldPHI->getIncomingValue(i); + NewPHI->setIncomingValue(i, getPoisonFor(ValToPoison, OldVal)); + } + } + return true; +} + + +PreservedAnalyses PoisonCheckingPass::run(Module &M, + ModuleAnalysisManager &AM) { + bool Changed = false; + for (auto &F : M) + Changed |= rewrite(F); + + return Changed ? PreservedAnalyses::none() : PreservedAnalyses::all(); +} + +PreservedAnalyses PoisonCheckingPass::run(Function &F, + FunctionAnalysisManager &AM) { + return rewrite(F) ? PreservedAnalyses::none() : PreservedAnalyses::all(); +} + + +/* Major TODO Items: + - Control dependent poison UB + - Strict mode - (i.e. must analyze every operand) + - Poison through memory + - Function ABIs + + Minor TODO items: + - Add propagation rules for and/or instructions + - Add hasPoisonFlags predicate to ValueTracking + - Add poison check rules for: + - exact flags, out of bounds operands + - inbounds (can't be strict due to unknown allocation sizes) + - fmf and fp casts + */ Index: llvm/trunk/test/Instrumentation/PoisonChecking/basic-flag-validation.ll =================================================================== --- llvm/trunk/test/Instrumentation/PoisonChecking/basic-flag-validation.ll +++ llvm/trunk/test/Instrumentation/PoisonChecking/basic-flag-validation.ll @@ -0,0 +1,158 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py +; RUN: opt -passes=poison-checking -S -poison-checking-function-local < %s | FileCheck %s + +; This file contains tests to exercise the custom flag validation rules + +define i32 @add_noflags(i32 %a, i32 %b) { +; CHECK-LABEL: @add_noflags( +; CHECK-NEXT: [[RES:%.*]] = add i32 [[A:%.*]], [[B:%.*]] +; CHECK-NEXT: ret i32 [[RES]] +; + %res = add i32 %a, %b + ret i32 %res +} + +define i32 @add_nsw(i32 %a, i32 %b) { +; CHECK-LABEL: @add_nsw( +; CHECK-NEXT: [[TMP1:%.*]] = call { i32, i1 } @llvm.sadd.with.overflow.i32(i32 [[A:%.*]], i32 [[B:%.*]]) +; CHECK-NEXT: [[TMP2:%.*]] = extractvalue { i32, i1 } [[TMP1]], 1 +; CHECK-NEXT: [[RES:%.*]] = add nsw i32 [[A]], [[B]] +; CHECK-NEXT: [[TMP3:%.*]] = xor i1 [[TMP2]], true +; CHECK-NEXT: call void @__poison_checker_assert(i1 [[TMP3]]) +; CHECK-NEXT: ret i32 [[RES]] +; + %res = add nsw i32 %a, %b + ret i32 %res +} + +define i32 @add_nuw(i32 %a, i32 %b) { +; CHECK-LABEL: @add_nuw( +; CHECK-NEXT: [[TMP1:%.*]] = call { i32, i1 } @llvm.uadd.with.overflow.i32(i32 [[A:%.*]], i32 [[B:%.*]]) +; CHECK-NEXT: [[TMP2:%.*]] = extractvalue { i32, i1 } [[TMP1]], 1 +; CHECK-NEXT: [[RES:%.*]] = add nuw i32 [[A]], [[B]] +; CHECK-NEXT: [[TMP3:%.*]] = xor i1 [[TMP2]], true +; CHECK-NEXT: call void @__poison_checker_assert(i1 [[TMP3]]) +; CHECK-NEXT: ret i32 [[RES]] +; + %res = add nuw i32 %a, %b + ret i32 %res +} + +define i32 @add_nsw_nuw(i32 %a, i32 %b) { +; CHECK-LABEL: @add_nsw_nuw( +; CHECK-NEXT: [[TMP1:%.*]] = call { i32, i1 } @llvm.sadd.with.overflow.i32(i32 [[A:%.*]], i32 [[B:%.*]]) +; CHECK-NEXT: [[TMP2:%.*]] = extractvalue { i32, i1 } [[TMP1]], 1 +; CHECK-NEXT: [[TMP3:%.*]] = call { i32, i1 } @llvm.uadd.with.overflow.i32(i32 [[A]], i32 [[B]]) +; CHECK-NEXT: [[TMP4:%.*]] = extractvalue { i32, i1 } [[TMP3]], 1 +; CHECK-NEXT: [[TMP5:%.*]] = or i1 [[TMP2]], [[TMP4]] +; CHECK-NEXT: [[RES:%.*]] = add nuw nsw i32 [[A]], [[B]] +; CHECK-NEXT: [[TMP6:%.*]] = xor i1 [[TMP5]], true +; CHECK-NEXT: call void @__poison_checker_assert(i1 [[TMP6]]) +; CHECK-NEXT: ret i32 [[RES]] +; + %res = add nsw nuw i32 %a, %b + ret i32 %res +} + +define i32 @sub_noflags(i32 %a, i32 %b) { +; CHECK-LABEL: @sub_noflags( +; CHECK-NEXT: [[RES:%.*]] = sub i32 [[A:%.*]], [[B:%.*]] +; CHECK-NEXT: ret i32 [[RES]] +; + %res = sub i32 %a, %b + ret i32 %res +} + +define i32 @sub_nsw(i32 %a, i32 %b) { +; CHECK-LABEL: @sub_nsw( +; CHECK-NEXT: [[TMP1:%.*]] = call { i32, i1 } @llvm.ssub.with.overflow.i32(i32 [[A:%.*]], i32 [[B:%.*]]) +; CHECK-NEXT: [[TMP2:%.*]] = extractvalue { i32, i1 } [[TMP1]], 1 +; CHECK-NEXT: [[RES:%.*]] = sub nsw i32 [[A]], [[B]] +; CHECK-NEXT: [[TMP3:%.*]] = xor i1 [[TMP2]], true +; CHECK-NEXT: call void @__poison_checker_assert(i1 [[TMP3]]) +; CHECK-NEXT: ret i32 [[RES]] +; + %res = sub nsw i32 %a, %b + ret i32 %res +} + +define i32 @sub_nuw(i32 %a, i32 %b) { +; CHECK-LABEL: @sub_nuw( +; CHECK-NEXT: [[TMP1:%.*]] = call { i32, i1 } @llvm.usub.with.overflow.i32(i32 [[A:%.*]], i32 [[B:%.*]]) +; CHECK-NEXT: [[TMP2:%.*]] = extractvalue { i32, i1 } [[TMP1]], 1 +; CHECK-NEXT: [[RES:%.*]] = sub nuw i32 [[A]], [[B]] +; CHECK-NEXT: [[TMP3:%.*]] = xor i1 [[TMP2]], true +; CHECK-NEXT: call void @__poison_checker_assert(i1 [[TMP3]]) +; CHECK-NEXT: ret i32 [[RES]] +; + %res = sub nuw i32 %a, %b + ret i32 %res +} + +define i32 @sub_nsw_nuw(i32 %a, i32 %b) { +; CHECK-LABEL: @sub_nsw_nuw( +; CHECK-NEXT: [[TMP1:%.*]] = call { i32, i1 } @llvm.ssub.with.overflow.i32(i32 [[A:%.*]], i32 [[B:%.*]]) +; CHECK-NEXT: [[TMP2:%.*]] = extractvalue { i32, i1 } [[TMP1]], 1 +; CHECK-NEXT: [[TMP3:%.*]] = call { i32, i1 } @llvm.usub.with.overflow.i32(i32 [[A]], i32 [[B]]) +; CHECK-NEXT: [[TMP4:%.*]] = extractvalue { i32, i1 } [[TMP3]], 1 +; CHECK-NEXT: [[TMP5:%.*]] = or i1 [[TMP2]], [[TMP4]] +; CHECK-NEXT: [[RES:%.*]] = sub nuw nsw i32 [[A]], [[B]] +; CHECK-NEXT: [[TMP6:%.*]] = xor i1 [[TMP5]], true +; CHECK-NEXT: call void @__poison_checker_assert(i1 [[TMP6]]) +; CHECK-NEXT: ret i32 [[RES]] +; + %res = sub nsw nuw i32 %a, %b + ret i32 %res +} + +define i32 @mul_noflags(i32 %a, i32 %b) { +; CHECK-LABEL: @mul_noflags( +; CHECK-NEXT: [[RES:%.*]] = mul i32 [[A:%.*]], [[B:%.*]] +; CHECK-NEXT: ret i32 [[RES]] +; + %res = mul i32 %a, %b + ret i32 %res +} + +define i32 @mul_nsw(i32 %a, i32 %b) { +; CHECK-LABEL: @mul_nsw( +; CHECK-NEXT: [[TMP1:%.*]] = call { i32, i1 } @llvm.smul.with.overflow.i32(i32 [[A:%.*]], i32 [[B:%.*]]) +; CHECK-NEXT: [[TMP2:%.*]] = extractvalue { i32, i1 } [[TMP1]], 1 +; CHECK-NEXT: [[RES:%.*]] = mul nsw i32 [[A]], [[B]] +; CHECK-NEXT: [[TMP3:%.*]] = xor i1 [[TMP2]], true +; CHECK-NEXT: call void @__poison_checker_assert(i1 [[TMP3]]) +; CHECK-NEXT: ret i32 [[RES]] +; + %res = mul nsw i32 %a, %b + ret i32 %res +} + +define i32 @mul_nuw(i32 %a, i32 %b) { +; CHECK-LABEL: @mul_nuw( +; CHECK-NEXT: [[TMP1:%.*]] = call { i32, i1 } @llvm.umul.with.overflow.i32(i32 [[A:%.*]], i32 [[B:%.*]]) +; CHECK-NEXT: [[TMP2:%.*]] = extractvalue { i32, i1 } [[TMP1]], 1 +; CHECK-NEXT: [[RES:%.*]] = mul nuw i32 [[A]], [[B]] +; CHECK-NEXT: [[TMP3:%.*]] = xor i1 [[TMP2]], true +; CHECK-NEXT: call void @__poison_checker_assert(i1 [[TMP3]]) +; CHECK-NEXT: ret i32 [[RES]] +; + %res = mul nuw i32 %a, %b + ret i32 %res +} + +define i32 @mul_nsw_nuw(i32 %a, i32 %b) { +; CHECK-LABEL: @mul_nsw_nuw( +; CHECK-NEXT: [[TMP1:%.*]] = call { i32, i1 } @llvm.smul.with.overflow.i32(i32 [[A:%.*]], i32 [[B:%.*]]) +; CHECK-NEXT: [[TMP2:%.*]] = extractvalue { i32, i1 } [[TMP1]], 1 +; CHECK-NEXT: [[TMP3:%.*]] = call { i32, i1 } @llvm.umul.with.overflow.i32(i32 [[A]], i32 [[B]]) +; CHECK-NEXT: [[TMP4:%.*]] = extractvalue { i32, i1 } [[TMP3]], 1 +; CHECK-NEXT: [[TMP5:%.*]] = or i1 [[TMP2]], [[TMP4]] +; CHECK-NEXT: [[RES:%.*]] = mul nuw nsw i32 [[A]], [[B]] +; CHECK-NEXT: [[TMP6:%.*]] = xor i1 [[TMP5]], true +; CHECK-NEXT: call void @__poison_checker_assert(i1 [[TMP6]]) +; CHECK-NEXT: ret i32 [[RES]] +; + %res = mul nsw nuw i32 %a, %b + ret i32 %res +} + Index: llvm/trunk/test/Instrumentation/PoisonChecking/ub-checks.ll =================================================================== --- llvm/trunk/test/Instrumentation/PoisonChecking/ub-checks.ll +++ llvm/trunk/test/Instrumentation/PoisonChecking/ub-checks.ll @@ -0,0 +1,137 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py +; RUN: opt -passes=poison-checking -S < %s | FileCheck %s + +; This file contains tests to exercise the UB triggering instructions with +; a potential source of UB. The UB source is kept simple; we focus on the +; UB triggering instructions here. + +define void @store(i8* %base, i32 %a) { +; CHECK-LABEL: @store( +; CHECK-NEXT: [[TMP1:%.*]] = call { i32, i1 } @llvm.sadd.with.overflow.i32(i32 [[A:%.*]], i32 1) +; CHECK-NEXT: [[TMP2:%.*]] = extractvalue { i32, i1 } [[TMP1]], 1 +; CHECK-NEXT: [[ADD:%.*]] = add nsw i32 [[A]], 1 +; CHECK-NEXT: [[P:%.*]] = getelementptr i8, i8* [[BASE:%.*]], i32 [[ADD]] +; CHECK-NEXT: [[TMP3:%.*]] = xor i1 [[TMP2]], true +; CHECK-NEXT: call void @__poison_checker_assert(i1 [[TMP3]]) +; CHECK-NEXT: store i8 0, i8* [[P]] +; CHECK-NEXT: ret void +; + %add = add nsw i32 %a, 1 + %p = getelementptr i8, i8* %base, i32 %add + store i8 0, i8* %p + ret void +} + +define void @load(i8* %base, i32 %a) { +; CHECK-LABEL: @load( +; CHECK-NEXT: [[TMP1:%.*]] = call { i32, i1 } @llvm.sadd.with.overflow.i32(i32 [[A:%.*]], i32 1) +; CHECK-NEXT: [[TMP2:%.*]] = extractvalue { i32, i1 } [[TMP1]], 1 +; CHECK-NEXT: [[ADD:%.*]] = add nsw i32 [[A]], 1 +; CHECK-NEXT: [[P:%.*]] = getelementptr i8, i8* [[BASE:%.*]], i32 [[ADD]] +; CHECK-NEXT: [[TMP3:%.*]] = xor i1 [[TMP2]], true +; CHECK-NEXT: call void @__poison_checker_assert(i1 [[TMP3]]) +; CHECK-NEXT: [[TMP4:%.*]] = load volatile i8, i8* [[P]] +; CHECK-NEXT: ret void +; + %add = add nsw i32 %a, 1 + %p = getelementptr i8, i8* %base, i32 %add + load volatile i8, i8* %p + ret void +} + +define void @atomicrmw(i8* %base, i32 %a) { +; CHECK-LABEL: @atomicrmw( +; CHECK-NEXT: [[TMP1:%.*]] = call { i32, i1 } @llvm.sadd.with.overflow.i32(i32 [[A:%.*]], i32 1) +; CHECK-NEXT: [[TMP2:%.*]] = extractvalue { i32, i1 } [[TMP1]], 1 +; CHECK-NEXT: [[ADD:%.*]] = add nsw i32 [[A]], 1 +; CHECK-NEXT: [[P:%.*]] = getelementptr i8, i8* [[BASE:%.*]], i32 [[ADD]] +; CHECK-NEXT: [[TMP3:%.*]] = xor i1 [[TMP2]], true +; CHECK-NEXT: call void @__poison_checker_assert(i1 [[TMP3]]) +; CHECK-NEXT: [[TMP4:%.*]] = atomicrmw add i8* [[P]], i8 1 seq_cst +; CHECK-NEXT: ret void +; + %add = add nsw i32 %a, 1 + %p = getelementptr i8, i8* %base, i32 %add + atomicrmw add i8* %p, i8 1 seq_cst + ret void +} + +define void @cmpxchg(i8* %base, i32 %a) { +; CHECK-LABEL: @cmpxchg( +; CHECK-NEXT: [[TMP1:%.*]] = call { i32, i1 } @llvm.sadd.with.overflow.i32(i32 [[A:%.*]], i32 1) +; CHECK-NEXT: [[TMP2:%.*]] = extractvalue { i32, i1 } [[TMP1]], 1 +; CHECK-NEXT: [[ADD:%.*]] = add nsw i32 [[A]], 1 +; CHECK-NEXT: [[P:%.*]] = getelementptr i8, i8* [[BASE:%.*]], i32 [[ADD]] +; CHECK-NEXT: [[TMP3:%.*]] = xor i1 [[TMP2]], true +; CHECK-NEXT: call void @__poison_checker_assert(i1 [[TMP3]]) +; CHECK-NEXT: [[TMP4:%.*]] = cmpxchg i8* [[P]], i8 1, i8 0 seq_cst seq_cst +; CHECK-NEXT: ret void +; + %add = add nsw i32 %a, 1 + %p = getelementptr i8, i8* %base, i32 %add + cmpxchg i8* %p, i8 1, i8 0 seq_cst seq_cst + ret void +} + +define i32 @udiv(i8* %base, i32 %a) { +; CHECK-LABEL: @udiv( +; CHECK-NEXT: [[TMP1:%.*]] = call { i32, i1 } @llvm.uadd.with.overflow.i32(i32 [[A:%.*]], i32 1) +; CHECK-NEXT: [[TMP2:%.*]] = extractvalue { i32, i1 } [[TMP1]], 1 +; CHECK-NEXT: [[ADD:%.*]] = add nuw i32 [[A]], 1 +; CHECK-NEXT: [[TMP3:%.*]] = xor i1 [[TMP2]], true +; CHECK-NEXT: call void @__poison_checker_assert(i1 [[TMP3]]) +; CHECK-NEXT: [[RES:%.*]] = udiv i32 2048, [[ADD]] +; CHECK-NEXT: ret i32 [[RES]] +; + %add = add nuw i32 %a, 1 + %res = udiv i32 2048, %add + ret i32 %res +} + +define i32 @sdiv(i8* %base, i32 %a) { +; CHECK-LABEL: @sdiv( +; CHECK-NEXT: [[TMP1:%.*]] = call { i32, i1 } @llvm.uadd.with.overflow.i32(i32 [[A:%.*]], i32 1) +; CHECK-NEXT: [[TMP2:%.*]] = extractvalue { i32, i1 } [[TMP1]], 1 +; CHECK-NEXT: [[ADD:%.*]] = add nuw i32 [[A]], 1 +; CHECK-NEXT: [[TMP3:%.*]] = xor i1 [[TMP2]], true +; CHECK-NEXT: call void @__poison_checker_assert(i1 [[TMP3]]) +; CHECK-NEXT: [[RES:%.*]] = sdiv i32 2048, [[ADD]] +; CHECK-NEXT: ret i32 [[RES]] +; + %add = add nuw i32 %a, 1 + %res = sdiv i32 2048, %add + ret i32 %res +} + +define i32 @urem(i8* %base, i32 %a) { +; CHECK-LABEL: @urem( +; CHECK-NEXT: [[TMP1:%.*]] = call { i32, i1 } @llvm.uadd.with.overflow.i32(i32 [[A:%.*]], i32 1) +; CHECK-NEXT: [[TMP2:%.*]] = extractvalue { i32, i1 } [[TMP1]], 1 +; CHECK-NEXT: [[ADD:%.*]] = add nuw i32 [[A]], 1 +; CHECK-NEXT: [[TMP3:%.*]] = xor i1 [[TMP2]], true +; CHECK-NEXT: call void @__poison_checker_assert(i1 [[TMP3]]) +; CHECK-NEXT: [[RES:%.*]] = urem i32 2048, [[ADD]] +; CHECK-NEXT: ret i32 [[RES]] +; + %add = add nuw i32 %a, 1 + %res = urem i32 2048, %add + ret i32 %res +} + +define i32 @srem(i8* %base, i32 %a) { +; CHECK-LABEL: @srem( +; CHECK-NEXT: [[TMP1:%.*]] = call { i32, i1 } @llvm.uadd.with.overflow.i32(i32 [[A:%.*]], i32 1) +; CHECK-NEXT: [[TMP2:%.*]] = extractvalue { i32, i1 } [[TMP1]], 1 +; CHECK-NEXT: [[ADD:%.*]] = add nuw i32 [[A]], 1 +; CHECK-NEXT: [[TMP3:%.*]] = xor i1 [[TMP2]], true +; CHECK-NEXT: call void @__poison_checker_assert(i1 [[TMP3]]) +; CHECK-NEXT: [[RES:%.*]] = srem i32 2048, [[ADD]] +; CHECK-NEXT: ret i32 [[RES]] +; + %add = add nuw i32 %a, 1 + %res = srem i32 2048, %add + ret i32 %res +} + + +