// 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.package fuzzimport ()// encVersion1 will be the first line of a file with version 1 encoding.var encVersion1 = "go test fuzz v1"// marshalCorpusFile encodes an arbitrary number of arguments into the file format for the// corpus.func marshalCorpusFile( ...any) []byte {iflen() == 0 {panic("must have at least one value to marshal") } := bytes.NewBuffer([]byte(encVersion1 + "\n"))// TODO(katiehockman): keep uint8 and int32 encoding where applicable, // instead of changing to byte and rune respectively.for , := range {switch t := .(type) {caseint, int8, int16, int64, uint, uint16, uint32, uint64, bool:fmt.Fprintf(, "%T(%v)\n", , )casefloat32:ifmath.IsNaN(float64()) && math.Float32bits() != math.Float32bits(float32(math.NaN())) {// We encode unusual NaNs as hex values, because that is how users are // likely to encounter them in literature about floating-point encoding. // This allows us to reproduce fuzz failures that depend on the specific // NaN representation (for float32 there are about 2^24 possibilities!), // not just the fact that the value is *a* NaN. // // Note that the specific value of float32(math.NaN()) can vary based on // whether the architecture represents signaling NaNs using a low bit // (as is common) or a high bit (as commonly implemented on MIPS // hardware before around 2012). We believe that the increase in clarity // from identifying "NaN" with math.NaN() is worth the slight ambiguity // from a platform-dependent value.fmt.Fprintf(, "math.Float32frombits(0x%x)\n", math.Float32bits()) } else {// We encode all other values — including the NaN value that is // bitwise-identical to float32(math.Nan()) — using the default // formatting, which is equivalent to strconv.FormatFloat with format // 'g' and can be parsed by strconv.ParseFloat. // // For an ordinary floating-point number this format includes // sufficiently many digits to reconstruct the exact value. For positive // or negative infinity it is the string "+Inf" or "-Inf". For positive // or negative zero it is "0" or "-0". For NaN, it is the string "NaN".fmt.Fprintf(, "%T(%v)\n", , ) }casefloat64:ifmath.IsNaN() && math.Float64bits() != math.Float64bits(math.NaN()) {fmt.Fprintf(, "math.Float64frombits(0x%x)\n", math.Float64bits()) } else {fmt.Fprintf(, "%T(%v)\n", , ) }casestring:fmt.Fprintf(, "string(%q)\n", )caserune: // int32// Although rune and int32 are represented by the same type, only a subset // of valid int32 values can be expressed as rune literals. Notably, // negative numbers, surrogate halves, and values above unicode.MaxRune // have no quoted representation. // // fmt with "%q" (and the corresponding functions in the strconv package) // would quote out-of-range values to the Unicode replacement character // instead of the original value (see https://go.dev/issue/51526), so // they must be treated as int32 instead. // // We arbitrarily draw the line at UTF-8 validity, which biases toward the // "rune" interpretation. (However, we accept either format as input.)ifutf8.ValidRune() {fmt.Fprintf(, "rune(%q)\n", ) } else {fmt.Fprintf(, "int32(%v)\n", ) }casebyte: // uint8// For bytes, we arbitrarily prefer the character interpretation. // (Every byte has a valid character encoding.)fmt.Fprintf(, "byte(%q)\n", )case []byte: // []uint8fmt.Fprintf(, "[]byte(%q)\n", )default:panic(fmt.Sprintf("unsupported type: %T", )) } }return .Bytes()}// unmarshalCorpusFile decodes corpus bytes into their respective values.func unmarshalCorpusFile( []byte) ([]any, error) {iflen() == 0 {returnnil, fmt.Errorf("cannot unmarshal empty string") } := bytes.Split(, []byte("\n"))iflen() < 2 {returnnil, fmt.Errorf("must include version and at least one value") } := strings.TrimSuffix(string([0]), "\r")if != encVersion1 {returnnil, fmt.Errorf("unknown encoding version: %s", ) }var []anyfor , := range [1:] { = bytes.TrimSpace()iflen() == 0 {continue } , := parseCorpusValue()if != nil {returnnil, fmt.Errorf("malformed line %q: %v", , ) } = append(, ) }return , nil}func parseCorpusValue( []byte) (any, error) { := token.NewFileSet() , := parser.ParseExprFrom(, "(test)", , 0)if != nil {returnnil, } , := .(*ast.CallExpr)if ! {returnnil, fmt.Errorf("expected call expression") }iflen(.Args) != 1 {returnnil, fmt.Errorf("expected call expression with 1 argument; got %d", len(.Args)) } := .Args[0]if , := .Fun.(*ast.ArrayType); {if .Len != nil {returnnil, fmt.Errorf("expected []byte or primitive type") } , := .Elt.(*ast.Ident)if ! || .Name != "byte" {returnnil, fmt.Errorf("expected []byte") } , := .(*ast.BasicLit)if ! || .Kind != token.STRING {returnnil, fmt.Errorf("string literal required for type []byte") } , := strconv.Unquote(.Value)if != nil {returnnil, }return []byte(), nil }var *ast.Identif , := .Fun.(*ast.SelectorExpr); { , := .X.(*ast.Ident)if ! || .Name != "math" {returnnil, fmt.Errorf("invalid selector type") }switch .Sel.Name {case"Float64frombits": = &ast.Ident{Name: "float64-bits"}case"Float32frombits": = &ast.Ident{Name: "float32-bits"}default:returnnil, fmt.Errorf("invalid selector type") } } else { , = .Fun.(*ast.Ident)if ! {returnnil, fmt.Errorf("expected []byte or primitive type") }if .Name == "bool" { , := .(*ast.Ident)if ! {returnnil, fmt.Errorf("malformed bool") }if .Name == "true" {returntrue, nil } elseif .Name == "false" {returnfalse, nil } else {returnnil, fmt.Errorf("true or false required for type bool") } } }var (stringtoken.Token )if , := .(*ast.UnaryExpr); {switch lit := .X.(type) {case *ast.BasicLit:if .Op != token.SUB {returnnil, fmt.Errorf("unsupported operation on int/float: %v", .Op) }// Special case for negative numbers. = .Op.String() + .Value// e.g. "-" + "124" = .Kindcase *ast.Ident:if .Name != "Inf" {returnnil, fmt.Errorf("expected operation on int or float type") }if .Op == token.SUB { = "-Inf" } else { = "+Inf" } = token.FLOATdefault:returnnil, fmt.Errorf("expected operation on int or float type") } } else {switch lit := .(type) {case *ast.BasicLit: , = .Value, .Kindcase *ast.Ident:if .Name != "NaN" {returnnil, fmt.Errorf("literal value required for primitive type") } , = "NaN", token.FLOATdefault:returnnil, fmt.Errorf("literal value required for primitive type") } }switch := .Name; {case"string":if != token.STRING {returnnil, fmt.Errorf("string literal value required for type string") }returnstrconv.Unquote()case"byte", "rune":if == token.INT {switch {case"rune":returnparseInt(, )case"byte":returnparseUint(, ) } }if != token.CHAR {returnnil, fmt.Errorf("character literal required for byte/rune types") } := len()if < 2 {returnnil, fmt.Errorf("malformed character literal, missing single quotes") } , , , := strconv.UnquoteChar([1:-1], '\'')if != nil {returnnil, }if == "rune" {return , nil }if >= 256 {returnnil, fmt.Errorf("can only encode single byte to a byte type") }returnbyte(), nilcase"int", "int8", "int16", "int32", "int64":if != token.INT {returnnil, fmt.Errorf("integer literal required for int types") }returnparseInt(, )case"uint", "uint8", "uint16", "uint32", "uint64":if != token.INT {returnnil, fmt.Errorf("integer literal required for uint types") }returnparseUint(, )case"float32":if != token.FLOAT && != token.INT {returnnil, fmt.Errorf("float or integer literal required for float32 type") } , := strconv.ParseFloat(, 32)returnfloat32(), case"float64":if != token.FLOAT && != token.INT {returnnil, fmt.Errorf("float or integer literal required for float64 type") }returnstrconv.ParseFloat(, 64)case"float32-bits":if != token.INT {returnnil, fmt.Errorf("integer literal required for math.Float32frombits type") } , := parseUint(, "uint32")if != nil {returnnil, }returnmath.Float32frombits(.(uint32)), nilcase"float64-bits":if != token.FLOAT && != token.INT {returnnil, fmt.Errorf("integer literal required for math.Float64frombits type") } , := parseUint(, "uint64")if != nil {returnnil, }returnmath.Float64frombits(.(uint64)), nildefault:returnnil, fmt.Errorf("expected []byte or primitive type") }}// parseInt returns an integer of value val and type typ.func parseInt(, string) (any, error) {switch {case"int":// The int type may be either 32 or 64 bits. If 32, the fuzz tests in the // corpus may include 64-bit values produced by fuzzing runs on 64-bit // architectures. When running those tests, we implicitly wrap the values to // fit in a regular int. (The test case is still “interesting”, even if the // specific values of its inputs are platform-dependent.) , := strconv.ParseInt(, 0, 64)returnint(), case"int8": , := strconv.ParseInt(, 0, 8)returnint8(), case"int16": , := strconv.ParseInt(, 0, 16)returnint16(), case"int32", "rune": , := strconv.ParseInt(, 0, 32)returnint32(), case"int64":returnstrconv.ParseInt(, 0, 64)default:panic("unreachable") }}// parseUint returns an unsigned integer of value val and type typ.func parseUint(, string) (any, error) {switch {case"uint": , := strconv.ParseUint(, 0, 64)returnuint(), case"uint8", "byte": , := strconv.ParseUint(, 0, 8)returnuint8(), case"uint16": , := strconv.ParseUint(, 0, 16)returnuint16(), case"uint32": , := strconv.ParseUint(, 0, 32)returnuint32(), case"uint64":returnstrconv.ParseUint(, 0, 64)default:panic("unreachable") }}
The pages are generated with Goldsv0.7.0-preview. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.