likes
comments
collection
share

go vet中的那些检测项

作者站长头像
站长
· 阅读数 23

go vet 是 Go 语言自带的一个工具,用于分析 Go 代码中的常见错误和潜在问题。它可以检查代码中可能存在的各种问题,例如:

  • 未使用的变量、函数或包
  • 可疑的函数调用
  • 错误的函数签名
  • 程序中的竞态条件
  • 错误的类型转换等

本文意图指令当前go vet所有的检测项及其作用

目前集成进go vet的只有30个,很多可能因为噪音太多,而没有集成进去

Run 'go help vet' for details. Run 'go tool vet help' for a full list of flags and analyzers. Run 'go tool vet -help' for an overview.

go tool vet help

vet is a tool for static analysis of Go programs.

vet examines Go source code and reports suspicious constructs,
such as Printf calls whose arguments do not align with the format
string. It uses heuristics that do not guarantee all reports are
genuine problems, but it can find errors not caught by the compilers.

Registered analyzers:

    asmdecl      report mismatches between assembly files and Go declarations
    assign       check for useless assignments
    atomic       check for common mistakes using the sync/atomic package
    bools        check for common mistakes involving boolean operators
    buildtag     check //go:build and // +build directives
    cgocall      detect some violations of the cgo pointer passing rules
    composites   check for unkeyed composite literals
    copylocks    check for locks erroneously passed by value
    directive    check Go toolchain directives such as //go:debug
    errorsas     report passing non-pointer or non-error values to errors.As
    framepointer report assembly that clobbers the frame pointer before saving it
    httpresponse check for mistakes using HTTP responses
    ifaceassert  detect impossible interface-to-interface type assertions
    loopclosure  check references to loop variables from within nested functions
    lostcancel   check cancel func returned by context.WithCancel is called
    nilfunc      check for useless comparisons between functions and nil
    printf       check consistency of Printf format strings and arguments
    shift        check for shifts that equal or exceed the width of the integer
    sigchanyzer  check for unbuffered channel of os.Signal
    slog         check for invalid structured logging calls
    stdmethods   check signature of methods of well-known interfaces
    stringintconv check for string(int) conversions
    structtag    check that struct field tags conform to reflect.StructTag.Get
    testinggoroutine report calls to (*testing.T).Fatal from goroutines started by a test.
    tests        check for common mistaken usages of tests and examples
    timeformat   check for calls of (time.Time).Format or time.Parse with 2006-02-01
    unmarshal    report passing non-pointer or non-interface values to unmarshal
    unreachable  check for unreachable code
    unsafeptr    check for invalid conversions of uintptr to unsafe.Pointer
    unusedresult check for unused results of calls to some functions

By default all analyzers are run.
To select specific analyzers, use the -NAME flag for each one,
 or -NAME=false to run all analyzers not explicitly disabled.

