From 7f9a85f2d9bffbdd784eb208c4c45a7e4720f262 Mon Sep 17 00:00:00 2001 From: Roland Shoemaker Date: Mon, 28 Feb 2022 05:42:11 -0800 Subject: [PATCH] [release-branch.go1.18] 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 Trust: Roland Shoemaker Run-TryBot: Roland Shoemaker Auto-Submit: Roland Shoemaker TryBot-Result: Gopher Robot (cherry picked from commit 2b8aa2b734721487bb718ee5fb6080f51b57efd9) Reviewed-on: https://go-review.googlesource.com/c/go/+/390095 Trust: Dmitri Shuralyov Run-TryBot: Dmitri Shuralyov --- src/internal/fuzz/encoding.go | 115 ++++++++++++++++++++++------- src/internal/fuzz/encoding_test.go | 14 ++++ 2 files changed, 104 insertions(+), 25 deletions(-) diff --git a/src/internal/fuzz/encoding.go b/src/internal/fuzz/encoding.go index 2bfa02b8c0..fe070eca34 100644 --- a/src/internal/fuzz/encoding.go +++ b/src/internal/fuzz/encoding.go @@ -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") } diff --git a/src/internal/fuzz/encoding_test.go b/src/internal/fuzz/encoding_test.go index b429d429c6..4b55892acd 100644 --- a/src/internal/fuzz/encoding_test.go +++ b/src/internal/fuzz/encoding_test.go @@ -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) { -- 2.48.1