invalid memory address or nil pointer dereference是在 go 语言编程中普遍存在的一个运行时错误,这是一个典型的nil panic。一个nil panic发生是由于程序尝试去解引用一个空指针。nil panic虽然看起来是很小的异常,但是由它产生的问题却不一定小。轻则当前服务失败,重则导致整个程序崩溃。nil panic甚至可以成为就拒绝服务攻击的突破口。
如何产生的
常见的产生nil panic的情况
- 尝试从空指针中取值
var i *int
fmt.Println(*i)
- 尝试操作未初始化的 slice、chan、map
var arr []int
arr[0] = 1
- 尝试操作空指针结构体属性
var p *Person
p.Name = "tom"
- 尝试通过空指针调用方法
var svc *Service
svc.Run()
如何规避
目前还没有一款能够可以依赖的检测工具能做多到完全检测nil panic。目前主要还是编程规范加工具辅助尽可能的规避nil panice。
规范编程
我们可以从nil panic产生的条件出发大致分为两个方向:
-
避免空指针的产生 针对 go 语言产生空指针的途径,进行相应的规范约束:
- var 声明指针变量或引用类型
- 避免使用 var 提前声明指针变量,而是在使用的地方带初始化的声明。
- 使用
:=替代 var 声明变量,以减少产生空指针的风险。
- map 中取不存在的指针变量或引用类型
- 使用
if v, ok := map[idx]; ok取指针变量并进行校验
- 使用
- 关闭的 close 中取指针变量或引用类型
- 使用
if v, ok := <-yourChan; ok取指针变量并进行校验
- 使用
- 函数入参
- 及时对函数入参进行校验
- 函数返回值
- 避免使用
func funcName(...) (t *T)函数签名 - 对于指针类型的函数返回值及时进行校验
- 避免使用
- var 声明指针变量或引用类型
-
在调用前进行非空约束 这个就比较简单了,在所有要使用指针变量的地方加上非空约束即可:
if p == nil { return } p.Name = "tom" -
保底手段 以上两种方式只要能实现一种,即可避免
nil panic的产生,但是这只一个非常理想化的情况,由于业务需求、性能需求、人为因素等,我们往往无法完全按照这些规范进行编码。这时我们会使用一种保底手段。通过recover避免由于nil panic产生的程序崩溃。在所有协程开启时加入:defer func() { err := recover() if err != nil { ... } }()
检测工具
nilnessgolang 提供的一个nil panic检测工具,但是遗憾的是,它只能应对一些非常明显的情况 (e.g., if x == nil { print(*x) })。- NilAway 是一个由 Uber 开发维护的 GitHub 开源项目,用于检测 Gocode 中的潜在 Nil panics。它融合了静态分析技术,全自动、快捷、实用,适合大型代码库使用。
- 下载
go install go.uber.org/nilaway/cmd/nilaway@latest - 使用
nilaway ./...同样遗憾的是,虽然NilAway的检测效果优于nilness,但是目前任然处于开发阶段。对于很多nil panic的检测任然无能为力。目前仅作为一种补充检测手段推荐。
- 下载