Core flags:

  -V    print version and exit
  -all
        no effect (deprecated)
  -asmdecl
        enable asmdecl analysis
  -assign
        enable assign analysis
  -atomic
        enable atomic analysis
  -bool
        deprecated alias for -bools
  -bools
        enable bools analysis
  -buildtag
        enable buildtag analysis
  -buildtags
        deprecated alias for -buildtag
  -c int
        display offending line with this many lines of context (default -1)
  -cgocall
        enable cgocall analysis
  -composites
        enable composites analysis
  -compositewhitelist
        deprecated alias for -composites.whitelist (default true)
  -copylocks
        enable copylocks analysis
  -directive
        enable directive analysis
  -errorsas
        enable errorsas analysis
  -flags
        print analyzer flags in JSON
  -framepointer
        enable framepointer analysis
  -httpresponse
        enable httpresponse analysis
  -ifaceassert
        enable ifaceassert analysis
  -json
        emit JSON output
  -loopclosure
        enable loopclosure analysis
  -lostcancel
        enable lostcancel analysis
  -methods
        deprecated alias for -stdmethods
  -nilfunc
        enable nilfunc analysis
  -printf
        enable printf analysis
  -printfuncs value
        deprecated alias for -printf.funcs (default (*log.Logger).Fatal,(*log.Logger).Fatalf,(*log.Logger).Fatalln,(*log.Logger).Panic,(*log.Logger).Panicf,(*log.Logger).Panicln,(*log.Logger).Print,(*log.Logger).Printf,(*log.Logger).Println,(*testing.common).Error,(*testing.common).Errorf,(*testing.common).Fatal,(*testing.common).Fatalf,(*testing.common).Log,(*testing.common).Logf,(*testing.common).Skip,(*testing.common).Skipf,(testing.TB).Error,(testing.TB).Errorf,(testing.TB).Fatal,(testing.TB).Fatalf,(testing.TB).Log,(testing.TB).Logf,(testing.TB).Skip,(testing.TB).Skipf,fmt.Append,fmt.Appendf,fmt.Appendln,fmt.Errorf,fmt.Fprint,fmt.Fprintf,fmt.Fprintln,fmt.Print,fmt.Printf,fmt.Println,fmt.Sprint,fmt.Sprintf,fmt.Sprintln,log.Fatal,log.Fatalf,log.Fatalln,log.Panic,log.Panicf,log.Panicln,log.Print,log.Printf,log.Println,runtime/trace.Logf)
  -rangeloops
        deprecated alias for -loopclosure
  -shift
        enable shift analysis
  -sigchanyzer
        enable sigchanyzer analysis
  -slog
        enable slog analysis
  -source
        no effect (deprecated)
  -stdmethods
        enable stdmethods analysis
  -stringintconv
        enable stringintconv analysis
  -structtag
        enable structtag analysis
  -tags string
        no effect (deprecated)
  -testinggoroutine
        enable testinggoroutine analysis
  -tests
        enable tests analysis
  -timeformat
        enable timeformat analysis
  -unmarshal
        enable unmarshal analysis
  -unreachable
        enable unreachable analysis
  -unsafeptr
        enable unsafeptr analysis
  -unusedfuncs value
        deprecated alias for -unusedresult.funcs (default context.WithCancel,context.WithDeadline,context.WithTimeout,context.WithValue,errors.New,fmt.Errorf,fmt.Sprint,fmt.Sprintf,sort.Reverse)
  -unusedresult
        enable unusedresult analysis
  -unusedstringmethods value
        deprecated alias for -unusedresult.stringmethods (default Error,String)
  -v    no effect (deprecated)

To see details and flags of a specific analyzer, run 'vet help name'.

vet 是一个用于对 Go 程序进行静态分析的工具。

vet 检查 Go 源代码并报告可疑的结构,例如 Printf 调用的参数与格式字符串不对齐。它使用的启发式方法不能保证所有报告都是真正的问题,但它可以发现编译器没有捕获的错误。

已注册的分析器:

- asmdecl:报告汇编文件和 Go 声明之间的不匹配
- assign:检查无用的赋值
- atomic:检查使用 sync/atomic 包的常见错误
- bools:检查涉及布尔运算符的常见错误
- buildtag:检查 //go:build 和 // +build 指令
- cgocall:检测一些违反 cgo 指针传递规则的情况
- composites:检查未键控的复合文字
- copylocks:检查错误地通过值传递的锁
- directive:检查 Go 工具链指令,如 //go:debug
- errorsas:报告将非指针或非错误值传递给 errors.As 的情况
- framepointer:报告在保存帧指针之前破坏帧指针的汇编
- httpresponse:检查使用 HTTP 响应时的错误
- ifaceassert:检测不可能的接口到接口类型断言
- loopclosure:检查在嵌套函数内部引用循环变量的情况
- lostcancel:检查是否调用了由 context.WithCancel 返回的 cancel 函数
- nilfunc:检查函数与 nil 的无用比较
- printf:检查 Printf 格式字符串和参数的一致性
- shift:检查移位是否等于或超过整数的宽度
- sigchanyzer:检查未缓冲的 os.Signal 通道
- slog:检查无效的结构化日志调用
- stdmethods:检查众所周知接口的方法签名
- stringintconv:检查 string(int) 转换
- structtag:检查结构体字段标记是否符合 reflect.StructTag.Get
- testinggoroutine:报告从测试启动的 goroutine 中调用 (*testing.T).Fatal。
- tests:检查测试和示例的常见错误用法
- timeformat:检查调用 (time.Time).Format 或 time.Parse 是否使用 2006-02-01
- unmarshal:报告将非指针或非接口值传递给 unmarshal 的情况
- unreachable:检查无法到达的代码
- unsafeptr:检查将 uintptr 转换为 unsafe.Pointer 的无效转换
- unusedresult:检查某些函数调用的未使用结果

