摘要
最近开发过程中遇到挺多空指针引发的Coredump,想在CI过程中集成一下Go的代码检查工具,但实际发现这些工具的效果都不行……
一、测试代码
简单写了一些空指针代码,如下所示:
package main
import (
"fmt"
"reflect"
"testGolint/context" // 加载这个库时,就会调用其init(),所以其内部打印在main()之前
"time"
)
type Address struct {
Home string
Email string
}
type User struct {
Name string
Address *Address
}
func testCase0() {
fmt.Println("Test Case 0 Begin..")
var a = User{}
address := a.Address
if address == nil {
fmt.Println(*address) // nil dereference
}
fmt.Printf("Email Address = [%s] \n", address.Email) // 典型的空指针,查不出来
fmt.Println("Test Case 0 Finish..")
}
func testCase1() {
fmt.Println("Test Case 1 Begin..")
var a = &context.Context{}
var user = a.UserAllocate()
fmt.Printf("User Name %s \n", user.Name)
var company = user.WorkExps[0]
fmt.Printf("User Company Name %s \n", company.CompanyName) // 典型的空指针,查不出来
fmt.Println("Test Case 1 Finish..")
}
func testCase2() {
fmt.Println("Test Case 2 Begin..")
var a *context.Context
go func() {
fmt.Println("Before a.")
a = &context.Context{}
fmt.Println("After a")
}()
//time.Sleep(1 * time.Second) // 加上这句就不会dump
go func() {
fmt.Println("Before UserInfo.")
a.UserInfo() // 这行很可能运行在a = &context.Context{}之前,会触发空指针dump
fmt.Println("After UserInfo.")
}()
fmt.Println("Test Case 2 Finish..")
time.Sleep(3 * time.Second)
}
func IsNil(i interface{}) bool {
vi := reflect.ValueOf(i)
if vi.Kind() == reflect.Ptr {
return vi.IsNil()
}
return false
}
type Worker interface {
String() string
}
type Person struct {
Age uint8
Name string
Sex string
}
type Employee struct {
Person
}
type Employer struct {
Title string
Person
}
func (p *Person) String() string {
return fmt.Sprintf("Name: %s, Sex: %s, Age: %d", p.Name, p.Sex, p.Age)
}
func (er *Employer) String() string {
return fmt.Sprintf("%s, Title: %s", er.Person.String(), er.Title)
}
func testCase3() {
var (
w Worker
emptyPoint *Employee
)
w = &Employer{
Title: "Title",
Person: Person{
Name: "Tom",
Age: 50,
Sex: "male",
},
}
if IsNil(w) {
fmt.Printf("interface w is nil")
return
}
// **bad** 是类型断言问题
e := w.(*Employee)
fmt.Println(e.String())
// ok
if e, ok := w.(*Employee); ok {
fmt.Printf("It's Employee, %s\n", e.String())
} else if e, ok := w.(*Employer); ok {
fmt.Printf("It's Employer, %s\n", e.String())
}
// **bad** 在嵌入式结构上定义方法时在nil指针上调用方法
fmt.Println(emptyPoint.String())
}
func main() {
fmt.Println("Test Begin..")
testCase0()
testCase1()
testCase2()
testCase3()
fmt.Println("Test Finish..")
}
开测!
二、Golangci-lint
名气最大的一个工具Golangci-lint。修改.golangci.yml,放开所有的限制,运行后发现核心的问题没查出来,只有一些格式问题:
三、Nilness
Nilness是官方的一个专门检查空指针的工具,但运行后发现只能查几种简单的空指针场景:
四、Staticcheck
Staticcheck,依然不行:
五、Gocritic
Gocritic,不行: