前言

Go 1.18在go工具链里引入了fuzzing含糊测试,能够帮忙咱们发现Go代码里的破绽或者可能导致程序解体的输出。Go官网团队也在官网公布了fuzzing入门教程,帮忙大家疾速上手。


自己对Go官网教程在翻译的根底上做了一些表述上的优化,以飨读者。

留神:fuzzing含糊测试和Go已有的单元测试以及性能测试框架是互为补充的,并不是代替关系。

教程内容

这篇教程会介绍Go fuzzing的入门基础知识。fuzzing能够结构随机数据来找出代码里的破绽或者可能导致程序解体的输出。通过fuzzing能够找出的破绽包含SQL注入、缓冲区溢出、拒绝服务(Denial of Service)攻打和XSS(cross-site scripting)攻打等。

在这个教程里,你会给一个函数写一段fuzz test(含糊测试)程序,而后运行go命令来发现代码里的问题,最初通过调试来修复问题。

本文里波及的专业术语,能够参考 Go Fuzzing glossary。

接下来会依照如下章节介绍:

  1. 为你的代码创立一个目录
  2. 实现一个函数
  3. 减少单元测试
  4. 减少含糊测试
  5. 修复2个bug
  6. 总结

筹备工作

  • 装置Go 1.18 Beta 1或者更新的版本。装置指引能够参考上面的介绍。
  • 有一个代码编辑工具。任何文本编辑器都能够。
  • 有一个命令行终端。Go能够运行在Linux,Mac上的任何命令行终端,也能够运行在Windows的PowerShell或者cmd之上。
  • 有一个反对fuzzing的环境。目前Go fuzzing只反对AMD64和ARM64架构。

装置和应用beta版本

这个教程须要应用Go 1.18 Beta 1或以上版本的泛型性能。应用如下步骤,装置beta版本

  1. 应用上面的命令装置beta版本

    $ go install golang.org/dl/go1.18beta1@latest
  2. 运行如下命令来下载更新

    $ go1.18beta1 download

    留神:如果在MAC或者Linux上执行go1.18beta1提醒command not found,须要设置bash或者zsh对应的profile环境变量文件。bash设置在~/.bash_profile文件里,内容为:

    export GOROOT=/usr/local/opt/go/libexecexport GOPATH=$HOME/goexport PATH=$PATH:$GOROOT/bin:$GOPATH/bin

    GOROOTGOPATH的值能够通过go env命令查看,设置完后执行source ~/.bash_profile让设置失效,再执行go1.18beta1就不报错了。

  3. 应用beta版本的go命令,不要去应用release版本的go命令

    你能够通过间接应用go1.18beta1命令或者给go1.18beta1起一个简略的别名

    • 间接应用go1.18beta1命令

      $ go1.18beta1 version
    • go1.18beta1命令起一个别名

      $ alias go=go1.18beta1$ go version

    上面的教程都假如你曾经把go1.18beta1命令设置了别名go

为你的代码创立一个目录

首先创立一个目录用于寄存你写的代码。

  1. 关上一个命令行终端,切换到你的home目录

    • 在Linux或者Mac上执行如下命令(Linux或者Mac上只须要执行cd就能够进入到home目录)

      cd
    • 在Windows上执行如下命令

      C:\> cd %HOMEPATH%
  2. 在命令行终端,创立一个名为fuzz的目录,并进入该目录

    $ mkdir fuzz$ cd fuzz
  3. 创立一个go module

    运行go mod init命令,来给你的我的项目设置module门路

    $ go mod init example/fuzz

    留神:对于生产代码,你能够依据我的项目理论状况来指定module门路,如果想理解更多,能够参考Go Module依赖治理。

接下来,咱们来应用map写一些简略的代码来做字符串的反转,而后应用fuzzing来做含糊测试。

实现一个函数

在这个章节,你须要实现一个函数来对字符串做反转。

编写代码

  1. 关上你的文本编辑器,在fuzz目录下创立一个main.go源文件。
  2. main.go里编写如下代码:

    // maing.gopackage mainimport "fmt"func Reverse(s string) string {    b := []byte(s)    for i, j := 0, len(b)-1; i < len(b)/2; i, j = i+1, j-1 {        b[i], b[j] = b[j], b[i]    }    return string(b)}func main() {    input := "The quick brown fox jumped over the lazy dog"    rev := Reverse(input)    doubleRev := Reverse(rev)    fmt.Printf("original: %q\n", input)    fmt.Printf("reversed: %q\n", rev)    fmt.Printf("reversed again: %q\n", doubleRev)}

运行代码

main.go所在目录执行命令go run .来运行代码,后果如下:

$ go run .original: "The quick brown fox jumped over the lazy dog"reversed: "god yzal eht revo depmuj xof nworb kciuq ehT"reversed again: "The quick brown fox jumped over the lazy dog"

减少单元测试

在这个章节,你会给Reverse函数编写单元测试代码。

编写单元测试

  1. 在fuzz目录下创立文件reverse_test.go
  2. reverse_test.go里编写如下代码:

    package mainimport (    "testing")func TestReverse(t *testing.T) {    testcases := []struct {        in, want string    }{        {"Hello, world", "dlrow ,olleH"},        {" ", " "},        {"!12345", "54321!"},    }    for _, tc := range testcases {        rev := Reverse(tc.in)        if rev != tc.want {                t.Errorf("Reverse: %q, want %q", rev, tc.want)        }    }}

运行单元测试

应用go test命令来运行单元测试

$ go testPASSok      example/fuzz  0.013s

接下来,咱们给Reverse函数减少含糊测试(fuzz test)代码。

减少含糊测试

单元测试有局限性,每个测试输出必须由开发者指定加到单元测试的测试用例里。

fuzzing的长处之一是能够基于开发者代码里指定的测试输出作为根底数据,进一步主动生成新的随机测试数据,用来发现指定测试输出没有笼罩到的边界状况。

在这个章节,咱们会把单元测试转换成含糊测试,这样能够更轻松地生成更多的测试输出。

留神:你能够把单元测试、性能测试和含糊测试放在同一个*_test.go文件里。

编写含糊测试

在文本编辑器里把reverse_test.go里的单元测试代码TestReverse替换成如下的含糊测试代码FuzzReverse

