| // 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 |
| } |