默认情况下,所有分析器都会运行。要选择特定的分析器,请为每个分析器使用 -NAME 标志,或者使用 -NAME=false 运行未明确禁用的所有分析器。

核心标志:

- -V:打印版本并退出
- -all:无效(已弃用)
- -asmdecl:启用 asmdecl 分析
- -assign:启用 assign 分析
- -atomic:启用 atomic 分析
- -bool:-bools 的已弃用别名
- -bools:启用 bools 分析
- -buildtag:启用 buildtag 分析
- -buildtags:-buildtag 的已弃用别名
- -c int:以这么多行的上下文显示有问题的代码行(默认值为 -1)
- -cgocall:启用 cgocall 分析
- -composites:启用 composites 分析
- -compositewhitelist:-composites.whitelist 的已弃用别名(默认值为 true)
- -copylocks:启用 copylocks 分析
- -directive:启用 directive 分析
- -errorsas:启用 errorsas 分析
- -flags:以 JSON 格式输出分析器标志
- -framepointer:启用 framepointer 分析
- -httpresponse:启用 httpresponse 分析
- -ifaceassert:启用 ifaceassert 分析
- -json:发出 JSON 输出
- -loopclosure:启用 loopclosure 分析
- -lostcancel:启用 lostcancel 分析
- -methods:-stdmethods 的已弃用别名
- -nilfunc:启用 nilfunc 分析
- -printf:启用 printf 分析
- -printfuncs value:-printf.funcs 的已弃用别名(默认值为 (*log.Logger).Fatal,(*log.Logger).Fatalf,(*log.Logger).Fatalln,(*log.Logger).Panic,(*log.Logger).Panicf,(*log.Logger).Panicln,(*log.Logger).Print,(*log.Logger).Printf,(*log.Logger).Println,(*testing.common).Error,(*testing.common).Errorf,(*testing.common).Fatal,(*testing.common).Fatalf,(*testing.common).Log,(*testing.common).Logf,(*testing.common).Skip,(*testing.common).Skipf,(testing.TB).Error,(testing.TB).Errorf,(testing.TB).Fatal,(testing.TB).Fatalf,(testing.TB).Log,(testing.TB).Logf,(testing.TB).Skip,(testing.TB).Skipf,fmt.Append,fmt.Appendf,fmt.Appendln,fmt.Errorf,fmt.Fprint,fmt.Fprintf,fmt.Fprintln,fmt.Print,fmt.Printf,fmt.Println,fmt.Sprint,fmt.Sprintf,fmt.Sprintln,log.Fatal,log.Fatalf,log.Fatalln,log.Panic,log.Panicf,log.Panicln,log.Print,log.Printf,log.Println,runtime/trace.Logf)
- -rangeloops:-loopclosure 的已弃用别名
- -shift:启用 shift 分析
- -sigchanyzer:启用 sigchanyzer 分析
- -slog:启用 slog 分析
- -source:无效(已弃用)
- -stdmethods:启用 stdmethods 分析
- -stringintconv:启用 stringintconv 分析
- -structtag:启用 structtag 分析
- -tags string:无效(已弃用)
- -testinggoroutine:启用 testinggoroutine 分析
- -tests:启用 tests 分析
- -timeformat:启用 timeformat 分析
- -unmarshal:启用 unmarshal 分析
- -unreachable:启用 unreachable 分析
- -unsafeptr:启用 unsafeptr 分析
- -unusedfuncs value:-unusedresult.funcs 的已弃用别名(默认值为 context.WithCancel,context.WithDeadline,context.WithTimeout,context.WithValue,errors.New,fmt.Errorf,fmt.Sprint,fmt.Sprintf,sort.Reverse)
- -unusedresult:启用 unusedresult 分析
- -unusedstringmethods value:-unusedresult.stringmethods 的已弃用别名(默认值为 Error,String)
- -v:无效(已弃用)

要查看特定分析器的详细信息和标志,请运行 'vet help name'。

文中完整代码,见 github.com/cuishuang/g…

相关使用,何时会抛出提示,可以参考相应的testdata中的a.go文件

1. asmdecl

report mismatches between assembly files and Go declarations

asmdecl 的全称是 assembly declaration,主要用于检查 Go 代码中与汇编声明相关的错误,例如在汇编代码中使用了无效的符号,或者在汇编代码中使用了错误的语法等。它有助于确保 Go 代码中的汇编部分正确地与 Go 代码进行交互,以避免由于汇编代码问题导致的潜在错误。


