diff --git a/fuzzing/fuzzer.go b/fuzzing/fuzzer.go index d2da8f1c..7898b58f 100644 --- a/fuzzing/fuzzer.go +++ b/fuzzing/fuzzer.go @@ -581,29 +581,27 @@ func chainSetupFromCompilations(fuzzer *Fuzzer, testChain *chain.TestChain) (*ex func defaultCallSequenceGeneratorConfigFunc(fuzzer *Fuzzer, valueSet *valuegeneration.ValueSet, randomProvider *rand.Rand) (*CallSequenceGeneratorConfig, error) { // Create the value generator and mutator for the worker. mutationalGeneratorConfig := &valuegeneration.MutationalValueGeneratorConfig{ - MinMutationRounds: 0, - MaxMutationRounds: 1, - GenerateRandomAddressBias: 0.05, - GenerateRandomIntegerBias: 0.5, - GenerateRandomStringBias: 0.05, - GenerateRandomBytesBias: 0.05, - MutateAddressProbability: 0.1, + MinMutationRounds: 0, + MaxMutationRounds: 1, + // Echidna: Generate a random ABI value 40% of the time + GenerateRandomAddressBias: 0.4, + GenerateRandomIntegerBias: 0.4, + GenerateRandomStringBias: 0.4, + GenerateRandomBytesBias: 0.4, + MutateAddressProbability: 0.0, MutateArrayStructureProbability: 0.1, - MutateBoolProbability: 0.1, + MutateBoolProbability: 1.0, MutateBytesProbability: 0.1, - MutateBytesGenerateNewBias: 0.45, MutateFixedBytesProbability: 0.1, MutateStringProbability: 0.1, - MutateStringGenerateNewBias: 0.7, MutateIntegerProbability: 0.1, - MutateIntegerGenerateNewBias: 0.5, RandomValueGeneratorConfig: &valuegeneration.RandomValueGeneratorConfig{ - GenerateRandomArrayMinSize: 0, - GenerateRandomArrayMaxSize: 100, - GenerateRandomBytesMinSize: 0, - GenerateRandomBytesMaxSize: 100, - GenerateRandomStringMinSize: 0, - GenerateRandomStringMaxSize: 100, + GenerateRandomArrayMinSize: 1, + GenerateRandomArrayMaxSize: 32, + GenerateRandomBytesMinSize: 1, + GenerateRandomBytesMaxSize: 32, + GenerateRandomStringMinSize: 1, + GenerateRandomStringMaxSize: 32, }, } mutationalGenerator := valuegeneration.NewMutationalValueGenerator(mutationalGeneratorConfig, valueSet, randomProvider) @@ -611,6 +609,7 @@ func defaultCallSequenceGeneratorConfigFunc(fuzzer *Fuzzer, valueSet *valuegener // Create a sequence generator config which uses the created value generator. sequenceGenConfig := &CallSequenceGeneratorConfig{ NewSequenceProbability: 0.3, + MutateCallSequenceElementProbability: 0.1, RandomUnmodifiedCorpusHeadWeight: 800, RandomUnmodifiedCorpusTailWeight: 100, RandomUnmodifiedSpliceAtRandomWeight: 200, diff --git a/fuzzing/fuzzer_worker_sequence_generator.go b/fuzzing/fuzzer_worker_sequence_generator.go index 488d4ebc..7ad6e065 100644 --- a/fuzzing/fuzzer_worker_sequence_generator.go +++ b/fuzzing/fuzzer_worker_sequence_generator.go @@ -46,6 +46,10 @@ type CallSequenceGeneratorConfig struct { // sequence rather than mutating one from the corpus. NewSequenceProbability float32 + // MutateCallSequenceElementProbability defines the probability that the CallSequenceGenerator should mutate a call + // sequence element. + MutateCallSequenceElementProbability float32 + // RandomUnmodifiedCorpusHeadWeight defines the weight that the CallSequenceGenerator should use the call sequence // generation strategy of taking the head of a corpus sequence (without mutations) and append newly generated calls // to the end of it. @@ -467,15 +471,28 @@ func prefetchModifyCallFuncMutate(sequenceGenerator *CallSequenceGenerator, elem return nil } - // Loop for each input value and mutate it + // If our bias directs us to it, do not mutate the element at all + randomGeneratorDecision := sequenceGenerator.worker.randomProvider.Float32() + if randomGeneratorDecision > sequenceGenerator.config.MutateCallSequenceElementProbability { + return nil + } + + // If this element has no input values, exit early. + if len(element.Call.DataAbiValues.InputValues) == 0 { + return nil + } + + // Choose which input value to mutate + idx := sequenceGenerator.worker.randomProvider.Intn(len(element.Call.DataAbiValues.InputValues)) + + // Mutate selected input value and replace the value abiValuesMsgData := element.Call.DataAbiValues - for i := 0; i < len(abiValuesMsgData.InputValues); i++ { - mutatedInput, err := valuegeneration.MutateAbiValue(sequenceGenerator.config.ValueGenerator, sequenceGenerator.config.ValueMutator, &abiValuesMsgData.Method.Inputs[i].Type, abiValuesMsgData.InputValues[i]) - if err != nil { - return fmt.Errorf("error when mutating call sequence input argument: %v", err) - } - abiValuesMsgData.InputValues[i] = mutatedInput + mutatedInput, err := valuegeneration.MutateAbiValue(sequenceGenerator.config.ValueGenerator, sequenceGenerator.config.ValueMutator, &abiValuesMsgData.Method.Inputs[idx].Type, abiValuesMsgData.InputValues[idx]) + if err != nil { + return fmt.Errorf("error when mutating call sequence input argument: %v", err) } + abiValuesMsgData.InputValues[idx] = mutatedInput + // Re-encode the message's calldata element.Call.WithDataAbiValues(abiValuesMsgData) diff --git a/fuzzing/valuegeneration/abi_values.go b/fuzzing/valuegeneration/abi_values.go index e9c574a5..a18d9969 100644 --- a/fuzzing/valuegeneration/abi_values.go +++ b/fuzzing/valuegeneration/abi_values.go @@ -3,13 +3,14 @@ package valuegeneration import ( "encoding/hex" "fmt" - "github.com/crytic/medusa/logging" - "github.com/crytic/medusa/utils" "math/big" "reflect" "strconv" "strings" + "github.com/crytic/medusa/logging" + "github.com/crytic/medusa/utils" + "github.com/crytic/medusa/utils/reflectionutils" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" @@ -211,41 +212,31 @@ func MutateAbiValue(generator ValueGenerator, mutator ValueMutator, inputType *a // Note: We create a copy, as existing arrays may not be assignable. array := reflectionutils.CopyReflectedType(reflect.ValueOf(value)) - // Mutate our array structure first - mutatedValues := mutator.MutateArray(reflectionutils.GetReflectedArrayValues(array), true) + // Mutate our array structure + mutatedValues := mutator.MutateArray(reflectionutils.GetReflectedArrayValues(array), true, inputType.Elem) + + // TODO: Make sure that we do not need to create a new copy of the array with some unit tests. // Create a new array of the appropriate size - array = reflect.New(reflect.ArrayOf(array.Len(), array.Type().Elem())).Elem() + /*array = reflect.New(reflect.ArrayOf(array.Len(), array.Type().Elem())).Elem() - // Next mutate each element in the array. + // Next set each element in the new array. for i := 0; i < array.Len(); i++ { - // Obtain the element's reflected value to access its getter/setters - reflectedElement := array.Index(i) - - // If any item is nil, we generate a new element in its place instead. Otherwise, we mutate the existing value. - if mutatedValues[i] == nil { - generatedElement := GenerateAbiValue(generator, inputType.Elem) - reflectedElement.Set(reflect.ValueOf(generatedElement)) - } else { - mutatedElement, err := MutateAbiValue(generator, mutator, inputType.Elem, mutatedValues[i]) - if err != nil { - return nil, fmt.Errorf("could not mutate array input as the value generator encountered an error: %v", err) - } - reflectedElement.Set(reflect.ValueOf(mutatedElement)) - } - } + array.Index(i).Set(reflect.ValueOf(mutatedValues[i])) + }*/ - return array.Interface(), nil + return mutatedValues, nil case abi.SliceTy: // Dynamic sized arrays are represented as slices. // Note: We create a copy, as existing slices may not be assignable. slice := reflectionutils.CopyReflectedType(reflect.ValueOf(value)) // Mutate our slice structure first - mutatedValues := mutator.MutateArray(reflectionutils.GetReflectedArrayValues(slice), false) + mutatedValues := mutator.MutateArray(reflectionutils.GetReflectedArrayValues(slice), false, inputType.Elem) + // TODO: Same TODO as for arrays above. // Create a new slice of the appropriate size - slice = reflect.MakeSlice(reflect.SliceOf(slice.Type().Elem()), len(mutatedValues), len(mutatedValues)) + /*slice = reflect.MakeSlice(reflect.SliceOf(slice.Type().Elem()), len(mutatedValues), len(mutatedValues)) // Next mutate each element in the slice. for i := 0; i < slice.Len(); i++ { @@ -263,8 +254,8 @@ func MutateAbiValue(generator ValueGenerator, mutator ValueMutator, inputType *a } reflectedElement.Set(reflect.ValueOf(mutatedElement)) } - } - return slice.Interface(), nil + }*/ + return mutatedValues, nil case abi.TupleTy: // Structs are used to represent tuples. // Note: We create a copy, as existing tuples may not be assignable. diff --git a/fuzzing/valuegeneration/abi_values_test.go b/fuzzing/valuegeneration/abi_values_test.go index 30c374ae..0c4729f1 100644 --- a/fuzzing/valuegeneration/abi_values_test.go +++ b/fuzzing/valuegeneration/abi_values_test.go @@ -250,27 +250,24 @@ func TestABIGenerationAndMutation(t *testing.T) { mutationalGeneratorConfig := &MutationalValueGeneratorConfig{ MinMutationRounds: 0, MaxMutationRounds: 1, - GenerateRandomAddressBias: 0.5, - GenerateRandomIntegerBias: 0.5, - GenerateRandomStringBias: 0.5, - GenerateRandomBytesBias: 0.5, - MutateAddressProbability: 0.8, - MutateArrayStructureProbability: 0.8, - MutateBoolProbability: 0.8, - MutateBytesProbability: 0.8, - MutateBytesGenerateNewBias: 0.45, - MutateFixedBytesProbability: 0.8, - MutateStringProbability: 0.8, - MutateStringGenerateNewBias: 0.7, - MutateIntegerProbability: 0.8, - MutateIntegerGenerateNewBias: 0.5, + GenerateRandomAddressBias: 0.4, + GenerateRandomIntegerBias: 0.4, + GenerateRandomStringBias: 0.4, + GenerateRandomBytesBias: 0.4, + MutateAddressProbability: 0.1, + MutateArrayStructureProbability: 0.1, + MutateBoolProbability: 0.1, + MutateBytesProbability: 0.1, + MutateFixedBytesProbability: 0.1, + MutateStringProbability: 0.1, + MutateIntegerProbability: 0.1, RandomValueGeneratorConfig: &RandomValueGeneratorConfig{ - GenerateRandomArrayMinSize: 0, - GenerateRandomArrayMaxSize: 100, - GenerateRandomBytesMinSize: 0, - GenerateRandomBytesMaxSize: 100, - GenerateRandomStringMinSize: 0, - GenerateRandomStringMaxSize: 100, + GenerateRandomArrayMinSize: 1, + GenerateRandomArrayMaxSize: 32, + GenerateRandomBytesMinSize: 1, + GenerateRandomBytesMaxSize: 32, + GenerateRandomStringMinSize: 1, + GenerateRandomStringMaxSize: 32, }, } mutationalGenerator := NewMutationalValueGenerator(mutationalGeneratorConfig, NewValueSet(), rand.New(rand.NewSource(time.Now().UnixNano()))) diff --git a/fuzzing/valuegeneration/generator_mutational.go b/fuzzing/valuegeneration/generator_mutational.go index 3e648a88..2df83423 100644 --- a/fuzzing/valuegeneration/generator_mutational.go +++ b/fuzzing/valuegeneration/generator_mutational.go @@ -1,11 +1,14 @@ package valuegeneration import ( - "github.com/crytic/medusa/utils" - "github.com/ethereum/go-ethereum/common" - "golang.org/x/exp/slices" "math/big" "math/rand" + "sync" + + "github.com/crytic/medusa/utils" + "github.com/crytic/medusa/utils/randomutils" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" ) // MutationalValueGenerator represents a ValueGenerator and ValueMutator for function inputs and call arguments. It @@ -59,24 +62,15 @@ type MutationalValueGeneratorConfig struct { // MutateBytesProbability defines the probability in which an existing dynamic-sized byte array value will be // mutated by the value generator. Value range is [0.0, 1.0]. MutateBytesProbability float32 - // MutateBytesGenerateNewBias defines the probability that when an existing dynamic-sized byte array will be - // mutated, it is done so by being replaced with a newly generated one instead. Value range is [0.0, 1.0]. - MutateBytesGenerateNewBias float32 // MutateFixedBytesProbability defines the probability in which an existing fixed-sized byte array value will be // mutated by the value generator. Value range is [0.0, 1.0]. MutateFixedBytesProbability float32 // MutateStringProbability defines the probability in which an existing string value will be mutated by // the value generator. Value range is [0.0, 1.0]. MutateStringProbability float32 - // MutateStringGenerateNewBias defines the probability that when an existing string will be mutated, - // it is done so by being replaced with a newly generated one instead. Value range is [0.0, 1.0]. - MutateStringGenerateNewBias float32 // MutateIntegerProbability defines the probability in which an existing integer value will be mutated by // the value generator. Value range is [0.0, 1.0]. MutateIntegerProbability float32 - // MutateIntegerGenerateNewBias defines the probability that when an existing integer will be mutated, - // it is done so by being replaced with a newly generated one instead. Value range is [0.0, 1.0]. - MutateIntegerGenerateNewBias float32 // RandomValueGeneratorConfig is adhered to in this structure, to power the underlying RandomValueGenerator. *RandomValueGeneratorConfig @@ -100,341 +94,179 @@ func NewMutationalValueGenerator(config *MutationalValueGeneratorConfig, valueSe return generator } -// getMutationParams takes a length of inputs and returns an initial input index to start with as a base value, as well -// as a random number of mutations which should be performed (within the mutation range specified by the -// MutationalValueGeneratorConfig). -func (g *MutationalValueGenerator) getMutationParams(inputsLen int) (int, int) { - inputIdx := g.randomProvider.Intn(inputsLen) - mutationCount := g.randomProvider.Intn(((g.config.MaxMutationRounds - g.config.MinMutationRounds) + 1) + g.config.MinMutationRounds) - return inputIdx, mutationCount -} - -// integerMutationMethods define methods which take a big integer and a set of inputs and -// transform the integer with a random input and operation. This is used in a loop to create -// mutated integer values. -var integerMutationMethods = []func(*MutationalValueGenerator, *big.Int, ...*big.Int) *big.Int{ - func(g *MutationalValueGenerator, x *big.Int, inputs ...*big.Int) *big.Int { - // Add a random input - return big.NewInt(0).Add(x, inputs[g.randomProvider.Intn(len(inputs))]) - }, - func(g *MutationalValueGenerator, x *big.Int, inputs ...*big.Int) *big.Int { - // Subtract a random input - return big.NewInt(0).Sub(x, inputs[g.randomProvider.Intn(len(inputs))]) - }, - func(g *MutationalValueGenerator, x *big.Int, inputs ...*big.Int) *big.Int { - // Multiply a random input - return big.NewInt(0).Mul(x, inputs[g.randomProvider.Intn(len(inputs))]) - }, - func(g *MutationalValueGenerator, x *big.Int, inputs ...*big.Int) *big.Int { - // Divide a random input - divisor := inputs[g.randomProvider.Intn(len(inputs))] - if divisor.Cmp(big.NewInt(0)) == 0 { - return big.NewInt(1) // leave unchanged if divisor was zero (would've caused panic) - } - return big.NewInt(0).Div(x, divisor) - }, - func(g *MutationalValueGenerator, x *big.Int, inputs ...*big.Int) *big.Int { - // Modulo divide a random input - divisor := inputs[g.randomProvider.Intn(len(inputs))] - if divisor.Cmp(big.NewInt(0)) == 0 { - return big.NewInt(0).Set(x) // leave unchanged if divisor was zero (would've caused panic) - } - return big.NewInt(0).Mod(x, divisor) - }, -} - -// mutateIntegerInternal takes an integer input and returns either a random new integer, or a mutated value based off the input. -// If a nil input is provided, this method uses an existing base value set value as the starting point for mutation. +// mutateIntegerInternal mutates an integer with a given sign and bit length. func (g *MutationalValueGenerator) mutateIntegerInternal(i *big.Int, signed bool, bitLength int) *big.Int { - // If our bias directs us to, use the random generator instead - randomGeneratorDecision := g.randomProvider.Float32() - if randomGeneratorDecision < g.config.GenerateRandomIntegerBias { - return g.RandomValueGenerator.GenerateInteger(signed, bitLength) + // If the integer is nil or it's zero, return zero + if i == nil || i.Sign() == 0 { + return big.NewInt(0) } - // Calculate our integer bounds + // Get the minimum and maximum values for this integer type min, max := utils.GetIntegerConstraints(signed, bitLength) - // Obtain our inputs. We also add our min/max values for this range to the list of inputs. - // Note: We exclude min being added if we're requesting an unsigned integer, as zero is already - // in our set, and we don't want duplicates. - var inputs []*big.Int - inputs = append(inputs, g.valueSet.Integers()...) - if signed { - inputs = append(inputs, min, max) - } else { - inputs = append(inputs, max) - } + // Pick a random value between `[0, abs(i)]` + delta := big.NewInt(0).Rand(g.randomProvider, big.NewInt(0).Abs(i)) - // Determine which value we'll use as an initial input, and how many mutations we will perform. - inputIdx, mutationCount := g.getMutationParams(len(inputs)) - input := new(big.Int) - if i != nil { - input.Set(i) + // Decide whether we are going to add or subtract this delta + if g.randomProvider.Float32() <= 0.5 { + // We will add the delta + i.Add(i, delta) } else { - input.Set(inputs[inputIdx]) + // We will subtract the delta + i.Sub(i, delta) } - input = utils.ConstrainIntegerToBounds(input, min, max) - // Perform the appropriate number of mutations. - for i := 0; i < mutationCount; i++ { - // Mutate input - input = integerMutationMethods[g.randomProvider.Intn(len(integerMutationMethods))](g, input, inputs...) - - // Correct value boundaries (underflow/overflow) - input = utils.ConstrainIntegerToBounds(input, min, max) - } - return input + // Bound the integer to its constraints and return it + return utils.ConstrainIntegerToBounds(i, min, max) } -// bytesMutationMethods define methods which take an initial bytes and a set of inputs to transform the input. The -// transformed input is returned. This is used in a loop to mutate byte slices. -var bytesMutationMethods = []func(*MutationalValueGenerator, []byte, ...[]byte) []byte{ - // Replace a random index with a random byte - func(g *MutationalValueGenerator, b []byte, inputs ...[]byte) []byte { - // Generate a random byte and replace an existing byte in our array with it. If our array has no bytes, we add - // it. - randomByteValue := byte(g.randomProvider.Intn(256)) - if len(b) > 0 { - b[g.randomProvider.Intn(len(b))] = randomByteValue - } else { - b = append(b, randomByteValue) - } - return b - }, - // Flip a random bit in it. - func(g *MutationalValueGenerator, b []byte, inputs ...[]byte) []byte { - // If we have bytes in our array, flip a random bit in a random byte. Otherwise, we add a random byte. - if len(b) > 0 { - i := g.randomProvider.Intn(len(b)) - b[i] = b[i] ^ (1 << (g.randomProvider.Intn(8))) - } else { - b = append(b, byte(g.randomProvider.Intn(256))) - } - return b - }, - // Add a random byte at a random position - func(g *MutationalValueGenerator, b []byte, inputs ...[]byte) []byte { - // Generate a random byte to insert - by := byte(g.randomProvider.Intn(256)) - - // If our provided byte array has no bytes, simply return a new array with this byte. - if len(b) == 0 { - return []byte{by} - } - - // Determine the index to insert our byte into and insert it accordingly. We add +1 here as we allow appending - // to the end here. - i := g.randomProvider.Intn(len(b) + 1) - if i >= len(b) { - return append(b, by) - } else { - return append(b[:i], append([]byte{by}, b[i:]...)...) - } - }, - // Remove a random byte - func(g *MutationalValueGenerator, b []byte, inputs ...[]byte) []byte { - // If we have no bytes to remove, do nothing. - if len(b) == 0 { - return b - } - - i := g.randomProvider.Intn(len(b)) - return append(b[:i], b[i+1:]...) - }, -} - -// mutateBytesInternal takes a byte array and a length. This function returns either a fixed length byte array (based on -// the provided length) or a byte slice. The returned byte array/slice is either randomly generated or mutated using -// the provided input. -// If a nil input is provided, this method uses an existing base value set value as the starting point for mutation. -func (g *MutationalValueGenerator) mutateBytesInternal(b []byte, length int) []byte { - // If we have no inputs or our bias directs us to, use the random generator instead - inputs := g.valueSet.Bytes() - randomGeneratorDecision := g.randomProvider.Float32() - if len(inputs) == 0 || randomGeneratorDecision < g.config.GenerateRandomBytesBias { - // If the length is non-zero, generate a fixed byte array - if length > 0 { - return g.RandomValueGenerator.GenerateFixedBytes(length) - } - // Otherwise, generate a random byte slice - return g.RandomValueGenerator.GenerateBytes() - } - - // Determine which value we'll use as an initial input, and how many mutations we will perform. - inputIdx, mutationCount := g.getMutationParams(len(inputs)) - var input []byte - if b != nil { - input = slices.Clone(b) - } else { - input = slices.Clone(inputs[inputIdx]) - } - - // Mutate the data for our desired number of rounds - for i := 0; i < mutationCount; i++ { - input = bytesMutationMethods[g.randomProvider.Intn(len(bytesMutationMethods))](g, input, inputs...) - } - - // If we want a fixed-byte array and the mutated input is smaller than the requested length, pad the array - // with zeros - if length > 0 && len(input) < length { - paddedZeros := make([]byte, length-len(input)) - input = append(input, paddedZeros...) - } - - // Similarly, if we want a fixed-byte array and the mutated input is larger than the requested length, then truncate - // the array - if length > 0 && len(input) > length { - return input[:length] - } - - return input -} - -// stringMutationMethods define methods which take an initial string and a set of inputs to transform the input. The -// transformed input is returned. This is used in a loop to mutate strings. -var stringMutationMethods = []func(*MutationalValueGenerator, string, ...string) string{ - // Replace a random index with a random character - func(g *MutationalValueGenerator, s string, inputs ...string) string { - // Generate a random rune - randomRune := rune(32 + g.randomProvider.Intn(95)) - - // If the string is empty, we can simply return a new string with just the rune in it. - r := []rune(s) - if len(r) == 0 { - return string(randomRune) - } - - // Otherwise, we replace a rune in it and return it. - r[g.randomProvider.Intn(len(r))] = randomRune - return string(r) - }, - // Flip a random bit - func(g *MutationalValueGenerator, s string, inputs ...string) string { - // If the string is empty, simply return a new one with a randomly added character. - r := []rune(s) - if len(r) == 0 { - return string(rune(32 + g.randomProvider.Int()%95)) - } - - // Otherwise, flip a random bit in it and return it. - i := g.randomProvider.Intn(len(r)) - r[i] = r[i] ^ (1 << (g.randomProvider.Int() % 8)) - return string(r) - }, - // Insert a random character at a random position - func(g *MutationalValueGenerator, s string, inputs ...string) string { - // Create a random character. - c := string(rune(32 + g.randomProvider.Intn(95))) - - // If we have an empty string, simply return it - if len(s) == 0 { - return c - } - - // Otherwise we insert it into a random position in the string. - i := g.randomProvider.Intn(len(s)) - return s[:i] + c + s[i+1:] - }, - // Remove a random character - func(g *MutationalValueGenerator, s string, inputs ...string) string { - // If we have no characters to remove, do nothing - if len(s) == 0 { - return s - } - - // Otherwise, remove a random character. - i := g.randomProvider.Intn(len(s)) - return s[:i] + s[i+1:] - }, -} - -// mutateStringInternal takes a string and returns either a random new string, or a mutated value based off the input. -// If a nil input is provided, this method uses an existing base value set value as the starting point for mutation. -func (g *MutationalValueGenerator) mutateStringInternal(s *string) string { - // If we have no inputs or our bias directs us to, use the random generator instead - inputs := g.valueSet.Strings() - randomGeneratorDecision := g.randomProvider.Float32() - if len(inputs) == 0 || randomGeneratorDecision < g.config.GenerateRandomStringBias { - return g.RandomValueGenerator.GenerateString() - } - - // Obtain a random input to mutate - inputIdx, mutationCount := g.getMutationParams(len(inputs)) - var input string - if s != nil { - input = *s - } else { - input = inputs[inputIdx] - } - - // Mutate the data for our desired number of rounds - for i := 0; i < mutationCount; i++ { - input = stringMutationMethods[g.randomProvider.Intn(len(stringMutationMethods))](g, input, inputs...) - } - - return input +// mutateListLikeObject mutates a list-like object (e.g. byte array, string, etc). The function will utilize one +// of four different mutation methods to mutate the list-like input. Returns the mutated list. +func mutateListLikeObject[T any](g *MutationalValueGenerator, list []T) []T { + // There are four different mutation methods for list-like objects. + listMutationMethods := randomutils.NewWeightedRandomChooserWithRand[func(g *MutationalValueGenerator, list []T) []T](g.randomProvider, &sync.Mutex{}) + listMutationMethods.AddChoices( + randomutils.NewWeightedRandomChoice( + // No-op: Return the original list as-is with a 1/31 chance + func(g *MutationalValueGenerator, list []T) []T { + return list + }, + + new(big.Int).SetUint64(1), + ), + randomutils.NewWeightedRandomChoice( + // Delete: Remove a random element from the list with a 10/31 chance + func(g *MutationalValueGenerator, list []T) []T { + idx := g.randomProvider.Intn(len(list)) + return append(list[:idx], list[idx+1:]...) + }, + new(big.Int).SetUint64(10), + ), + randomutils.NewWeightedRandomChoice( + // Expand: Expand the list by expanding a random element from the random list + // a random number of times with a 10/31 chance + func(g *MutationalValueGenerator, list []T) []T { + // We also do not want to expand lists if they are already greater than 32 elements + if len(list) >= 32 { + return list + } + // Generate a random index to replicate + idx := g.randomProvider.Intn(len(list)) + // Generate a random number of times to replicate the element + num := g.randomProvider.Intn(min(32, len(list))) + 1 + + // Create a new list to store the replicated elements + expandedList := make([]T, 0) + // Append the elements before the index + expandedList = append(expandedList, list[:idx]...) + + // Append the element of choice num time + for i := 0; i < num; i++ { + expandedList = append(expandedList, list[idx]) + } + + // Append the elements after the index + expandedList = append(expandedList, list[idx+1:]...) + return expandedList + }, + new(big.Int).SetUint64(10), + ), + randomutils.NewWeightedRandomChoice( + // Swap: Swap two random elements in the list with a 10/31 chance + func(g *MutationalValueGenerator, list []T) []T { + idx1 := g.randomProvider.Intn(len(list)) + idx2 := g.randomProvider.Intn(len(list)) + list[idx1], list[idx2] = list[idx2], list[idx1] + return list + }, + new(big.Int).SetUint64(10), + ), + ) + + // Choose a random mutation method + // We shouldn't have any errors here so ignore the error + mutationMethod, _ := listMutationMethods.Choose() + + // Apply the mutation method to the list + return (*mutationMethod)(g, list) } // GenerateAddress obtains an existing address from its underlying value set or generates a random one. func (g *MutationalValueGenerator) GenerateAddress() common.Address { - // If our bias directs us to, use the random generator instead + // If our bias directs us to or if we have nothing in the value set, use the random generator instead randomGeneratorDecision := g.randomProvider.Float32() - if randomGeneratorDecision < g.config.GenerateRandomAddressBias { + if randomGeneratorDecision < g.config.GenerateRandomAddressBias || len(g.valueSet.Addresses()) == 0 { return g.RandomValueGenerator.GenerateAddress() } - // Obtain our addresses from our value set. If we have none, generate a random one instead. + // Obtain our addresses from our value set. addresses := g.valueSet.Addresses() - if len(addresses) == 0 { - return g.RandomValueGenerator.GenerateAddress() - } // Select a random address from our set of addresses. address := addresses[g.randomProvider.Intn(len(addresses))] return address } -// MutateAddress takes an address input and sometimes returns a mutated value based off the input. +// MutateAddress takes an address input and mutates it. func (g *MutationalValueGenerator) MutateAddress(addr common.Address) common.Address { - // Determine whether to perform mutations against this input or just return it as-is. - randomGeneratorDecision := g.randomProvider.Float32() - if randomGeneratorDecision < g.config.MutateAddressProbability { - return g.RandomValueGenerator.GenerateAddress() - } + // Currently, we do not mutate addresses and we return it as-is. return addr } // MutateArray takes a dynamic or fixed sized array as input, and returns a mutated value based off of the input. -// Returns the mutated value. If any element of the returned array is nil, the value generator will be called upon -// to generate it new. -func (g *MutationalValueGenerator) MutateArray(value []any, fixedLength bool) []any { +// The ABI type of the array is also provided in case new values need to be generated. Returns the mutated value. +func (g *MutationalValueGenerator) MutateArray(array []any, fixedLength bool, abiType *abi.Type) []any { // Determine whether to perform mutations against this input or just return it as-is. randomGeneratorDecision := g.randomProvider.Float32() - if randomGeneratorDecision < g.config.MutateArrayStructureProbability { - // Determine how many mutations we'll apply - _, mutationCount := g.getMutationParams(1) - for i := 0; i < mutationCount; i++ { - // TODO: Apply array structure mutations (swap, insert, delete) - } - return value + if randomGeneratorDecision > g.config.MutateArrayStructureProbability { + return array + } + // Calculate length of the array + length := len(array) + + // Mutate the array + mutatedArray := mutateListLikeObject(g, array) + + // If it's a dynamic length array, we can return early + if !fixedLength { + return mutatedArray + } + + // If the mutated array is too long, truncate it + if len(mutatedArray) > length { + return mutatedArray[:length] + } + + // If the mutated array is too short, pad it with newly generated elements + for i := len(mutatedArray); i < length; i++ { + mutatedArray = append(mutatedArray, GenerateAbiValue(g, abiType)) } - return value + + // Return the mutated array + return mutatedArray } // MutateBool takes a boolean input and returns a mutated value based off the input. func (g *MutationalValueGenerator) MutateBool(bl bool) bool { - // Determine whether to perform mutations against this input or just return it as-is. - randomGeneratorDecision := g.randomProvider.Float32() - if randomGeneratorDecision < g.config.MutateBoolProbability { - return g.RandomValueGenerator.GenerateBool() - } - return bl + // We do not really mutate booleans, so we just flip the coin again + return g.RandomValueGenerator.GenerateBool() } // GenerateBytes generates bytes and returns them. func (g *MutationalValueGenerator) GenerateBytes() []byte { - return g.mutateBytesInternal(nil, 0) + // If our bias directs us to or if we have nothing in the value set, use the random generator instead + randomGeneratorDecision := g.randomProvider.Float32() + if randomGeneratorDecision < g.config.GenerateRandomBytesBias || len(g.valueSet.Bytes()) == 0 { + return g.RandomValueGenerator.GenerateBytes() + } + + // Obtain our byte arrays/slices from our value set. + byteSlices := g.valueSet.Bytes() + + // Select a random byte slice from our set of byte slices. + byteSlice := byteSlices[g.randomProvider.Intn(len(byteSlices))] + + return byteSlice } // MutateBytes takes a dynamic-sized byte array input and returns a mutated value based off the input. @@ -442,30 +274,80 @@ func (g *MutationalValueGenerator) MutateBytes(b []byte) []byte { // Determine whether to perform mutations against this input or just return it as-is. randomGeneratorDecision := g.randomProvider.Float32() if randomGeneratorDecision < g.config.MutateBytesProbability { - // Determine whether to return a newly generated value, or mutate our existing. - randomGeneratorDecision = g.randomProvider.Float32() - if randomGeneratorDecision < g.config.MutateBytesGenerateNewBias { - return g.GenerateBytes() - } else { - return g.mutateBytesInternal(b, 0) - } + return mutateListLikeObject(g, b) } + return b } // MutateFixedBytes takes a fixed-sized byte array input and returns a mutated value based off the input. func (g *MutationalValueGenerator) MutateFixedBytes(b []byte) []byte { - return g.mutateBytesInternal(b, len(b)) + // Determine whether to perform mutations against this input or just return it as-is. + randomGeneratorDecision := g.randomProvider.Float32() + if randomGeneratorDecision > g.config.MutateFixedBytesProbability { + // Return the original byte array + return b + } + // Capture the original length of the byte array + length := len(b) + + // Now, mutate the fixed bytes + mutatedFixedBytes := mutateListLikeObject(g, b) + + // If the array is too long, truncate it + if len(mutatedFixedBytes) > length { + return mutatedFixedBytes[:length] + } + // The mutated fixed bytes is too short, pad it with random bytes + if len(mutatedFixedBytes) < length { + randomBytes := g.RandomValueGenerator.GenerateFixedBytes(length - len(mutatedFixedBytes)) + mutatedFixedBytes = append(mutatedFixedBytes, randomBytes...) + } + + return mutatedFixedBytes } // GenerateFixedBytes generates a fixed-sized byte array to use when populating inputs. func (g *MutationalValueGenerator) GenerateFixedBytes(length int) []byte { - return g.mutateBytesInternal(nil, length) + // If our bias directs us to or if we have nothing in the value set, use the random generator instead + randomGeneratorDecision := g.randomProvider.Float32() + if randomGeneratorDecision < g.config.GenerateRandomBytesBias || len(g.valueSet.Bytes()) == 0 { + return g.RandomValueGenerator.GenerateFixedBytes(length) + } + + // Obtain our byte arrays/slices from our value set. + byteArrays := g.valueSet.Bytes() + + // Select a random byte array from our set of byte arrays. + byteArray := byteArrays[g.randomProvider.Intn(len(byteArrays))] + + // If the array is smaller than the requested length, then pad the array with zeros + if len(byteArray) < length { + paddedZeros := make([]byte, length-len(byteArray)) + byteArray = append(byteArray, paddedZeros...) + } + + // Similarly, if it's too long, truncate it + if len(byteArray) > length { + return byteArray[:length] + } + return byteArray } -// GenerateString generates strings and returns them. +// GenerateString generates a new string and returns it. func (g *MutationalValueGenerator) GenerateString() string { - return g.mutateStringInternal(nil) + // If our bias directs us to or if we have nothing in the value set, use the random generator instead + randomGeneratorDecision := g.randomProvider.Float32() + if randomGeneratorDecision < g.config.GenerateRandomStringBias || len(g.valueSet.Strings()) == 0 { + return g.RandomValueGenerator.GenerateString() + } + + // Obtain our strings from our value set. + strings := g.valueSet.Strings() + + // Select a random string from our set of strings. + string := strings[g.randomProvider.Intn(len(strings))] + return string } // MutateString takes a string input and returns a mutated value based off the input. @@ -473,21 +355,27 @@ func (g *MutationalValueGenerator) MutateString(s string) string { // Determine whether to perform mutations against this input or just return it as-is. randomGeneratorDecision := g.randomProvider.Float32() if randomGeneratorDecision < g.config.MutateStringProbability { - // Determine whether to return a newly generated value, or mutate our existing. - randomGeneratorDecision = g.randomProvider.Float32() - if randomGeneratorDecision < g.config.MutateStringGenerateNewBias { - return g.GenerateString() - } else { - return g.mutateStringInternal(&s) - } + mutatedStringAsRunes := mutateListLikeObject(g, []rune(s)) + return string(mutatedStringAsRunes) } + return s } // GenerateInteger generates an integer of the provided properties and returns a big.Int representing it. func (g *MutationalValueGenerator) GenerateInteger(signed bool, bitLength int) *big.Int { - // Call our internal mutation method with no starting input. This will generate a new input. - return g.mutateIntegerInternal(nil, signed, bitLength) + // If our bias directs us to or if we have nothing in the value set, use the random generator instead + randomGeneratorDecision := g.randomProvider.Float32() + if randomGeneratorDecision < g.config.GenerateRandomIntegerBias || len(g.valueSet.Integers()) == 0 { + return g.RandomValueGenerator.GenerateInteger(signed, bitLength) + } + + // Obtain our integers from our value set. + integers := g.valueSet.Integers() + + // Select a random integer from our set of integers. + integer := integers[g.randomProvider.Intn(len(integers))] + return integer } // MutateInteger takes an integer input and applies optional mutations to the provided value. @@ -496,13 +384,7 @@ func (g *MutationalValueGenerator) MutateInteger(i *big.Int, signed bool, bitLen // Determine whether to perform mutations against this input or just return it as-is. randomGeneratorDecision := g.randomProvider.Float32() if randomGeneratorDecision < g.config.MutateIntegerProbability { - // Determine whether to return a newly generated value, or mutate our existing. - randomGeneratorDecision = g.randomProvider.Float32() - if randomGeneratorDecision < g.config.MutateIntegerGenerateNewBias { - return g.GenerateInteger(signed, bitLength) - } else { - return g.mutateIntegerInternal(i, signed, bitLength) - } + return g.mutateIntegerInternal(i, signed, bitLength) } return i } diff --git a/fuzzing/valuegeneration/generator_random.go b/fuzzing/valuegeneration/generator_random.go index eb9de140..96cfe6ba 100644 --- a/fuzzing/valuegeneration/generator_random.go +++ b/fuzzing/valuegeneration/generator_random.go @@ -1,10 +1,14 @@ package valuegeneration import ( - "github.com/crytic/medusa/utils" - "github.com/ethereum/go-ethereum/common" "math/big" "math/rand" + "sync" + + "github.com/crytic/medusa/utils" + "github.com/crytic/medusa/utils/randomutils" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" ) // RandomValueGenerator represents a ValueGenerator used to generate transaction fields and call arguments with values @@ -45,10 +49,16 @@ func NewRandomValueGenerator(config *RandomValueGeneratorConfig, randomProvider // GenerateAddress generates a random address to use when populating inputs. func (g *RandomValueGenerator) GenerateAddress() common.Address { - // Generate random bytes of the address length, then convert it to an address. - addressBytes := make([]byte, common.AddressLength) - g.randomProvider.Read(addressBytes) - return common.BytesToAddress(addressBytes) + // Use these predefined addresses for random address generation + addresses := []common.Address{ + common.HexToAddress("0x000000000000000000000000000000000FFFFFFFF"), + common.HexToAddress("0x00000000000000000000000000000001FFFFFFFE"), + common.HexToAddress("0x00000000000000000000000000000002FFFFFFFD"), + } + + // Pick a random address from our predefined set + idx := g.randomProvider.Intn(len(addresses)) + return addresses[idx] } // MutateAddress takes an address input and returns a mutated value based off the input. @@ -61,13 +71,13 @@ func (g *RandomValueGenerator) MutateAddress(addr common.Address) common.Address // many elements a non-byte, non-string array should have. func (g *RandomValueGenerator) GenerateArrayOfLength() int { rangeSize := uint64(g.config.GenerateRandomArrayMaxSize-g.config.GenerateRandomArrayMinSize) + 1 - return int(g.GenerateInteger(false, 16).Uint64()%rangeSize) + g.config.GenerateRandomArrayMinSize + return int(g.randomProvider.Uint64()%rangeSize) + g.config.GenerateRandomArrayMinSize } // MutateArray takes a dynamic or fixed sized array as input, and returns a mutated value based off of the input. // Returns the mutated value. If any element of the returned array is nil, the value generator will be called upon // to generate it new. -func (g *RandomValueGenerator) MutateArray(value []any, fixedLength bool) []any { +func (g *RandomValueGenerator) MutateArray(value []any, fixedLength bool, abiType *abi.Type) []any { // This value generator does not apply mutations. return value } @@ -126,15 +136,107 @@ func (g *RandomValueGenerator) MutateString(s string) string { // GenerateInteger generates a random integer to use when populating inputs. func (g *RandomValueGenerator) GenerateInteger(signed bool, bitLength int) *big.Int { - // Fill a byte array of the appropriate size with random bytes - b := make([]byte, bitLength/8) - g.randomProvider.Read(b) + // There are four different generation methods for unsigned integers. + unsignedIntegerGenerationMethods := randomutils.NewWeightedRandomChooserWithRand[func(g *RandomValueGenerator, bitLength int) *big.Int](g.randomProvider, &sync.Mutex{}) + unsignedIntegerGenerationMethods.AddChoices( + randomutils.NewWeightedRandomChoice( + // Generate a random value between [0, 1023] with a 9% chance + func(g *RandomValueGenerator, bitLength int) *big.Int { + return new(big.Int).SetInt64(int64(g.randomProvider.Intn(1024))) + }, + new(big.Int).SetUint64(2), + ), + randomutils.NewWeightedRandomChoice( + // Generate a random value between [0, 2^n - 5] with a 76% chance + func(g *RandomValueGenerator, bitLength int) *big.Int { + // Create upper bound (2^n - 5) + // We calculate 2^n - 4 to account for the fact that we want to include the upper bound in our range + upperBound := new(big.Int).Lsh(big.NewInt(1), uint(bitLength)) + upperBound.Sub(upperBound, big.NewInt(4)) + + // Generate random number in range [0, upperBound] + return new(big.Int).Rand(g.randomProvider, upperBound) + }, + new(big.Int).SetUint64(16), + ), + randomutils.NewWeightedRandomChoice( + // Generate a random value between [2^n-5, 2^n - 1] with a 9% chance + func(g *RandomValueGenerator, bitLength int) *big.Int { + // Create 2^n first + maxValue := new(big.Int).Lsh(big.NewInt(1), uint(bitLength)) + + // Subtract a random number between 1 and 5 to get our expected range + return maxValue.Sub(maxValue, big.NewInt(int64(1+g.randomProvider.Intn(5)))) + }, + new(big.Int).SetUint64(2), + ), + randomutils.NewWeightedRandomChoice( + // Generate a random value between [2^(n/2), 2^n] where n = n-5 with a 4% chance + func(g *RandomValueGenerator, bitLength int) *big.Int { + // Guard clause if bitLength is too small + if bitLength <= 5 { + return big.NewInt(0) + } + bitLength -= 5 + + // Generate random exponent between [min(20, bitLength), max(20, bitLength)] + exp := g.randomProvider.Intn(max(bitLength-20+1, 1)) + 20 + + // Create bounds: [2^(exp/2), 2^exp] + lowerBound := new(big.Int).Lsh(big.NewInt(1), uint(exp/2)) + upperBound := new(big.Int).Lsh(big.NewInt(1), uint(exp)) + + // Generate random number in range [lowerBound, upperBound] + rangeSize := new(big.Int).Sub(upperBound, lowerBound) + result := new(big.Int).Rand(g.randomProvider, rangeSize.Add(rangeSize, big.NewInt(1))) + return result.Add(result, lowerBound) + + }, + new(big.Int).SetUint64(1), + ), + ) + + // There are two different generation methods for signed integers. + signedIntegerGenerationMethods := randomutils.NewWeightedRandomChooserWithRand[func(g *RandomValueGenerator, bitLength int) *big.Int](g.randomProvider, &sync.Mutex{}) + signedIntegerGenerationMethods.AddChoices( + randomutils.NewWeightedRandomChoice( + // Generate a random number between [-1023, 1023] with a 10% chance + func(g *RandomValueGenerator, bitLength int) *big.Int { + // Generate random number in range [0, 2046] (which covers -1023 to 1023) + n := g.randomProvider.Intn(2047) + + // Shift the range from [0, 2046] to [-1023, 1023] + return big.NewInt(int64(n - 1023)) + }, + new(big.Int).SetUint64(1), + ), + randomutils.NewWeightedRandomChoice( + // Generate a random number between [-2^n, 2^n-1] with a 90% chance + func(g *RandomValueGenerator, bitLength int) *big.Int { + // Fill a byte array of the appropriate size with random bytes + b := make([]byte, bitLength/8) + g.randomProvider.Read(b) + + // Create an unsigned integer. + res := big.NewInt(0).SetBytes(b) + + // Constrain our integer bounds + return utils.ConstrainIntegerToBitLength(res, true, bitLength) + }, + new(big.Int).SetUint64(9), + ), + ) + + // Select a generation method + var generatorFunc *func(g *RandomValueGenerator, bitLength int) *big.Int + if signed { + generatorFunc, _ = signedIntegerGenerationMethods.Choose() + } else { + generatorFunc, _ = unsignedIntegerGenerationMethods.Choose() - // Create an unsigned integer. - res := big.NewInt(0).SetBytes(b) + } - // Constrain our integer bounds - return utils.ConstrainIntegerToBitLength(res, signed, bitLength) + return (*generatorFunc)(g, bitLength) } // MutateInteger takes an integer input and returns a mutated value based off the input. diff --git a/fuzzing/valuegeneration/mutator.go b/fuzzing/valuegeneration/mutator.go index a045bc02..bcc85a2f 100644 --- a/fuzzing/valuegeneration/mutator.go +++ b/fuzzing/valuegeneration/mutator.go @@ -1,8 +1,10 @@ package valuegeneration import ( - "github.com/ethereum/go-ethereum/common" "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" ) // ValueMutator represents an interface for a provider used to mutate function inputs and call arguments for use @@ -12,9 +14,8 @@ type ValueMutator interface { MutateAddress(addr common.Address) common.Address // MutateArray takes a dynamic or fixed sized array as input, and returns a mutated value based off of the input. - // Returns the mutated value. If any element of the returned array is nil, the value generator will be called upon - // to generate a new value in its place. - MutateArray(value []any, fixedLength bool) []any + // The ABI type of the array is also provided in case new values need to be generated. Returns the mutated value. + MutateArray(value []any, fixedLength bool, abiType *abi.Type) []any // MutateBool takes a boolean input and returns a mutated value based off the input. MutateBool(bl bool) bool diff --git a/fuzzing/valuegeneration/mutator_shrinking.go b/fuzzing/valuegeneration/mutator_shrinking.go index 20b7ff49..3df6ed77 100644 --- a/fuzzing/valuegeneration/mutator_shrinking.go +++ b/fuzzing/valuegeneration/mutator_shrinking.go @@ -1,10 +1,11 @@ package valuegeneration import ( - "github.com/crytic/medusa/utils" - "github.com/ethereum/go-ethereum/common" "math/big" "math/rand" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" ) // ShrinkingValueMutator represents a ValueMutator used to shrink function inputs and call arguments. @@ -50,17 +51,17 @@ func (g *ShrinkingValueMutator) MutateAddress(addr common.Address) common.Addres } // MutateArray takes a dynamic or fixed sized array as input, and returns a mutated value based off of the input. -// Returns the mutated value. If any element of the returned array is nil, the value generator will be called upon -// to generate it new. +// The ABI type of the array is also provided in case new values need to be generated. Returns the mutated value. // This type is not mutated by the ShrinkingValueMutator. -func (g *ShrinkingValueMutator) MutateArray(value []any, fixedLength bool) []any { +func (g *ShrinkingValueMutator) MutateArray(value []any, fixedLength bool, abiType *abi.Type) []any { return value } // MutateBool takes a boolean input and returns a mutated value based off the input. // This type is not mutated by the ShrinkingValueMutator. func (g *ShrinkingValueMutator) MutateBool(bl bool) bool { - return bl + // Always return false + return false } // MutateFixedBytes takes a fixed-sized byte array input and returns a mutated value based off the input. @@ -102,58 +103,26 @@ func (g *ShrinkingValueMutator) MutateBytes(b []byte) []byte { return b } -// integerShrinkingMethods define methods which take a big integer and a set of inputs and -// transform the integer with a random input and operation. -var integerShrinkingMethods = []func(*ShrinkingValueMutator, *big.Int, ...*big.Int) *big.Int{ - func(g *ShrinkingValueMutator, x *big.Int, inputs ...*big.Int) *big.Int { - // If our base value is positive, we subtract from it. If it's positive, we add to it. - // If it's zero, we leave it unchanged. - r := big.NewInt(0) - if x.Cmp(r) > 0 { - r = r.Sub(x, inputs[g.randomProvider.Intn(len(inputs))]) - } else if x.Cmp(r) < 0 { - r = r.Add(x, inputs[g.randomProvider.Intn(len(inputs))]) - } - return r - - }, - func(g *ShrinkingValueMutator, x *big.Int, inputs ...*big.Int) *big.Int { - // Divide by two - return big.NewInt(0).Div(x, big.NewInt(2)) - }, -} - // MutateInteger takes an integer input and applies optional mutations to the provided value. // Returns an optionally mutated copy of the input. func (g *ShrinkingValueMutator) MutateInteger(i *big.Int, signed bool, bitLength int) *big.Int { - randomGeneratorDecision := g.randomProvider.Float32() - if randomGeneratorDecision < g.config.ShrinkValueProbability { - // Calculate our integer bounds - min, max := utils.GetIntegerConstraints(signed, bitLength) - - // Obtain our inputs. We also add our min/max values for this range to the list of inputs. - // Note: We exclude min being added if we're requesting an unsigned integer, as zero is already - // in our set, and we don't want duplicates. - var inputs []*big.Int - inputs = append(inputs, g.valueSet.Integers()...) - if signed { - inputs = append(inputs, min, max) - } else { - inputs = append(inputs, max) - } + // If the integer is zero, we can simply return it as-is. + if i.Sign() == 0 { + return i + } - // Set the input and ensure it is constrained to the value boundaries - input := new(big.Int).Set(i) - input = utils.ConstrainIntegerToBounds(input, min, max) + // For unsigned integers or positive signed integers, generate a new integer between [0, i) + if !signed || i.Sign() > 0 { + return big.NewInt(0).Rand(g.randomProvider, i) + } - // Shrink input - input = integerShrinkingMethods[g.randomProvider.Intn(len(integerShrinkingMethods))](g, input, inputs...) + // For negative numbers, generate between (i, 0] + // First get absolute value and generate random number between [0, abs(i)) + offset := big.NewInt(0).Rand(g.randomProvider, big.NewInt(0).Abs(i)) + offset.Add(offset, big.NewInt(1)) - // Correct value boundaries (underflow/overflow) - input = utils.ConstrainIntegerToBounds(input, min, max) - return input - } - return i + // Add it to the original value to reach the range (i, 0] + return i.Add(i, offset) } // stringShrinkingMethods define methods which take an initial string and a set of inputs to transform the input. The