NilAway
[!WARNING]
NilAway 目前正处于积极开发阶段:可能会出现误报和破坏性变更。 我们非常感谢您的任何反馈和贡献!
NilAway 是一个静态分析工具,旨在通过捕获 nil 恐慌来帮助开发者避免在生产环境中出现 nil 恐慌, 在编译时而非运行时捕获它们。NilAway 类似于标准的 nilness 分析器,然而,它采用了更 复杂和强大的静态分析技术来跟踪包内以及_跨_包的 nil 流,并 报告错误,为用户提供 nil 流以便更轻松地调试。
NilAway 具有三个使其脱颖而出的关键特性:
-
它是全自动的:NilAway 配备了推断引擎,使其除了标准 Go 代码外,不需要 开发者提供任何额外信息(例如,注释)。
-
它是快速的:我们设计 NilAway 以快速且可扩展,使其适用于大型代码库。在我们的 测量中,当启用 NilAway 时,我们观察到构建时间开销小于 5%。我们还在不断应用 优化以进一步减少其占用空间。
-
它是实用的:它不会阻止代码中_所有_可能的 nil 恐慌,但它捕获了我们在生产中观察到的大多数潜在 nil 恐慌,使 NilAway 能够在有用性和构建时间 开销之间保持良好的平衡。
:star2: 如需更详细的技术讨论,请参阅我们的 docs、Engineering Blog 以及论文(WIP)。
Running NilAway
NilAway 使用标准的 go/analysis 实现,使其易于与现有的分析器 驱动程序(即 golangci-lint、nogo 或 作为独立检查器运行)集成。
[!IMPORTANT]
默认情况下,NilAway 会分析 所有 Go 代码,包括标准库和依赖项。这有助于 NilAway 更好地理解来自依赖项的代码形式,并减少其假阴性。然而,对于拥有大量依赖项的大型 Go 项目, 这也会带来显著的性能开销(对于支持模块化的驱动程序仅发生一次),并增加依赖项中不可操作 错误的数量。我们强烈建议使用 include-pkgs 标志将分析范围缩小到仅针对您的项目 代码。这指示 NilAway 跳过对依赖项(例如第三方库)的分析,使您能够 专注于 NilAway 在您的一方代码中报告的潜在 nil 恐慌!
Standalone Checker
[!IMPORTANT]
由于 NilAway 所执行的分析具有高度复杂性,NilAway 会通过 go/analysis 框架中的 Fact Mechanism 来缓存其针对特定包的分析结果。因此,强烈 建议使用支持模块化分析的驱动程序(即 bazel/nogo 或 golangci-lint,但 不包括 独立检查器,因为它将所有事实存储在内存中),以便在大型项目中获得更好的性能。提供独立检查器主要是出于评估目的,因为它易于上手。
通过运行以下命令从源代码安装二进制文件:
go install go.uber.org/nilaway/cmd/nilaway@latest
然后,通过以下方式运行 linter:
nilaway -include-pkgs="<YOUR_PKG_PREFIX>,<YOUR_PKG_PREFIX_2>" ./...
[!TIP]
以 JSON 格式输出时,请禁用pretty-print标志:nilaway -json -pretty-print=false -include-pkgs="<YOUR_PKG_PREFIX>,<YOUR_PKG_PREFIX_2>" ./...
golangci-lint (>= v1.57.0)
NilAway 目前的形式可能会报告误报。不幸的是,这阻碍了它立即合并到 golangci-lint 并作为 linter 提供(参见 PR#4045)。 因此,您需要将 NilAway 构建为 golangci-lint 的插件,以作为私有 linter 执行。golangci-lint 中有两种插件系统,使用 Module Plugin System(自 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: "<YOUR_PACKAGE_PREFIXES>"
对于 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: "<YOUR_PACKAGE_PREFIXES>"
# 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 获取
说明)。
[!TIP]
缓存自定义二进制文件以避免再次构建以节省资源,如果你使用的是固定版本的 NilAway, 可以使用.custom-gcl.yml文件的哈希值作为缓存键。 如果你使用latest作为 NilAway 版本,可以在缓存键中附加构建日期, 以强制缓存经过一定时间后过期。
(4) 运行自定义二进制文件而不是 golangci-lint:
# Arguments are the same as `golangci-lint`.
$ ./custom-gcl run ./...
Bazel/nogo
使用 bazel/nogo 运行需要稍多一些工作。首先,请按照 rules_go、 gazelle 和 nogo 的说明来配置你的 Go 项目,使其能够使用 bazel/nogo 构建,且不配置或 使用默认的 linter 集合。然后,
(1) 在 tools.go 文件中(或你用于配置工具
依赖的其他文件,参见 Go Modules 文档中的 How can I track tool dependencies for a module?)添加 import _ "go.uber.org/nilaway",
以避免 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 文档 了解如何向 nogo 驱动程序传递配置 JSON,并参阅 我们的 文档 了解如何向 NilAway 传递配置。
代码示例
让我们看几个示例,了解 NilAway 如何帮助防止 nil 恐慌。
// Example 1:
var p *P
if someCondition {
p = &P{}
}
print(p.f) // nilness reports NO error here, but NilAway does.
在此示例中,局部变量 p 仅在 someCondition 为 true 时初始化。在字段访问 p.f 处,如果 someCondition 为 false,可能会发生 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`
如果我们对这个解引用加上一个 nil 检查(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 流,并报告以下错误,描述跨越函数边界的 nil 状态流:
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 中的标准标志传递机制暴露一组标志。 请查看 docs/Configurations 以查看可用的标志以及 如何使用不同的 linter 驱动程序传递它们。
支持
我们遵循与 Go 项目相同的 版本支持策略:我们支持并测试 Go 的最后两个主要版本。
如果您有任何问题、错误 报告或功能请求,请随时 在 GitHub 上提交 issue。
贡献
我们非常欢迎您为 NilAway 做出贡献!请注意,一旦您创建了一个 pull request,您将被要求签署 我们的 Uber 贡献者许可协议。
请参阅 docs/developing 以获取有关开发 NilAway 的更多信息。我们还支持 AI 代理 (它们使用此文件作为初始上下文),但在提交 PR 之前,请审查任何 AI 生成的代码以确保高质量。
许可证
本项目版权归 2023 年 Uber Technologies, Inc. 所有,并根据 Apache 2.0 许可。