2. assign

check for useless assignments

该检查器报告 x = x 或 a[i] = a[i] 形式的分配。 这些几乎总是无用的,即使没有用,它们通常也是一个错误。

assigngo vet 中的一个检查项,主要用于检查可能出现的变量赋值问题。

具体来说,assign 检查的是在变量赋值时可能出现的问题,比如:

  1. 将变量赋值给自身,例如 x = x
  2. 在多重赋值中,左边的变量数量和右边的值数量不一致;
  3. 将一个值赋给一个不兼容的变量类型,例如将一个字符串赋给一个整型变量;
  4. ifforswitch 等语句中,将一个值赋给一个布尔型变量而不是比较表达式;
  5. 在赋值语句中,使用了未定义的变量。

go vet中的那些检测项

以下是一些示例代码,展示了 assign 可能会检查出的问题:

package main

func main() {
    x := 1
    x = x  // 将变量赋值给自身
    y, z := 1, 2
    y, z, _ = 1, 2  // 左边的变量数量和右边的值数量不一致
    var a int
    a = "hello"  // 将一个字符串赋给一个整型变量
    b := true
    b = 1  // 在 if、for、switch 等语句中,将一个值赋给一个布尔型变量而不是比较表达式
    c = 1  // 使用了未定义的变量
}

使用 go vet 命令检查上述代码时,会输出以下警告信息:(这些信息不会一次性全部输出,但凡有一个满足就会抛出)

# command-line-arguments
./main.go:5:5: self-assignment of x
./main.go:7:9: assignment mismatch: 2 variables but 3 values
./main.go:9:5: cannot use "hello" (type string) as type int in assignment
./main.go:11:5: b is bool, suggest `b == 1` instead
./main.go:13:5: undefined: c

相关代码: github.com/golang/tool…


3. atomic

check for common mistakes using the sync/atomic package

atomic 是 Go 语言自带的一种原子操作库,用于实现在多个 goroutine 中安全地读写共享变量。go vet 中的 atomic 检查项主要用于检查在使用原子操作时可能出现的一些问题。

go vet中的那些检测项

package main

import (
	"sync/atomic"
)

func main() {
	var x int64
	x = atomic.AddInt64(&x, 1) //  direct assignment to atomic value

}

使用 go vet 命令检查上述代码时,会输出以下警告信息:

# command-line-arguments
./main.go:9:2: direct assignment to atomic value

更多case,参考 github.com/golang/tool…

链接代码展示了 go vet 中的 atomic 检查项可能会检查到的几种直接赋值给原子变量的情况。这些情况可能会导致并发问题,因此需要使用原子操作来确保多个 goroutine 安全地访问共享变量。

具体来说,这段代码分别展示了以下几种情况:

  1. 直接赋值给原子变量:x = atomic.AddUint64(&x, 1)
  2. 左侧包含其他变量的赋值语句:_, x = 10, atomic.AddUint64(&x, 1)x, _ = atomic.AddUint64(&x, 1), 10
  3. 通过指针访问原子变量:*y = atomic.AddUint64(y, 1)
  4. 在结构体中定义的原子变量:su.Counter = atomic.AddUint64(&su.Counter, 1)
  5. 在结构体中定义的指针类型的原子变量:*sp.Counter = atomic.AddUint64(sp.Counter, 1)
  6. 在切片中访问原子变量:au[0] = atomic.AddUint64(&au[0], 1)
  7. 在指针切片中访问原子变量:*ap[0] = atomic.AddUint64(ap[0], 1)

在这些情况中,go vetatomic 检查器会给出警告,提醒程序员需要使用原子操作来保证多个 goroutine 安全地访问共享变量。正确的做法是使用原子操作函数的返回值,而不是将原子操作的结果再次赋值给变量。


4. atomicalign (未集成)

check for non-64-bits-aligned arguments to sync/atomic functions

atomicalign 包定义了一个分析器,用于检查 sync/atomic 函数的非 64 位对齐参数。 在非 32 位平台上,如果这些函数的参数变量不是 64 位对齐,则会出现错误。 因此,调用者有责任安排此类变量的 64 位对齐。

请参阅golang.org/pkg/sync/at…


5. bools