func FuzzReverse(f *testing.F) {    testcases := []string{"Hello, world", " ", "!12345"}    for _, tc := range testcases {        f.Add(tc)  // Use f.Add to provide a seed corpus    }    f.Fuzz(func(t *testing.T, orig string) {        rev := Reverse(orig)        doubleRev := Reverse(rev)        if orig != doubleRev {            t.Errorf("Before: %q, after: %q", orig, doubleRev)        }        if utf8.ValidString(orig) && !utf8.ValidString(rev) {            t.Errorf("Reverse produced invalid UTF-8 string %q", rev)        }    })}

Fuzzing也有肯定的局限性。

在单元测试里,因为测试输出是固定的,你能够晓得调用Reverse函数后每个输出字符串失去的反转字符串应该是什么,而后在单元测试的代码里判断Reverse的执行后果是否和预期相符。例如,对于测试用例Reverse("Hello, world"),单元测试预期的后果是 "dlrow ,olleH"

然而应用fuzzing时,咱们没方法预期输入后果是什么,因为测试的输出除了咱们代码里指定的用例之外,还有fuzzing随机生成的。对于随机生成的测试输出,咱们当然没方法提前晓得输入后果是什么。

尽管如此,本文里的Reverse函数有几个个性咱们还是能够在含糊测试里做验证。

  1. 对一个字符串做2次反转,失去的后果和源字符串雷同
  2. 反转后的字符串也依然是一个无效的UTF-8编码的字符串

留神:fuzzing含糊测试和Go已有的单元测试以及性能测试框架是互为补充的,并不是代替关系。

比方咱们实现的Reverse函数如果是一个谬误的版本,间接return返回输出的字符串,是齐全能够通过下面的含糊测试的,然而没法通过咱们后面编写的单元测试。因而单元测试和含糊测试是互为补充的,不是代替关系。

Go含糊测试和单元测试在语法上有如下差别:

  • Go含糊测试函数以FuzzXxx结尾,单元测试函数以TestXxx结尾
  • Go含糊测试函数以 *testing.F作为入参,单元测试函数以*testing.T作为入参
  • Go含糊测试会调用f.Add函数和f.Fuzz函数。

    • f.Add函数把指定输出作为含糊测试的种子语料库(seed corpus),fuzzing基于种子语料库生成随机输出。
    • f.Fuzz函数接管一个fuzz target函数作为入参。fuzz target函数有多个参数,第一个参数是*testing.T,其它参数是被含糊的类型(留神:被含糊的类型目前只反对局部内置类型, 列在 Go Fuzzing docs,将来会反对更多的内置类型)。

下面的FuzzReverse函数里用到了utf8这个package,因而要在reverse_test.go结尾import这个package,参考如下代码:

package mainimport (    "testing"    "unicode/utf8")

运行含糊测试

  1. 执行如下命令来运行含糊测试。

    这个形式只会应用种子语料库,而不会生成随机测试数据。通过这种形式能够用来验证种子语料库的测试数据是否能够测试通过。(fuzz test without fuzzing)

    $ go testPASSok      example/fuzz  0.013s

    如果reverse_test.go文件里有其它单元测试函数或者含糊测试函数,然而只想运行FuzzReverse含糊测试函数,咱们能够执行go test -run=FuzzReverse命令。

    留神go test默认会执行所有以TestXxx结尾的单元测试函数和以FuzzXxx结尾的含糊测试函数,默认不运行以BenchmarkXxx结尾的性能测试函数,如果咱们想运行 benchmark用例,则须要加上 -bench 参数。

  2. 如果要基于种子语料库生成随机测试数据用于含糊测试,须要给go test命令减少 -fuzz参数。(fuzz test with fuzzing)

    $ go test -fuzz=Fuzzfuzz: elapsed: 0s, gathering baseline coverage: 0/3 completedfuzz: elapsed: 0s, gathering baseline coverage: 3/3 completed, now fuzzing with 8 workersfuzz: minimizing 38-byte failing input file...--- FAIL: FuzzReverse (0.01s)    --- FAIL: FuzzReverse (0.00s)        reverse_test.go:20: Reverse produced invalid UTF-8 string "\x9c\xdd"    Failing input written to testdata/fuzz/FuzzReverse/af69258a12129d6cbba438df5d5f25ba0ec050461c116f777e77ea7c9a0d217a    To re-run:    go test -run=FuzzReverse/af69258a12129d6cbba438df5d5f25ba0ec050461c116f777e77ea7c9a0d217aFAILexit status 1FAIL    example/fuzz  0.030s

    下面的fuzzing测试后果是FAIL,引起FAIL的输出数据被写到了一个语料库文件里。下次运行go test命令的时候,即便没有-fuzz参数,这个语料库文件里的测试数据也会被用到。

    能够用文本编辑器关上testdata/fuzz/FuzzReverse目录下的文件,看看引起Fuzzing测试失败的测试数据长什么样。上面是一个示例文件,你那边运行后失去的测试数据可能和这个不一样,但文件里的内容格局会是一样的。

    go test fuzz v1string("泃")

    语料库文件里的第1行标识的是编码版本(encoding version,说直白点,就是这个种子语料库文件里内容格局的版本),尽管目前只有v1这1个版本,然而Fuzzing设计者思考到将来可能引入新的编码版本,于是加了编码版本的概念。

    从第2行开始,每一行数据对应的是语料库的每条测试数据(corpus entry)的其中一个参数,依照参数先后顺序排列。

    f.Fuzz(func(t *testing.T, orig string) {        rev := Reverse(orig)        doubleRev := Reverse(rev)        if orig != doubleRev {            t.Errorf("Before: %q, after: %q", orig, doubleRev)        }        if utf8.ValidString(orig) && !utf8.ValidString(rev) {            t.Errorf("Reverse produced invalid UTF-8 string %q %q", orig, rev)        }})

    本文的FuzzReverse里的fuzz target函数func(t *testing.T, orig string)只有orig这1个参数作为真正的测试输出,也就是每条测试数据其实就1个输出,因而在下面示例的testdata/fuzz/FuzzReverse目录下的文件里只有string("泃")这一行。

    如果每条测试数据有N个参数,那fuzzing找出的导致fuzz test失败的每条测试数据在testdata目录下的文件里会有N行,第i行对应第i个参数。

  3. 再次运行go test命令,这次不带-fuzz参数。

    咱们会发现尽管没有-fuzz参数,然而含糊测试的时候依然用到了下面第2步找到的测试数据。

    $ go test--- FAIL: FuzzReverse (0.00s)    --- FAIL: FuzzReverse/af69258a12129d6cbba438df5d5f25ba0ec050461c116f777e77ea7c9a0d217a (0.00s)        reverse_test.go:20: Reverse produced invalid stringFAILexit status 1FAIL    example/fuzz  0.016s

    既然Go fuzzing测试没通过,那就须要咱们调试代码来找出问题所在了。

修复2个bug

在这个章节,咱们会调试程序,修复Go fuzzing测进去的bug。

你能够本人花一些工夫思考下,先尝试本人解决问题。

定位问题

你能够应用不同的办法来调试下面发现的bug。

如果你应用的是VS Code,那能够在VS Code里设置你的Debug调试器来加断点进行调试。

本文里,咱们会应用打印日志的形式进行调试。

运行含糊测试时的报错信息为:reverse_test.go:20: Reverse produced invalid UTF-8 string "\x9c\xdd"

基于这个报错,咱们来看看文档里对于 utf8.ValidString的形容。

ValidString reports whether s consists entirely of valid UTF-8-encoded runes.

咱们实现的Reverse函数是依照字节(byte)为维度进行字符串反转,这就是问题所在。

比方中文里的字符 其实是由3个字节组成的,如果依照字节反转,反转后失去的就是一个有效的字符串了。

因而为了保障字符串反转后失去的依然是一个无效的UTF-8编码的字符串,咱们要依照rune进行字符串反转。

为了更好中央便大家了解中文里的字符 依照rune为维度有多少个rune,以及依照byte反转后失去的后果长什么样,咱们对代码做一些批改。

编写代码

依照如下形式批改FuzzReverse里的代码。

f.Fuzz(func(t *testing.T, orig string) {    rev := Reverse(orig)    doubleRev := Reverse(rev)    t.Logf("Number of runes: orig=%d, rev=%d, doubleRev=%d", utf8.RuneCountInString(orig), utf8.RuneCountInString(rev), utf8.RuneCountInString(doubleRev))    if orig != doubleRev {        t.Errorf("Before: %q, after: %q", orig, doubleRev)    }    if utf8.ValidString(orig) && !utf8.ValidString(rev) {        t.Errorf("Reverse produced invalid UTF-8 string %q", rev)    }})

运行代码

$ go test--- FAIL: FuzzReverse (0.00s)    --- FAIL: FuzzReverse/28f36ef487f23e6c7a81ebdaa9feffe2f2b02b4cddaa6252e87f69863046a5e0 (0.00s)        reverse_test.go:16: Number of runes: orig=1, rev=3, doubleRev=1        reverse_test.go:21: Reverse produced invalid UTF-8 string "\x83\xb3\xe6"FAILexit status 1FAIL    example/fuzz    0.598s

咱们的种子语料库里每个符号都是单个字节。然而像 这样的中文符号由多个字节组成,如果以字节为维度进行反转,就会失去有效的后果。

留神:如果你对于Go如何解决字符串感兴趣,能够浏览官网博客里的这篇文章 Strings, bytes, runes and characters in Go 来加深了解。

既然咱们明确了问题,那咱们就能够修复这个bug了。

修复问题

rune为维度进行字符串反转。

编写代码

批改Reverse函数的实现如下:

func Reverse(s string) string {    r := []rune(s)    for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {        r[i], r[j] = r[j], r[i]    }    return string(r)}

运行代码

  1. 运行命令:go test

    $ go testPASSok      example/fuzz  0.016s

    测试通过啦!(别快乐太早,这个只是通过了种子语料库和之前)

  2. 再次运行 go test -fuzz,看看咱们是否会发现新的bug

    $ go test -fuzz=Fuzzfuzz: elapsed: 0s, gathering baseline coverage: 0/37 completedfuzz: minimizing 506-byte failing input file...fuzz: elapsed: 0s, gathering baseline coverage: 5/37 completed--- FAIL: FuzzReverse (0.02s)    --- FAIL: FuzzReverse (0.00s)        reverse_test.go:33: Before: "\x91", after: "�"    Failing input written to testdata/fuzz/FuzzReverse/1ffc28f7538e29d79fce69fef20ce5ea72648529a9ca10bea392bcff28cd015c    To re-run:    go test -run=FuzzReverse/1ffc28f7538e29d79fce69fef20ce5ea72648529a9ca10bea392bcff28cd015cFAILexit status 1FAIL    example/fuzz  0.032s

    通过下面的报错,咱们发现对一个字符串做了2次反转后失去的和原字符串不一样。

    这次测试输出自身是非法的unicode,然而为什么会2次反转后失去的字符串还不一样呢?

    咱们持续调试。

修复2次字符串反转的bug

定位问题

对于这个问题,加断点调试会很好定位。为了不便解说,本文应用加日志的形式进行调试。

咱们能够仔细观察原字符串第一次反转后失去的后果来定位问题。

编写代码

  1. 批改 Reverse 函数。

    func Reverse(s string) string {    fmt.Printf("input: %q\n", s)    r := []rune(s)    fmt.Printf("runes: %q\n", r)    for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {        r[i], r[j] = r[j], r[i]    }    return string(r)}

    这能够帮忙咱们了解把原字符串转成rune切片后产生了什么。

运行代码

这一次,咱们只运行让fuzz test失败的测试数据,应用 go test -run命令。

运行FuzzXxx/testdata目录下指定的语料库测试数据,能够给-run参数指定值 {FuzzTestName}/{filename} ,这能够让咱们聚焦在让fuzz test失败的测试数据上。

$ go test -run=FuzzReverse/28f36ef487f23e6c7a81ebdaa9feffe2f2b02b4cddaa6252e87f69863046a5e0input: "\x91"runes: ['�']input: "�"runes: ['�']--- FAIL: FuzzReverse (0.00s)    --- FAIL: FuzzReverse/28f36ef487f23e6c7a81ebdaa9feffe2f2b02b4cddaa6252e87f69863046a5e0 (0.00s)        reverse_test.go:16: Number of runes: orig=1, rev=1, doubleRev=1        reverse_test.go:18: Before: "\x91", after: "�"FAILexit status 1FAIL    example/fuzz    0.145s

首先咱们要理解:在Go语言里,字符串是只读的字节切片(In Go, a string is a read only slice of bytes),字节切片里的每个字节不肯定都是无效的UTF-8编码的字节,详情能够参考a string is a read only slice of bytes。

下面的例子里,输出的字符串是只有1个byte的字节切片,这1个byte是\x91

当咱们把这个输出的字符串转成[]rune时,Go会把字节切片编码为UTF-8,于是就把\x91替换成了'�','�'饭庄后还是'�',一次就导致原字符串\x91反转后失去的字符串是'�'了。

当初问题明确了,是因为输出的数据是非法的unicode。那接下来咱们就能够修改Reverse函数的实现了。

修复问题

修复形式为:在Reverse里查看输出是否为非法的UTF-8编码字符串,如果非法,就返回eror。

编写代码

  1. 批改Reverse 实现如下:

    func Reverse(s string) (string, error) {    if !utf8.ValidString(s) {        return s, errors.New("input is not valid UTF-8")    }    r := []rune(s)    for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {        r[i], r[j] = r[j], r[i]    }    return string(r), nil}
  2. 因为Reverse函数当初会返回error,因而要批改main.go里的对应代码,批改如下:

    func main() {    input := "The quick brown fox jumped over the lazy dog"    rev, revErr := Reverse(input)    doubleRev, doubleRevErr := Reverse(rev)    fmt.Printf("original: %q\n", input)    fmt.Printf("reversed: %q, err: %v\n", rev, revErr)    fmt.Printf("reversed again: %q, err: %v\n", doubleRev, doubleRevErr)}

    因为main函数里都是无效的UTF-8编码字符串,所以对Reverse的调用会返回一个值为nil的error。

  3. 因为Reverse函数用到了errorsutf8这2个package,因而在main.go的结尾要import这2个package。

    import (    "errors"    "fmt"    "unicode/utf8")
  4. 同样,咱们须要批改reverse_test.go文件,对于非法的字符串输出,能够间接跳过测试。

    func FuzzReverse(f *testing.F) {    testcases := []string {"Hello, world", " ", "!12345"}    for _, tc := range testcases {        f.Add(tc)  // Use f.Add to provide a seed corpus    }    f.Fuzz(func(t *testing.T, orig string) {        rev, err1 := Reverse(orig)        if err1 != nil {            return        }        doubleRev, err2 := Reverse(rev)        if err2 != nil {             return        }        if orig != doubleRev {            t.Errorf("Before: %q, after: %q", orig, doubleRev)        }        if utf8.ValidString(orig) && !utf8.ValidString(rev) {            t.Errorf("Reverse produced invalid UTF-8 string %q", rev)        }    })}

    除了应用return,你还能够调用 t.Skip() 来跳过以后的测试输出,持续下一轮测试输出。

运行代码

  1. 运行测试代码

    $ go testPASSok      example/fuzz  0.019s
  2. 运行含糊测试 go test -fuzz=Fuzz,执行几秒后,应用 ctrl-C 完结测试。

    $ go test -fuzz=Fuzzfuzz: elapsed: 0s, gathering baseline coverage: 0/38 completedfuzz: elapsed: 0s, gathering baseline coverage: 38/38 completed, now fuzzing with 4 workersfuzz: elapsed: 3s, execs: 86342 (28778/sec), new interesting: 2 (total: 35)fuzz: elapsed: 6s, execs: 193490 (35714/sec), new interesting: 4 (total: 37)fuzz: elapsed: 9s, execs: 304390 (36961/sec), new interesting: 4 (total: 37)...fuzz: elapsed: 3m45s, execs: 7246222 (32357/sec), new interesting: 8 (total: 41)^Cfuzz: elapsed: 3m48s, execs: 7335316 (31648/sec), new interesting: 8 (total: 41)PASSok      example/fuzz  228.000s

    fuzz test如果没有遇到谬误,默认会始终运行上来,须要应用 ctrl-C 完结测试。

    也能够传递-fuzztime参数来指定测试工夫,这样就不必 ctrl-C 了。

  3. 指定测试工夫。 go test -fuzz=Fuzz -fuzztime 30s 如果没有遇到谬误会执行30s后主动完结。

    $ go test -fuzz=Fuzz -fuzztime 30sfuzz: elapsed: 0s, gathering baseline coverage: 0/5 completedfuzz: elapsed: 0s, gathering baseline coverage: 5/5 completed, now fuzzing with 4 workersfuzz: elapsed: 3s, execs: 80290 (26763/sec), new interesting: 12 (total: 12)fuzz: elapsed: 6s, execs: 210803 (43501/sec), new interesting: 14 (total: 14)fuzz: elapsed: 9s, execs: 292882 (27360/sec), new interesting: 14 (total: 14)fuzz: elapsed: 12s, execs: 371872 (26329/sec), new interesting: 14 (total: 14)fuzz: elapsed: 15s, execs: 517169 (48433/sec), new interesting: 15 (total: 15)fuzz: elapsed: 18s, execs: 663276 (48699/sec), new interesting: 15 (total: 15)fuzz: elapsed: 21s, execs: 771698 (36143/sec), new interesting: 15 (total: 15)fuzz: elapsed: 24s, execs: 924768 (50990/sec), new interesting: 16 (total: 16)fuzz: elapsed: 27s, execs: 1082025 (52427/sec), new interesting: 17 (total: 17)fuzz: elapsed: 30s, execs: 1172817 (30281/sec), new interesting: 17 (total: 17)fuzz: elapsed: 31s, execs: 1172817 (0/sec), new interesting: 17 (total: 17)PASSok      example/fuzz  31.025s

    Fuzzing测试通过!

    除了-fuzz参数外,有几个新的参数也被引入到了go test命令,具体能够参考 documentation。

总结

目前你曾经学会了Go fuzzing的应用办法。

接下来,你能够在本人写过的代码里,尝试应用fuzzing来发现代码里的bug。

如果你真的发现了bug,请思考把案例提交到了trophy case。

如果你发现了Go fuzzing的任何问题或者想提feature,能够在这里反馈file an issue。

查看文档 go.dev/doc/fuzz理解更多Go Fuzzing的常识。

本文的残缺代码参考Go Fuzzing示例代码。

开源地址

文章和示例代码开源在GitHub: Go语言高级、中级和高级教程。

公众号:coding进阶。关注公众号能够获取最新Go面试题和技术栈。

集体网站:Jincheng's Blog。

知乎:无忌。

References

  • Fuzzing教程:https://go.dev/doc/tutorial/fuzz
  • Fuzzing提案:https://github.com/golang/go/...
  • Fuzzing介绍:https://go.dev/doc/fuzz/