19. httpresponse
check for mistakes using HTTP responses
使用 net/http 包时的一个常见错误,是在检查确定响应是否有效的错误之前使用defer 延迟关闭 http.Response Body 的函数调用:
resp, err := http.Head(url) defer resp.Body.Close() if err != nil { log.Fatal(err) } // (defer statement belongs here)
该检查器通过报告此类错误的诊断来帮助发现潜在的 nil dereference bugs
nil dereference bugs是什么?
nil dereference bugs指的是试图访问null或不是期望对象的对象,从而导致程序崩溃的bug。
在某些语言中,比如Go语言和Rust语言,当尝试访问nil指针时会触发panic或panic。这可以帮助发现编程错误。
一个典型的nil dereference bug案例是:
var p *Person p.Name // 崩溃,因为p是nil指针
这里p是nil指针,试图访问p.Name就会导致nil指针引用错误,程序崩溃。
为避免nil dereference bug,我们需要检查指针是否为nil,然后才能安全的访问:
if p != nil { fmt.Println(p.Name) }
所以总的来说,nil dereference bugs就是由于未检查指针是否为nil从而导致的尝试访问nil指针的错误。
package main import ( "log" "net/http" ) func main() { badHTTPGet() } func badHTTPGet() { res, err := http.Get("http://foo.com") // 如果err不是nil,则res为nil,最后调用res.Body.Close() 会panic defer res.Body.Close() // want "using res before checking for errors" if err != nil { log.Fatal(err) } }
更多case,参考 github.com/golang/tool…
20. ifaceassert
detect impossible interface-to-interface type assertions
此检查器标记类型断言 v.(T) 和相应的类型转换情况,其中 v 的静态类型 V 是不可能实现目标接口 T 的接口。当 V 和 T 包含具有相同名称但不同签名的方法时,就会发生这种情况。
// var v interface { // Read() // } // _ = v.(io.Reader)
v 中的 Read 方法与 io.Reader 中的 Read 方法具有不同的签名,因此该断言无法成功。
package ifaceassert type Foo interface { Bar() } type FooImpl struct{} func (f *FooImpl) Bar() {} type SomeType struct { } func main() { var f Foo f = &FooImpl{} f1 := f.(*FooImpl) // 这是一个有效的类型断言 f2 := f.(*SomeType) // 这是一个无效的类型断言 }
下面这两段仅供参考:
ifaceassert是go vet的一个功能,它用于检查接口值是否被有效断言。
举个例子:
type Foo interface { Bar() } type FooImpl struct{} func (f *FooImpl) Bar() {} func main() { var f Foo f = &FooImpl{} f1 := f.(*FooImpl) // 这是一个有效的类型断言 f2 := f.(*SomeType) // 这是一个无效的类型断言 }
这里我们定义了一个Foo接口,有一个FooImpl实现。 然后使用类型断言(f.(*FooImpl)
)有效地断言了接口值。 但是f.(*SomeType)
是无效的类型断言,因为接口值实际类型不是*SomeType
。
go vet的ifaceassert可以检测到这个错误:
./main.go:17: invalid type assertion: f.(sometype.*SomeType) (non-interface type *FooImpl on left)
它告诉我们,左边是*FooImpl
类型,但是我们尝试断言为*SomeType
,这是无效的。
所以ifaceassert可以帮助我们检查类型断言是否有效,避免由于无效类型断言而引起的bug。 当然正确的写法是:
f1 := f.(*FooImpl)
使用接口值的实际类型来断言。
总的来说,go vet的ifaceassert用于检查接口类型断言是否有效,避免由于无效类型断言而导致的bug。
ifaceassert是go vet提供的另一个功能,用于检测接口断言是否正确。
接口断言的格式是:
obj.(T)
这里obj必须实现接口T,否则会panic。
举个例子:
type I interface { M() } type T struct {} func (t T) M() {} func foo(i I) { t := i.(T) // 这里可能panic! t.M() }
这里我们对i执行接口断言,假设i可能是不同类型的对象,那么 i.(T) 可能就会panic。
go vet可以检测到这种情况:
./main.go:11: assertion `i.(T)` used in non-type assertion context, advisable to check type first t := i.(T) ^
它建议我们先检查i的类型,然后再执行接口断言:
t, ok := i.(T) if ok { t.M() }
使用ok判断变量是否具备该类型,再执行接口断言。
所以,ifaceassert能帮助我们检查接口断言是否合理,避免由于错误的接口断言而导致的panic。
21. inspect (不算分析器)
optimize AST traversal for later passes
inspect包 定义了一个分析器,它为包的语法树提供 AST 检查器 (golang.org/x/tools/go/ast/inspector.Inspector)。 它只是其他分析器的构建块。 另一种分析中的使用示例:
package xxxxx import ( "go/ast" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/inspect" "golang.org/x/tools/go/ast/inspector" ) var Analyzer = &analysis.Analyzer{ ... Requires: []*analysis.Analyzer{inspect.Analyzer}, } func run(pass *analysis.Pass) (interface{}, error) { inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) inspect.Preorder(nil, func(n ast.Node) { //... }) return nil, nil }
22. internal(不算分析器)
Package analysisutil defines various helper functions used by two or more packages beneath go/analysis.
Analysisutil 包定义了 go/analysis 下的两个或多个包使用的各种辅助函数。
即一些公用的辅助函数
23. loopclosure
check references to loop variables from within nested functions
检查嵌套函数内对循环变量的引用此分析器报告函数文字引用封闭循环的迭代变量的位置,并且循环以这样的方式(例如使用 go 或 defer )调用该函数,该方式可能比循环迭代寿命更长,并且可能观察到错误的变量值。
在此示例中,所有延迟函数在循环完成后运行,因此所有函数都会观察 v 的最终值。
for _, v := range list { defer func() { use(v) // incorrect }() }
一种解决方法是为循环的每次迭代创建一个新变量:
for _, v := range list { v := v // new var per iteration defer func() { use(v) // ok }() }
下一个示例使用 go 语句并有类似的问题。 此外,它还存在数据竞争,因为循环更新 v 与访问它的 goroutine 并发。
for _, v := range elem { go func() { use(v) // incorrect, and a data race }() }
修复与之前相同。 检查器还报告由 golang.org/x/sync/errgroup.Group 启动的 goroutine 中的问题。 这种形式的一个难以发现的变体在并行测试中很常见:
func Test(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { t.Parallel() use(test) // incorrect, and a data race }) } }
t.Parallel() 调用导致函数的其余部分与循环同时执行。
分析器仅在最后一个语句中报告引用,因为它不够深入,无法理解可能使引用良性的后续语句的影响。 (“最后一条语句”在 if、switch 和 select 等复合语句中递归定义。)
以下供参考:
loopclosure是go vet提供的一个功能,用于检测for循环内定义的函数是否引用了循环变量。
举个例子:
func main() { for i := 0; i < 5; i++ { go func() { fmt.Println(i) // 这里引用了循环变量i }() } }
这里我们在for循环中定义了一个匿名函数,该函数引用了循环变量i。
由于go是通过复制变量来传入函数的参数,所以循环结束后,所有的函数都会引用同一个i变量的值(5)。
go vet可以检测到这个 Situation:
./main.go:4: i captured by func literal fmt.Println(i) ^
它告诉我们func literal(匿名函数)捕捉到了循环变量i。
正确的写法应该是:
for i := 0; i < 5; i++ { i := i go func() { fmt.Println(i) }() }
使用i := i重新定义i变量,这样每个匿名函数都会引用自己的i变量值。
所以总的来说, loopclosure可以帮助我们检查for循环中的匿名函数是否正确引用了循环变量,避免常见的闭包错误。
24. lostcancel
check cancel func returned by context.WithCancel is called
The cancellation function returned by context.WithCancel, WithTimeout, and WithDeadline must be called or the new context will remain live until its parent context is cancelled. (The background context is never cancelled.)
必须调用 context.WithCancel、WithTimeout 和 WithDeadline 返回的取消函数,否则新上下文将保持活动状态,直到其父上下文被取消。 (后台上下文永远不会被取消。)
这段文本是关于 Go 语言中使用 context
包创建上下文的函数 context.WithCancel
、context.WithTimeout
和 context.WithDeadline
的使用说明。
在 Go 语言中,context
包提供了一种用于在多个 Goroutine 之间传递取消信号和截止时间的机制,以便在合适的时候取消或超时某个操作。
这段文本中提到的内容是指:当使用 context.WithCancel
、context.WithTimeout
或 context.WithDeadline
函数创建一个新的上下文时,会返回一个取消函数(cancel function)。这个取消函数可以用来取消上下文,即停止所有使用该上下文派生出来的 Goroutine。
然而,如果你创建了一个新的上下文(使用上述函数之一)但没有调用返回的取消函数,那么这个新的上下文将一直处于活动状态,不会自动取消。这意味着其中的 Goroutine 将不会被释放,会继续执行。这可能导致资源泄漏或程序逻辑错误。
需要注意的是,背景上下文(background context)是一个特殊的上下文,它永远不会被取消,因为它是所有上下文的根节点。在使用 context.Background()
创建背景上下文时,不需要担心未调用取消函数的问题,因为它本身永远不会被取消。
所以,如果你在代码中使用了 context.WithCancel
、context.WithTimeout
或 context.WithDeadline
创建了新的上下文,请确保在不再需要该上下文时及时调用返回的取消函数,以便及时释放相关资源并停止相关 Goroutine 的执行。
package main import ( "context" "time" ) func main() { ctx, _ := context.WithTimeout(context.Background(), 1*time.Second) go func() { // 忘记调用cancel,导致context泄漏 }() _ = ctx time.Sleep(2 * time.Second) }
lostcancel可以帮助我们检查是否正确地调用了context的cancel函数,避免context泄漏问题。
25. nilfunc
check for useless comparisons between functions and nil
一种无用的比较是类似 f == nil 而不是 f() == nil 的比较。
package main func main() { if shuang == nil { print(123) } } func shuang() { print(678) }
26. nilness(未集成)
check for redundant or impossible nil comparisons
检查冗余或不可能的零比较
包 nilness 检查 SSA 函数的控制流图并报告错误,例如 nil 指针取消引用和退化 nil 指针比较。
nilness 检查器检查包中每个函数的控制流图,并报告 nil 指针取消引用、退化 nil 指针以及 nil 值的恐慌。 简并比较的形式为 x==nil 或 x!=nil,其中 x 静态地已知为 nil 或非 nil。 这些常常是错误,特别是与控制流相关的错误。 检查带有 nil 值的恐慌,因为它们无法被检测到
相关case参考: github.com/golang/tool…
2023年新增
27. pkgfact(未集成)
gather name/value pairs from constant declarations
从常量声明中收集 name/value 对
pkgfact 包是包事实机制(package fact mechanism)的演示和测试。 pkgfact 分析的输出是一组从分析的包及其导入的依赖项中收集的键/值对。 每个键/值对都来自一个顶级常量声明,其名称以“_”开头和结尾。 例如:
package p const _greeting_ = "hello" const _audience_ = "world"
包 p 的 pkgfact 分析输出将是:
{"greeting": "hello", "audience": "world"}.
In addition, the analysis reports a diagnostic at each import showing which key/value pairs it contributes.
package fact mechanism是什么?
Package fact mechanism是Go语言的一种机制,它可以在编译时收集包的信息,并且这些信息可以在运行时被访问。
Go收集的两种fact信息是:
- Package facts: 包级别的信息。包括包的路径、名字、导入路径等。
- Type facts:类型级别的信息。包括类型的大小、内置方法等。
这些fact信息会被记录在一个叫.a的文件中,这个文件会随代码一起编译。然后在运行时,程序可以通过reflection访问这些信息。
举个例子,要获取一个包的名称,可以这样做:
import ( "reflect" "runtime" ) func main() { pname := runtime.FuncForPC(reflect.ValueOf(main).Pointer()).Name() parts := strings.Split(pname, ".") fmt.Println(parts[len(parts)-1]) // prints "main" }
这里使用reflect获取main函数的指针,然后通过runtime获取函数名称。这个名称包含了包名,因此我们可以提取出包名。
Package fact mechanism让程序可以在运行时获取编译时已知的信息。比如包名、导入路径等。
总的来说:
- Go在编译时收集包级别和类型级别的信息
- 这些信息储存在.a文件中
- 程序可以在运行时通过reflection访问这些信息
- Package facts mechanism让程序可以获取编译时已知的元信息
以下内容供参考,未必准确:
pkgfact是一个go vet提供的功能,它用于检查包级别的fact信息。包括:
- 包是否被导入但未使用
- 多个包是否引用同一个导入路径
我们来看个例子:
import ( "fmt" "math" // 这个包未使用 ) func main() { fmt.Println("hello") }
这里我们导入了math包但是未使用,go vet可以检查到:
./main.go:3: import of package "math" is unused "math" ^^^
它提示math包被导入但未使用。
另一个例子:
import ( "fmt" "encoding/json" ) import . "morejson" // 使用了相同的导入路径
这里同一个导入路径"json"被多个包导入,go vet可以检测到:
./main.go:6: import redefinition: encoding/json previously imported at ./main.go:3 morejson imported here
它提示有import redefinition。
通过检查包的fact信息,pkgfact可以帮助我们:
- 检测包是否被导入但未使用
- 检测import是否存在redefinition
- 避免因此产生一些潜在的bug
总的来说: pkgfact用于检查包级别的fact信息,以保持imports的正确性。它会检查:
- 包是否被导入但未使用
- 是否有import redefinition的情况
28. printf
check consistency of Printf format strings and arguments
检查 Printf 格式字符串和参数的一致性
package main import "fmt" func main() { fmt.Printf("%d %s", 1) // ./main.go:7:2: fmt.Printf format %s reads arg #2, but call has 1 arg fmt.Printf("%s", 1) // ./main.go:9:2: fmt.Printf format %s has arg 1 of wrong type int }
29. reflectvaluecompare(未集成)
check for comparing reflect.Value values with == or reflect.DeepEqual
检查将reflect.Value值与==或reflect.DeepEqual进行比较
ReflectValueCompare 检查器查找以下形式的表达式:
v1 == v2 v1 != v2 reflect.DeepEqual(v1, v2)
其中 v1 或 v2 是 Reflect.Values。 直接比较reflect.Values几乎肯定是不正确的,因为它比较的是reflect包的内部表示,而不是底层值。 可能的目的是:
v1.Interface() == v2.Interface() v1.Interface() != v2.Interface() reflect.DeepEqual(v1.Interface(), v2.Interface())
相关case参考: github.com/golang/tool…
30. shadow(未集成)
check for possible unintended shadowing of variables
检查变量可能出现的意外阴影
该分析器检查隐藏变量。 隐藏变量是在内部作用域中声明的变量,其名称和类型与外部作用域中的变量相同,并且在声明内部变量之后提及外部变量。
(这个定义可以细化;该模块产生太多误报,并且默认情况下尚未启用。)
例如:
func BadRead(f *os.File, buf []byte) error { var err error for { n, err := f.Read(buf) // shadows the function variable 'err' if err != nil { break // causes return of wrong value } foo(buf) } return err }
31. shift
check for shifts that equal or exceed the width of the integer
检查是否存在等于或超过整数宽度的移位
package shift import "unsafe" func ShiftTest() { var i8 int8 _ = i8 << 7 _ = (i8 + 1) << 8 // want ".i8 . 1. .8 bits. too small for shift of 8" _ = i8 << (7 + 1) // want "i8 .8 bits. too small for shift of 8" _ = i8 >> 8 // want "i8 .8 bits. too small for shift of 8" i8 <<= 8 // want "i8 .8 bits. too small for shift of 8" i8 >>= 8 // want "i8 .8 bits. too small for shift of 8" var i16 int16 _ = i16 << 15 _ = i16 << 16 // want "i16 .16 bits. too small for shift of 16" _ = i16 >> 16 // want "i16 .16 bits. too small for shift of 16" i16 <<= 16 // want "i16 .16 bits. too small for shift of 16" i16 >>= 16 // want "i16 .16 bits. too small for shift of 16" var i32 int32 _ = i32 << 31 _ = i32 << 32 // want "i32 .32 bits. too small for shift of 32" _ = i32 >> 32 // want "i32 .32 bits. too small for shift of 32" i32 <<= 32 // want "i32 .32 bits. too small for shift of 32" i32 >>= 32 // want "i32 .32 bits. too small for shift of 32" var i64 int64 _ = i64 << 63 _ = i64 << 64 // want "i64 .64 bits. too small for shift of 64" _ = i64 >> 64 // want "i64 .64 bits. too small for shift of 64" i64 <<= 64 // want "i64 .64 bits. too small for shift of 64" i64 >>= 64 // want "i64 .64 bits. too small for shift of 64" var u8 uint8 _ = u8 << 7 _ = u8 << 8 // want "u8 .8 bits. too small for shift of 8" _ = u8 >> 8 // want "u8 .8 bits. too small for shift of 8" u8 <<= 8 // want "u8 .8 bits. too small for shift of 8" u8 >>= 8 // want "u8 .8 bits. too small for shift of 8" var u16 uint16 _ = u16 << 15 _ = u16 << 16 // want "u16 .16 bits. too small for shift of 16" _ = u16 >> 16 // want "u16 .16 bits. too small for shift of 16" u16 <<= 16 // want "u16 .16 bits. too small for shift of 16" u16 >>= 16 // want "u16 .16 bits. too small for shift of 16" var u32 uint32 _ = u32 << 31 _ = u32 << 32 // want "u32 .32 bits. too small for shift of 32" _ = u32 >> 32 // want "u32 .32 bits. too small for shift of 32" u32 <<= 32 // want "u32 .32 bits. too small for shift of 32" u32 >>= 32 // want "u32 .32 bits. too small for shift of 32" var u64 uint64 _ = u64 << 63 _ = u64 << 64 // want "u64 .64 bits. too small for shift of 64" _ = u64 >> 64 // want "u64 .64 bits. too small for shift of 64" u64 <<= 64 // want "u64 .64 bits. too small for shift of 64" u64 >>= 64 // want "u64 .64 bits. too small for shift of 64" _ = u64 << u64 // Non-constant shifts should succeed. var i int _ = i << 31 const in = 8 * unsafe.Sizeof(i) _ = i << in // want "too small for shift" _ = i >> in // want "too small for shift" i <<= in // want "too small for shift" i >>= in // want "too small for shift" const ix = 8*unsafe.Sizeof(i) - 1 _ = i << ix _ = i >> ix i <<= ix i >>= ix var u uint _ = u << 31 const un = 8 * unsafe.Sizeof(u) _ = u << un // want "too small for shift" _ = u >> un // want "too small for shift" u <<= un // want "too small for shift" u >>= un // want "too small for shift" const ux = 8*unsafe.Sizeof(u) - 1 _ = u << ux _ = u >> ux u <<= ux u >>= ux var p uintptr _ = p << 31 const pn = 8 * unsafe.Sizeof(p) _ = p << pn // want "too small for shift" _ = p >> pn // want "too small for shift" p <<= pn // want "too small for shift" p >>= pn // want "too small for shift" const px = 8*unsafe.Sizeof(p) - 1 _ = p << px _ = p >> px p <<= px p >>= px const oneIf64Bit = ^uint(0) >> 63 // allow large shifts of constants; they are used for 32/64 bit compatibility tricks var h uintptr h = h<<8 | (h >> (8 * (unsafe.Sizeof(h) - 1))) h <<= 8 * unsafe.Sizeof(h) // want "too small for shift" h >>= 7 * unsafe.Alignof(h) h >>= 8 * unsafe.Alignof(h) // want "too small for shift" }
执行go vet main.go后输出:
# command-line-arguments ./main.go:8:6: (i8 + 1) (8 bits) too small for shift of 8 ./main.go:9:6: i8 (8 bits) too small for shift of 8 ./main.go:10:6: i8 (8 bits) too small for shift of 8 ./main.go:11:2: i8 (8 bits) too small for shift of 8 ./main.go:12:2: i8 (8 bits) too small for shift of 8 ./main.go:15:6: i16 (16 bits) too small for shift of 16 ./main.go:16:6: i16 (16 bits) too small for shift of 16 ./main.go:17:2: i16 (16 bits) too small for shift of 16 ./main.go:18:2: i16 (16 bits) too small for shift of 16 ./main.go:21:6: i32 (32 bits) too small for shift of 32 ./main.go:22:6: i32 (32 bits) too small for shift of 32 ./main.go:23:2: i32 (32 bits) too small for shift of 32 ./main.go:24:2: i32 (32 bits) too small for shift of 32 ./main.go:27:6: i64 (64 bits) too small for shift of 64 ./main.go:28:6: i64 (64 bits) too small for shift of 64 ./main.go:29:2: i64 (64 bits) too small for shift of 64 ./main.go:30:2: i64 (64 bits) too small for shift of 64 ./main.go:33:6: u8 (8 bits) too small for shift of 8 ./main.go:34:6: u8 (8 bits) too small for shift of 8 ./main.go:35:2: u8 (8 bits) too small for shift of 8 ./main.go:36:2: u8 (8 bits) too small for shift of 8 ./main.go:39:6: u16 (16 bits) too small for shift of 16 ./main.go:40:6: u16 (16 bits) too small for shift of 16 ./main.go:41:2: u16 (16 bits) too small for shift of 16 ./main.go:42:2: u16 (16 bits) too small for shift of 16 ./main.go:45:6: u32 (32 bits) too small for shift of 32 ./main.go:46:6: u32 (32 bits) too small for shift of 32 ./main.go:47:2: u32 (32 bits) too small for shift of 32 ./main.go:48:2: u32 (32 bits) too small for shift of 32 ./main.go:51:6: u64 (64 bits) too small for shift of 64 ./main.go:52:6: u64 (64 bits) too small for shift of 64 ./main.go:53:2: u64 (64 bits) too small for shift of 64 ./main.go:54:2: u64 (64 bits) too small for shift of 64 ./main.go:60:6: i (64 bits) too small for shift of 64 ./main.go:61:6: i (64 bits) too small for shift of 64 ./main.go:62:2: i (64 bits) too small for shift of 64 ./main.go:63:2: i (64 bits) too small for shift of 64 ./main.go:73:6: u (64 bits) too small for shift of 64 ./main.go:74:6: u (64 bits) too small for shift of 64 ./main.go:75:2: u (64 bits) too small for shift of 64 ./main.go:76:2: u (64 bits) too small for shift of 64 ./main.go:86:6: p (64 bits) too small for shift of 64 ./main.go:87:6: p (64 bits) too small for shift of 64 ./main.go:88:2: p (64 bits) too small for shift of 64 ./main.go:89:2: p (64 bits) too small for shift of 64 ./main.go:100:2: h (64 bits) too small for shift of 64 ./main.go:102:2: h (64 bits) too small for shift of 64
以下内容供参考:
shift是go vet提供的一个功能,它用于检查移位操作是否正确。
在Go语言中,不同类型的数值 shifting是不一样的。
- uint类型接受无符号移位
- int类型接受有符号移位
举个例子:
var a uint = 1 a << 2 // 4 var b int = 1 b << 2 // -8
对uint做左移是无符号的,结果是4。 而对int做左移是有符号的,结果是-8。
go vet可以检测到这种混用:
./main.go:4: result of left shift of int by uint will be uint b << 2 ^^
它提示左移的结果将是uint类型。
为了避免混用不同类型的移位操作,我们应该把它们转换为相同的类型:
b << uint(2) // 强制转换为uint类型
所以,shift功能主要用于检查:
- int和uint之间的移位混用
- 是否使用正确的类型转换
以避免由此产生的bug。
总的来说:
- int和uint的移位结果不同
- shift可以检查int和uint之间是否存在移位混用
- 通过提示添加类型转换,可以避免潜在的bug
32. sigchanyzer
check for unbuffered channel of os.Signal
检查 os.Signal 的无缓冲通道
该检查器报告这种形式的调用表达式
signal.Notify(c <-chan os.Signal, sig ...os.Signal)
其中 c 是无缓冲通道,可能存在丢失信号的风险。
package sigchanyzer import ( "os" "os/signal" ) func g() { c := make(chan os.Signal) signal.Notify(c, os.Interrupt) // want "misuse of unbuffered os.Signal channel as argument to signal.Notify" _ = <-c }
执行 go vet main.go 后输出:
# command-line-arguments ./main.go:10:2: misuse of unbuffered os.Signal channel as argument to signal.Notify
更多case,参考: github.com/golang/tool…
以下内容供参考:
sigchanyzer是go vet提供的一个功能,它用于检查信号处理是否正确。
在Go语言中,我们使用signal包来处理OS信号,像SIGINT、SIGTERM等。
举个例子,监听SIGINT信号:
signal.Notify(c, os.Interrupt) for { select { case <-c: // 处理SIGINT信号 } }
这里我们使用signal.Notify注册了SIGINT信号,然后不停检查channel是否有信号。
go vet可以检查我们是否正确处理了信号:
signal.Notify(c, os.Interrupt) <-c // 错误! 循环只检查一次channel
这里我们只检查一次channel,没有正确处理SIGINT信号。
go vet可以检测到:
./main.go:5: ignoring subsequent SIGINT after first <-c ^
所以sigchanyzer主要可以检查是否:
- 正确使用select监听信号channel
- 在循环中多次检查信号channel
以确保全部信号都被处理。
通过go vet,我们可以避免 accidentally ignoring OS signals的错误。
总的来说,sigchanyzer用来检查信号处理是否正确,包括:
- 使用select机制监听信号
- 在循环中多次检查信号channel
- 避免忽略信号
以确保我们的代码能正确处理OS信号。
33. slog
check for invalid structured logging calls
slog 检查器从 log/slog 包中查找采用交替键值对的函数调用。 它报告键位置中的参数既不是字符串也不是 slog.Attr 且最终键缺少其值的调用。
例如,它会报告
slog.Warn("message", 11, "k") // slog.Warn arg "11" should be a string or a slog.Attr
和
slog.Info("message", "k1", v1, "k2") // call to slog.Info missing a final value
更多case,参考: github.com/golang/tool…
2023年新增
34. sortslice(未集成)
check the argument type of sort.Slice
sort.Slice 需要切片类型的参数。 检查一下传递给 sort.Slice 的 interface{} 值实际上是一个切片
package main import "sort" func main() { i := 5 sortFn := func(i, j int) bool { return false } sort.Slice(i, sortFn) // want "sort.Slice's argument must be a slice; is called with int" sort.SliceStable(i, sortFn) // want "sort.SliceStable's argument must be a slice; is called with int" sort.SliceIsSorted(i, sortFn) // want "sort.SliceIsSorted's argument must be a slice; is called with int" }
执行 go vet main.go后未生效,这是因为tools项目中提供了,但是go项目中未集成进来
更多case,参考: github.com/golang/tool…
当前go vet中集成进来的分析器,见 github.com/golang/go/b…
可以研究下为什么没有集成进去,是不是忘了..
看起来不是,github.com/golang/go/t… 这里提到 Over time many checks have been added to vet's suite, but many more have been rejected as not appropriate for the tool.
35. stdmethods
checks for misspellings in the signatures of methods similar to well-known interfaces
检查类似于众所周知的接口的方法签名中的拼写错误。
有时,类型可能旨在满足接口,但可能由于其方法签名中的错误而无法满足接口的要求。
例如,这个 WriteTo 方法的结果应该是 (int64, error),而不是 error,以满足 io.WriterTo:
type myWriterTo struct{...} func (myWriterTo) WriteTo(w io.Writer) error { ... }
此检查可确保名称与标准库中几个众所周知的接口方法之一匹配的每个方法都具有该接口的正确签名。 检查的方法名称包括:
- Format GobEncode GobDecode MarshalJSON MarshalXML
- Peek ReadByte ReadFrom ReadRune Scan Seek
- UnmarshalJSON UnreadByte UnreadRune WriteByte WriteTo
更多case,参考 github.com/golang/tool…
36. stringintconv
check for string(int) conversions
此检查器标记 string(x) 形式的转换,其中 x 是整数(但不是字节byte或符文rune)类型。 不鼓励进行此类转换,因为它们返回 Unicode 代码点 x 的 UTF-8 表示形式,而不是人们所期望的 x 的十进制字符串表示形式。 此外,如果 x 表示无效代码点,则不能静态拒绝转换。
对于打算使用代码点的转换,请考虑将其替换为 string(rune(x))。 否则,strconv.Itoa 及其等效项返回所需基数中值的字符串表示形式。
package stringintconv type A string type B = string type C int type D = uintptr func StringTest() { var ( i int j rune k byte l C m D n = []int{0, 1, 2} o struct{ x int } ) const p = 0 _ = string(i) // want `^conversion from int to string yields a string of one rune, not a string of digits \(did you mean fmt\.Sprint\(x\)\?\)$` _ = string(j) _ = string(k) _ = string(p) // want `^conversion from untyped int to string yields a string of one rune, not a string of digits \(did you mean fmt\.Sprint\(x\)\?\)$` _ = A(l) // want `^conversion from C \(int\) to A \(string\) yields a string of one rune, not a string of digits \(did you mean fmt\.Sprint\(x\)\?\)$` _ = B(m) // want `^conversion from uintptr to B \(string\) yields a string of one rune, not a string of digits \(did you mean fmt\.Sprint\(x\)\?\)$` _ = string(n[1]) // want `^conversion from int to string yields a string of one rune, not a string of digits \(did you mean fmt\.Sprint\(x\)\?\)$` _ = string(o.x) // want `^conversion from int to string yields a string of one rune, not a string of digits \(did you mean fmt\.Sprint\(x\)\?\)$` }
执行 go vet main.go, 输出:
# command-line-arguments ./main.go:22:6: conversion from int to string yields a string of one rune, not a string of digits (did you mean fmt.Sprint(x)?) ./main.go:25:6: conversion from untyped int to string yields a string of one rune, not a string of digits (did you mean fmt.Sprint(x)?) ./main.go:26:6: conversion from C (int) to A (string) yields a string of one rune, not a string of digits (did you mean fmt.Sprint(x)?) ./main.go:27:6: conversion from uintptr to B (string) yields a string of one rune, not a string of digits (did you mean fmt.Sprint(x)?) ./main.go:28:6: conversion from int to string yields a string of one rune, not a string of digits (did you mean fmt.Sprint(x)?) ./main.go:29:6: conversion from int to string yields a string of one rune, not a string of digits (did you mean fmt.Sprint(x)?)
37. structtag
checks struct field tags are well formed
检查结构字段标签的格式是否正确
检查结构体字段标签是否符合 reflect.StructTag.Get
还报告与未导出字段一起使用的某些结构标签(json、xml)
package structtag type StructTagTest struct { A int "hello" // want "`hello` not compatible with reflect.StructTag.Get: bad syntax for struct tag pair" B int "\tx:\"y\"" // want "not compatible with reflect.StructTag.Get: bad syntax for struct tag key" C int "x:\"y\"\tx:\"y\"" // want "not compatible with reflect.StructTag.Get" D int "x:`y`" // want "not compatible with reflect.StructTag.Get: bad syntax for struct tag value" E int "ct\brl:\"char\"" // want "not compatible with reflect.StructTag.Get: bad syntax for struct tag pair" F int `:"emptykey"` // want "not compatible with reflect.StructTag.Get: bad syntax for struct tag key" G int `x:"noEndQuote` // want "not compatible with reflect.StructTag.Get: bad syntax for struct tag value" H int `x:"trunc\x0"` // want "not compatible with reflect.StructTag.Get: bad syntax for struct tag value" I int `x:"foo",y:"bar"` // want "not compatible with reflect.StructTag.Get: key:.value. pairs not separated by spaces" J int `x:"foo"y:"bar"` // want "not compatible with reflect.StructTag.Get: key:.value. pairs not separated by spaces" OK0 int `x:"y" u:"v" w:""` OK1 int `x:"y:z" u:"v" w:""` // note multiple colons. OK2 int "k0:\"values contain spaces\" k1:\"literal\ttabs\" k2:\"and\\tescaped\\tabs\"" OK3 int `under_scores:"and" CAPS:"ARE_OK"` }
执行 go vet main.go后输出:
# command-line-arguments ./main.go:4:2: struct field tag `hello` not compatible with reflect.StructTag.Get: bad syntax for struct tag pair ./main.go:5:2: struct field tag ` x:"y"` not compatible with reflect.StructTag.Get: bad syntax for struct tag key ./main.go:6:2: struct field tag `x:"y" x:"y"` not compatible with reflect.StructTag.Get: key:"value" pairs not separated by spaces ./main.go:7:2: struct field tag "x:`y`" not compatible with reflect.StructTag.Get: bad syntax for struct tag value ./main.go:8:2: struct field tag "ct\brl:\"char\"" not compatible with reflect.StructTag.Get: bad syntax for struct tag pair ./main.go:9:2: struct field tag `:"emptykey"` not compatible with reflect.StructTag.Get: bad syntax for struct tag key ./main.go:10:2: struct field tag `x:"noEndQuote` not compatible with reflect.StructTag.Get: bad syntax for struct tag value ./main.go:11:2: struct field tag `x:"trunc\x0"` not compatible with reflect.StructTag.Get: bad syntax for struct tag value ./main.go:12:2: struct field tag `x:"foo",y:"bar"` not compatible with reflect.StructTag.Get: key:"value" pairs not separated by spaces ./main.go:13:2: struct field tag `x:"foo"y:"bar"` not compatible with reflect.StructTag.Get: key:"value" pairs not separated by spaces
更多case,参考 github.com/golang/tool…
38. testinggoroutine
*report calls to (testing.T).Fatal from goroutines started by a test
报告由测试启动的 goroutine 对 (*testing.T).Fatal 的调用
突然终止测试的函数,例如 *testing.T 的 Fatal、Fatalf、FailNow 和 Skip{,f,Now} 方法,必须从测试 goroutine 本身调用。 该检查器检测由测试启动的 goroutine 中发生的对这些函数的调用。 例如:
func TestFoo(t *testing.T) { go func() { t.Fatal("oops") // error: (*T).Fatal called from non-test goroutine }() }
package testinggoroutine import ( "sync" "testing" ) func TestBadFatalf(t *testing.T) { var wg sync.WaitGroup defer wg.Wait() for i := 0; i < 2; i++ { wg.Add(1) go func(id int) { defer wg.Done() t.Fatalf("TestFailed: id = %v\n", id) // want "call to .+T.+Fatalf from a non-test goroutine" }(i) } }
更多case,参考 github.com/golang/tool…
39. tests
check for common mistaken usages of tests and examples
这个分析器 运行测试、基准、模糊测试和示例功能,检查格式错误的名称、错误的签名和记录不存在的标识符的示例。
请参阅 golang.org/pkg/testing 中的包测试文档,了解测试、基准和示例强制执行的约定。
这一块有不少todo可以去看看
相关case,参考 github.com/golang/tool…
40. timeformat
check for calls of (time.Time).Format or time.Parse with 2006-02-01
查找格式为 2006-02-01 (yyyy-dd-mm) 的时间格式。 在国际上,“yyyy-dd-mm”不会出现在通用日历日期标准中,因此更可能是 2006-01-02 (yyyy-mm-dd)
package timeformat import "time" func hasError() { a, _ := time.Parse("2006-02-01 15:04:05", "2021-01-01 00:00:00") // want `2006-02-01 should be 2006-01-02` a.Format(`2006-02-01`) // want `2006-02-01 should be 2006-01-02` a.Format("2006-02-01 15:04:05") // want `2006-02-01 should be 2006-01-02` const c = "2006-02-01" a.Format(c) // want `2006-02-01 should be 2006-01-02` }
执行go vet main.go后输出:
# command-line-arguments ./main.go:6:22: 2006-02-01 should be 2006-01-02 ./main.go:7:12: 2006-02-01 should be 2006-01-02 ./main.go:8:12: 2006-02-01 should be 2006-01-02 ./main.go:11:11: 2006-02-01 should be 2006-01-02
更多case,参考 github.com/golang/tool…
41. unmarshal
report passing non-pointer or non-interface values to unmarshal
报告对 json.Unmarshal 等函数的调用,但其中参数类型不是指针或接口的情况。
更多case,参考 github.com/golang/tool…
42. unreachable
check for unreachable code
该分析器会查找执行永远无法到达的语句,因为它们前面有 return 语句、对panic的调用、无限循环或类似的结构。
package unreachable func _() int { print(1) return 2 println() // want "unreachable code" } func _() int { L: print(1) goto L println() // want "unreachable code" } func _() int { print(1) panic(2) println() // want "unreachable code" }
输出:
# command-line-arguments vet: ./main.go:7:1: missing return
更多case,参考 github.com/golang/tool…
43. unsafeptr
check for invalid conversions of uintptr to unsafe.Pointer
检查 uintptr 到 unsafe.Pointer 的无效转换
报告可能错误地使用 unsafe.Pointer 将整数转换为指针。 如果从 uintptr 到 unsafe.Pointer 的转换意味着内存中存在一个保存指针值的 uintptr 类型的字,则该转换无效,因为该字对于堆栈复制和垃圾收集器来说是不可见的。
package unsafeptr import "unsafe" func f() { var x unsafe.Pointer var y uintptr x = unsafe.Pointer(y) // want "possible misuse of unsafe.Pointer" y = uintptr(x) // only allowed pointer arithmetic is ptr +/-/&^ num. // num+ptr is technically okay but still flagged: write ptr+num instead. x = unsafe.Pointer(uintptr(x) + 1) x = unsafe.Pointer(((uintptr((x))) + 1)) x = unsafe.Pointer(1 + uintptr(x)) // want "possible misuse of unsafe.Pointer" x = unsafe.Pointer(uintptr(x) + uintptr(x)) // want "possible misuse of unsafe.Pointer" }
更多case,参考 github.com/golang/tool…
44. unusedresult
checks for unused results of calls to certain pure functions
有些函数(例如 fmt.Errorf)会返回结果并且没有副作用,因此丢弃结果始终是错误的。 其他函数可能会返回一个不能被忽略的错误,或者一个必须调用的清理操作。 当调用结果被忽略时,该分析器会报告对此类函数的调用。
可以使用标志来控制该组函数。
package unusedresult import ( "bytes" "errors" "fmt" . "fmt" ) func _() { fmt.Errorf("") // want "result of fmt.Errorf call not used" _ = fmt.Errorf("") errors.New("") // want "result of errors.New call not used" err := errors.New("") err.Error() // want `result of \(error\).Error call not used` var buf bytes.Buffer buf.String() // want `result of \(\*bytes.Buffer\).String call not used` fmt.Sprint("") // want "result of fmt.Sprint call not used" fmt.Sprintf("") // want "result of fmt.Sprintf call not used" Sprint("") // want "result of fmt.Sprint call not used" Sprintf("") // want "result of fmt.Sprintf call not used" }
输出:
# command-line-arguments ./main.go:11:12: result of fmt.Errorf call not used ./main.go:14:12: result of errors.New call not used ./main.go:17:11: result of (error).Error call not used ./main.go:20:12: result of (*bytes.Buffer).String call not used ./main.go:22:12: result of fmt.Sprint call not used ./main.go:23:13: result of fmt.Sprintf call not used ./main.go:25:8: result of fmt.Sprint call not used ./main.go:26:9: result of fmt.Sprintf call not used
相关case,参考 github.com/golang/tool…
45. unusedwrite(未集成)
checks for unused writes to the elements of a struct or array object
检查对结构或数组对象元素的未使用写入
报告从未读取的结构体字段和数组的写入实例。 具体来说,当复制结构体对象或数组时,编译器会隐式复制其元素,并且写入此副本的任何元素都不会对原始对象执行任何操作。
例如:
type T struct { x int } func f(input []T) { for i, v := range input { // v is a copy v.x = i // unused write to field x } }
另一个例子是关于非指针接收者:
type T struct { x int } func (t T) f() { // t is a copy t.x = i // unused write to field x }
更多case,参考 github.com/golang/tool…
tools项目中提供了,但是go项目中未集成进来
46. usesgenerics(未集成)
checks for usage of generic features added in Go 1.18
检查 Go 1.18 中添加的泛型功能的使用情况
usegenerics 分析报告包是否直接或间接使用与 Go 中泛型编程相关的某些功能。
相关case参考: github.com/golang/tool…
tools项目中提供了,但是go项目中未集成进来