detects common mistakes involving boolean operators

-bools 分析器会检查以下几种常见的布尔表达式错误:

  • 在布尔表达式中使用了非布尔类型的值;
  • 在布尔表达式中使用了常量 truefalse,但该常量实际上不是布尔类型;
  • 在布尔表达式中使用了多余的括号。

go vet中的那些检测项

更多case 参考: github.com/golang/tool…


6. buildssa(未集成)

build SSA-form IR for later passes

包 buildssa 定义了一个分析器,用于构造无错误包的 SSA 表示形式并返回其中所有函数的集合。 它本身不报告任何诊断,但可以用作其他分析器的输入。

buildssa 是 Go Vet 工具中的一个阶段,用于将 Go 代码构建成 SSA 形式(Static Single Assignment)。Go Vet 使用 SSA 形式来分析 Go 代码并执行静态分析。SSA 形式是一种中间表示形式,其中每个变量只赋值一次,并且变量的作用域已经确定。

在构建 SSA 形式时,Go Vet 还会执行其他优化步骤,例如将循环结构转换为尾递归形式,以便更容易地进行静态分析。构建 SSA 形式还可以帮助 Go Vet 检测不同函数之间的数据依赖性。

go vet 命令行上,可以使用 -ssa 标志来控制是否执行 buildssa 阶段。默认情况下,buildssa 阶段是开启的。如果您对 Go 代码进行了自己的静态分析,并且只需要构建 SSA 形式,则可以使用 -ssa=false 来禁用其他分析器,只执行 buildssa 阶段。

buildssa 是 Go 语言静态分析工具 go vet 中的一个阶段,它的作用是将 Go 代码转换为静态单赋值形式(Static Single Assignment,简称 SSA),以便进行更精确的分析。

buildssa 阶段之前,go vet 会先对 Go 代码进行语法分析和类型检查。然后,buildssa 阶段将 Go 代码转换为 SSA 形式,这是一种中间表示形式,对于代码分析和优化非常有用。

SSA 形式的一个重要特点是每个变量只能被赋值一次。这样,每个变量都有一个唯一的定义点,这使得数据流分析更加容易。例如,buildssa 阶段可以检测到未初始化的变量、未使用的变量等问题。此外,由于 SSA 形式是一种静态单赋值形式,它还可以帮助检测一些并发和同步问题。

总之,buildssa 阶段是 go vet 中非常重要的一个阶段,它将 Go 代码转换为 SSA 形式,为后续的分析提供了更精确的基础。


7. buildtag

check //go:build and // +build directives

buildtag 是 Go 语言静态分析工具 go vet 中的一个分析器,用于检测 Go 代码中的 // +build//go:build 编译指令是否正确使用。

在 Go 语言中,// +build//go:build 是编译指令,用于限制编译时的条件。这些指令允许您为不同的平台、操作系统、架构或编译时选项编写不同的代码。例如,您可以使用 // +build linux 来指定只在 Linux 上编译该文件,或者使用 //go:build !windows 来指定在非 Windows 平台上编译该文件。

然而,这些编译指令的错误使用可能会导致编译错误或运行时错误。例如,如果编写了不正确的条件,可能会导致代码在错误的平台上编译或运行。buildtag 分析器会检测这些问题并向您报告它们。

buildtag 分析器检查以下问题:

  • 无效的 // +build//go:build 编译指令,如拼写错误或语法错误。
  • 不同的文件之间的编译指令不一致。
  • 指令中使用了未定义的标记。
  • 指令中使用了未定义的环境变量。
  • 指令中使用了未定义的 GOOS 或 GOARCH。

go vet 命令行上,可以使用 -buildtag 标志来控制是否执行 buildtag 分析器。默认情况下,buildtag 分析器是开启的。如果您不需要检查 // +build//go:build 编译指令,可以使用 -buildtag=false 来禁用它。

当您编写一个跨平台的 Go 项目时,可能会使用 // +build//go:build 编译指令来限制编译时的条件。例如,您可能有一个名为 myutil.go 的文件,其中包含以下编译指令:

// +build linux darwin

package myutil

import "fmt"

func PrintHello() {
    fmt.Println("Hello, World!")
}

这个文件只会在 Linux 和 Darwin 上编译。如果您在 Windows 上尝试编译这个文件,Go 编译器会忽略它,并输出以下警告信息:

