uber-go/nilaway
GitHub: uber-go/nilaway
NilAway 是一款 Go 语言的静态分析工具,通过在编译时追踪跨函数和跨包的 nil 流来检测并预防潜在的 nil panic。
Stars: 3829 | Forks: 92
# NilAway
[][doc] [][ci] [][cov]
NilAway 是一个静态分析工具,旨在通过在编译时而非运行时捕获 nil panic,来帮助开发者避免在生产环境中发生此类错误。NilAway 类似于标准的
[nilness analyzer](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/nilness),但是,它采用了更加复杂和强大的静态分析技术,来跟踪包内以及_跨_包的 nil 流,并报告错误,为用户提供 nilness 流以便于调试。
NilAway 具备三个使其脱颖而出的关键特性:
- **完全自动化**:NilAway 配备了推理引擎,因此除了标准的 Go 代码外,它_不_需要开发者提供任何额外信息(例如注解)。
- **速度快**:我们将 NilAway 设计得快速且可扩展,使其适用于大型代码库。在我们的测试中,我们发现启用 NilAway 带来的构建时间开销不到 5%。我们也在不断应用优化以进一步减少其资源占用。
- **实用性强**:它不能阻止代码中_所有_可能出现的 nil panic,但它能捕获我们在生产环境中观察到的大多数潜在 nil panic,这使得 NilAway 能够在实用性和构建时间开销之间保持良好的平衡。
:star2: 欲了解更多详细的技术讨论,请查看我们的 [文档][docs]、[工程博客][blog] 和论文(编写中)。
## 运行 NilAway
NilAway 使用标准的 [go/analysis][go-analysis] 实现,使其易于与现有的分析器驱动(例如 [golangci-lint][golangci-lint]、[nogo][nogo] 或 [作为独立检查器运行][singlechecker])集成。
### 独立检查器
通过运行以下命令从源码安装二进制文件:
```
go install go.uber.org/nilaway/cmd/nilaway@latest
```
然后,通过以下命令运行 linter:
```
nilaway -include-pkgs="," ./...
```
### golangci-lint (>= v1.57.0)
NilAway 目前的形式可能会报告误报。这不幸地阻碍了它直接合并到 [golangci-lint][golangci-lint] 中并作为 linter 提供(参见 [PR#4045][pr-4045])。
因此,你需要将 NilAway 构建为 golangci-lint 的插件,以便作为私有 linter 执行。golangci-lint 中有两个插件系统,而使用 [Module Plugin System][golangci-lint-module-plugin](自 v1.57.0 引入)要容易得多,这也是在 golangci-lint 中运行 NilAway 唯一受支持的方法。
(1) 如果尚未在仓库根目录创建 `.custom-gcl.yml` 文件,请先创建它,并添加以下内容:
```
# This has to be >= v1.57.0 for module plugin system support.
version: v1.57.0
plugins:
- module: "go.uber.org/nilaway"
import: "go.uber.org/nilaway/cmd/gclplugin"
version: latest # Or a fixed version for reproducible builds.
```
(2) 将 NilAway 添加到 linter 配置文件 `.golangci.yaml` 中:
对于 golangci-lint v2:
```
version: "2"
linters:
enable:
- nilaway
settings:
custom:
nilaway:
type: module
description: Static analysis tool to detect potential nil panics in Go code.
settings:
# Settings must be a "map from string to string" to mimic command line flags: the keys are
# flag names and the values are the values to the particular flags.
include-pkgs: ""
```
"
# NilAway can be referred to as `nilaway` just like any other golangci-lint analyzers in other
# parts of the configuration file.
```
(3) 构建包含 NilAway 的自定义 golangci-lint 二进制文件:
```
# Note that your `golangci-lint` to bootstrap the custom binary must also be version >= v1.57.0.
$ golangci-lint custom
```
默认情况下,自定义二进制文件将构建在 `.` 目录下,名称为 `custom-gcl`,可以在 `.custom-gcl.yml` 文件中进一步自定义(说明请参见 [Module Plugin System][golangci-lint-module-plugin])。
(4) 运行自定义二进制文件而不是 `golangci-lint`:
```
# Arguments are the same as `golangci-lint`.
$ ./custom-gcl run ./...
```
### Bazel/nogo
使用 bazel/nogo 运行需要稍微多一些步骤。首先,请按照 [rules_go][rules-go]、[gazelle][gazelle] 和 [nogo][nogo] 的说明设置你的 Go 项目,使其能够通过 bazel/nogo 构建,并且不配置或仅配置默认的 linter 集合。然后,
(1) 将 `import _ "go.uber.org/nilaway"` 添加到你的 `tools.go` 文件中(或你用于配置工具依赖项的其他文件,参见 Go Modules 文档中的[如何为模块跟踪工具依赖项?][track-tool-dependencies]),以避免 `go mod tidy` 将 NilAway 作为工具依赖项移除。
(2) 运行以下命令将 NilAway 作为工具依赖项添加到你的项目中:
```
# Get NilAway as a dependency, as well as getting its transitive dependencies in go.mod file.
$ go get go.uber.org/nilaway@latest
# This should not remove NilAway as a dependency in your go.mod file.
$ go mod tidy
# Run gazelle to sync dependencies from go.mod to WORKSPACE file.
$ bazel run //:gazelle -- update-repos -from_file=go.mod
```
(3) 将 NilAway 添加到 nogo 配置中(通常在顶层的 `BUILD.bazel` 文件中):
```
nogo(
name = "my_nogo",
visibility = ["//visibility:public"], # must have public visibility
deps = [
+ "@org_uber_go_nilaway//:go_default_library",
+ "@org_uber_go_nilaway//config:go_default_library", # Add this line if your have rules_go < 0.55.0
],
config = "config.json",
)
```
(4) 运行 bazel build 以查看 NilAway 的工作情况(任何 nogo 错误都会停止 bazel 构建,你可以使用 `--keep_going` 标志来请求 bazel 尽可能多地进行构建):
```
$ bazel build --keep_going //...
```
(5) 关于如何向 nogo 驱动程序传递配置 JSON,请参阅 [nogo 文档][nogo-configure-analyzers];关于如何向 NilAway 传递配置,请参阅我们的[文档][nogo-configure-nilaway]。
## 代码示例
让我们看几个示例,了解 NilAway 如何帮助防止 nil panic。
```
// Example 1:
var p *P
if someCondition {
p = &P{}
}
print(p.f) // nilness reports NO error here, but NilAway does.
```
在此示例中,局部变量 `p` 仅在 `someCondition` 为真时初始化。在字段访问 `p.f` 处,如果 `someCondition` 为假,就可能发生 panic。NilAway 能够捕获这种潜在的 nil 流,并报告以下显示该 nilness 流的错误:
```
go.uber.org/example.go:12:9: error: Potential nil panic detected. Observed nil flow from source to dereference point:
- go.uber.org/example.go:12:9: unassigned variable `p` accessed field `f`
```
如果我们使用 nilness 检查(`if p != nil`)来保护此解引用,错误就会消失。
NilAway 还能捕获跨函数的 nil 流。例如,考虑以下代码片段:
```
// Example 2:
func foo() *int {
return nil
}
func bar() {
print(*foo()) // nilness reports NO error here, but NilAway does.
}
```
在此示例中,函数 `foo` 返回一个 nil 指针,该指针在 `bar` 中被直接解引用,导致只要调用 `bar` 就会发生 panic。NilAway 能够捕获这种潜在的 nil 流,并报告以下错误,描述了跨函数边界的 nilness 流:
```
go.uber.org/example.go:23:13: error: Potential nil panic detected. Observed nil flow from source to dereference point:
- go.uber.org/example.go:20:14: literal `nil` returned from `foo()` in position 0
- go.uber.org/example.go:23:13: result 0 of `foo()` dereferenced
```
请注意,在上面的示例中,`foo` 不一定要与 `bar` 位于同一个包中。NilAway 也能够跟踪跨包的 nil 流。此外,NilAway 还能处理 Go 特定的语言结构,例如接收者、接口、类型断言、类型开关等。
## 配置
我们通过 [go/analysis](https://pkg.go.dev/golang.org/x/tools/go/analysis) 中的标准标志传递机制公开了一组标志。
请查看 [docs/Configurations][docs-configurations] 以了解可用的标志,以及如何使用不同的 linter 驱动程序传递它们。
## 支持
我们遵循与 [Go](https://golang.org/) 项目相同的[版本支持策略](https://go.dev/doc/devel/release#policy):
我们支持并测试 Go 的最后两个主要版本。
如果您有任何问题、错误报告或功能请求,请随时[提交 GitHub issue](https://github.com/uber-go/nilaway/issues)。
## 许可证
该项目版权归 Uber Technologies, Inc. 2023 所有,并在 Apache 2.0 许可下授权。
对于 golangci-lint v1:
``` linters-settings: custom: nilaway: type: "module" description: Static analysis tool to detect potential nil panics in Go code. settings: # Settings must be a "map from string to string" to mimic command line flags: the keys are # flag names and the values are the values to the particular flags. include-pkgs: "标签:EVTX分析, Go, Ruby工具, SOC Prime, 云安全监控, 开发工具, 日志审计, 空指针检测, 静态分析