ITADN
uber-go/nilaway
uber-go/nilaway · 文件 下载 ZIP
文件最后提交记录最后更新时间
README.md
以下内容由 AI 翻译,如有问题请点此提交 issue 反馈

NilAway

GoDoc Build Status Coverage Status

[!WARNING]
NilAway 目前正处于积极开发阶段:可能会出现误报和破坏性变更。 我们非常感谢您的任何反馈和贡献!

NilAway 是一个静态分析工具,旨在通过捕获 nil 恐慌来帮助开发者避免在生产环境中出现 nil 恐慌, 在编译时而非运行时捕获它们。NilAway 类似于标准的 nilness 分析器,然而,它采用了更 复杂和强大的静态分析技术来跟踪包内以及_跨_包的 nil 流,并 报告错误,为用户提供 nil 流以便更轻松地调试。

NilAway 具有三个使其脱颖而出的关键特性:

  • 它是全自动的:NilAway 配备了推断引擎,使其除了标准 Go 代码外,不需要 开发者提供任何额外信息(例如,注释)。

  • 它是快速的:我们设计 NilAway 以快速且可扩展,使其适用于大型代码库。在我们的 测量中,当启用 NilAway 时,我们观察到构建时间开销小于 5%。我们还在不断应用 优化以进一步减少其占用空间。

  • 它是实用的:它不会阻止代码中_所有_可能的 nil 恐慌,但它捕获了我们在生产中观察到的大多数潜在 nil 恐慌,使 NilAway 能够在有用性和构建时间 开销之间保持良好的平衡。

:star2: 如需更详细的技术讨论,请参阅我们的 docsEngineering Blog 以及论文(WIP)。

Running NilAway

NilAway 使用标准的 go/analysis 实现,使其易于与现有的分析器 驱动程序(即 golangci-lintnogo作为独立检查器运行)集成。

[!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_gogazellenogo 的说明来配置你的 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 许可。