# myutil
.\myutil.go:1:2: invalid +build comment: unknown compile target "linux"

这是因为 // +build linux darwin 指令只适用于 Linux 和 Darwin,而不适用于 Windows。在这种情况下,buildtag 分析器会检测到该错误,并向您报告它。

此外,如果您在其他文件中使用了不同的编译指令,例如:

// +build windows

package main

import "myutil"

func main() {
    myutil.PrintHello()
}

这个文件只会在 Windows 上编译。但是,由于它使用了 myutil 包,而 myutil 包只能在 Linux 和 Darwin 上编译,因此会导致编译错误。在这种情况下,buildtag 分析器会检测到不一致的编译指令,并向您报告它。

总之,buildtag 分析器可以帮助您检测和调试 // +build//go:build 编译指令中的错误,以确保您的代码可以正确地编译和运行。


8. cgocall

Package cgocall defines an Analyzer that detects some violations of the cgo pointer passing rules.

cgocall 包定义了一个分析器,用于检测某些违反 cgo 指针传递规则的行为。


9. composite

checks for unkeyed composite literals.

在 Go 语言中,unkeyed literals(非键入字面量)是指在使用 struct 类型的字面量时,不使用字段名来给值赋值的情况。在 struct 类型的字面量中,可以使用字段名来指定每个字段的值,也可以按照 struct 定义中字段的顺序,直接给出每个字段的值,这就是 unkeyed literals。

例如,假设我们有以下定义:

type Person struct {
    Name string
    Age  int
}

我们可以使用以下方式创建一个 Person 类型的字面量:

p1 := Person{Name: "Alice", Age: 30}
p2 := Person{"Bob", 25}

在上面的例子中,p1 使用了字段名来指定每个字段的值,而 p2 没有使用字段名,而是按照 struct 定义中字段的顺序,直接给出了每个字段的值。因此,p2 是一个 unkeyed literals。

需要注意的是,使用 unkeyed literals 可能会导致代码变得脆弱,因为添加或删除字段可能会破坏字面量的结构。因此,通常建议在使用 struct 类型的字面量时,使用字段名来指定每个字段的值,以提高代码的可读性和健壮性。


10. copylock

checks for locks erroneously passed by value.

检查是否有错误地按值传递的锁。

无意中复制包含锁的值,例如sync.Mutex或sync.WaitGroup,可能会导致两个副本都出现故障。

一般这样的值应该通过指针来引用。

copylocks 是 Go 语言静态分析工具 go vet 中的一个分析器,用于检测在并发程序中是否正确地使用了 sync.Mutexsync.RWMutex

在 Go 语言中,sync.Mutexsync.RWMutex 是用于保护共享资源的常用同步原语。使用这些类型的互斥锁来保护共享资源时,需要确保在访问这些资源时正确地加锁和解锁。如果在访问共享资源时没有正确地加锁和解锁,可能会导致数据竞争和其他并发问题。

copylocks 分析器会检查以下问题:

  • 在使用 sync.Mutexsync.RWMutex 时,是否正确地保护了共享资源,并在访问这些资源时正确地加锁和解锁。
  • 在使用 sync.Mutexsync.RWMutex 时,是否正确地处理了复制和移动操作。例如,如果一个值包含一个互斥锁,那么复制或移动该值可能会导致锁的状态不正确。

go vet 命令行上,可以使用 -copylocks 标志来控制是否执行 copylocks 分析器。默认情况下,copylocks 分析器是开启的。如果您不需要检查 sync.Mutexsync.RWMutex 的使用,可以使用 -copylocks=false 来禁用它。

总之,copylocks 分析器可以帮助您编写更健壮的并发程序,确保在访问共享资源时正确地加锁和解锁,并处理复制和移动操作时正确地处理互斥锁的状态。

go vet 是 Go 语言的一个静态分析工具,它可以帮助开发者检查代码中可能存在的错误。其中,copylocksgo vet 的一个检查规则,它的作用是检查是否复制了值含有锁或原子类型的值。

当你复制一个包含锁(如 sync.Mutexsync.WaitGroup)或原子类型(如 sync/atomic 包中的类型)的值时,可能会导致一些非常微妙的错误。例如,如果你复制一个 sync.Mutex,你可能会得到一个未锁定的新 Mutex,这可能会导致数据竞争或其他并发问题。同样,复制一个包含原子类型的值也可能导致类似的问题。

