Golang nilpanic

170 阅读3分钟

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) 函数签名
      • 对于指针类型的函数返回值及时进行校验
  • 在调用前进行非空约束 这个就比较简单了,在所有要使用指针变量的地方加上非空约束即可:

    if p == nil {
    return
    }
    p.Name = "tom"
    
  • 保底手段 以上两种方式只要能实现一种,即可避免nil panic的产生,但是这只一个非常理想化的情况,由于业务需求、性能需求、人为因素等,我们往往无法完全按照这些规范进行编码。这时我们会使用一种保底手段。通过recover避免由于nil panic产生的程序崩溃。在所有协程开启时加入:

    defer func() {
    	err := recover()
    	if err != nil {
    	...
    	}
    }()
    

检测工具

  • nilness golang 提供的一个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的检测任然无能为力。目前仅作为一种补充检测手段推荐。