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.
The target audience for this tool is developers working on or targetting LLVM from a frontend. The idea is to be able to take arbitrary IR (with the assumption of known inputs), and evaluate it concretely after having made poison semantics explicit to detect cases where either a) the original code executes UB, or b) a transform pass introduces UB which didn't exist in the original program.
At the moment, this is mostly the framework and still needs to be fleshed out. By reusing existing code we have decent coverage, but there's a lot of cases not yet handled. What's here is good enough to handle interesting cases though; for instance, one of the recent LFTR bugs involved UB being triggered by integer induction variables with nsw/nuw flags would be reported by the current code.
(See comment in PoisonChecking.cpp for full explanation and context)
Differential Revision: https://reviews.llvm.org/D64215
llvm-svn: 365536
--- /dev/null
+//===- 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<PoisonCheckingPass> {
+ PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM);
+ PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
+};
+
+}
+
+
+#endif // LLVM_TRANSFORMS_INSTRUMENTATION_POISON_CHECKING_H
cast<GetElementPtrInst>(this)->setIsInBounds(false);
break;
}
+ // TODO: FastMathFlags!
}
+
bool Instruction::isExact() const {
return cast<PossiblyExactOperator>(this)->isExact();
}
#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"
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
InstrProfiling.cpp
PGOInstrumentation.cpp
PGOMemOPSizeOpt.cpp
+ PoisonChecking.cpp
SanitizerCoverage.cpp
ThreadSanitizer.cpp
HWAddressSanitizer.cpp
--- /dev/null
+//===- 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<bool>
+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<ConstantInt>(V))
+ return CI->isZero();
+ return false;
+}
+
+static Value *buildOrChain(IRBuilder<> &B, ArrayRef<Value*> 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<Value*, 2> &Checks) {
+ assert(isa<BinaryOperator>(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<Value*, 2> Checks;
+ if (isa<BinaryOperator>(I))
+ generatePoisonChecksForBinOp(I, Checks);
+ return buildOrChain(B, Checks);
+}
+
+static Value *getPoisonFor(DenseMap<Value *, Value *> &ValToPoison, Value *V) {
+ auto Itr = ValToPoison.find(V);
+ if (Itr != ValToPoison.end())
+ return Itr->second;
+ if (isa<Constant>(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<ConstantInt>(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<Value *, Value *> ValToPoison;
+
+ for (BasicBlock &BB : F)
+ for (auto I = BB.begin(); isa<PHINode>(&*I); I++) {
+ auto *OldPHI = cast<PHINode>(&*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<PHINode>(I)) continue;
+
+ IRBuilder<> B(cast<Instruction>(&I));
+ if (Value *Op = const_cast<Value*>(getGuaranteedNonFullPoisonOp(&I)))
+ CreateAssertNot(B, getPoisonFor(ValToPoison, Op));
+
+ if (LocalCheck)
+ if (auto *RI = dyn_cast<ReturnInst>(&I))
+ if (RI->getNumOperands() != 0) {
+ Value *Op = RI->getOperand(0);
+ CreateAssertNot(B, getPoisonFor(ValToPoison, Op));
+ }
+
+ SmallVector<Value*, 4> 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<PHINode>(&*I); I++) {
+ auto *OldPHI = cast<PHINode>(&*I);
+ if (!ValToPoison.count(OldPHI))
+ continue; // skip the newly inserted phis
+ auto *NewPHI = cast<PHINode>(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
+ */
--- /dev/null
+; 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
+}
+
--- /dev/null
+; 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
+}
+
+
+