以下是一个简单的实例,演示了 go vetcopylocks 检查如何工作:

package main

import (
	"sync"
)

type myStruct struct {
	sync.Mutex
	value int
}

func main() {
	var original myStruct
	var copy = original // 这里复制了一个含有锁的值
	copy.Lock()
	copy.value = 5
	copy.Unlock()
}

在上述代码中,我们定义了一个结构体 myStruct,它包含一个 sync.Mutex。然后我们创建了 myStruct 的一个实例,然后复制了这个实例。这样就复制了一个含有锁的值,这是 go vetcopylocks 检查希望避免的。

如果你运行 go vet,它会提醒你这个问题:

$ go vet main.go
# command-line-arguments
./main.go:13:13: assignment copies lock value to copy: main.myStruct contains sync.Mutex

在这个错误消息中,go vet 正确地指出了 copy 赋值操作复制了锁值,这可能会导致未预期的行为。

总的来说,go vetcopylocks 检查是一个非常有用的工具,可以帮助你避免一些可能非常微妙和难以调试的并发问题。

go vet中的那些检测项


11. ctrlflow(未集成)

build a control-flow graph

ctrlflow 为函数体提供语法控制流图 (CFG) 的分析。 它记录函数是否无法返回。 其本身不报告任何诊断。

ctrlflow 用于检查 Go 代码中可能存在的控制流问题,例如死循环、空循环、无条件跳转等。

package main

func main() {
    // 死循环
    for {
    }

    // 空循环
    for ; ; {
    }

    // 无条件跳转
    goto label
label:
}

ctrlflow 会检查上述代码中的所有控制流语句,并报告任何可能存在的问题。例如,在第一个例子中,ctrlflow 会报告一个死循环错误。在第二个例子中,ctrlflow 会报告一个空循环错误。在第三个例子中,ctrlflow 会报告一个无条件跳转错误。

ctrlflow 是一个非常有用的工具,可以帮助开发人员发现代码中可能存在的控制流问题。

go vet中的那些检测项


12. deepequalerrors(未集成)

checks for the use of reflect.DeepEqual with error values.

即 不鼓励使用reflect.DeepEqual比较两个error类型的变量

package main

import (
	"errors"
	"fmt"
	"reflect"
)

func main() {
	err1 := errors.New("error 1")
	err2 := errors.New("error 2")

	// 使用 reflect.DeepEqual 比较两个错误。
	if reflect.DeepEqual(err1, err2) {
		fmt.Println("The errors are equal.")
	} else {
		fmt.Println("The errors are not equal.")
	}
}

对上面这段代码执行 go vet ./...,应该会有相应提示。

但很疑惑并没有..

相关issue:

cmd/vet: warn for reflect.DeepEqual on errors

Warn when calling reflect.DeepEqual with errors


13. defers(未集成)

report common mistakes in defer statements

当 defer 语句导致非延迟调用 time 时,defer 分析器会报告诊断。因为,经验表明这几乎总是一个错误。 例如:


//	start := time.Now()
//	...
//	defer recordLatency(time.Since(start)) // error: call to time.Since is not deferred
//
// The correct code is:
//
//	defer func() { recordLatency(time.Since(start)) }()

package main

import (
	"fmt"
	"time"
)

func main() {

	start := time.Now()
	fmt.Println("当前时间:", start)

	defer recordLatency(time.Since(start))

	time.Sleep(3 * time.Second)

}

func recordLatency(duration time.Duration) {

	fmt.Println("共耗时:", duration)

}

输出并不是预期的3s多,而是200多µs

这是因为time.Since会立刻计算,不会等defer执行时才算

上面这段代码,加defer和不加defer效果一样

正确的写法如下:

package main

import (
	"fmt"
	"time"
)

func main() {

	start := time.Now()
	fmt.Println("当前时间:", start)

	defer func() {
		recordLatency(time.Since(start))
	}()

	time.Sleep(3 * time.Second)

}

func recordLatency(duration time.Duration) {

	fmt.Println("共耗时:", duration)

}

输出:

当前时间: 2023-07-25 16:44:26.90604 +0800 CST m=+0.000169751
共耗时: 3.001356959s

这个检测项 是2023年新增的


14. directive

checks known Go toolchain directives

检查已知的 Go 工具链指令

检查 Go 工具链指令,例如 //go:debug

