diff --git a/go/analysis/passes/nilness/nilness.go b/go/analysis/passes/nilness/nilness.go
index 6f35396..18a100a 100644
--- a/go/analysis/passes/nilness/nilness.go
+++ b/go/analysis/passes/nilness/nilness.go
@@ -7,6 +7,7 @@
import (
_ "embed"
"fmt"
+ "go/ast"
"go/token"
"go/types"
@@ -14,6 +15,7 @@
"golang.org/x/tools/go/analysis/passes/buildssa"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/internal/analysis/analyzerutil"
+ "golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/typeparams"
)
@@ -37,6 +39,14 @@
}
func runFunc(pass *analysis.Pass, fn *ssa.Function) {
+ // Skip cgo-generated functions annotated with
+ // //go:cgo_unsafe_args such as _cgo_cmalloc since
+ // they behave in magical ways not captured by the
+ // SSA representation, leading to false positives.
+ if hasCgoUnsafeArgs(fn) {
+ return
+ }
+
reportf := func(category string, pos token.Pos, format string, args ...any) {
// We ignore nil-checking ssa.Instructions
// that don't correspond to syntax.
@@ -495,3 +505,17 @@
}
return false
}
+
+func hasCgoUnsafeArgs(fn *ssa.Function) bool {
+ for ; fn != nil; fn = fn.Parent() {
+ if decl, ok := fn.Syntax().(*ast.FuncDecl); ok && decl != nil {
+ for _, d := range astutil.Directives(decl.Doc) {
+ if d.Tool == "go" && d.Name == "cgo_unsafe_args" {
+ return true
+ }
+ }
+ }
+
+ }
+ return false
+}
diff --git a/go/analysis/passes/nilness/testdata/src/a/a.go b/go/analysis/passes/nilness/testdata/src/a/a.go
index f7caa01..aa26e66 100644
--- a/go/analysis/passes/nilness/testdata/src/a/a.go
+++ b/go/analysis/passes/nilness/testdata/src/a/a.go
@@ -319,3 +319,20 @@
}
f.Close()
}
+
+//go:cgo_unsafe_args
+func f21(ptr *int) {
+ if ptr == nil {
+ print(*ptr) // nope: cgo_unsafe_args means there is magic afoot that SSA cannot see
+ }
+}
+
+//go:cgo_unsafe_args
+func f22(ptr *int) {
+ if ptr == nil {
+ f := func() {
+ print(*ptr) // nope: cgo_unsafe_args means there is magic afoot that SSA cannot see
+ }
+ f()
+ }
+}