Files
odidere/vendor/honnef.co/go/tools/staticcheck/sa4031/sa4031.go

163 lines
4.1 KiB
Go
Raw Normal View History

2026-06-06 01:17:20 +00:00
package sa4031
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"sort"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/internal/passes/buildir"
"honnef.co/go/tools/pattern"
"honnef.co/go/tools/staticcheck/sa4022"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA4031",
Run: run,
Requires: []*analysis.Analyzer{buildir.Analyzer, inspect.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Checking never-nil value against nil`,
Since: "2022.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var allocationNilCheckQ = pattern.MustParse(`(IfStmt _ cond@(BinaryExpr lhs op@(Or "==" "!=") (Builtin "nil")) _ _)`)
func run(pass *analysis.Pass) (any, error) {
irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg
var path []ast.Node
fn := func(node ast.Node, stack []ast.Node) {
m, ok := code.Match(pass, allocationNilCheckQ, node)
if !ok {
return
}
cond := m.State["cond"].(ast.Node)
if _, ok := code.Match(pass, sa4022.CheckAddressIsNilQ, cond); ok {
// Don't duplicate diagnostics reported by SA4022
return
}
lhs := m.State["lhs"].(ast.Expr)
path = path[:0]
for i := len(stack) - 1; i >= 0; i-- {
path = append(path, stack[i])
}
irfn := ir.EnclosingFunction(irpkg, path)
if irfn == nil {
// For example for functions named "_", because we don't generate IR for them.
return
}
v, isAddr := irfn.ValueForExpr(lhs)
if isAddr {
return
}
seen := map[ir.Value]struct{}{}
var values []ir.Value
var neverNil func(v ir.Value, track bool) bool
neverNil = func(v ir.Value, track bool) bool {
if _, ok := seen[v]; ok {
return true
}
seen[v] = struct{}{}
switch v := v.(type) {
case *ir.MakeClosure, *ir.Function:
if track {
values = append(values, v)
}
return true
case *ir.MakeChan, *ir.MakeMap, *ir.MakeSlice, *ir.Alloc:
if track {
values = append(values, v)
}
return true
case *ir.Slice:
if track {
values = append(values, v)
}
return neverNil(v.X, false)
case *ir.FieldAddr:
if track {
values = append(values, v)
}
return neverNil(v.X, false)
case *ir.Sigma:
return neverNil(v.X, true)
case *ir.Phi:
for _, e := range v.Edges {
if !neverNil(e, true) {
return false
}
}
return true
default:
return false
}
}
if !neverNil(v, true) {
return
}
var qualifier string
if op := m.State["op"].(token.Token); op == token.EQL {
qualifier = "never"
} else {
qualifier = "always"
}
fallback := fmt.Sprintf("this nil check is %s true", qualifier)
sort.Slice(values, func(i, j int) bool { return values[i].Pos() < values[j].Pos() })
if ident, ok := m.State["lhs"].(*ast.Ident); ok {
if _, ok := pass.TypesInfo.ObjectOf(ident).(*types.Var); ok {
var opts []report.Option
if v.Parent() == irfn {
if len(values) == 1 {
opts = append(opts, report.Related(values[0], fmt.Sprintf("this is the value of %s", ident.Name)))
} else {
for _, vv := range values {
opts = append(opts, report.Related(vv, fmt.Sprintf("this is one of the value of %s", ident.Name)))
}
}
}
switch v.(type) {
case *ir.MakeClosure, *ir.Function:
report.Report(pass, cond, "the checked variable contains a function and is never nil; did you mean to call it?", opts...)
default:
report.Report(pass, cond, fallback, opts...)
}
} else {
if _, ok := v.(*ir.Function); ok {
report.Report(pass, cond, "functions are never nil; did you mean to call it?")
} else {
report.Report(pass, cond, fallback)
}
}
} else {
if _, ok := v.(*ir.Function); ok {
report.Report(pass, cond, "functions are never nil; did you mean to call it?")
} else {
report.Report(pass, cond, fallback)
}
}
}
code.PreorderStack(pass, fn, (*ast.IfStmt)(nil))
return nil, nil
}