该分析器检查包目录中所有 Go 源文件中已知 Go 工具链指令的问题,甚至是那些被 //go:build 约束排除的文件,以及所有非 Go 源文件。

对于 //go:debug (请参阅 go.dev/doc/godebug… Go 源文件中、仅放置在包注释上方以及仅放置在 package main 或 *_test.go 文件中。

将来可能会添加对其他已知指令的支持。

该分析器不检查 //go:build,它由 buildtag 分析器处理

这个检测项 也是2023年新增的


15. errorsas

checks that the second argument to errors.As is a pointer to a type implementing error

检查 error.As 的第二个参数是否是指向实现错误的类型的指针

1.19新增

package main

import (
	"errors"
	"fmt"
)

func main() {
	err := &MyError{}
	var target error
	fmt.Println(errors.As(err, &target))
}

type MyError struct{}

func (err *MyError) Error() string {
	return "oops!"
}

go vet中的那些检测项

The error is reported by the go vet command. The go test command automatically runs go vet to report significant problems. The go build command does not run the go vet command.

[[Why do I get "second argument to errors.As should not be *error" build error in test only?](stackoverflow.com/questions/7… "[Why do I get "second argument to errors.As should not be *error" build error in test only?")]

go test命令会自动执行go vet; 而执行go build则不会执行go vet


16. fieldalignment(未集成)

find structs that would use less memory if their fields were sorted

该分析器查找可以重新排列以使用更少内存的结构,并提供 具有最紧凑顺序的建议编辑。

结构体字段排序,以占用更少的内存空间


17. findcall(未集成)

find calls to a particular function

findcall 分析器 报告对特定名称的函数或方法的调用

findcall 包定义了一个分析器,用作分析 API 的简单示例和测试。 它报告对由其 -name 标志指定的名称的函数或方法的每次调用的诊断。 它还为每个与名称匹配的声明导出一个事实,如果包包含一个或多个此类声明,则还导出一个包级fact。


18. framepointer

report assembly that clobbers the frame pointer before saving it

报告程序集在保存之前破坏帧指针

framepointer 即FP寄存器 FP寄存器及frame pointer介绍

读取汇编文件 并检查在保存帧指针之前是否破坏了帧指针。如果帧指针被破坏,分析器将使用 Pass.Reportf 函数报告错误。


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)
	}
}

go vet中的那些检测项

更多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) // 这是一个无效的类型断言
}

go vet中的那些检测项

下面这两段仅供参考:

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 等复合语句中递归定义。)

请参阅:golang.org/doc/go_faq.…

go vet中的那些检测项

以下供参考:

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.WithCancelcontext.WithTimeoutcontext.WithDeadline 的使用说明。

在 Go 语言中,context 包提供了一种用于在多个 Goroutine 之间传递取消信号和截止时间的机制,以便在合适的时候取消或超时某个操作。

这段文本中提到的内容是指:当使用 context.WithCancelcontext.WithTimeoutcontext.WithDeadline 函数创建一个新的上下文时,会返回一个取消函数(cancel function)。这个取消函数可以用来取消上下文,即停止所有使用该上下文派生出来的 Goroutine。

然而,如果你创建了一个新的上下文(使用上述函数之一)但没有调用返回的取消函数,那么这个新的上下文将一直处于活动状态,不会自动取消。这意味着其中的 Goroutine 将不会被释放,会继续执行。这可能导致资源泄漏或程序逻辑错误。

需要注意的是,背景上下文(background context)是一个特殊的上下文,它永远不会被取消,因为它是所有上下文的根节点。在使用 context.Background() 创建背景上下文时,不需要担心未调用取消函数的问题,因为它本身永远不会被取消。

所以,如果你在代码中使用了 context.WithCancelcontext.WithTimeoutcontext.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)
}

go vet中的那些检测项

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)
}

go vet中的那些检测项


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信息是:

  1. Package facts: 包级别的信息。包括包的路径、名字、导入路径等。

  2. 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

}

go vet中的那些检测项


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

go vet中的那些检测项

更多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

go vet中的那些检测项

更多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)
	}
}

go vet中的那些检测项

更多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 等函数的调用,但其中参数类型不是指针或接口的情况。

go vet中的那些检测项

更多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"
}

go vet中的那些检测项

更多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项目中未集成进来


转载自:https://juejin.cn/post/7271613128361214013
评论
请登录