blob: e26ae5a5836c2e47707019d3596dbf705a43374f [file] [log] [blame]
// Copyright 2020 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.
package fuzz
import (
"encoding/binary"
"fmt"
"math"
"reflect"
"unsafe"
)
type mutator struct {
r mutatorRand
scratch []byte // scratch slice to avoid additional allocations
}
func newMutator() *mutator {
return &mutator{r: newPcgRand()}
}
func (m *mutator) rand(n int) int {
return m.r.intn(n)
}
func (m *mutator) randByteOrder() binary.ByteOrder {
if m.r.bool() {
return binary.LittleEndian
}
return binary.BigEndian
}
// chooseLen chooses length of range mutation in range [1,n]. It gives
// preference to shorter ranges.
func (m *mutator) chooseLen(n int) int {
switch x := m.rand(100); {
case x < 90:
return m.rand(min(8, n)) + 1
case x < 99:
return m.rand(min(32, n)) + 1
default:
return m.rand(n) + 1
}
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
// mutate performs several mutations on the provided values.
func (m *mutator) mutate(vals []any, maxBytes int) {
// TODO(katiehockman): pull some of these functions into helper methods and
// test that each case is working as expected.
// TODO(katiehockman): perform more types of mutations for []byte.
// maxPerVal will represent the maximum number of bytes that each value be
// allowed after mutating, giving an equal amount of capacity to each line.
// Allow a little wiggle room for the encoding.
maxPerVal := maxBytes/len(vals) - 100
// Pick a random value to mutate.
// TODO: consider mutating more than one value at a time.
i := m.rand(len(vals))
switch v := vals[i].(type) {
case int:
vals[i] = int(m.mutateInt(int64(v), maxInt))
case int8:
vals[i] = int8(m.mutateInt(int64(v), math.MaxInt8))
case int16:
vals[i] = int16(m.mutateInt(int64(v), math.MaxInt16))
case int64:
vals[i] = m.mutateInt(v, maxInt)
case uint:
vals[i] = uint(m.mutateUInt(uint64(v), maxUint))
case uint16:
vals[i] = uint16(m.mutateUInt(uint64(v), math.MaxUint16))
case uint32:
vals[i] = uint32(m.mutateUInt(uint64(v), math.MaxUint32))
case uint64:
vals[i] = m.mutateUInt(uint64(v), maxUint)
case float32:
vals[i] = float32(m.mutateFloat(float64(v), math.MaxFloat32))
case float64:
vals[i] = m.mutateFloat(v, math.MaxFloat64)
case bool:
if m.rand(2) == 1 {
vals[i] = !v // 50% chance of flipping the bool
}
case rune: // int32
vals[i] = rune(m.mutateInt(int64(v), math.MaxInt32))
case byte: // uint8
vals[i] = byte(m.mutateUInt(uint64(v), math.MaxUint8))
case string:
if len(v) > maxPerVal {
panic(fmt.Sprintf("cannot mutate bytes of length %d", len(v)))
}
if cap(m.scratch) < maxPerVal {
m.scratch = append(make([]byte, 0, maxPerVal), v...)
} else {
m.scratch = m.scratch[:len(v)]
copy(m.scratch, v)
}
m.mutateBytes(&m.scratch)
vals[i] = string(m.scratch)
case []byte:
if len(v) > maxPerVal {
panic(fmt.Sprintf("cannot mutate bytes of length %d", len(v)))
}
if cap(m.scratch) < maxPerVal {
m.scratch = append(make([]byte, 0, maxPerVal), v...)
} else {
m.scratch = m.scratch[:len(v)]
copy(m.scratch, v)
}
m.mutateBytes(&m.scratch)
vals[i] = m.scratch
default:
panic(fmt.Sprintf("type not supported for mutating: %T", vals[i]))
}
}
func (m *mutator) mutateInt(v, maxValue int64) int64 {
var max int64
for {
max = 100
switch m.rand(2) {
case 0:
// Add a random number
if v >= maxValue {
continue
}
if v > 0 && maxValue-v < max {
// Don't let v exceed maxValue
max = maxValue - v
}
v += int64(1 + m.rand(int(max)))
return v
case 1:
// Subtract a random number
if v <= -maxValue {
continue
}
if v < 0 && maxValue+v < max {
// Don't let v drop below -maxValue
max = maxValue + v
}
v -= int64(1 + m.rand(int(max)))
return v
}
}
}
func (m *mutator) mutateUInt(v, maxValue uint64) uint64 {
var max uint64
for {
max = 100
switch m.rand(2) {
case 0:
// Add a random number
if v >= maxValue {
continue
}
if v > 0 && maxValue-v < max {
// Don't let v exceed maxValue
max = maxValue - v
}
v += uint64(1 + m.rand(int(max)))
return v
case 1:
// Subtract a random number
if v <= 0 {
continue
}
if v < max {
// Don't let v drop below 0
max = v
}
v -= uint64(1 + m.rand(int(max)))
return v
}
}
}
func (m *mutator) mutateFloat(v, maxValue float64) float64 {
var max float64
for {
switch m.rand(4) {
case 0:
// Add a random number
if v >= maxValue {
continue
}
max = 100
if v > 0 && maxValue-v < max {
// Don't let v exceed maxValue
max = maxValue - v
}
v += float64(1 + m.rand(int(max)))
return v
case 1:
// Subtract a random number
if v <= -maxValue {
continue
}
max = 100
if v < 0 && maxValue+v < max {
// Don't let v drop below -maxValue
max = maxValue + v
}
v -= float64(1 + m.rand(int(max)))
return v
case 2:
// Multiply by a random number
absV := math.Abs(v)
if v == 0 || absV >= maxValue {
continue
}
max = 10
if maxValue/absV < max {
// Don't let v go beyond the minimum or maximum value
max = maxValue / absV
}
v *= float64(1 + m.rand(int(max)))
return v
case 3:
// Divide by a random number
if v == 0 {
continue
}
v /= float64(1 + m.rand(10))
return v
}
}
}
type byteSliceMutator func(*mutator, []byte) []byte
var byteSliceMutators = []byteSliceMutator{
byteSliceRemoveBytes,
byteSliceInsertRandomBytes,
byteSliceDuplicateBytes,
byteSliceOverwriteBytes,
byteSliceBitFlip,
byteSliceXORByte,
byteSliceSwapByte,
byteSliceArithmeticUint8,
byteSliceArithmeticUint16,
byteSliceArithmeticUint32,
byteSliceArithmeticUint64,
byteSliceOverwriteInterestingUint8,
byteSliceOverwriteInterestingUint16,
byteSliceOverwriteInterestingUint32,
byteSliceInsertConstantBytes,
byteSliceOverwriteConstantBytes,
byteSliceShuffleBytes,
byteSliceSwapBytes,
}
func (m *mutator) mutateBytes(ptrB *[]byte) {
b := *ptrB
defer func() {
oldHdr := (*reflect.SliceHeader)(unsafe.Pointer(ptrB))
newHdr := (*reflect.SliceHeader)(unsafe.Pointer(&b))
if oldHdr.Data != newHdr.Data {
panic("data moved to new address")
}
*ptrB = b
}()
for {
mut := byteSliceMutators[m.rand(len(byteSliceMutators))]
if mutated := mut(m, b); mutated != nil {
b = mutated
return
}
}
}
var (
interesting8 = []int8{-128, -1, 0, 1, 16, 32, 64, 100, 127}
interesting16 = []int16{-32768, -129, 128, 255, 256, 512, 1000, 1024, 4096, 32767}
interesting32 = []int32{-2147483648, -100663046, -32769, 32768, 65535, 65536, 100663045, 2147483647}
)
const (
maxUint = uint64(^uint(0))
maxInt = int64(maxUint >> 1)
)
func init() {
for _, v := range interesting8 {
interesting16 = append(interesting16, int16(v))
}
for _, v := range interesting16 {
interesting32 = append(interesting32, int32(v))
}
}