blob: ba5f055251632fb47b7454c273a7934d8ed3b616 [file] [log] [blame]
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This package generates source code for a stand-alone Go program
// useful for function signature fuzzing. The generated program is a
// series of function pairs, a "Caller" function and a "Checker"
// function. The signature of the Checker function is generated
// randomly (random number of parameters and returns, each with
// randomly chosen types). The "Caller" func contains invocations of
// the "Checker" function, each passing randomly chosen values to the
// params of the "Checker", then the caller verifies that expected
// values are returned correctly. The "Checker" function in turn has
// code to verify that the expected values arrive correctly, and so
// on.
//
// The main exported items of interest for this package are:
//
// - the Generate function, which takes a GenConfig object and emits
// code according to the config's specification
//
// - the GenConfig struct, which is basically a large collection of
// knobs/switches to control the mechanics of how/where code is
// generated
//
// - the TunableParams struct, which controls the nature of the
// generated code (for example, the maximum number of function
// parameters, etc), and the SetTunables func which tells the
// package what tunable parameters to use.
// Notes for posterity:
// - many parts of this package would have been better off being written
// using text/template instead of generating code directly; perhaps
// at some point it could be converted over (big job).
// - for the various 'fractions' fields in the TunableParams struct,
// it would be good to have a named type of some sort, with methods
// for managing things like checking to make sure values sum to 100.
package generator
import (
"bytes"
"crypto/sha1"
"errors"
"fmt"
"html/template"
"log"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
)
// GenConfig contains configuration parameters relating to the
// mechanics of the code generation, e.g. how many packages/functions
// to emit, path to a directory into which we place the generated
// code, prefixes/packagenames for the generate code, and so on.
type GenConfig struct {
// Tag is a string prefix prepended to functions within
// the generated code.
Tag string
// Output directory in to which we'll emit generated code.
// This will be created if it does not exist.
OutDir string
// Packagepath prefix given to the generated code.
PkgPath string
// Number of test packages created within the generated corpus.
// Each test package is essentially an independent collection
// generated code; the point of having multiple packages is to
// be able to get faster builds (more parallelism), and to avoid
// the compile time issues that crop up with 'giant' packages.
NumTestPackages int
// Number of test function pairs within each generated test package.
// Each pair consists of a "caller" function and "callee" function.
NumTestFunctions int
// Seed for random number generator.
Seed int64
// Pragma is a "// go:..." compiler directive to apply to the
// callee function as part of a generated function pair.
Pragma string
// Function and package mask used for minimization purposes.
// If a given mask is non-nil, then the generator will only
// emit code for a given func or package if its index is
// present in the mask map.
FcnMask map[int]int
PkgMask map[int]int
// Maximum number of failures to encounter before bailing out.
MaxFail int
// forcestackgrowth if set tells the generator to insert
// calls to runtime.gcTestMoveStackOnNextCall at various points
// in the generated code.
ForceStackGrowth bool
// Random number generator control flag (debugging)
RandCtl int
// Tells the generator to run "goimports" on the emitted code.
RunGoImports bool
// Debugging/testing hook. If set to 1, emit code that will cause the
// build to fail; if set to 2, emit code that will cause a test to fail.
EmitBad int
// If EmitBad above is set, then these can be used to select the ID of
// a specific bad func/package.
BadPackageIdx int
BadFuncIdx int
}
const CallerName = "Caller"
const CheckerName = "Checker"
// TunableParams contains configuration parameters that control the
// flavor of code generated for a given test function. This includes
// things like the number of params/returns, the percentages of types
// (int, struct, etc) of the params/returns, and so on.
type TunableParams struct {
// between 0 and N params
nParmRange uint8
// between 0 and N returns
nReturnRange uint8
// structs have between 0 and N members
nStructFields uint8
// arrays/slices have between 0 and N elements
nArrayElements uint8
// fraction of slices vs arrays. This is a value between 0 and 100 (0 meaning
// no slices [only arrays] and 100 meaning all slices, no arrays).
sliceFraction uint8
// Controls how often "int" vars wind up as 8/16/32/64, should
// add up to 100. Ex: 100 0 0 0 means all ints are 8 bit, 25
// 25 25 25 means equal likelihood of all types.
intBitRanges [4]uint8
// Similar to the above but for 32/64 float types
floatBitRanges [2]uint8
// Similar to the above but for unsigned, signed ints.
unsignedRanges [2]uint8
// Percentage of params, struct fields that should be "_". Ranges
// from 0 to 100.
blankPerc uint8
// How deeply structs are allowed to be nested (ranges from 0 to N).
structDepth uint8
// Fraction of param and return types assigned to each of:
// struct/array/map/pointer/int/float/complex/byte/string at the
// top level. If nesting precludes using a struct, other types
// are chosen from instead according to same proportions. The sum
// of typeFractions values should add up to 100.
typeFractions [9]uint8
// Percentage of the time we'll emit recursive calls, from 0 to 100.
recurPerc uint8
// Percentage of time that we turn the test function into a method,
// and if it is a method, fraction of time that we use a pointer
// method call vs value method call. Each range from 0 to 100.
methodPerc uint8
pointerMethodCallPerc uint8
// If true, test reflect.Call path as well.
doReflectCall bool
// If true, then randomly take addresses of params/returns.
takeAddress bool
// Fraction of the time that any params/returns are address taken.
// Ranges from 0 to 100.
takenFraction uint8
// For a given address-taken param or return, controls the
// manner in which the indirect read or write takes
// place. This is a set of percentages for
// not/simple/passed/heap, where "not" means not address
// taken, "simple" means a simple read or write, "passed"
// means that the address is passed to a well-behaved
// function, and "heap" means that the address is assigned to
// a global. Values in addrFractions should add up to 100.
addrFractions [4]uint8
// If true, then perform testing of go/defer statements.
doDefer bool
// fraction of test functions for which we emit a defer. Ranges from 0 to 100.
deferFraction uint8
// If true, randomly pick between emitting a value by literal
// (e.g. "int(1)" vs emitting a call to a function that
// will produce the same value (e.g. "myHelperEmitsInt1()").
doFuncCallValues bool
// Fraction of the time that we emit a function call to create
// a param value vs emitting a literal. Ranges from 0 to 100.
funcCallValFraction uint8
// If true, randomly decide to not check selected components of
// a composite value (e.g. for a struct, check field F1 but not F2).
// The intent is to generate partially live values.
doSkipCompare bool
// Fraction of the time that we decided to skip sub-components of
// composite values. Ranges from 0 to 100.
skipCompareFraction uint8
}
// SetTunables accepts a TunableParams object, checks to make sure
// that the settings in it are sane/logical, and applies the
// parameters for any subsequent calls to the Generate function. This
// function will issue a fatal error if any of the tunable params are
// incorrect/insane (for example, a 'percentage' value outside the
// range of 0-100).
func SetTunables(t TunableParams) {
checkTunables(t)
tunables = t
}
var defaultTypeFractions = [9]uint8{
10, // struct
10, // array
10, // map
15, // pointer
20, // numeric
15, // float
5, // complex
5, // byte
10, // string
}
const (
// Param not address taken.
StructTfIdx = iota
ArrayTfIdx
MapTfIdx
PointerTfIdx
NumericTfIdx
FloatTfIdx
ComplexTfIdx
ByteTfIdx
StringTfIdx
)
var tunables = TunableParams{
nParmRange: 15,
nReturnRange: 7,
nStructFields: 7,
nArrayElements: 5,
sliceFraction: 50,
intBitRanges: [4]uint8{30, 20, 20, 30},
floatBitRanges: [2]uint8{50, 50},
unsignedRanges: [2]uint8{50, 50},
blankPerc: 15,
structDepth: 3,
typeFractions: defaultTypeFractions,
recurPerc: 20,
methodPerc: 10,
pointerMethodCallPerc: 50,
doReflectCall: true,
doDefer: true,
takeAddress: true,
doFuncCallValues: true,
takenFraction: 20,
deferFraction: 30,
funcCallValFraction: 5,
doSkipCompare: true,
skipCompareFraction: 10,
addrFractions: [4]uint8{50, 25, 15, 10},
}
func DefaultTunables() TunableParams {
return tunables
}
func checkTunables(t TunableParams) {
var s int = 0
for _, v := range t.intBitRanges {
s += int(v)
}
if s != 100 {
log.Fatal(errors.New("intBitRanges tunable does not sum to 100"))
}
s = 0
for _, v := range t.unsignedRanges {
s += int(v)
}
if s != 100 {
log.Fatal(errors.New("unsignedRanges tunable does not sum to 100"))
}
if t.blankPerc > 100 {
log.Fatal(errors.New("blankPerc bad value, over 100"))
}
if t.recurPerc > 100 {
log.Fatal(errors.New("recurPerc bad value, over 100"))
}
if t.methodPerc > 100 {
log.Fatal(errors.New("methodPerc bad value, over 100"))
}
if t.pointerMethodCallPerc > 100 {
log.Fatal(errors.New("pointerMethodCallPerc bad value, over 100"))
}
s = 0
for _, v := range t.floatBitRanges {
s += int(v)
}
if s != 100 {
log.Fatal(errors.New("floatBitRanges tunable does not sum to 100"))
}
s = 0
for _, v := range t.typeFractions {
s += int(v)
}
if s != 100 {
panic(errors.New("typeFractions tunable does not sum to 100"))
}
s = 0
for _, v := range t.addrFractions {
s += int(v)
}
if s != 100 {
log.Fatal(errors.New("addrFractions tunable does not sum to 100"))
}
if t.takenFraction > 100 {
log.Fatal(errors.New("takenFraction not between 0 and 100"))
}
if t.deferFraction > 100 {
log.Fatal(errors.New("deferFraction not between 0 and 100"))
}
if t.sliceFraction > 100 {
log.Fatal(errors.New("sliceFraction not between 0 and 100"))
}
if t.skipCompareFraction > 100 {
log.Fatal(errors.New("skipCompareFraction not between 0 and 100"))
}
}
func (t *TunableParams) DisableReflectionCalls() {
t.doReflectCall = false
}
func (t *TunableParams) DisableRecursiveCalls() {
t.recurPerc = 0
}
func (t *TunableParams) DisableMethodCalls() {
t.methodPerc = 0
}
func (t *TunableParams) DisableTakeAddr() {
t.takeAddress = false
}
func (t *TunableParams) DisableDefer() {
t.doDefer = false
}
func (t *TunableParams) LimitInputs(n int) error {
if n > 100 {
return fmt.Errorf("value %d passed to LimitInputs is too large *(max 100)", n)
}
if n < 0 {
return fmt.Errorf("value %d passed to LimitInputs is invalid", n)
}
t.nParmRange = uint8(n)
return nil
}
func (t *TunableParams) LimitOutputs(n int) error {
if n > 100 {
return fmt.Errorf("value %d passed to LimitOutputs is too large *(max 100)", n)
}
if n < 0 {
return fmt.Errorf("value %d passed to LimitOutputs is invalid", n)
}
t.nReturnRange = uint8(n)
return nil
}
// ParseMaskString parses a string of the form K,J,...,M-N,Q-R,...,Z
// e.g. comma-separated integers or ranges of integers, returning the
// result in a form suitable for FcnMask or PkgMask fields in a
// Config. Here "tag" holds the mask flavor (fcn or pkg) and "arg" is
// the string argument to be parsed.
func ParseMaskString(arg string, tag string) (map[int]int, error) {
if arg == "" {
return nil, nil
}
verb(1, "%s mask is %s", tag, arg)
m := make(map[int]int)
ss := strings.Split(arg, ":")
for _, s := range ss {
if strings.Contains(s, "-") {
rng := strings.Split(s, "-")
if len(rng) != 2 {
return nil, fmt.Errorf("malformed range %s in %s mask arg", s, tag)
}
i, err := strconv.Atoi(rng[0])
if err != nil {
return nil, fmt.Errorf("malformed range value %s in %s mask arg", rng[0], tag)
}
j, err2 := strconv.Atoi(rng[1])
if err2 != nil {
return nil, fmt.Errorf("malformed range value %s in %s mask arg", rng[1], tag)
}
for k := i; k < j; k++ {
m[k] = 1
}
} else {
i, err := strconv.Atoi(s)
if err != nil {
return nil, fmt.Errorf("malformed value %s in %s mask arg", s, tag)
}
m[i] = 1
}
}
return m, nil
}
func writeCom(b *bytes.Buffer, i int) {
if i != 0 {
b.WriteString(", ")
}
}
var Verbctl int = 0
func verb(vlevel int, s string, a ...interface{}) {
if Verbctl >= vlevel {
fmt.Printf(s, a...)
fmt.Printf("\n")
}
}
type funcdef struct {
idx int
structdefs []structparm
arraydefs []arrayparm
typedefs []typedefparm
mapdefs []mapparm
mapkeytypes []parm
mapkeytmps []string
mapkeyts string
receiver parm
params []parm
returns []parm
values []int
dodefc uint8
dodefp []uint8
rstack int
recur bool
isMethod bool
}
type genstate struct {
GenConfig
ipref string
//tag string
//numtpk int
pkidx int
errs int
//pragma string
//sforce bool
//randctl int
tunables TunableParams
tstack []TunableParams
derefFuncs map[string]string
newDerefFuncs []funcdesc
assignFuncs map[string]string
newAssignFuncs []funcdesc
allocFuncs map[string]string
newAllocFuncs []funcdesc
genvalFuncs map[string]string
newGenvalFuncs []funcdesc
globVars map[string]string
newGlobVars []funcdesc
wr *wraprand
}
func (s *genstate) intFlavor() string {
which := uint8(s.wr.Intn(100))
if which < s.tunables.unsignedRanges[0] {
return "uint"
}
return "int"
}
func (s *genstate) intBits() uint32 {
which := uint8(s.wr.Intn(100))
var t uint8 = 0
var bits uint32 = 8
for _, v := range s.tunables.intBitRanges {
t += v
if which < t {
return bits
}
bits *= 2
}
return uint32(s.tunables.intBitRanges[3])
}
func (s *genstate) floatBits() uint32 {
which := uint8(s.wr.Intn(100))
if which < s.tunables.floatBitRanges[0] {
return uint32(32)
}
return uint32(64)
}
func (s *genstate) genAddrTaken() addrTakenHow {
which := uint8(s.wr.Intn(100))
res := notAddrTaken
var t uint8 = 0
for _, v := range s.tunables.addrFractions {
t += v
if which < t {
return res
}
res++
}
return notAddrTaken
}
func (s *genstate) pushTunables() {
s.tstack = append(s.tstack, s.tunables)
}
func (s *genstate) popTunables() {
if len(s.tstack) == 0 {
panic("untables stack underflow")
}
s.tunables = s.tstack[0]
s.tstack = s.tstack[1:]
}
// redistributeFraction accepts a value 'toIncorporate' and updates
// 'typeFraction' to add in the values from 'toIncorporate' equally to
// all slots not in 'avoid'. This is done by successively walking
// through 'typeFraction' adding 1 to each non-avoid slot, then
// repeating until we've added a total of 'toIncorporate' elements.
// See precludeSelectedTypes below for more info.
func (s *genstate) redistributeFraction(toIncorporate uint8, avoid []int) {
inavoid := func(j int) bool {
for _, k := range avoid {
if j == k {
return true
}
}
return false
}
doredis := func() {
for {
for i := range s.tunables.typeFractions {
if inavoid(i) {
continue
}
s.tunables.typeFractions[i]++
toIncorporate--
if toIncorporate == 0 {
return
}
}
}
}
doredis()
checkTunables(s.tunables)
}
// precludeSelectedTypes accepts a set of values (t, t2, ...)
// corresponding to slots in 'typeFractions', sums up the values from
// the slots, zeroes out the slots, and finally takes the values and
// redistributes them equally to the other slots. For example,
// suppose 'typeFractions' starts as [10, 10, 10, 15, 20, 15, 5, 5, 10],
// then we decide we want to eliminate or 'knock out' map types and
// pointer types (slots 2 and 3 in the array above) going forward. To
// restore the invariant that values in 'typeFractions' sum to 100, we
// take the values from slots 2 and 3 (a total of 25) and evenly
// distribute those values to the other slots in the array.
func (s *genstate) precludeSelectedTypes(t int, t2 ...int) {
avoid := []int{t}
avoid = append(avoid, t2...)
f := uint8(0)
for _, idx := range avoid {
f += s.tunables.typeFractions[idx]
s.tunables.typeFractions[idx] = 0
}
s.redistributeFraction(f, avoid)
}
func (s *genstate) GenMapKeyType(f *funcdef, depth int, pidx int) parm {
s.pushTunables()
defer s.popTunables()
// maps we can't allow at all; pointers might be possible but
// would be too much work to arrange. Avoid slices as well.
s.tunables.sliceFraction = 0
s.precludeSelectedTypes(MapTfIdx, PointerTfIdx)
return s.GenParm(f, depth+1, false, pidx)
}
func (s *genstate) GenParm(f *funcdef, depth int, mkctl bool, pidx int) parm {
// Enforcement for struct/array/map/pointer array nesting depth.
toodeep := depth >= int(s.tunables.structDepth)
if toodeep {
s.pushTunables()
defer s.popTunables()
s.precludeSelectedTypes(StructTfIdx, ArrayTfIdx, MapTfIdx, PointerTfIdx)
}
// Convert tf into a cumulative sum
tf := s.tunables.typeFractions
sum := uint8(0)
for i := 0; i < len(tf); i++ {
sum += tf[i]
tf[i] = sum
}
isblank := uint8(s.wr.Intn(100)) < s.tunables.blankPerc
addrTaken := notAddrTaken
if depth == 0 && tunables.takeAddress && !isblank {
addrTaken = s.genAddrTaken()
}
isGenValFunc := tunables.doFuncCallValues &&
uint8(s.wr.Intn(100)) < s.tunables.funcCallValFraction
// Make adjusted selection (pick a bucket within tf)
which := uint8(s.wr.Intn(100))
verb(3, "which=%d", which)
var retval parm
switch {
case which < tf[StructTfIdx]:
{
if toodeep {
panic("should not be here")
}
var sp structparm
ns := len(f.structdefs)
sp.sname = fmt.Sprintf("StructF%dS%d", f.idx, ns)
sp.qname = fmt.Sprintf("%s.StructF%dS%d",
s.checkerPkg(pidx), f.idx, ns)
f.structdefs = append(f.structdefs, sp)
tnf := int64(s.tunables.nStructFields) / int64(depth+1)
nf := int(s.wr.Intn(tnf))
for fi := 0; fi < nf; fi++ {
fp := s.GenParm(f, depth+1, false, pidx)
skComp := tunables.doSkipCompare &&
uint8(s.wr.Intn(100)) < s.tunables.skipCompareFraction
if skComp && checkableElements(fp) != 0 {
fp.SetSkipCompare(SkipAll)
}
sp.fields = append(sp.fields, fp)
}
f.structdefs[ns] = sp
retval = &sp
}
case which < tf[ArrayTfIdx]:
{
if toodeep {
panic("should not be here")
}
var ap arrayparm
ns := len(f.arraydefs)
nel := uint8(s.wr.Intn(int64(s.tunables.nArrayElements)))
issl := uint8(s.wr.Intn(100)) < s.tunables.sliceFraction
ap.aname = fmt.Sprintf("ArrayF%dS%dE%d", f.idx, ns, nel)
ap.qname = fmt.Sprintf("%s.ArrayF%dS%dE%d", s.checkerPkg(pidx),
f.idx, ns, nel)
f.arraydefs = append(f.arraydefs, ap)
ap.nelements = nel
ap.slice = issl
ap.eltype = s.GenParm(f, depth+1, false, pidx)
ap.eltype.SetBlank(false)
skComp := tunables.doSkipCompare &&
uint8(s.wr.Intn(100)) < s.tunables.skipCompareFraction
if skComp && checkableElements(ap.eltype) != 0 {
if issl {
ap.SetSkipCompare(SkipPayload)
}
}
f.arraydefs[ns] = ap
retval = &ap
}
case which < tf[MapTfIdx]:
{
if toodeep {
panic("should not be here")
}
var mp mapparm
ns := len(f.mapdefs)
// append early, since calls below might also append
f.mapdefs = append(f.mapdefs, mp)
f.mapkeytmps = append(f.mapkeytmps, "")
f.mapkeytypes = append(f.mapkeytypes, mp.keytype)
mp.aname = fmt.Sprintf("MapF%dM%d", f.idx, ns)
if f.mapkeyts == "" {
f.mapkeyts = fmt.Sprintf("MapKeysF%d", f.idx)
}
mp.qname = fmt.Sprintf("%s.MapF%dM%d", s.checkerPkg(pidx),
f.idx, ns)
mkt := fmt.Sprintf("Mk%dt%d", f.idx, ns)
mp.keytmp = mkt
mk := s.GenMapKeyType(f, depth+1, pidx)
mp.keytype = mk
mp.valtype = s.GenParm(f, depth+1, false, pidx)
mp.valtype.SetBlank(false)
mp.keytype.SetBlank(false)
// now update the previously appended placeholders
f.mapdefs[ns] = mp
f.mapkeytypes[ns] = mk
f.mapkeytmps[ns] = mkt
retval = &mp
}
case which < tf[PointerTfIdx]:
{
if toodeep {
panic("should not be here")
}
pp := mkPointerParm(s.GenParm(f, depth+1, false, pidx))
retval = &pp
}
case which < tf[NumericTfIdx]:
{
var ip numparm
ip.tag = s.intFlavor()
ip.widthInBits = s.intBits()
if mkctl {
ip.ctl = true
}
retval = &ip
}
case which < tf[FloatTfIdx]:
{
var fp numparm
fp.tag = "float"
fp.widthInBits = s.floatBits()
retval = &fp
}
case which < tf[ComplexTfIdx]:
{
var fp numparm
fp.tag = "complex"
fp.widthInBits = s.floatBits() * 2
retval = &fp
}
case which < tf[ByteTfIdx]:
{
var bp numparm
bp.tag = "byte"
bp.widthInBits = 8
retval = &bp
}
case which < tf[StringTfIdx]:
{
var sp stringparm
sp.tag = "string"
skComp := tunables.doSkipCompare &&
uint8(s.wr.Intn(100)) < s.tunables.skipCompareFraction
if skComp {
sp.SetSkipCompare(SkipPayload)
}
retval = &sp
}
default:
{
// fallback
var ip numparm
ip.tag = "uint"
ip.widthInBits = 8
retval = &ip
}
}
if !mkctl {
retval.SetBlank(isblank)
}
retval.SetAddrTaken(addrTaken)
retval.SetIsGenVal(isGenValFunc)
return retval
}
func (s *genstate) GenReturn(f *funcdef, depth int, pidx int) parm {
return s.GenParm(f, depth, false, pidx)
}
// GenFunc cooks up the random signature (and other attributes) of a
// given checker function, returning a funcdef object that describes
// the new fcn.
func (s *genstate) GenFunc(fidx int, pidx int) *funcdef {
f := new(funcdef)
f.idx = fidx
numParams := int(s.wr.Intn(int64(1 + int(s.tunables.nParmRange))))
numReturns := int(s.wr.Intn(int64(1 + int(s.tunables.nReturnRange))))
f.recur = uint8(s.wr.Intn(100)) < s.tunables.recurPerc
f.isMethod = uint8(s.wr.Intn(100)) < s.tunables.methodPerc
genReceiverType := func() {
// Receiver type can't be pointer type. Temporarily update
// tunables to eliminate that possibility.
s.pushTunables()
defer s.popTunables()
s.precludeSelectedTypes(PointerTfIdx)
target := s.GenParm(f, 0, false, pidx)
target.SetBlank(false)
f.receiver = s.makeTypedefParm(f, target, pidx)
if f.receiver.IsBlank() {
f.recur = false
}
}
if f.isMethod {
genReceiverType()
}
needControl := f.recur
f.dodefc = uint8(s.wr.Intn(100))
pTaken := uint8(s.wr.Intn(100)) < s.tunables.takenFraction
for pi := 0; pi < numParams; pi++ {
newparm := s.GenParm(f, 0, needControl, pidx)
if !pTaken {
newparm.SetAddrTaken(notAddrTaken)
}
if newparm.IsControl() {
needControl = false
}
f.params = append(f.params, newparm)
f.dodefp = append(f.dodefp, uint8(s.wr.Intn(100)))
}
if f.recur && needControl {
f.recur = false
}
rTaken := uint8(s.wr.Intn(100)) < s.tunables.takenFraction
for ri := 0; ri < numReturns; ri++ {
r := s.GenReturn(f, 0, pidx)
if !rTaken {
r.SetAddrTaken(notAddrTaken)
}
f.returns = append(f.returns, r)
}
spw := uint(s.wr.Intn(11))
rstack := 1 << spw
if rstack < 4 {
rstack = 4
}
f.rstack = rstack
return f
}
func genDeref(p parm) (parm, string) {
curp := p
star := ""
for {
if pp, ok := curp.(*pointerparm); ok {
star += "*"
curp = pp.totype
} else {
return curp, star
}
}
}
func (s *genstate) eqFuncRef(f *funcdef, t parm, caller bool) string {
cp := ""
if f.mapkeyts != "" {
cp = "mkt."
} else if caller {
cp = s.checkerPkg(s.pkidx) + "."
}
return cp + "Equal" + t.TypeName()
}
// emitCompareFunc creates an 'equals' function for a specific
// generated type (this is basically a way to compare objects that
// contain pointer fields / pointery things).
func (s *genstate) emitCompareFunc(f *funcdef, b *bytes.Buffer, p parm) {
if !p.HasPointer() {
return
}
tn := p.TypeName()
b.WriteString(fmt.Sprintf("// equal func for %s\n", tn))
b.WriteString("//go:noinline\n")
rcvr := ""
if f.mapkeyts != "" {
rcvr = fmt.Sprintf("(mkt *%s) ", f.mapkeyts)
}
b.WriteString(fmt.Sprintf("func %sEqual%s(left %s, right %s) bool {\n", rcvr, tn, tn, tn))
b.WriteString(" return ")
numel := p.NumElements()
ncmp := 0
for i := 0; i < numel; i++ {
lelref, lelparm := p.GenElemRef(i, "left")
relref, _ := p.GenElemRef(i, "right")
if lelref == "" || lelref == "_" {
continue
}
basep, star := genDeref(lelparm)
// Handle *p where p is an empty struct.
if basep.NumElements() == 0 {
continue
}
if ncmp != 0 {
b.WriteString(" && ")
}
ncmp++
if basep.HasPointer() {
efn := s.eqFuncRef(f, basep, false)
b.WriteString(fmt.Sprintf(" %s(%s%s, %s%s)", efn, star, lelref, star, relref))
} else {
b.WriteString(fmt.Sprintf("%s%s == %s%s", star, lelref, star, relref))
}
}
if ncmp == 0 {
b.WriteString("true")
}
b.WriteString("\n}\n\n")
}
// emitStructAndArrayDefs writes out definitions of the random types
// we happened to cook up while generating code for a specific
// function pair.
func (s *genstate) emitStructAndArrayDefs(f *funcdef, b *bytes.Buffer) {
for _, str := range f.structdefs {
b.WriteString(fmt.Sprintf("type %s struct {\n", str.sname))
for fi, sp := range str.fields {
sp.Declare(b, " "+str.FieldName(fi), "\n", false)
}
b.WriteString("}\n\n")
s.emitCompareFunc(f, b, &str)
}
for _, a := range f.arraydefs {
elems := fmt.Sprintf("%d", a.nelements)
if a.slice {
elems = ""
}
b.WriteString(fmt.Sprintf("type %s [%s]%s\n\n", a.aname,
elems, a.eltype.TypeName()))
s.emitCompareFunc(f, b, &a)
}
for _, a := range f.mapdefs {
b.WriteString(fmt.Sprintf("type %s map[%s]%s\n\n", a.aname,
a.keytype.TypeName(), a.valtype.TypeName()))
s.emitCompareFunc(f, b, &a)
}
for _, td := range f.typedefs {
b.WriteString(fmt.Sprintf("type %s %s\n\n", td.aname,
td.target.TypeName()))
s.emitCompareFunc(f, b, &td)
}
if f.mapkeyts != "" {
b.WriteString(fmt.Sprintf("type %s struct {\n", f.mapkeyts))
for i := range f.mapkeytypes {
f.mapkeytypes[i].Declare(b, " "+f.mapkeytmps[i], "\n", false)
}
b.WriteString("}\n\n")
}
}
// GenValue method of genstate wraps the parm method of the same
// name, but optionally returns a call to a function to produce
// the value as opposed to a literal value.
func (s *genstate) GenValue(f *funcdef, p parm, value int, caller bool) (string, int) {
var valstr string
valstr, value = p.GenValue(s, f, value, caller)
if !s.tunables.doFuncCallValues || !p.IsGenVal() || caller {
return valstr, value
}
mkInvoc := func(fname string) string {
meth := ""
if f.mapkeyts != "" {
meth = "mkt."
}
return fmt.Sprintf("%s%s()", meth, fname)
}
b := bytes.NewBuffer(nil)
p.Declare(b, "x", "", false)
h := sha1.New()
h.Write([]byte(valstr))
h.Write(b.Bytes())
if f.mapkeyts != "" {
h.Write([]byte(f.mapkeyts))
}
h.Write(b.Bytes())
bs := h.Sum(nil)
hashstr := fmt.Sprintf("%x", bs)
b.WriteString(hashstr)
tag := b.String()
fname, ok := s.genvalFuncs[tag]
if ok {
return mkInvoc(fname), value
}
fname = fmt.Sprintf("genval_%d", len(s.genvalFuncs))
s.newGenvalFuncs = append(s.newGenvalFuncs, funcdesc{p: p, name: fname, tag: tag, payload: valstr})
s.genvalFuncs[tag] = fname
return mkInvoc(fname), value
}
func (s *genstate) emitMapKeyTmps(f *funcdef, b *bytes.Buffer, pidx int, value int, caller bool) int {
if f.mapkeyts == "" {
return value
}
// map key tmps
cp := ""
if caller {
cp = s.checkerPkg(pidx) + "."
}
b.WriteString(" var mkt " + cp + f.mapkeyts + "\n")
for i, t := range f.mapkeytypes {
var keystr string
keystr, value = s.GenValue(f, t, value, caller)
tname := f.mapkeytmps[i]
b.WriteString(fmt.Sprintf(" %s := %s\n", tname, keystr))
b.WriteString(fmt.Sprintf(" mkt.%s = %s\n", tname, tname))
}
return value
}
func (s *genstate) emitCheckReturnsInCaller(f *funcdef, b *bytes.Buffer, pidx int, reflectCall bool) {
cm := f.complexityMeasure()
rvalp := func(ri int) string {
if reflectCall {
return fmt.Sprintf("rr%dv", ri)
}
return fmt.Sprintf("r%d", ri)
}
failTag := "\"return\""
if reflectCall {
failTag = "\"reflect return\""
}
for ri, rp := range f.returns {
if reflectCall {
b.WriteString(fmt.Sprintf(" rr%di := rvslice[%d].Interface()\n", ri, ri))
b.WriteString(fmt.Sprintf(" rr%dv:= rr%di.(", ri, ri))
rp.Declare(b, "", "", true)
b.WriteString(")\n")
}
pfc := ""
curp, star := genDeref(rp)
// Handle *p where p is an empty struct.
if curp.NumElements() == 0 {
b.WriteString(fmt.Sprintf(" _, _ = %s, c%d // zero size\n", rvalp(ri), ri))
continue
}
if star != "" {
pfc = fmt.Sprintf("ParamFailCount[%d] == 0 && ", pidx)
}
if curp.HasPointer() {
efn := "!" + s.eqFuncRef(f, curp, true)
b.WriteString(fmt.Sprintf(" if %s%s(%s%s, %sc%d) {\n", pfc, efn, star, rvalp(ri), star, ri))
} else {
b.WriteString(fmt.Sprintf(" if %s%s%s != %sc%d {\n", pfc, star, rvalp(ri), star, ri))
}
b.WriteString(fmt.Sprintf(" NoteFailure(%d, %d, %d, \"%s\", %s, %d, true, uint64(0))\n", cm, pidx, f.idx, s.checkerPkg(pidx), failTag, ri))
b.WriteString(" }\n")
}
}
func (s *genstate) emitCaller(f *funcdef, b *bytes.Buffer, pidx int) {
b.WriteString(fmt.Sprintf("func %s%d(mode string) {\n", CallerName, f.idx))
b.WriteString(fmt.Sprintf(" BeginFcn(%d)\n", pidx))
if s.EmitBad == 1 {
if s.BadPackageIdx == pidx && s.BadFuncIdx == f.idx {
b.WriteString(" bad code here, should cause build failure <<==\n")
}
}
var value int = 1
s.wr.Checkpoint("before mapkeytmps")
value = s.emitMapKeyTmps(f, b, pidx, value, true)
// generate return constants
s.wr.Checkpoint("before return constants")
for ri, r := range f.returns {
rc := fmt.Sprintf("c%d", ri)
value = s.emitVarAssign(f, b, r, rc, value, true)
}
// generate param constants
s.wr.Checkpoint("before param constants")
for pi, p := range f.params {
verb(4, "emitCaller gen p%d value=%d", pi, value)
if p.IsControl() {
_ = uint8(s.wr.Intn(100)) < 50
p.Declare(b, fmt.Sprintf(" var p%d ", pi), " = 10\n", true)
} else {
pc := fmt.Sprintf("p%d", pi)
value = s.emitVarAssign(f, b, p, pc, value, true)
}
f.values = append(f.values, value)
}
// generate receiver constant if applicable
if f.isMethod {
s.wr.Checkpoint("before receiver constant")
f.receiver.Declare(b, " var rcvr", "\n", true)
valstr, value := s.GenValue(f, f.receiver, value, true)
b.WriteString(fmt.Sprintf(" rcvr = %s\n", valstr))
f.values = append(f.values, value)
}
b.WriteString(fmt.Sprintf(" Mode[%d] = \"\"\n", pidx))
// calling code
b.WriteString(fmt.Sprintf(" // %d returns %d params\n",
len(f.returns), len(f.params)))
if s.ForceStackGrowth {
b.WriteString(" hackStack() // force stack growth on next call\n")
}
b.WriteString(" if mode == \"normal\" {\n")
b.WriteString(" ")
for ri := range f.returns {
writeCom(b, ri)
b.WriteString(fmt.Sprintf("r%d", ri))
}
if len(f.returns) > 0 {
b.WriteString(" := ")
}
pref := s.checkerPkg(pidx)
if f.isMethod {
pref = "rcvr"
}
b.WriteString(fmt.Sprintf("%s.Test%d(", pref, f.idx))
for pi := range f.params {
writeCom(b, pi)
b.WriteString(fmt.Sprintf("p%d", pi))
}
b.WriteString(")\n")
// check values returned (normal call case)
s.emitCheckReturnsInCaller(f, b, pidx, false /* not a reflect call */)
b.WriteString(" }") // end of 'if normal call' block
if s.tunables.doReflectCall {
b.WriteString("else {\n") // beginning of reflect call block
// now make the same call via reflection
b.WriteString(" // same call via reflection\n")
b.WriteString(fmt.Sprintf(" Mode[%d] = \"reflect\"\n", pidx))
if f.isMethod {
b.WriteString(" rcv := reflect.ValueOf(rcvr)\n")
b.WriteString(fmt.Sprintf(" rc := rcv.MethodByName(\"Test%d\")\n", f.idx))
} else {
b.WriteString(fmt.Sprintf(" rc := reflect.ValueOf(%s.Test%d)\n",
s.checkerPkg(pidx), f.idx))
}
b.WriteString(" ")
if len(f.returns) > 0 {
b.WriteString("rvslice := ")
}
b.WriteString(" rc.Call([]reflect.Value{")
for pi := range f.params {
writeCom(b, pi)
b.WriteString(fmt.Sprintf("reflect.ValueOf(p%d)", pi))
}
b.WriteString("})\n")
// check values returned (reflect call case)
s.emitCheckReturnsInCaller(f, b, pidx, true /* is a reflect call */)
b.WriteString("}\n") // end of reflect call block
}
b.WriteString(fmt.Sprintf("\n EndFcn(%d)\n", pidx))
b.WriteString("}\n\n")
}
func checkableElements(p parm) int {
if p.IsBlank() {
return 0
}
sp, isstruct := p.(*structparm)
if isstruct {
s := 0
for fi := range sp.fields {
s += checkableElements(sp.fields[fi])
}
return s
}
ap, isarray := p.(*arrayparm)
if isarray {
if ap.nelements == 0 {
return 0
}
return int(ap.nelements) * checkableElements(ap.eltype)
}
return 1
}
// funcdesc describes an auto-generated helper function or global
// variable, such as an allocation function (returns new(T)) or a
// pointer assignment function (assigns value of T to type *T). Here
// 'p' is a param type T, 'pp' is a pointer type *T, 'name' is the
// name within the generated code of the function or variable and
// 'tag' is a descriptive tag used to look up the entity in a map (so
// that we don't have to emit multiple copies of a function that
// assigns int to *int, for example).
type funcdesc struct {
p parm
pp parm
name string
tag string
payload string
}
func (s *genstate) emitDerefFuncs(b *bytes.Buffer, emit bool) {
b.WriteString("// dereference helpers\n")
for _, fd := range s.newDerefFuncs {
if !emit {
b.WriteString(fmt.Sprintf("\n// skip derefunc %s\n", fd.name))
delete(s.derefFuncs, fd.tag)
continue
}
b.WriteString("\n//go:noinline\n")
b.WriteString(fmt.Sprintf("func %s(", fd.name))
fd.pp.Declare(b, "x", "", false)
b.WriteString(") ")
fd.p.Declare(b, "", "", false)
b.WriteString(" {\n")
b.WriteString(" return *x\n")
b.WriteString("}\n")
}
s.newDerefFuncs = nil
}
func (s *genstate) emitAssignFuncs(b *bytes.Buffer, emit bool) {
b.WriteString("// assign helpers\n")
for _, fd := range s.newAssignFuncs {
if !emit {
b.WriteString(fmt.Sprintf("\n// skip assignfunc %s\n", fd.name))
delete(s.assignFuncs, fd.tag)
continue
}
b.WriteString("\n//go:noinline\n")
b.WriteString(fmt.Sprintf("func %s(", fd.name))
fd.pp.Declare(b, "x", "", false)
b.WriteString(", ")
fd.p.Declare(b, "v", "", false)
b.WriteString(") {\n")
b.WriteString(" *x = v\n")
b.WriteString("}\n")
}
s.newAssignFuncs = nil
}
func (s *genstate) emitNewFuncs(b *bytes.Buffer, emit bool) {
b.WriteString("// 'new' funcs\n")
for _, fd := range s.newAllocFuncs {
if !emit {
b.WriteString(fmt.Sprintf("\n// skip newfunc %s\n", fd.name))
delete(s.allocFuncs, fd.tag)
continue
}
b.WriteString("\n//go:noinline\n")
b.WriteString(fmt.Sprintf("func %s(", fd.name))
fd.p.Declare(b, "i", "", false)
b.WriteString(") ")
fd.pp.Declare(b, "", "", false)
b.WriteString(" {\n")
b.WriteString(" x := new(")
fd.p.Declare(b, "", "", false)
b.WriteString(")\n")
b.WriteString(" *x = i\n")
b.WriteString(" return x\n")
b.WriteString("}\n\n")
}
s.newAllocFuncs = nil
}
func (s *genstate) emitGlobalVars(b *bytes.Buffer, emit bool) {
b.WriteString("// global vars\n")
for _, fd := range s.newGlobVars {
if !emit {
b.WriteString(fmt.Sprintf("\n// skip gvar %s\n", fd.name))
delete(s.globVars, fd.tag)
continue
}
b.WriteString("var ")
fd.pp.Declare(b, fd.name, "", false)
b.WriteString("\n")
}
s.newGlobVars = nil
b.WriteString("\n")
}
func (s *genstate) emitGenValFuncs(f *funcdef, b *bytes.Buffer, emit bool) {
b.WriteString("// genval helpers\n")
for _, fd := range s.newGenvalFuncs {
if !emit {
b.WriteString(fmt.Sprintf("\n// skip genvalfunc %s\n", fd.name))
delete(s.genvalFuncs, fd.tag)
continue
}
b.WriteString("\n//go:noinline\n")
rcvr := ""
if f.mapkeyts != "" {
rcvr = fmt.Sprintf("(mkt *%s) ", f.mapkeyts)
}
b.WriteString(fmt.Sprintf("func %s%s() ", rcvr, fd.name))
fd.p.Declare(b, "", "", false)
b.WriteString(" {\n")
if f.mapkeyts != "" {
contained := containedParms(fd.p)
for _, cp := range contained {
mp, ismap := cp.(*mapparm)
if ismap {
b.WriteString(fmt.Sprintf(" %s := mkt.%s\n",
mp.keytmp, mp.keytmp))
b.WriteString(fmt.Sprintf(" _ = %s\n", mp.keytmp))
}
}
}
b.WriteString(fmt.Sprintf(" return %s\n", fd.payload))
b.WriteString("}\n")
}
s.newGenvalFuncs = nil
}
func (s *genstate) emitAddrTakenHelpers(f *funcdef, b *bytes.Buffer, emit bool) {
b.WriteString("// begin addr taken helpers\n")
s.emitDerefFuncs(b, emit)
s.emitAssignFuncs(b, emit)
s.emitNewFuncs(b, emit)
s.emitGlobalVars(b, emit)
s.emitGenValFuncs(f, b, emit)
b.WriteString("// end addr taken helpers\n")
}
func (s *genstate) genGlobVar(p parm) string {
var pp parm
ppp := mkPointerParm(p)
pp = &ppp
b := bytes.NewBuffer(nil)
pp.Declare(b, "gv", "", false)
tag := b.String()
gv, ok := s.globVars[tag]
if ok {
return gv
}
gv = fmt.Sprintf("gvar_%d", len(s.globVars))
s.newGlobVars = append(s.newGlobVars, funcdesc{pp: pp, p: p, name: gv, tag: tag})
s.globVars[tag] = gv
return gv
}
func (s *genstate) genParamDerefFunc(p parm) string {
var pp parm
ppp := mkPointerParm(p)
pp = &ppp
b := bytes.NewBuffer(nil)
pp.Declare(b, "x", "", false)
tag := b.String()
f, ok := s.derefFuncs[tag]
if ok {
return f
}
f = fmt.Sprintf("deref_%d", len(s.derefFuncs))
s.newDerefFuncs = append(s.newDerefFuncs, funcdesc{pp: pp, p: p, name: f, tag: tag})
s.derefFuncs[tag] = f
return f
}
func (s *genstate) genAssignFunc(p parm) string {
var pp parm
ppp := mkPointerParm(p)
pp = &ppp
b := bytes.NewBuffer(nil)
pp.Declare(b, "x", "", false)
tag := b.String()
f, ok := s.assignFuncs[tag]
if ok {
return f
}
f = fmt.Sprintf("retassign_%d", len(s.assignFuncs))
s.newAssignFuncs = append(s.newAssignFuncs, funcdesc{pp: pp, p: p, name: f, tag: tag})
s.assignFuncs[tag] = f
return f
}
func (s *genstate) genAllocFunc(p parm) string {
var pp parm
ppp := mkPointerParm(p)
pp = &ppp
b := bytes.NewBuffer(nil)
pp.Declare(b, "x", "", false)
tag := b.String()
f, ok := s.allocFuncs[tag]
if ok {
return f
}
f = fmt.Sprintf("New_%d", len(s.allocFuncs))
s.newAllocFuncs = append(s.newAllocFuncs, funcdesc{pp: pp, p: p, name: f, tag: tag})
s.allocFuncs[tag] = f
return f
}
func (s *genstate) genParamRef(p parm, idx int) string {
switch p.AddrTaken() {
case notAddrTaken:
return fmt.Sprintf("p%d", idx)
case addrTakenSimple, addrTakenHeap:
return fmt.Sprintf("(*ap%d)", idx)
case addrTakenPassed:
f := s.genParamDerefFunc(p)
return fmt.Sprintf("%s(ap%d)", f, idx)
default:
panic("bad")
}
}
func (s *genstate) genReturnAssign(b *bytes.Buffer, r parm, idx int, val string) {
switch r.AddrTaken() {
case notAddrTaken:
b.WriteString(fmt.Sprintf(" r%d = %s\n", idx, val))
case addrTakenSimple, addrTakenHeap:
b.WriteString(fmt.Sprintf(" (*ar%d) = %v\n", idx, val))
case addrTakenPassed:
f := s.genAssignFunc(r)
b.WriteString(fmt.Sprintf(" %s(ar%d, %v)\n", f, idx, val))
default:
panic("bad")
}
}
func (s *genstate) emitParamElemCheck(f *funcdef, b *bytes.Buffer, p parm, pvar string, cvar string, paramidx int, elemidx int) {
if p.SkipCompare() == SkipAll {
b.WriteString(fmt.Sprintf(" // selective skip of %s\n", pvar))
b.WriteString(fmt.Sprintf(" _ = %s\n", cvar))
return
} else if p.SkipCompare() == SkipPayload {
switch p.(type) {
case *stringparm, *arrayparm:
b.WriteString(fmt.Sprintf(" if len(%s) != len(%s) { // skip payload\n",
pvar, cvar))
default:
panic("should never happen")
}
} else {
basep, star := genDeref(p)
// Handle *p where p is an empty struct.
if basep.NumElements() == 0 {
return
}
if basep.HasPointer() {
efn := s.eqFuncRef(f, basep, false)
b.WriteString(fmt.Sprintf(" if !%s(%s%s, %s%s) {\n",
efn, star, pvar, star, cvar))
} else {
b.WriteString(fmt.Sprintf(" if %s%s != %s%s {\n",
star, pvar, star, cvar))
}
}
cm := f.complexityMeasure()
b.WriteString(fmt.Sprintf(" NoteFailureElem(%d, %d, %d, \"%s\", \"parm\", %d, %d, false, pad[0])\n", cm, s.pkidx, f.idx, s.checkerPkg(s.pkidx), paramidx, elemidx))
b.WriteString(" return\n")
b.WriteString(" }\n")
}
func (s *genstate) emitParamChecks(f *funcdef, b *bytes.Buffer, pidx int, value int) (int, bool) {
var valstr string
haveControl := false
dangling := []int{}
for pi, p := range f.params {
verb(4, "emitting parmcheck p%d numel=%d pt=%s value=%d",
pi, p.NumElements(), p.TypeName(), value)
// To balance code in caller
_ = uint8(s.wr.Intn(100)) < 50
if p.IsControl() {
b.WriteString(fmt.Sprintf(" if %s == 0 {\n",
s.genParamRef(p, pi)))
s.emitReturn(f, b, false)
b.WriteString(" }\n")
haveControl = true
} else if p.IsBlank() {
valstr, value = s.GenValue(f, p, value, false)
if f.recur {
b.WriteString(fmt.Sprintf(" brc%d := %s\n", pi, valstr))
} else {
b.WriteString(fmt.Sprintf(" _ = %s\n", valstr))
}
} else {
numel := p.NumElements()
cel := checkableElements(p)
for i := 0; i < numel; i++ {
verb(4, "emitting check-code for p%d el %d value=%d", pi, i, value)
elref, elparm := p.GenElemRef(i, s.genParamRef(p, pi))
valstr, value = s.GenValue(f, elparm, value, false)
if elref == "" || elref == "_" || cel == 0 {
b.WriteString(fmt.Sprintf(" // blank skip: %s\n", valstr))
continue
} else {
basep, _ := genDeref(elparm)
// Handle *p where p is an empty struct.
if basep.NumElements() == 0 {
continue
}
cvar := fmt.Sprintf("p%df%dc", pi, i)
b.WriteString(fmt.Sprintf(" %s := %s\n", cvar, valstr))
s.emitParamElemCheck(f, b, elparm, elref, cvar, pi, i)
}
}
if p.AddrTaken() != notAddrTaken {
dangling = append(dangling, pi)
}
}
if value != f.values[pi] {
fmt.Fprintf(os.Stderr, "internal error: checker/caller value mismatch after emitting param %d func Test%d pkg %s: caller %d checker %d\n", pi, f.idx, s.checkerPkg(pidx), f.values[pi], value)
s.errs++
}
}
for _, pi := range dangling {
b.WriteString(fmt.Sprintf(" _ = ap%d // ref\n", pi))
}
// receiver value check
if f.isMethod {
numel := f.receiver.NumElements()
for i := 0; i < numel; i++ {
verb(4, "emitting check-code for rcvr el %d value=%d", i, value)
elref, elparm := f.receiver.GenElemRef(i, "rcvr")
valstr, value = s.GenValue(f, elparm, value, false)
if elref == "" || strings.HasPrefix(elref, "_") || f.receiver.IsBlank() {
verb(4, "empty skip rcvr el %d", i)
continue
} else {
basep, _ := genDeref(elparm)
// Handle *p where p is an empty struct.
if basep.NumElements() == 0 {
continue
}
cvar := fmt.Sprintf("rcvrf%dc", i)
b.WriteString(fmt.Sprintf(" %s := %s\n", cvar, valstr))
s.emitParamElemCheck(f, b, elparm, elref, cvar, -1, i)
}
}
}
return value, haveControl
}
// emitDeferChecks creates code like
//
// defer func(...args...) {
// check arg
// check param
// }(...)
//
// where we randomly choose to either pass a param through to the
// function literal, or have the param captured by the closure, then
// check its value in the defer.
func (s *genstate) emitDeferChecks(f *funcdef, b *bytes.Buffer, pidx int, value int) int {
if len(f.params) == 0 {
return value
}
// make a pass through the params and randomly decide which will be passed into the func.
passed := []bool{}
for i := range f.params {
p := f.dodefp[i] < 50
passed = append(passed, p)
}
b.WriteString(" defer func(")
pc := 0
for pi, p := range f.params {
if p.IsControl() || p.IsBlank() {
continue
}
if passed[pi] {
writeCom(b, pc)
n := fmt.Sprintf("p%d", pi)
p.Declare(b, n, "", false)
pc++
}
}
b.WriteString(") {\n")
for pi, p := range f.params {
if p.IsControl() || p.IsBlank() {
continue
}
which := "passed"
if !passed[pi] {
which = "captured"
}
b.WriteString(" // check parm " + which + "\n")
numel := p.NumElements()
cel := checkableElements(p)
for i := 0; i < numel; i++ {
elref, elparm := p.GenElemRef(i, s.genParamRef(p, pi))
if elref == "" || elref == "_" || cel == 0 {
verb(4, "empty skip p%d el %d", pi, i)
continue
} else {
basep, _ := genDeref(elparm)
// Handle *p where p is an empty struct.
if basep.NumElements() == 0 {
continue
}
cvar := fmt.Sprintf("p%df%dc", pi, i)
s.emitParamElemCheck(f, b, elparm, elref, cvar, pi, i)
}
}
}
b.WriteString(" } (")
pc = 0
for pi, p := range f.params {
if p.IsControl() || p.IsBlank() {
continue
}
if passed[pi] {
writeCom(b, pc)
b.WriteString(fmt.Sprintf("p%d", pi))
pc++
}
}
b.WriteString(")\n\n")
return value
}
func (s *genstate) emitVarAssign(f *funcdef, b *bytes.Buffer, r parm, rname string, value int, caller bool) int {
var valstr string
isassign := uint8(s.wr.Intn(100)) < 50
if rmp, ismap := r.(*mapparm); ismap && isassign {
// emit: var m ... ; m[k] = v
r.Declare(b, " "+rname+" := make(", ")\n", caller)
valstr, value = s.GenValue(f, rmp.valtype, value, caller)
b.WriteString(fmt.Sprintf(" %s[mkt.%s] = %s\n",
rname, rmp.keytmp, valstr))
} else {
// emit r = c
valstr, value = s.GenValue(f, r, value, caller)
b.WriteString(fmt.Sprintf(" %s := %s\n", rname, valstr))
}
return value
}
func (s *genstate) emitChecker(f *funcdef, b *bytes.Buffer, pidx int, emit bool) {
verb(4, "emitting struct and array defs")
s.emitStructAndArrayDefs(f, b)
b.WriteString(fmt.Sprintf("// %d returns %d params\n", len(f.returns), len(f.params)))
if s.Pragma != "" {
b.WriteString("//go:" + s.Pragma + "\n")
}
b.WriteString("//go:noinline\n")
b.WriteString("func")
if f.isMethod {
b.WriteString(" (")
n := "rcvr"
if f.receiver.IsBlank() {
n = "_"
}
f.receiver.Declare(b, n, "", false)
b.WriteString(")")
}
b.WriteString(fmt.Sprintf(" Test%d(", f.idx))
verb(4, "emitting checker p%d/Test%d", pidx, f.idx)
// params
for pi, p := range f.params {
writeCom(b, pi)
n := fmt.Sprintf("p%d", pi)
if p.IsBlank() {
n = "_"
}
p.Declare(b, n, "", false)
}
b.WriteString(") ")
// returns
if len(f.returns) > 0 {
b.WriteString("(")
}
for ri, r := range f.returns {
writeCom(b, ri)
r.Declare(b, fmt.Sprintf("r%d", ri), "", false)
}
if len(f.returns) > 0 {
b.WriteString(")")
}
b.WriteString(" {\n")
// local storage
b.WriteString(" // consume some stack space, so as to trigger morestack\n")
b.WriteString(fmt.Sprintf(" var pad [%d]uint64\n", f.rstack))
b.WriteString(fmt.Sprintf(" pad[FailCount[%d] & 0x1]++\n", pidx))
value := 1
// generate map key tmps
s.wr.Checkpoint("before map key temps")
value = s.emitMapKeyTmps(f, b, pidx, value, false)
// generate return constants
s.wr.Checkpoint("before return constants")
for ri, r := range f.returns {
rc := fmt.Sprintf("rc%d", ri)
value = s.emitVarAssign(f, b, r, rc, value, false)
}
// Prepare to reference params/returns by address.
lists := [][]parm{f.params, f.returns}
names := []string{"p", "r"}
var aCounts [2]int
for i, lst := range lists {
for pi, p := range lst {
if p.AddrTaken() == notAddrTaken {
continue
}
aCounts[i]++
n := names[i]
b.WriteString(fmt.Sprintf(" a%s%d := &%s%d\n", n, pi, n, pi))
if p.AddrTaken() == addrTakenHeap {
gv := s.genGlobVar(p)
b.WriteString(fmt.Sprintf(" %s = a%s%d\n", gv, n, pi))
}
}
}
if s.EmitBad == 2 {
if s.BadPackageIdx == pidx && s.BadFuncIdx == f.idx {
b.WriteString(" // force runtime failure here (debugging)\n")
b.WriteString(fmt.Sprintf(" NoteFailure(%d, %d, %d, \"%s\", \"artificial\", %d, true, uint64(0))\n", f.complexityMeasure(), pidx, f.idx, s.checkerPkg(pidx), 0))
}
}
// parameter checking code
var haveControl bool
s.wr.Checkpoint("before param checks")
value, haveControl = s.emitParamChecks(f, b, pidx, value)
// defer testing
if s.tunables.doDefer && f.dodefc < s.tunables.deferFraction {
s.wr.Checkpoint("before defer checks")
_ = s.emitDeferChecks(f, b, pidx, value)
}
// returns
s.emitReturn(f, b, haveControl)
b.WriteString(fmt.Sprintf(" // %d addr-taken params, %d addr-taken returns\n",
aCounts[0], aCounts[1]))
b.WriteString("}\n\n")
// emit any new helper funcs referenced by this test function
s.emitAddrTakenHelpers(f, b, emit)
}
// complexityMeasure returns an integer that estimates how complex a
// given test function is relative to some other function. The more
// parameters + returns and the more complicated the types of the
// params/returns, the higher the number returned here. In theory this
// could be worked into the minimization process (e.g. pick the least
// complex func that reproduces the failure), but for now that isn't
// wired up yet.
func (f *funcdef) complexityMeasure() int {
v := int(0)
if f.isMethod {
v += f.receiver.NumElements()
}
for _, p := range f.params {
v += p.NumElements()
}
for _, r := range f.returns {
v += r.NumElements()
}
return v
}
// emitRecursiveCall generates a recursive call to the test function in question.
func (s *genstate) emitRecursiveCall(f *funcdef) string {
b := bytes.NewBuffer(nil)
rcvr := ""
if f.isMethod {
rcvr = "rcvr."
}
b.WriteString(fmt.Sprintf(" %sTest%d(", rcvr, f.idx))
for pi, p := range f.params {
writeCom(b, pi)
if p.IsControl() {
b.WriteString(fmt.Sprintf(" %s-1", s.genParamRef(p, pi)))
} else {
if !p.IsBlank() {
b.WriteString(fmt.Sprintf(" %s", s.genParamRef(p, pi)))
} else {
b.WriteString(fmt.Sprintf(" brc%d", pi))
}
}
}
b.WriteString(")")
return b.String()
}
// emitReturn generates a return sequence.
func (s *genstate) emitReturn(f *funcdef, b *bytes.Buffer, doRecursiveCall bool) {
// If any of the return values are address-taken, then instead of
//
// return x, y, z
//
// we emit
//
// r1 = ...
// r2 = ...
// ...
// return
//
// Make an initial pass through the returns to see if we need to do this.
// Figure out the final return values in the process.
indirectReturn := false
retvals := []string{}
for ri, r := range f.returns {
if r.AddrTaken() != notAddrTaken {
indirectReturn = true
}
t := ""
if doRecursiveCall {
t = "t"
}
retvals = append(retvals, fmt.Sprintf("rc%s%d", t, ri))
}
// generate the recursive call itself if applicable
if doRecursiveCall {
b.WriteString(" // recursive call\n ")
if s.ForceStackGrowth {
b.WriteString(" hackStack() // force stack growth on next call\n")
}
rcall := s.emitRecursiveCall(f)
if indirectReturn {
for ri := range f.returns {
writeCom(b, ri)
b.WriteString(fmt.Sprintf(" rct%d", ri))
}
b.WriteString(" := ")
b.WriteString(rcall)
b.WriteString("\n")
} else {
if len(f.returns) == 0 {
b.WriteString(fmt.Sprintf("%s\n return\n", rcall))
} else {
b.WriteString(fmt.Sprintf(" return %s\n", rcall))
}
return
}
}
// now the actual return
if indirectReturn {
for ri, r := range f.returns {
s.genReturnAssign(b, r, ri, retvals[ri])
}
b.WriteString(" return\n")
} else {
b.WriteString(" return ")
for ri := range f.returns {
writeCom(b, ri)
b.WriteString(retvals[ri])
}
b.WriteString("\n")
}
}
func (s *genstate) GenPair(calloutfile *os.File, checkoutfile *os.File, fidx int, pidx int, b *bytes.Buffer, seed int64, emit bool) int64 {
verb(1, "gen fidx %d pidx %d", fidx, pidx)
checkTunables(tunables)
s.tunables = tunables
// Generate a function with a random number of params and returns
s.wr = NewWrapRand(seed, s.RandCtl)
s.wr.tag = "genfunc"
fp := s.GenFunc(fidx, pidx)
// Emit caller side
wrcaller := NewWrapRand(seed, s.RandCtl)
s.wr = wrcaller
s.wr.tag = "caller"
s.emitCaller(fp, b, pidx)
if emit {
b.WriteTo(calloutfile)
}
b.Reset()
// Emit checker side
wrchecker := NewWrapRand(seed, s.RandCtl)
s.wr = wrchecker
s.wr.tag = "checker"
s.emitChecker(fp, b, pidx, emit)
if emit {
b.WriteTo(checkoutfile)
}
b.Reset()
wrchecker.Check(wrcaller)
return seed + 1
}
func (s *genstate) openOutputFile(filename string, pk string, imports []string, ipref string) *os.File {
iprefix := func(f string) string {
if ipref == "" {
return f
}
return ipref + "/" + f
}
verb(1, "opening %s", filename)
outf, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
log.Fatal(err)
}
haveunsafe := false
outf.WriteString(fmt.Sprintf("package %s\n\n", pk))
for _, imp := range imports {
if imp == "reflect" {
outf.WriteString("import \"reflect\"\n")
continue
}
if imp == "unsafe" {
outf.WriteString("import _ \"unsafe\"\n")
haveunsafe = true
continue
}
if imp == s.utilsPkg() {
outf.WriteString(fmt.Sprintf("import . \"%s\"\n", iprefix(imp)))
continue
}
outf.WriteString(fmt.Sprintf("import \"%s\"\n", iprefix(imp)))
}
outf.WriteString("\n")
if s.ForceStackGrowth && haveunsafe {
outf.WriteString("// Hack: reach into runtime to grab this testing hook.\n")
outf.WriteString("//go:linkname hackStack runtime.gcTestMoveStackOnNextCall\n")
outf.WriteString("func hackStack()\n\n")
}
return outf
}
type miscVals struct {
NumTpk int
MaxFail int
NumTests int
}
const utilsTemplate = `
import (
"fmt"
"os"
)
type UtilsType int
var ParamFailCount [{{.NumTpk}}]int
var ReturnFailCount [{{.NumTpk}}]int
var FailCount [{{.NumTpk}}]int
var Mode [{{.NumTpk}}]string
//go:noinline
func NoteFailure(cm int, pidx int, fidx int, pkg string, pref string, parmNo int, isret bool, _ uint64) {
if isret {
if ParamFailCount[pidx] != 0 {
return
}
ReturnFailCount[pidx]++
} else {
ParamFailCount[pidx]++
}
fmt.Fprintf(os.Stderr, "Error: fail %s |%d|%d|%d| =%s.Test%d= %s %d\n", Mode, cm, pidx, fidx, pkg, fidx, pref, parmNo)
if ParamFailCount[pidx]+FailCount[pidx]+ReturnFailCount[pidx] > {{.MaxFail}} {
os.Exit(1)
}
}
//go:noinline
func NoteFailureElem(cm int, pidx int, fidx int, pkg string, pref string, parmNo int, elem int, isret bool, _ uint64) {
if isret {
if ParamFailCount[pidx] != 0 {
return
}
ReturnFailCount[pidx]++
} else {
ParamFailCount[pidx]++
}
fmt.Fprintf(os.Stderr, "Error: fail %s |%d|%d|%d| =%s.Test%d= %s %d elem %d\n", Mode, cm, pidx, fidx, pkg, fidx, pref, parmNo, elem)
if ParamFailCount[pidx]+FailCount[pidx]+ReturnFailCount[pidx] > {{.MaxFail}} {
os.Exit(1)
}
}
func BeginFcn(p int) {
ParamFailCount[p] = 0
ReturnFailCount[p] = 0
}
func EndFcn(p int) {
FailCount[p] += ParamFailCount[p]
FailCount[p] += ReturnFailCount[p]
}
`
func (s *genstate) emitUtils(outf *os.File, maxfail int, numtpk int) {
vals := miscVals{
NumTpk: numtpk,
MaxFail: maxfail,
}
t := template.Must(template.New("utils").Parse(utilsTemplate))
err := t.Execute(outf, vals)
if err != nil {
log.Fatal(err)
}
}
const mainPreamble = `
import (
"fmt"
"os"
)
func main() {
fmt.Fprintf(os.Stderr, "starting main\n")
`
func (s *genstate) emitMain(outf *os.File, numit int, fcnmask map[int]int, pkmask map[int]int) {
fmt.Fprintf(outf, "%s", mainPreamble)
fmt.Fprintf(outf, " pch := make(chan bool, %d)\n", s.NumTestPackages)
for k := 0; k < s.NumTestPackages; k++ {
cp := fmt.Sprintf("%s%s%d", s.Tag, CallerName, k)
fmt.Fprintf(outf, " go func(ch chan bool) {\n")
for i := 0; i < numit; i++ {
if shouldEmitFP(i, k, fcnmask, pkmask) {
fmt.Fprintf(outf, " %s.%s%d(\"normal\")\n", cp, CallerName, i)
if s.tunables.doReflectCall {
fmt.Fprintf(outf, " %s.%s%d(\"reflect\")\n", cp, CallerName, i)
}
}
}
fmt.Fprintf(outf, " pch <- true\n")
fmt.Fprintf(outf, " }(pch)\n")
}
fmt.Fprintf(outf, " for pidx := 0; pidx < %d; pidx++ {\n", s.NumTestPackages)
fmt.Fprintf(outf, " _ = <- pch\n")
fmt.Fprintf(outf, " }\n")
fmt.Fprintf(outf, " tf := 0\n")
fmt.Fprintf(outf, " for pidx := 0; pidx < %d; pidx++ {\n", s.NumTestPackages)
fmt.Fprintf(outf, " tf += FailCount[pidx]\n")
fmt.Fprintf(outf, " }\n")
fmt.Fprintf(outf, " if tf != 0 {\n")
fmt.Fprintf(outf, " fmt.Fprintf(os.Stderr, \"FAILURES: %%d\\n\", tf)\n")
fmt.Fprintf(outf, " os.Exit(2)\n")
fmt.Fprintf(outf, " }\n")
fmt.Fprintf(outf, " fmt.Fprintf(os.Stderr, \"finished %d tests\\n\")\n", numit*s.NumTestPackages)
fmt.Fprintf(outf, "}\n")
}
func makeDir(d string) {
fi, err := os.Stat(d)
if err == nil && fi.IsDir() {
return
}
verb(1, "creating %s", d)
if err := os.Mkdir(d, 0777); err != nil {
log.Fatal(err)
}
}
func (s *genstate) callerPkg(which int) string {
return s.Tag + CallerName + strconv.Itoa(which)
}
func (s *genstate) callerFile(which int) string {
cp := s.callerPkg(which)
return filepath.Join(s.OutDir, cp, cp+".go")
}
func (s *genstate) checkerPkg(which int) string {
return s.Tag + CheckerName + strconv.Itoa(which)
}
func (s *genstate) checkerFile(which int) string {
cp := s.checkerPkg(which)
return filepath.Join(s.OutDir, cp, cp+".go")
}
func (s *genstate) utilsPkg() string {
return s.Tag + "Utils"
}
func (s *genstate) beginPackage(pkidx int) {
s.pkidx = pkidx
s.derefFuncs = make(map[string]string)
s.assignFuncs = make(map[string]string)
s.allocFuncs = make(map[string]string)
s.globVars = make(map[string]string)
s.genvalFuncs = make(map[string]string)
}
func runImports(files []string) {
verb(1, "... running goimports")
args := make([]string, 0, len(files)+1)
args = append(args, "-w")
args = append(args, files...)
cmd := exec.Command("goimports", args...)
coutput, cerr := cmd.CombinedOutput()
if cerr != nil {
log.Fatalf("goimports command failed: %s", string(coutput))
}
verb(1, "... goimports run complete")
}
// shouldEmitFP returns true if we should actually emit code for the function
// with the specified package + fcn indices. For "regular" runs, fcnmask and pkmask
// will be empty, meaning we want to emit every function in every package. The
// fuzz-runner program also tries to do testcase "minimization", which means that it
// will try to whittle down the set of packages and functions (by running the generator
// using the fcnmask and pkmask options) to emit only specific packages or functions.
func shouldEmitFP(fn int, pk int, fcnmask map[int]int, pkmask map[int]int) bool {
emitpk := true
emitfn := true
if len(pkmask) != 0 {
emitpk = false
if _, ok := pkmask[pk]; ok {
emitpk = true
}
}
if len(fcnmask) != 0 {
emitfn = false
if _, ok := fcnmask[fn]; ok {
emitfn = true
}
}
doemit := emitpk && emitfn
verb(2, "shouldEmitFP(F=%d,P=%d) returns %v", fn, pk, doemit)
return doemit
}
// Generate is the top level code generation hook for this package.
// Emits code according to the schema in config object 'c'.
func Generate(c GenConfig) int {
mainpkg := c.Tag + "Main"
var ipref string
if len(c.PkgPath) > 0 {
ipref = c.PkgPath
}
s := genstate{
GenConfig: c,
ipref: ipref,
}
if s.OutDir != "." {
verb(1, "creating %s", s.OutDir)
makeDir(s.OutDir)
}
mainimports := []string{}
for i := 0; i < s.NumTestPackages; i++ {
if shouldEmitFP(-1, i, nil, s.PkgMask) {
makeDir(s.OutDir + "/" + s.callerPkg(i))
makeDir(s.OutDir + "/" + s.checkerPkg(i))
makeDir(s.OutDir + "/" + s.utilsPkg())
mainimports = append(mainimports, s.callerPkg(i))
}
}
mainimports = append(mainimports, s.utilsPkg())
// Emit utils package.
verb(1, "emit utils")
utilsfile := s.OutDir + "/" + s.utilsPkg() + "/" + s.utilsPkg() + ".go"
utilsoutfile := s.openOutputFile(utilsfile, s.utilsPkg(), []string{}, "")
s.emitUtils(utilsoutfile, s.MaxFail, s.NumTestPackages)
utilsoutfile.Close()
mainfile := s.OutDir + "/" + mainpkg + ".go"
mainoutfile := s.openOutputFile(mainfile, "main", mainimports, ipref)
allfiles := []string{mainfile, utilsfile}
for k := 0; k < s.NumTestPackages; k++ {
callerImports := []string{s.checkerPkg(k), s.utilsPkg()}
checkerImports := []string{s.utilsPkg()}
if tunables.doReflectCall {
callerImports = append(callerImports, "reflect")
}
if s.ForceStackGrowth {
callerImports = append(callerImports, "unsafe")
checkerImports = append(checkerImports, "unsafe")
}
var calleroutfile, checkeroutfile *os.File
if shouldEmitFP(-1, k, nil, s.PkgMask) {
calleroutfile = s.openOutputFile(s.callerFile(k), s.callerPkg(k),
callerImports, ipref)
checkeroutfile = s.openOutputFile(s.checkerFile(k), s.checkerPkg(k),
checkerImports, ipref)
allfiles = append(allfiles, s.callerFile(k), s.checkerFile(k))
}
s.beginPackage(k)
var b bytes.Buffer
for i := 0; i < s.NumTestFunctions; i++ {
doemit := shouldEmitFP(i, k, s.FcnMask, s.PkgMask)
s.Seed = s.GenPair(calleroutfile, checkeroutfile, i, k,
&b, s.Seed, doemit)
}
// When minimization is in effect, we sometimes wind
// up eliminating all refs to the utils package. Add a
// dummy to help with this.
fmt.Fprintf(calleroutfile, "\n// dummy\nvar Dummy UtilsType\n")
fmt.Fprintf(checkeroutfile, "\n// dummy\nvar Dummy UtilsType\n")
calleroutfile.Close()
checkeroutfile.Close()
}
s.emitMain(mainoutfile, s.NumTestFunctions, s.FcnMask, s.PkgMask)
// emit go.mod
verb(1, "opening go.mod")
fn := s.OutDir + "/go.mod"
outf, err := os.OpenFile(fn, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
log.Fatal(err)
}
outf.WriteString(fmt.Sprintf("module %s\n\ngo 1.17\n", s.PkgPath))
outf.Close()
verb(1, "closing files")
mainoutfile.Close()
if s.errs == 0 && s.RunGoImports {
runImports(allfiles)
}
return s.errs
}