[FuzzMutate] Handle BB without predecessor, avoid insertion after musttail call, avoid sinking token type

FuzzMutate didn't consider some corner cases and leads to mutation failure when mutating some modules.
This patch fixes 3 bugs:

- Add null check when encountering basic blocks without predecessor to avoid segmentation fault
- Avoid insertion after `musttail call` instruction
- Avoid sinking token type

Unit tests are also added.

Reviewed By: Peter

Differential Revision: https://reviews.llvm.org/D151936
This commit is contained in:
Henry Yu 2023-06-01 19:51:41 -07:00 committed by Peter Rong
parent 15a719de01
commit 258cd1fc38
4 changed files with 117 additions and 11 deletions

View File

@ -114,10 +114,16 @@ InjectorIRStrategy::chooseOperation(Value *Src, RandomIRBuilder &IB) {
return *RS;
}
static inline iterator_range<BasicBlock::iterator>
getInsertionRange(BasicBlock &BB) {
auto End = BB.getTerminatingMustTailCall() ? std::prev(BB.end()) : BB.end();
return make_range(BB.getFirstInsertionPt(), End);
}
void InjectorIRStrategy::mutate(BasicBlock &BB, RandomIRBuilder &IB) {
SmallVector<Instruction *, 32> Insts;
for (auto I = BB.getFirstInsertionPt(), E = BB.end(); I != E; ++I)
Insts.push_back(&*I);
for (Instruction &I : getInsertionRange(BB))
Insts.push_back(&I);
if (Insts.size() < 1)
return;
@ -360,6 +366,10 @@ void InsertFunctionStrategy::mutate(BasicBlock &BB, RandomIRBuilder &IB) {
auto RS = makeSampler(IB.Rand, Functions);
Function *F = RS.getSelection();
// Some functions accept metadata type or token type as arguments.
// We don't call those functions for now.
// For example, `@llvm.dbg.declare(metadata, metadata, metadata)`
// https://llvm.org/docs/SourceLevelDebugging.html#llvm-dbg-declare
auto IsUnsupportedTy = [](Type *T) {
return T->isMetadataTy() || T->isTokenTy();
};
@ -385,7 +395,7 @@ void InsertFunctionStrategy::mutate(BasicBlock &BB, RandomIRBuilder &IB) {
};
SmallVector<Instruction *, 32> Insts;
for (Instruction &I : make_range(BB.getFirstInsertionPt(), BB.end()))
for (Instruction &I : getInsertionRange(BB))
Insts.push_back(&I);
if (Insts.size() < 1)
return;
@ -411,7 +421,7 @@ void InsertFunctionStrategy::mutate(BasicBlock &BB, RandomIRBuilder &IB) {
void InsertCFGStrategy::mutate(BasicBlock &BB, RandomIRBuilder &IB) {
SmallVector<Instruction *, 32> Insts;
for (Instruction &I : make_range(BB.getFirstInsertionPt(), BB.end()))
for (Instruction &I : getInsertionRange(BB))
Insts.push_back(&I);
if (Insts.size() < 1)
return;
@ -551,7 +561,7 @@ void InsertPHIStrategy::mutate(BasicBlock &BB, RandomIRBuilder &IB) {
PHI->addIncoming(Src, Pred);
}
SmallVector<Instruction *, 32> InstsAfter;
for (Instruction &I : make_range(BB.getFirstInsertionPt(), BB.end()))
for (Instruction &I : getInsertionRange(BB))
InstsAfter.push_back(&I);
IB.connectToSink(BB, InstsAfter, PHI);
}
@ -563,7 +573,7 @@ void SinkInstructionStrategy::mutate(Function &F, RandomIRBuilder &IB) {
}
void SinkInstructionStrategy::mutate(BasicBlock &BB, RandomIRBuilder &IB) {
SmallVector<Instruction *, 32> Insts;
for (Instruction &I : make_range(BB.getFirstInsertionPt(), BB.end()))
for (Instruction &I : getInsertionRange(BB))
Insts.push_back(&I);
if (Insts.size() < 1)
return;
@ -572,9 +582,9 @@ void SinkInstructionStrategy::mutate(BasicBlock &BB, RandomIRBuilder &IB) {
Instruction *Inst = Insts[Idx];
// `Idx + 1` so we don't sink to ourselves.
auto InstsAfter = ArrayRef(Insts).slice(Idx + 1);
LLVMContext &C = BB.getParent()->getParent()->getContext();
// Don't sink terminators, void function calls, etc.
if (Inst->getType() != Type::getVoidTy(C))
Type *Ty = Inst->getType();
// Don't sink terminators, void function calls, token, etc.
if (!Ty->isVoidTy() && !Ty->isTokenTy())
// Find a new sink and wire up the results of the operation.
IB.connectToSink(BB, InstsAfter, Inst);
}

View File

@ -27,7 +27,12 @@ using namespace fuzzerop;
static std::vector<BasicBlock *> getDominators(BasicBlock *BB) {
std::vector<BasicBlock *> ret;
DominatorTree DT(*BB->getParent());
DomTreeNode *Node = DT[BB]->getIDom();
DomTreeNode *Node = DT.getNode(BB);
// It's possible that an orphan block is not in the dom tree. In that case we
// just return nothing.
if (!Node)
return ret;
Node = Node->getIDom();
while (Node && Node->getBlock()) {
ret.push_back(Node->getBlock());
// Get parent block.
@ -41,7 +46,12 @@ static std::vector<BasicBlock *> getDominators(BasicBlock *BB) {
static std::vector<BasicBlock *> getDominatees(BasicBlock *BB) {
DominatorTree DT(*BB->getParent());
std::vector<BasicBlock *> ret;
for (DomTreeNode *Child : DT[BB]->children())
DomTreeNode *Parent = DT.getNode(BB);
// It's possible that an orphan block is not in the dom tree. In that case we
// just return nothing.
if (!Parent)
return ret;
for (DomTreeNode *Child : Parent->children())
ret.push_back(Child->getBlock());
uint64_t Idx = 0;
while (Idx < ret.size()) {

View File

@ -563,4 +563,53 @@ TEST(RandomIRBuilderTest, DoNotCallPointerWhenSink) {
}
ASSERT_FALSE(Modified);
}
TEST(RandomIRBuilderTest, SrcAndSinkWOrphanBlock) {
const char *Source = "\n\
define i1 @test(i1 %Bool, i32 %Int, i64 %Long) { \n\
Entry: \n\
%Eq0 = icmp eq i64 %Long, 0 \n\
br i1 %Eq0, label %True, label %False \n\
True: \n\
%Or = or i1 %Bool, %Eq0 \n\
ret i1 %Or \n\
False: \n\
%And = and i1 %Bool, %Eq0 \n\
ret i1 %And \n\
Orphan_1: \n\
%NotBool = sub i1 1, %Bool \n\
ret i1 %NotBool \n\
Orphan_2: \n\
%Le42 = icmp sle i32 %Int, 42 \n\
ret i1 %Le42 \n\
}";
LLVMContext Ctx;
std::mt19937 mt(Seed);
std::uniform_int_distribution<int> RandInt(INT_MIN, INT_MAX);
std::array<Type *, 3> IntTys(
{Type::getInt64Ty(Ctx), Type::getInt32Ty(Ctx), Type::getInt1Ty(Ctx)});
std::vector<Value *> Constants;
for (Type *IntTy : IntTys) {
for (size_t v : {1, 42}) {
Constants.push_back(ConstantInt::get(IntTy, v));
}
}
for (int i = 0; i < 10; i++) {
RandomIRBuilder IB(RandInt(mt), IntTys);
std::unique_ptr<Module> M = parseAssembly(Source, Ctx);
Function &F = *M->getFunction("test");
for (BasicBlock &BB : F) {
SmallVector<Instruction *, 4> Insts;
for (Instruction &I : BB) {
Insts.push_back(&I);
}
for (int j = 0; j < 10; j++) {
IB.findOrCreateSource(BB, Insts);
}
for (Value *V : Constants) {
IB.connectToSink(BB, Insts, V);
}
}
}
}
} // namespace

View File

@ -129,6 +129,30 @@ TEST(InjectorIRStrategyTest, LargeInsertion) {
mutateAndVerifyModule(Source, Mutator, 100);
}
TEST(InjectorIRStrategyTest, InsertWMustTailCall) {
StringRef Source = "\n\
define i1 @recursive() { \n\
Entry: \n\
%Ret = musttail call i1 @recursive() \n\
ret i1 %Ret \n\
}";
auto Mutator = createInjectorMutator();
ASSERT_TRUE(Mutator);
mutateAndVerifyModule(Source, Mutator, 100);
}
TEST(InjectorIRStrategyTest, InsertWTailCall) {
StringRef Source = "\n\
define i1 @recursive() { \n\
Entry: \n\
%Ret = tail call i1 @recursive() \n\
ret i1 %Ret \n\
}";
auto Mutator = createInjectorMutator();
ASSERT_TRUE(Mutator);
mutateAndVerifyModule(Source, Mutator, 100);
}
TEST(InstDeleterIRStrategyTest, EmptyFunction) {
// Test that we don't crash even if we can't remove from one of the functions.
@ -576,6 +600,19 @@ TEST(SinkInstructionStrategy, Operand) {
mutateAndVerifyModule<SinkInstructionStrategy>(Source);
}
TEST(SinkInstructionStrategy, DoNotSinkTokenType) {
StringRef Source = "\n\
declare ptr @fake_personality_function() \n\
declare token @llvm.experimental.gc.statepoint.p0(i64 immarg %0, i32 immarg %1, ptr %2, i32 immarg %3, i32 immarg %4, ...) \n\
define void @test() gc \"statepoint-example\" personality ptr @fake_personality_function { \n\
Entry: \n\
%token1 = call token (i64, i32, ptr, i32, i32, ...) \
@llvm.experimental.gc.statepoint.p0(i64 0, i32 0, ptr elementtype(ptr addrspace(1) ()) undef, i32 0, i32 0, i32 0, i32 0) \n\
ret void \n\
}";
mutateAndVerifyModule<SinkInstructionStrategy>(Source);
}
static void VerifyBlockShuffle(StringRef Source) {
LLVMContext Ctx;
auto Mutator = createMutator<ShuffleBlockStrategy>();