blob: fea8cc1463dbe1b008158ad8ca3dcf899f4b45d2 [file] [log] [blame] [edit]
// Copyright 2021 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
// Contains prog transformations that intend to trigger more races.
package prog
import (
"fmt"
"math/rand"
)
// The executor has no more than 32 threads that are used both for async calls and for calls
// that timed out. If we just ignore that limit, we could end up generating programs that
// would force the executor to fail and thus stall the fuzzing process.
// As an educated guess, let's use no more than 24 async calls to let executor handle everything.
const maxAsyncPerProg = 24
// Ensures that if an async call produces a resource, then
// it is distanced from a call consuming the resource at least
// by one non-async call.
// This does not give 100% guarantee that the async call finishes
// by that time, but hopefully this is enough for most cases.
func AssignRandomAsync(origProg *Prog, rand *rand.Rand) *Prog {
var unassigned map[*ResultArg]bool
leftAsync := maxAsyncPerProg
prog := origProg.Clone()
for i := len(prog.Calls) - 1; i >= 0 && leftAsync > 0; i-- {
call := prog.Calls[i]
producesUnassigned := false
consumes := make(map[*ResultArg]bool)
ForeachArg(call, func(arg Arg, ctx *ArgCtx) {
res, ok := arg.(*ResultArg)
if !ok {
return
}
if res.Dir() != DirIn && unassigned[res] {
// If this call is made async, at least one of the resources
// will be empty when it's needed.
producesUnassigned = true
}
if res.Dir() != DirOut {
consumes[res.Res] = true
}
})
// Make async with a 66% chance (but never the last call).
if !producesUnassigned && i+1 != len(prog.Calls) && rand.Intn(3) != 0 {
call.Props.Async = true
for res := range consumes {
unassigned[res] = true
}
leftAsync--
} else {
call.Props.Async = false
unassigned = consumes
}
}
return prog
}
var rerunSteps = []int{32, 64}
func AssignRandomRerun(prog *Prog, rand *rand.Rand) {
for i := 0; i+1 < len(prog.Calls); i++ {
if !prog.Calls[i].Props.Async || rand.Intn(4) != 0 {
continue
}
// We assign rerun to consecutive pairs of calls, where the first call is async.
// TODO: consider assigning rerun also to non-collided progs.
rerun := rerunSteps[rand.Intn(len(rerunSteps))]
prog.Calls[i].Props.Rerun = rerun
prog.Calls[i+1].Props.Rerun = rerun
i++
}
}
// We append prog to itself, but let the second part only reference resource from the first one.
// Then we execute all the duplicated calls simultaneously.
// This somehow resembles the way the previous collide mode was implemented - a program was executed
// normally and then one more time again, while keeping resource values from the first execution and
// not waiting until every other call finishes.
func DoubleExecCollide(origProg *Prog, rand *rand.Rand) (*Prog, error) {
if len(origProg.Calls)*2 > MaxCalls {
return nil, fmt.Errorf("the prog is too big for the DoubleExecCollide transformation")
}
prog := origProg.Clone()
dupCalls := cloneCalls(prog.Calls, nil)
leftAsync := maxAsyncPerProg
for _, c := range dupCalls {
if leftAsync == 0 {
break
}
c.Props.Async = true
leftAsync--
}
prog.Calls = append(prog.Calls, dupCalls...)
return prog, nil
}