internal/fuzz: handle Inf/NaN float values

Fixes #51258

Change-Id: I3c8b785ac912d66e1a6e2179625e6903032b8330
Reviewed-on: https://go-review.googlesource.com/c/go/+/388354
Reviewed-by: Bryan Mills <bcmills@google.com>
Trust: Roland Shoemaker <roland@golang.org>
Run-TryBot: Roland Shoemaker <roland@golang.org>
Auto-Submit: Roland Shoemaker <roland@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
This commit is contained in:
Roland Shoemaker 2022-02-28 05:42:11 -08:00 committed by Gopher Robot
parent 7d7b9bbc7a
commit 2b8aa2b734
2 changed files with 104 additions and 25 deletions

View File

@ -10,6 +10,7 @@ import (
"go/ast"
"go/parser"
"go/token"
"math"
"strconv"
)
@ -27,8 +28,20 @@ func marshalCorpusFile(vals ...any) []byte {
// instead of changing to byte and rune respectively.
for _, val := range vals {
switch t := val.(type) {
case int, int8, int16, int64, uint, uint16, uint32, uint64, float32, float64, bool:
case int, int8, int16, int64, uint, uint16, uint32, uint64, bool:
fmt.Fprintf(b, "%T(%v)\n", t, t)
case float32:
if math.IsNaN(float64(t)) && math.Float32bits(t) != math.Float32bits(float32(math.NaN())) {
fmt.Fprintf(b, "math.Float32frombits(%v)\n", math.Float32bits(t))
} else {
fmt.Fprintf(b, "%T(%v)\n", t, t)
}
case float64:
if math.IsNaN(t) && math.Float64bits(t) != math.Float64bits(math.NaN()) {
fmt.Fprintf(b, "math.Float64frombits(%v)\n", math.Float64bits(t))
} else {
fmt.Fprintf(b, "%T(%v)\n", t, t)
}
case string:
fmt.Fprintf(b, "string(%q)\n", t)
case rune: // int32
@ -105,44 +118,78 @@ func parseCorpusValue(line []byte) (any, error) {
return []byte(s), nil
}
idType, ok := call.Fun.(*ast.Ident)
if !ok {
return nil, fmt.Errorf("expected []byte or primitive type")
}
if idType.Name == "bool" {
id, ok := arg.(*ast.Ident)
var idType *ast.Ident
if selector, ok := call.Fun.(*ast.SelectorExpr); ok {
xIdent, ok := selector.X.(*ast.Ident)
if !ok || xIdent.Name != "math" {
return nil, fmt.Errorf("invalid selector type")
}
switch selector.Sel.Name {
case "Float64frombits":
idType = &ast.Ident{Name: "float64-bits"}
case "Float32frombits":
idType = &ast.Ident{Name: "float32-bits"}
default:
return nil, fmt.Errorf("invalid selector type")
}
} else {
idType, ok = call.Fun.(*ast.Ident)
if !ok {
return nil, fmt.Errorf("malformed bool")
return nil, fmt.Errorf("expected []byte or primitive type")
}
if id.Name == "true" {
return true, nil
} else if id.Name == "false" {
return false, nil
} else {
return nil, fmt.Errorf("true or false required for type bool")
if idType.Name == "bool" {
id, ok := arg.(*ast.Ident)
if !ok {
return nil, fmt.Errorf("malformed bool")
}
if id.Name == "true" {
return true, nil
} else if id.Name == "false" {
return false, nil
} else {
return nil, fmt.Errorf("true or false required for type bool")
}
}
}
var (
val string
kind token.Token
)
if op, ok := arg.(*ast.UnaryExpr); ok {
// Special case for negative numbers.
lit, ok := op.X.(*ast.BasicLit)
if !ok || (lit.Kind != token.INT && lit.Kind != token.FLOAT) {
switch lit := op.X.(type) {
case *ast.BasicLit:
if op.Op != token.SUB {
return nil, fmt.Errorf("unsupported operation on int/float: %v", op.Op)
}
// Special case for negative numbers.
val = op.Op.String() + lit.Value // e.g. "-" + "124"
kind = lit.Kind
case *ast.Ident:
if lit.Name != "Inf" {
return nil, fmt.Errorf("expected operation on int or float type")
}
if op.Op == token.SUB {
val = "-Inf"
} else {
val = "+Inf"
}
kind = token.FLOAT
default:
return nil, fmt.Errorf("expected operation on int or float type")
}
if op.Op != token.SUB {
return nil, fmt.Errorf("unsupported operation on int: %v", op.Op)
}
val = op.Op.String() + lit.Value // e.g. "-" + "124"
kind = lit.Kind
} else {
lit, ok := arg.(*ast.BasicLit)
if !ok {
switch lit := arg.(type) {
case *ast.BasicLit:
val, kind = lit.Value, lit.Kind
case *ast.Ident:
if lit.Name != "NaN" {
return nil, fmt.Errorf("literal value required for primitive type")
}
val, kind = "NaN", token.FLOAT
default:
return nil, fmt.Errorf("literal value required for primitive type")
}
val, kind = lit.Value, lit.Kind
}
switch typ := idType.Name; typ {
@ -191,6 +238,24 @@ func parseCorpusValue(line []byte) (any, error) {
return nil, fmt.Errorf("float or integer literal required for float64 type")
}
return strconv.ParseFloat(val, 64)
case "float32-bits":
if kind != token.INT {
return nil, fmt.Errorf("integer literal required for math.Float32frombits type")
}
bits, err := parseUint(val, "uint32")
if err != nil {
return nil, err
}
return math.Float32frombits(bits.(uint32)), nil
case "float64-bits":
if kind != token.FLOAT && kind != token.INT {
return nil, fmt.Errorf("integer literal required for math.Float64frombits type")
}
bits, err := parseUint(val, "uint64")
if err != nil {
return nil, err
}
return math.Float64frombits(bits.(uint64)), nil
default:
return nil, fmt.Errorf("expected []byte or primitive type")
}

View File

@ -103,6 +103,20 @@ float64(-12.5)
float32(2.5)`,
ok: true,
},
{
in: `go test fuzz v1
float32(-0)
float64(-0)
float32(+Inf)
float32(-Inf)
float32(NaN)
float64(+Inf)
float64(-Inf)
float64(NaN)
math.Float64frombits(9221120237041090560)
math.Float32frombits(2143289343)`,
ok: true,
},
}
for _, test := range tests {
t.Run(test.in, func(t *testing.T) {