Update go version
This commit is contained in:
161
vendor/honnef.co/go/tools/analysis/callcheck/callcheck.go
vendored
Normal file
161
vendor/honnef.co/go/tools/analysis/callcheck/callcheck.go
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
// Package callcheck provides a framework for validating arguments in function calls.
|
||||
package callcheck
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/constant"
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/go/ir/irutil"
|
||||
"honnef.co/go/tools/go/types/typeutil"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
)
|
||||
|
||||
type Call struct {
|
||||
Pass *analysis.Pass
|
||||
Instr ir.CallInstruction
|
||||
Args []*Argument
|
||||
|
||||
Parent *ir.Function
|
||||
|
||||
invalids []string
|
||||
}
|
||||
|
||||
func (c *Call) Invalid(msg string) {
|
||||
c.invalids = append(c.invalids, msg)
|
||||
}
|
||||
|
||||
type Argument struct {
|
||||
Value Value
|
||||
invalids []string
|
||||
}
|
||||
|
||||
type Value struct {
|
||||
Value ir.Value
|
||||
}
|
||||
|
||||
func (arg *Argument) Invalid(msg string) {
|
||||
arg.invalids = append(arg.invalids, msg)
|
||||
}
|
||||
|
||||
type Check func(call *Call)
|
||||
|
||||
func Analyzer(rules map[string]Check) func(pass *analysis.Pass) (any, error) {
|
||||
return func(pass *analysis.Pass) (any, error) {
|
||||
return checkCalls(pass, rules)
|
||||
}
|
||||
}
|
||||
|
||||
func checkCalls(pass *analysis.Pass, rules map[string]Check) (any, error) {
|
||||
cb := func(caller *ir.Function, site ir.CallInstruction, callee *ir.Function) {
|
||||
obj, ok := callee.Object().(*types.Func)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
r, ok := rules[typeutil.FuncName(obj)]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var args []*Argument
|
||||
irargs := site.Common().Args
|
||||
if callee.Signature.Recv() != nil {
|
||||
irargs = irargs[1:]
|
||||
}
|
||||
for _, arg := range irargs {
|
||||
if iarg, ok := arg.(*ir.MakeInterface); ok {
|
||||
arg = iarg.X
|
||||
}
|
||||
args = append(args, &Argument{Value: Value{arg}})
|
||||
}
|
||||
call := &Call{
|
||||
Pass: pass,
|
||||
Instr: site,
|
||||
Args: args,
|
||||
Parent: site.Parent(),
|
||||
}
|
||||
r(call)
|
||||
|
||||
var astcall *ast.CallExpr
|
||||
switch source := site.Source().(type) {
|
||||
case *ast.CallExpr:
|
||||
astcall = source
|
||||
case *ast.DeferStmt:
|
||||
astcall = source.Call
|
||||
case *ast.GoStmt:
|
||||
astcall = source.Call
|
||||
case nil:
|
||||
// TODO(dh): I am not sure this can actually happen. If it
|
||||
// can't, we should remove this case, and also stop
|
||||
// checking for astcall == nil in the code that follows.
|
||||
default:
|
||||
panic(fmt.Sprintf("unhandled case %T", source))
|
||||
}
|
||||
|
||||
for idx, arg := range call.Args {
|
||||
for _, e := range arg.invalids {
|
||||
if astcall != nil {
|
||||
if idx < len(astcall.Args) {
|
||||
report.Report(pass, astcall.Args[idx], e)
|
||||
} else {
|
||||
// this is an instance of fn1(fn2()) where fn2
|
||||
// returns multiple values. Report the error
|
||||
// at the next-best position that we have, the
|
||||
// first argument. An example of a check that
|
||||
// triggers this is checkEncodingBinaryRules.
|
||||
report.Report(pass, astcall.Args[0], e)
|
||||
}
|
||||
} else {
|
||||
report.Report(pass, site, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, e := range call.invalids {
|
||||
report.Report(pass, call.Instr, e)
|
||||
}
|
||||
}
|
||||
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
|
||||
eachCall(fn, cb)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func eachCall(fn *ir.Function, cb func(caller *ir.Function, site ir.CallInstruction, callee *ir.Function)) {
|
||||
for _, b := range fn.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
if site, ok := instr.(ir.CallInstruction); ok {
|
||||
if g := site.Common().StaticCallee(); g != nil {
|
||||
cb(fn, site, g)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ExtractConstExpectKind(v Value, kind constant.Kind) *ir.Const {
|
||||
k := extractConst(v.Value)
|
||||
if k == nil || k.Value == nil || k.Value.Kind() != kind {
|
||||
return nil
|
||||
}
|
||||
return k
|
||||
}
|
||||
|
||||
func ExtractConst(v Value) *ir.Const {
|
||||
return extractConst(v.Value)
|
||||
}
|
||||
|
||||
func extractConst(v ir.Value) *ir.Const {
|
||||
v = irutil.Flatten(v)
|
||||
switch v := v.(type) {
|
||||
case *ir.Const:
|
||||
return v
|
||||
case *ir.MakeInterface:
|
||||
return extractConst(v.X)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
591
vendor/honnef.co/go/tools/analysis/code/code.go
vendored
Normal file
591
vendor/honnef.co/go/tools/analysis/code/code.go
vendored
Normal file
@@ -0,0 +1,591 @@
|
||||
// Package code answers structural and type questions about Go code.
|
||||
package code
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build/constraint"
|
||||
"go/constant"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"go/version"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"honnef.co/go/tools/analysis/facts/generated"
|
||||
"honnef.co/go/tools/analysis/facts/purity"
|
||||
"honnef.co/go/tools/analysis/facts/tokenfile"
|
||||
"honnef.co/go/tools/go/types/typeutil"
|
||||
"honnef.co/go/tools/knowledge"
|
||||
"honnef.co/go/tools/pattern"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
type Positioner interface {
|
||||
Pos() token.Pos
|
||||
}
|
||||
|
||||
func IsOfStringConvertibleByteSlice(pass *analysis.Pass, expr ast.Expr) bool {
|
||||
typ, ok := pass.TypesInfo.TypeOf(expr).Underlying().(*types.Slice)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
elem := types.Unalias(typ.Elem())
|
||||
if version.Compare(LanguageVersion(pass, expr), "go1.18") >= 0 {
|
||||
// Before Go 1.18, one could not directly convert from []T (where 'type T byte')
|
||||
// to string. See also https://github.com/golang/go/issues/23536.
|
||||
elem = elem.Underlying()
|
||||
}
|
||||
return types.Identical(elem, types.Typ[types.Byte])
|
||||
}
|
||||
|
||||
func IsOfPointerToTypeWithName(pass *analysis.Pass, expr ast.Expr, name string) bool {
|
||||
ptr, ok := types.Unalias(pass.TypesInfo.TypeOf(expr)).(*types.Pointer)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return typeutil.IsTypeWithName(ptr.Elem(), name)
|
||||
}
|
||||
|
||||
func IsOfTypeWithName(pass *analysis.Pass, expr ast.Expr, name string) bool {
|
||||
return typeutil.IsTypeWithName(pass.TypesInfo.TypeOf(expr), name)
|
||||
}
|
||||
|
||||
func IsInTest(pass *analysis.Pass, node Positioner) bool {
|
||||
// FIXME(dh): this doesn't work for global variables with
|
||||
// initializers
|
||||
f := pass.Fset.File(node.Pos())
|
||||
return f != nil && strings.HasSuffix(f.Name(), "_test.go")
|
||||
}
|
||||
|
||||
// IsMain reports whether the package being processed is a package
|
||||
// main.
|
||||
func IsMain(pass *analysis.Pass) bool {
|
||||
return pass.Pkg.Name() == "main"
|
||||
}
|
||||
|
||||
// IsMainLike reports whether the package being processed is a
|
||||
// main-like package. A main-like package is a package that is
|
||||
// package main, or that is intended to be used by a tool framework
|
||||
// such as cobra to implement a command.
|
||||
//
|
||||
// Note that this function errs on the side of false positives; it may
|
||||
// return true for packages that aren't main-like. IsMainLike is
|
||||
// intended for analyses that wish to suppress diagnostics for
|
||||
// main-like packages to avoid false positives.
|
||||
func IsMainLike(pass *analysis.Pass) bool {
|
||||
if pass.Pkg.Name() == "main" {
|
||||
return true
|
||||
}
|
||||
for _, imp := range pass.Pkg.Imports() {
|
||||
if imp.Path() == "github.com/spf13/cobra" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func SelectorName(pass *analysis.Pass, expr *ast.SelectorExpr) string {
|
||||
info := pass.TypesInfo
|
||||
sel := info.Selections[expr]
|
||||
if sel == nil {
|
||||
switch x := expr.X.(type) {
|
||||
case *ast.Ident:
|
||||
pkg, ok := info.ObjectOf(x).(*types.PkgName)
|
||||
if !ok {
|
||||
return fmt.Sprintf("(%s).%s", info.TypeOf(x), expr.Sel.Name)
|
||||
}
|
||||
return fmt.Sprintf("%s.%s", pkg.Imported().Path(), expr.Sel.Name)
|
||||
case *ast.SelectorExpr:
|
||||
return fmt.Sprintf("(%s).%s", SelectorName(pass, x), expr.Sel.Name)
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported selector: %v", expr))
|
||||
}
|
||||
}
|
||||
if v, ok := sel.Obj().(*types.Var); ok && v.IsField() {
|
||||
return fmt.Sprintf("(%s).%s", typeutil.DereferenceR(sel.Recv()), sel.Obj().Name())
|
||||
} else {
|
||||
return fmt.Sprintf("(%s).%s", sel.Recv(), sel.Obj().Name())
|
||||
}
|
||||
}
|
||||
|
||||
func IsNil(pass *analysis.Pass, expr ast.Expr) bool {
|
||||
return pass.TypesInfo.Types[expr].IsNil()
|
||||
}
|
||||
|
||||
func BoolConst(pass *analysis.Pass, expr ast.Expr) bool {
|
||||
val := pass.TypesInfo.ObjectOf(expr.(*ast.Ident)).(*types.Const).Val()
|
||||
return constant.BoolVal(val)
|
||||
}
|
||||
|
||||
func IsBoolConst(pass *analysis.Pass, expr ast.Expr) bool {
|
||||
// We explicitly don't support typed bools because more often than
|
||||
// not, custom bool types are used as binary enums and the explicit
|
||||
// comparison is desired. We err on the side of false negatives and
|
||||
// treat aliases like other custom types.
|
||||
|
||||
ident, ok := expr.(*ast.Ident)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
obj := pass.TypesInfo.ObjectOf(ident)
|
||||
c, ok := obj.(*types.Const)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
basic, ok := c.Type().(*types.Basic)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if basic.Kind() != types.UntypedBool && basic.Kind() != types.Bool {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func ExprToInt(pass *analysis.Pass, expr ast.Expr) (int64, bool) {
|
||||
tv := pass.TypesInfo.Types[expr]
|
||||
if tv.Value == nil {
|
||||
return 0, false
|
||||
}
|
||||
if tv.Value.Kind() != constant.Int {
|
||||
return 0, false
|
||||
}
|
||||
return constant.Int64Val(tv.Value)
|
||||
}
|
||||
|
||||
func ExprToString(pass *analysis.Pass, expr ast.Expr) (string, bool) {
|
||||
val := pass.TypesInfo.Types[expr].Value
|
||||
if val == nil {
|
||||
return "", false
|
||||
}
|
||||
if val.Kind() != constant.String {
|
||||
return "", false
|
||||
}
|
||||
return constant.StringVal(val), true
|
||||
}
|
||||
|
||||
func CallName(pass *analysis.Pass, call *ast.CallExpr) string {
|
||||
// See the comment in typeutil.FuncName for why this doesn't require special handling
|
||||
// of aliases.
|
||||
|
||||
fun := ast.Unparen(call.Fun)
|
||||
|
||||
// Instantiating a function cannot return another generic function, so doing this once is enough
|
||||
switch idx := fun.(type) {
|
||||
case *ast.IndexExpr:
|
||||
fun = idx.X
|
||||
case *ast.IndexListExpr:
|
||||
fun = idx.X
|
||||
}
|
||||
|
||||
// (foo)[T] is not a valid instantiation, so no need to unparen again.
|
||||
|
||||
switch fun := fun.(type) {
|
||||
case *ast.SelectorExpr:
|
||||
fn, ok := pass.TypesInfo.ObjectOf(fun.Sel).(*types.Func)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return typeutil.FuncName(fn)
|
||||
case *ast.Ident:
|
||||
obj := pass.TypesInfo.ObjectOf(fun)
|
||||
switch obj := obj.(type) {
|
||||
case *types.Func:
|
||||
return typeutil.FuncName(obj)
|
||||
case *types.Builtin:
|
||||
return obj.Name()
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func IsCallTo(pass *analysis.Pass, node ast.Node, name string) bool {
|
||||
// See the comment in typeutil.FuncName for why this doesn't require special handling
|
||||
// of aliases.
|
||||
|
||||
call, ok := node.(*ast.CallExpr)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return CallName(pass, call) == name
|
||||
}
|
||||
|
||||
func IsCallToAny(pass *analysis.Pass, node ast.Node, names ...string) bool {
|
||||
// See the comment in typeutil.FuncName for why this doesn't require special handling
|
||||
// of aliases.
|
||||
|
||||
call, ok := node.(*ast.CallExpr)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
q := CallName(pass, call)
|
||||
return slices.Contains(names, q)
|
||||
}
|
||||
|
||||
func File(pass *analysis.Pass, node Positioner) *ast.File {
|
||||
m := pass.ResultOf[tokenfile.Analyzer].(map[*token.File]*ast.File)
|
||||
return m[pass.Fset.File(node.Pos())]
|
||||
}
|
||||
|
||||
// BuildConstraints returns the build constraints for file f. It considers both //go:build lines as well as
|
||||
// GOOS and GOARCH in file names.
|
||||
func BuildConstraints(pass *analysis.Pass, f *ast.File) (constraint.Expr, bool) {
|
||||
var expr constraint.Expr
|
||||
for _, cmt := range f.Comments {
|
||||
if len(cmt.List) == 0 {
|
||||
continue
|
||||
}
|
||||
for _, el := range cmt.List {
|
||||
if el.Pos() > f.Package {
|
||||
break
|
||||
}
|
||||
if line := el.Text; strings.HasPrefix(line, "//go:build") {
|
||||
var err error
|
||||
expr, err = constraint.Parse(line)
|
||||
if err != nil {
|
||||
expr = nil
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
name := pass.Fset.PositionFor(f.Pos(), false).Filename
|
||||
oexpr := constraintsFromName(name)
|
||||
if oexpr != nil {
|
||||
if expr == nil {
|
||||
expr = oexpr
|
||||
} else {
|
||||
expr = &constraint.AndExpr{X: expr, Y: oexpr}
|
||||
}
|
||||
}
|
||||
|
||||
return expr, expr != nil
|
||||
}
|
||||
|
||||
func constraintsFromName(name string) constraint.Expr {
|
||||
name = filepath.Base(name)
|
||||
name = strings.TrimSuffix(name, ".go")
|
||||
name = strings.TrimSuffix(name, "_test")
|
||||
var goos, goarch string
|
||||
switch strings.Count(name, "_") {
|
||||
case 0:
|
||||
// No GOOS or GOARCH in the file name.
|
||||
case 1:
|
||||
_, c, _ := strings.Cut(name, "_")
|
||||
if _, ok := knowledge.KnownGOOS[c]; ok {
|
||||
goos = c
|
||||
} else if _, ok := knowledge.KnownGOARCH[c]; ok {
|
||||
goarch = c
|
||||
}
|
||||
default:
|
||||
n := strings.LastIndex(name, "_")
|
||||
if _, ok := knowledge.KnownGOOS[name[n+1:]]; ok {
|
||||
// The file name is *_stuff_GOOS.go
|
||||
goos = name[n+1:]
|
||||
} else if _, ok := knowledge.KnownGOARCH[name[n+1:]]; ok {
|
||||
// The file name is *_GOOS_GOARCH.go or *_stuff_GOARCH.go
|
||||
goarch = name[n+1:]
|
||||
_, c, _ := strings.Cut(name[:n], "_")
|
||||
if _, ok := knowledge.KnownGOOS[c]; ok {
|
||||
// The file name is *_GOOS_GOARCH.go
|
||||
goos = c
|
||||
}
|
||||
} else {
|
||||
// The file name could also be something like foo_windows_nonsense.go — and because nonsense
|
||||
// isn't a known GOARCH, "windows" won't be interpreted as a GOOS, either.
|
||||
}
|
||||
}
|
||||
|
||||
var expr constraint.Expr
|
||||
if goos != "" {
|
||||
expr = &constraint.TagExpr{Tag: goos}
|
||||
}
|
||||
if goarch != "" {
|
||||
if expr == nil {
|
||||
expr = &constraint.TagExpr{Tag: goarch}
|
||||
} else {
|
||||
expr = &constraint.AndExpr{X: expr, Y: &constraint.TagExpr{Tag: goarch}}
|
||||
}
|
||||
}
|
||||
return expr
|
||||
}
|
||||
|
||||
// IsGenerated reports whether pos is in a generated file. It ignores
|
||||
// //line directives.
|
||||
func IsGenerated(pass *analysis.Pass, pos token.Pos) bool {
|
||||
_, ok := Generator(pass, pos)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Generator returns the generator that generated the file containing
|
||||
// pos. It ignores //line directives.
|
||||
func Generator(pass *analysis.Pass, pos token.Pos) (generated.Generator, bool) {
|
||||
file := pass.Fset.PositionFor(pos, false).Filename
|
||||
m := pass.ResultOf[generated.Analyzer].(map[string]generated.Generator)
|
||||
g, ok := m[file]
|
||||
return g, ok
|
||||
}
|
||||
|
||||
// MayHaveSideEffects reports whether expr may have side effects. If
|
||||
// the purity argument is nil, this function implements a purely
|
||||
// syntactic check, meaning that any function call may have side
|
||||
// effects, regardless of the called function's body. Otherwise,
|
||||
// purity will be consulted to determine the purity of function calls.
|
||||
func MayHaveSideEffects(pass *analysis.Pass, expr ast.Expr, purity purity.Result) bool {
|
||||
switch expr := expr.(type) {
|
||||
case *ast.BadExpr:
|
||||
return true
|
||||
case *ast.Ellipsis:
|
||||
return MayHaveSideEffects(pass, expr.Elt, purity)
|
||||
case *ast.FuncLit:
|
||||
// the literal itself cannot have side effects, only calling it
|
||||
// might, which is handled by CallExpr.
|
||||
return false
|
||||
case *ast.ArrayType, *ast.StructType, *ast.FuncType, *ast.InterfaceType, *ast.MapType, *ast.ChanType:
|
||||
// types cannot have side effects
|
||||
return false
|
||||
case *ast.BasicLit:
|
||||
return false
|
||||
case *ast.BinaryExpr:
|
||||
return MayHaveSideEffects(pass, expr.X, purity) || MayHaveSideEffects(pass, expr.Y, purity)
|
||||
case *ast.CallExpr:
|
||||
if purity == nil {
|
||||
return true
|
||||
}
|
||||
switch obj := typeutil.Callee(pass.TypesInfo, expr).(type) {
|
||||
case *types.Func:
|
||||
if _, ok := purity[obj]; !ok {
|
||||
return true
|
||||
}
|
||||
case *types.Builtin:
|
||||
switch obj.Name() {
|
||||
case "len", "cap":
|
||||
default:
|
||||
return true
|
||||
}
|
||||
default:
|
||||
return true
|
||||
}
|
||||
for _, arg := range expr.Args {
|
||||
if MayHaveSideEffects(pass, arg, purity) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
case *ast.CompositeLit:
|
||||
if MayHaveSideEffects(pass, expr.Type, purity) {
|
||||
return true
|
||||
}
|
||||
for _, elt := range expr.Elts {
|
||||
if MayHaveSideEffects(pass, elt, purity) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
case *ast.Ident:
|
||||
return false
|
||||
case *ast.IndexExpr:
|
||||
return MayHaveSideEffects(pass, expr.X, purity) || MayHaveSideEffects(pass, expr.Index, purity)
|
||||
case *ast.IndexListExpr:
|
||||
// In theory, none of the checks are necessary, as IndexListExpr only involves types. But there is no harm in
|
||||
// being safe.
|
||||
if MayHaveSideEffects(pass, expr.X, purity) {
|
||||
return true
|
||||
}
|
||||
for _, idx := range expr.Indices {
|
||||
if MayHaveSideEffects(pass, idx, purity) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
case *ast.KeyValueExpr:
|
||||
return MayHaveSideEffects(pass, expr.Key, purity) || MayHaveSideEffects(pass, expr.Value, purity)
|
||||
case *ast.SelectorExpr:
|
||||
return MayHaveSideEffects(pass, expr.X, purity)
|
||||
case *ast.SliceExpr:
|
||||
return MayHaveSideEffects(pass, expr.X, purity) ||
|
||||
MayHaveSideEffects(pass, expr.Low, purity) ||
|
||||
MayHaveSideEffects(pass, expr.High, purity) ||
|
||||
MayHaveSideEffects(pass, expr.Max, purity)
|
||||
case *ast.StarExpr:
|
||||
return MayHaveSideEffects(pass, expr.X, purity)
|
||||
case *ast.TypeAssertExpr:
|
||||
return MayHaveSideEffects(pass, expr.X, purity)
|
||||
case *ast.UnaryExpr:
|
||||
if MayHaveSideEffects(pass, expr.X, purity) {
|
||||
return true
|
||||
}
|
||||
return expr.Op == token.ARROW || expr.Op == token.AND
|
||||
case *ast.ParenExpr:
|
||||
return MayHaveSideEffects(pass, expr.X, purity)
|
||||
case nil:
|
||||
return false
|
||||
default:
|
||||
panic(fmt.Sprintf("internal error: unhandled type %T", expr))
|
||||
}
|
||||
}
|
||||
|
||||
// LanguageVersion returns the version of the Go language that node has access to. This
|
||||
// might differ from the version of the Go standard library.
|
||||
func LanguageVersion(pass *analysis.Pass, node Positioner) string {
|
||||
// As of Go 1.21, two places can specify the minimum Go version:
|
||||
// - 'go' directives in go.mod and go.work files
|
||||
// - individual files by using '//go:build'
|
||||
//
|
||||
// Individual files can upgrade to a higher version than the module version. Individual files
|
||||
// can also downgrade to a lower version, but only if the module version is at least Go 1.21.
|
||||
//
|
||||
// The restriction on downgrading doesn't matter to us. All language changes before Go 1.22 will
|
||||
// not type-check on versions that are too old, and thus never reach our analyzes. In practice,
|
||||
// such ineffective downgrading will always be useless, as the compiler will not restrict the
|
||||
// language features used, and doesn't ever rely on minimum versions to restrict the use of the
|
||||
// standard library. However, for us, both choices (respecting or ignoring ineffective
|
||||
// downgrading) have equal complexity, but only respecting it has a non-zero chance of reducing
|
||||
// noisy positives.
|
||||
//
|
||||
// The minimum Go versions are exposed via go/ast.File.GoVersion and go/types.Package.GoVersion.
|
||||
// ast.File's version is populated by the parser, whereas types.Package's version is populated
|
||||
// from the Go version specified in the types.Config, which is set by our package loader, based
|
||||
// on the module information provided by go/packages, via 'go list -json'.
|
||||
//
|
||||
// As of Go 1.21, standard library packages do not present themselves as modules, and thus do
|
||||
// not have a version set on their types.Package. In this case, we fall back to the version
|
||||
// provided by our '-go' flag. In most cases, '-go' defaults to 'module', which falls back to
|
||||
// the Go version that Staticcheck was built with when no module information exists. In the
|
||||
// future, the standard library will hopefully be a proper module (see
|
||||
// https://github.com/golang/go/issues/61174#issuecomment-1622471317). In that case, the version
|
||||
// of standard library packages will match that of the used Go version. At that point,
|
||||
// Staticcheck will refuse to work with Go versions that are too new, to avoid misinterpreting
|
||||
// code due to language changes.
|
||||
//
|
||||
// We also lack module information when building in GOPATH mode. In this case, the implied
|
||||
// language version is at most Go 1.21, as per https://github.com/golang/go/issues/60915. We
|
||||
// don't handle this yet, and it will not matter until Go 1.22.
|
||||
//
|
||||
// It is not clear how per-file downgrading behaves in GOPATH mode. On the one hand, no module
|
||||
// version at all is provided, which should preclude per-file downgrading. On the other hand,
|
||||
// https://github.com/golang/go/issues/60915 suggests that the language version is at most 1.21
|
||||
// in GOPATH mode, which would allow per-file downgrading. Again it doesn't affect us, as all
|
||||
// relevant language changes before Go 1.22 will lead to type-checking failures and never reach
|
||||
// us.
|
||||
//
|
||||
// Per-file upgrading is permitted in GOPATH mode.
|
||||
|
||||
// If the file has its own Go version, we will return that. Otherwise, we default to
|
||||
// the type checker's GoVersion, which is populated from either the Go module, or from
|
||||
// our '-go' flag.
|
||||
return pass.TypesInfo.FileVersions[File(pass, node)]
|
||||
}
|
||||
|
||||
// StdlibVersion returns the version of the Go standard library that node can expect to
|
||||
// have access to. This might differ from the language version for versions of Go older
|
||||
// than 1.21.
|
||||
func StdlibVersion(pass *analysis.Pass, node Positioner) string {
|
||||
// The Go version as specified in go.mod or via the '-go' flag
|
||||
n := pass.Pkg.GoVersion()
|
||||
|
||||
f := File(pass, node)
|
||||
if f == nil {
|
||||
panic(fmt.Sprintf("no file found for node with position %s", pass.Fset.PositionFor(node.Pos(), false)))
|
||||
}
|
||||
|
||||
if nf := f.GoVersion; nf != "" {
|
||||
if version.Compare(n, "go1.21") == -1 {
|
||||
// Before Go 1.21, the Go version set in go.mod specified the maximum language
|
||||
// version available to the module. It wasn't uncommon to set the version to
|
||||
// Go 1.20 but restrict usage of 1.20 functionality (both language and stdlib)
|
||||
// to files tagged for 1.20, and supporting a lower version overall. As such,
|
||||
// a file tagged lower than the module version couldn't expect to have access
|
||||
// to the standard library of the version set in go.mod.
|
||||
//
|
||||
// At the same time, a file tagged higher than the module version, while not
|
||||
// able to use newer language features, would still have been able to use a
|
||||
// newer standard library.
|
||||
//
|
||||
// While Go 1.21's behavior has been backported to 1.19.11 and 1.20.6, users'
|
||||
// expectations have not.
|
||||
return nf
|
||||
} else {
|
||||
// Go 1.21 and newer refuse to build modules that depend on versions newer
|
||||
// than the used version of the Go toolchain. This means that in a 1.22 module
|
||||
// with a file tagged as 1.17, the file can expect to have access to 1.22's
|
||||
// standard library (but not to 1.22 language features). A file tagged with a
|
||||
// version higher than the minimum version has access to the newer standard
|
||||
// library (and language features.)
|
||||
//
|
||||
// Do note that strictly speaking we're conflating the Go version and the
|
||||
// module version in our check. Nothing is stopping a user from using Go 1.17
|
||||
// (which didn't implement the new rules for versions in go.mod) to build a Go
|
||||
// 1.22 module, in which case a file tagged with go1.17 will not have access to the 1.22
|
||||
// standard library. However, we believe that if a module requires 1.21 or
|
||||
// newer, then the author clearly expects the new behavior, and doesn't care
|
||||
// for the old one. Otherwise they would've specified an older version.
|
||||
//
|
||||
// In other words, the module version also specifies what it itself actually means, with
|
||||
// >=1.21 being a minimum version for the toolchain, and <1.21 being a maximum version for
|
||||
// the language.
|
||||
|
||||
if version.Compare(nf, n) == 1 {
|
||||
return nf
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
var integerLiteralQ = pattern.MustParse(`(IntegerLiteral tv)`)
|
||||
|
||||
func IntegerLiteral(pass *analysis.Pass, node ast.Node) (types.TypeAndValue, bool) {
|
||||
m, ok := Match(pass, integerLiteralQ, node)
|
||||
if !ok {
|
||||
return types.TypeAndValue{}, false
|
||||
}
|
||||
return m.State["tv"].(types.TypeAndValue), true
|
||||
}
|
||||
|
||||
func IsIntegerLiteral(pass *analysis.Pass, node ast.Node, value constant.Value) bool {
|
||||
tv, ok := IntegerLiteral(pass, node)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return constant.Compare(tv.Value, token.EQL, value)
|
||||
}
|
||||
|
||||
// IsMethod reports whether expr is a method call of a named method with signature meth.
|
||||
// If name is empty, it is not checked.
|
||||
// For now, method expressions (Type.Method(recv, ..)) are not considered method calls.
|
||||
func IsMethod(pass *analysis.Pass, expr *ast.SelectorExpr, name string, meth *types.Signature) bool {
|
||||
if name != "" && expr.Sel.Name != name {
|
||||
return false
|
||||
}
|
||||
sel, ok := pass.TypesInfo.Selections[expr]
|
||||
if !ok || sel.Kind() != types.MethodVal {
|
||||
return false
|
||||
}
|
||||
return types.Identical(sel.Type(), meth)
|
||||
}
|
||||
|
||||
func RefersTo(pass *analysis.Pass, expr ast.Expr, ident types.Object) bool {
|
||||
found := false
|
||||
fn := func(node ast.Node) bool {
|
||||
ident2, ok := node.(*ast.Ident)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
if ident == pass.TypesInfo.ObjectOf(ident2) {
|
||||
found = true
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
ast.Inspect(expr, fn)
|
||||
return found
|
||||
}
|
||||
153
vendor/honnef.co/go/tools/analysis/code/visit.go
vendored
Normal file
153
vendor/honnef.co/go/tools/analysis/code/visit.go
vendored
Normal file
@@ -0,0 +1,153 @@
|
||||
package code
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/format"
|
||||
"go/types"
|
||||
"iter"
|
||||
"slices"
|
||||
|
||||
typeindexanalyzer "honnef.co/go/tools/internal/xtools-internal/analysis/typeindex"
|
||||
"honnef.co/go/tools/internal/xtools-internal/typesinternal/typeindex"
|
||||
"honnef.co/go/tools/pattern"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
"golang.org/x/tools/go/ast/inspector"
|
||||
)
|
||||
|
||||
var RequiredAnalyzers = []*analysis.Analyzer{inspect.Analyzer, typeindexanalyzer.Analyzer}
|
||||
|
||||
func Cursor(pass *analysis.Pass) inspector.Cursor {
|
||||
return pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Root()
|
||||
}
|
||||
|
||||
func Preorder(pass *analysis.Pass, fn func(ast.Node), types ...ast.Node) {
|
||||
pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Preorder(types, fn)
|
||||
}
|
||||
|
||||
func PreorderStack(pass *analysis.Pass, fn func(ast.Node, []ast.Node), types ...ast.Node) {
|
||||
pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).WithStack(types, func(n ast.Node, push bool, stack []ast.Node) (proceed bool) {
|
||||
if push {
|
||||
fn(n, stack)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func Matches(pass *analysis.Pass, qs ...pattern.Pattern) iter.Seq2[ast.Node, *pattern.Matcher] {
|
||||
return func(yield func(ast.Node, *pattern.Matcher) bool) {
|
||||
for _, q := range qs {
|
||||
if !CouldMatchAny(pass, q) {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(q.RootCallSymbols) != 0 {
|
||||
index := pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
|
||||
for _, isym := range q.RootCallSymbols {
|
||||
var obj types.Object
|
||||
if isym.Type == "" {
|
||||
obj = index.Object(isym.Path, isym.Ident)
|
||||
} else {
|
||||
obj = index.Selection(isym.Path, isym.Type, isym.Ident)
|
||||
}
|
||||
for c := range index.Calls(obj) {
|
||||
node := c.Node()
|
||||
if m, ok := Match(pass, q, node); ok {
|
||||
if !yield(node, m) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ins := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
|
||||
fn := func(node ast.Node, push bool) bool {
|
||||
if !push {
|
||||
return true
|
||||
}
|
||||
|
||||
if m, ok := Match(pass, q, node); ok {
|
||||
return yield(node, m)
|
||||
}
|
||||
return true
|
||||
}
|
||||
ins.Nodes(q.EntryNodes, fn)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Match(pass *analysis.Pass, q pattern.Pattern, node ast.Node) (*pattern.Matcher, bool) {
|
||||
// Note that we ignore q.Relevant – callers of Match usually use
|
||||
// AST inspectors that already filter on nodes we're interested
|
||||
// in.
|
||||
m := &pattern.Matcher{TypesInfo: pass.TypesInfo}
|
||||
ok := m.Match(q, node)
|
||||
return m, ok
|
||||
}
|
||||
|
||||
func CouldMatchAny(pass *analysis.Pass, qs ...pattern.Pattern) bool {
|
||||
index := pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
|
||||
var do func(node pattern.Node) bool
|
||||
do = func(node pattern.Node) bool {
|
||||
switch node := node.(type) {
|
||||
case pattern.Any:
|
||||
return true
|
||||
case pattern.Or:
|
||||
return slices.ContainsFunc(node.Nodes, do)
|
||||
case pattern.And:
|
||||
for _, child := range node.Nodes {
|
||||
if !do(child) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case pattern.IndexSymbol:
|
||||
if node.Type == "" {
|
||||
return index.Object(node.Path, node.Ident) != nil
|
||||
} else {
|
||||
return index.Selection(node.Path, node.Type, node.Ident) != nil
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("internal error: unexpected type %T", node))
|
||||
}
|
||||
}
|
||||
|
||||
for _, q := range qs {
|
||||
if do(q.SymbolsPattern) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func MatchAndEdit(pass *analysis.Pass, before, after pattern.Pattern, node ast.Node) (*pattern.Matcher, []analysis.TextEdit, bool) {
|
||||
m, ok := Match(pass, before, node)
|
||||
if !ok {
|
||||
return m, nil, false
|
||||
}
|
||||
r := pattern.NodeToAST(after.Root, m.State)
|
||||
buf := &bytes.Buffer{}
|
||||
format.Node(buf, pass.Fset, r)
|
||||
edit := []analysis.TextEdit{{
|
||||
Pos: node.Pos(),
|
||||
End: node.End(),
|
||||
NewText: buf.Bytes(),
|
||||
}}
|
||||
return m, edit, true
|
||||
}
|
||||
|
||||
func EditMatch(pass *analysis.Pass, node ast.Node, m *pattern.Matcher, after pattern.Pattern) []analysis.TextEdit {
|
||||
r := pattern.NodeToAST(after.Root, m.State)
|
||||
buf := &bytes.Buffer{}
|
||||
format.Node(buf, pass.Fset, r)
|
||||
edit := []analysis.TextEdit{{
|
||||
Pos: node.Pos(),
|
||||
End: node.End(),
|
||||
NewText: buf.Bytes(),
|
||||
}}
|
||||
return edit
|
||||
}
|
||||
60
vendor/honnef.co/go/tools/analysis/dfa/dense/flow.go
vendored
Normal file
60
vendor/honnef.co/go/tools/analysis/dfa/dense/flow.go
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright 2026 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package flow implements a monotone flow analysis framework.
|
||||
package dense
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"slices"
|
||||
|
||||
"honnef.co/go/tools/internal/xtools-internal/graph"
|
||||
)
|
||||
|
||||
const debug = false
|
||||
|
||||
// Analysis is the result of a monotone analysis. Fact is the type of elements
|
||||
// in the analysis semilattice, and represents the outcome of the analysis at
|
||||
// every node and edge.
|
||||
type Analysis[Fact any, NodeID comparable] struct {
|
||||
nodeMap *graph.Index[NodeID]
|
||||
ins []Fact // By NodeID
|
||||
edges []edgeFact[Fact] // Sorted by (from, to)
|
||||
}
|
||||
|
||||
// In returns the analysis fact on entry to nid. This is the merge of the facts
|
||||
// on all incoming edges.
|
||||
func (a *Analysis[Fact, NodeID]) In(nid NodeID) Fact {
|
||||
return a.ins[a.nodeMap.Index(nid)]
|
||||
}
|
||||
|
||||
// Edge returns the analysis fact propagated on edge from ==> to.
|
||||
func (a *Analysis[Fact, NodeID]) Edge(from, to NodeID) Fact {
|
||||
i, found := slices.BinarySearchFunc(a.edges, a.edge(from, to), edgeFact[Fact].compare)
|
||||
if !found {
|
||||
panic("no such edge")
|
||||
}
|
||||
return a.edges[i].fact
|
||||
}
|
||||
|
||||
func (a *Analysis[Fact, NodeID]) edge(from, to NodeID) edge {
|
||||
fromNum, toNum := a.nodeMap.Index(from), a.nodeMap.Index(to)
|
||||
return edge{fromNum, toNum}
|
||||
}
|
||||
|
||||
type edge struct {
|
||||
from, to int
|
||||
}
|
||||
|
||||
func (e edge) compare(f edge) int {
|
||||
if v := cmp.Compare(e.from, f.from); v != 0 {
|
||||
return v
|
||||
}
|
||||
return cmp.Compare(e.to, f.to)
|
||||
}
|
||||
|
||||
type edgeFact[Fact any] struct {
|
||||
edge
|
||||
fact Fact
|
||||
}
|
||||
271
vendor/honnef.co/go/tools/analysis/dfa/dense/forward.go
vendored
Normal file
271
vendor/honnef.co/go/tools/analysis/dfa/dense/forward.go
vendored
Normal file
@@ -0,0 +1,271 @@
|
||||
// Copyright 2026 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package dense
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"log"
|
||||
"slices"
|
||||
|
||||
"honnef.co/go/tools/analysis/dfa"
|
||||
"honnef.co/go/tools/internal/xtools-internal/graph"
|
||||
)
|
||||
|
||||
// Forward performs a forward monotone analysis over a control flow graph.
|
||||
//
|
||||
// The entry map provides initial state for entry blocks (blocks with zero
|
||||
// predecessors). For each edge, it calls transfer(fact, edge), where fact is
|
||||
// the analysis state on entry to edge.Pred. The transfer function must return
|
||||
// the outgoing analysis state of the edge (which may be fact, if the edge has
|
||||
// no effect on the analysis state).
|
||||
func Forward[L dfa.Semilattice[Fact], Fact any, NodeID comparable](g graph.Graph[NodeID], entry map[NodeID]Fact, transfer func(from, to NodeID, fact Fact) Fact) *Analysis[Fact, NodeID] {
|
||||
cg, nodeMap := graph.Compact(g)
|
||||
|
||||
nNodes := cg.NumNodes()
|
||||
fb := &fwdBuilder[L, Fact, NodeID]{
|
||||
cfg: cg,
|
||||
nodeMap: nodeMap,
|
||||
transfer: transfer,
|
||||
blocks: make([]blockInfo[Fact], nNodes),
|
||||
}
|
||||
fb.queue.init(cg)
|
||||
|
||||
// Initialize each node.
|
||||
totalEdges := 0
|
||||
for ni := range nNodes {
|
||||
b := &fb.blocks[ni]
|
||||
|
||||
// Construct back-edges.
|
||||
//
|
||||
// I experimented with making Graph support iterating over in-edges, but
|
||||
// in practice that just meant each Graph implementation had a copy of
|
||||
// this logic. So instead we keep Graph as simple as possible and
|
||||
// compute the auxiliary data in the algorithm. One drawback of this is
|
||||
// that, for the [Transpose] graph, this information is redundant with
|
||||
// the underlying graph. We could potentially special-case that.
|
||||
outs := 0
|
||||
for succID := range cg.Out(ni) {
|
||||
succ := &fb.blocks[succID]
|
||||
succ.preds = append(succ.preds, blockEdge{ni, outs})
|
||||
outs++
|
||||
totalEdges++
|
||||
}
|
||||
|
||||
// Initialize in & out states.
|
||||
fact, ok := entry[nodeMap.Value(ni)]
|
||||
if !ok {
|
||||
fact = fb.l.Ident()
|
||||
}
|
||||
b.in = fact
|
||||
b.out = slices.Repeat([]Fact{fb.l.Ident()}, outs)
|
||||
|
||||
// Enqueue block.
|
||||
//
|
||||
// It's tempting to enqueue only the entry blocks, but this is wrong.
|
||||
// The entry map may be empty if there are no interesting entry states,
|
||||
// but the transfer function may still introduce interesting states
|
||||
// anywhere.
|
||||
b.dirty = true
|
||||
fb.queue.enqueue(ni)
|
||||
}
|
||||
|
||||
// Propagate over blocks.
|
||||
fb.propagate()
|
||||
|
||||
// Collect the final analysis results.
|
||||
a := Analysis[Fact, NodeID]{
|
||||
nodeMap: nodeMap,
|
||||
ins: make([]Fact, nNodes),
|
||||
edges: make([]edgeFact[Fact], 0, totalEdges),
|
||||
}
|
||||
for pred := range nNodes {
|
||||
a.ins[pred] = fb.blocks[pred].in
|
||||
i := 0
|
||||
for succ := range cg.Out(pred) {
|
||||
edge := edge{pred, succ}
|
||||
a.edges = append(a.edges, edgeFact[Fact]{edge, fb.blocks[pred].out[i]})
|
||||
i++
|
||||
}
|
||||
}
|
||||
slices.SortFunc(a.edges, func(a, b edgeFact[Fact]) int { return a.edge.compare(b.edge) })
|
||||
return &a
|
||||
}
|
||||
|
||||
// fwdBuilder is the state used during [Forward] analysis.
|
||||
type fwdBuilder[L dfa.Semilattice[Fact], Fact any, NodeID comparable] struct {
|
||||
l L // Lattice
|
||||
|
||||
cfg graph.Graph[int] // Control flow graph (compact)
|
||||
nodeMap *graph.Index[NodeID] // Map from cfg to original NodeIDs
|
||||
|
||||
// transfer is the edge transfer function.
|
||||
transfer func(from, to NodeID, fact Fact) Fact
|
||||
|
||||
blocks []blockInfo[Fact]
|
||||
|
||||
queue nodeHeap
|
||||
}
|
||||
|
||||
type blockInfo[Fact any] struct {
|
||||
dirty bool // The in fact has never been propagated.
|
||||
|
||||
preds []blockEdge
|
||||
|
||||
in Fact
|
||||
out []Fact // Corresponds to i'th out edge
|
||||
}
|
||||
|
||||
type blockEdge struct {
|
||||
node int
|
||||
i int // Out edge index
|
||||
}
|
||||
|
||||
// nodeHeap implements a heap of NodeIDs, ordered topologically.
|
||||
//
|
||||
// We use this ordering so forward analysis converges more quickly.
|
||||
type nodeHeap struct {
|
||||
heap []int // Remaining nodes in the current sweep
|
||||
deferred []int // Nodes of next sweep
|
||||
inQueue []int64 // Bitmap over node IDs
|
||||
prio []int // NodeID -> priority
|
||||
currentPrio int // Priority of last dequeued node, or -1
|
||||
}
|
||||
|
||||
func (h *nodeHeap) init(g graph.Graph[int]) {
|
||||
nNodes := g.NumNodes()
|
||||
*h = nodeHeap{
|
||||
inQueue: make([]int64, (nNodes+63)/64),
|
||||
prio: make([]int, nNodes),
|
||||
currentPrio: -1,
|
||||
}
|
||||
for p, nid := range graph.ReversePostorder(g) {
|
||||
h.prio[nid] = p
|
||||
}
|
||||
}
|
||||
|
||||
func (h *nodeHeap) enqueue(nid int) {
|
||||
if h.inQueue[nid/64]&(1<<(nid%64)) != 0 {
|
||||
return
|
||||
}
|
||||
h.inQueue[nid/64] |= 1 << (nid % 64)
|
||||
|
||||
if h.currentPrio >= 0 && h.prio[nid] <= h.currentPrio {
|
||||
// This is a retreating edge, self-edge, or other update to a node
|
||||
// already passed in this sweep. Coalesce it into the next sweep.
|
||||
h.deferred = append(h.deferred, nid)
|
||||
} else {
|
||||
heap.Push(h, nid)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *nodeHeap) dequeue() int {
|
||||
if len(h.heap) == 0 {
|
||||
// Start the next RPO sweep.
|
||||
h.heap, h.deferred = h.deferred, h.heap[:0]
|
||||
h.currentPrio = -1
|
||||
heap.Init(h)
|
||||
}
|
||||
|
||||
nid := h.heap[0]
|
||||
heap.Pop(h)
|
||||
h.inQueue[nid/64] &^= 1 << (nid % 64)
|
||||
h.currentPrio = h.prio[nid]
|
||||
return nid
|
||||
}
|
||||
|
||||
func (h *nodeHeap) pending() bool { return len(h.heap) != 0 || len(h.deferred) != 0 }
|
||||
func (h nodeHeap) Len() int { return len(h.heap) }
|
||||
func (h nodeHeap) Less(i, j int) bool { return h.prio[h.heap[i]] < h.prio[h.heap[j]] }
|
||||
func (h nodeHeap) Swap(i, j int) { h.heap[i], h.heap[j] = h.heap[j], h.heap[i] }
|
||||
func (h *nodeHeap) Push(x any) { h.heap = append(h.heap, x.(int)) }
|
||||
func (h *nodeHeap) Pop() any {
|
||||
n := len(h.heap)
|
||||
x := h.heap[n-1]
|
||||
h.heap = h.heap[:n-1]
|
||||
return x
|
||||
}
|
||||
|
||||
func (fb *fwdBuilder[L, Fact, NodeID]) merge(a, b Fact) Fact {
|
||||
if fb.l.Equals(a, b) {
|
||||
return a
|
||||
}
|
||||
return fb.l.Merge(a, b)
|
||||
}
|
||||
|
||||
func (fb *fwdBuilder[L, Fact, NodeID]) propagate() {
|
||||
for fb.queue.pending() {
|
||||
bi := fb.queue.dequeue()
|
||||
block := &fb.blocks[bi]
|
||||
|
||||
// Merge predecessor facts to compute updated "in" fact.
|
||||
var in Fact
|
||||
first := true
|
||||
for _, edge := range block.preds {
|
||||
pred := &fb.blocks[edge.node]
|
||||
var edgeFact Fact
|
||||
if pred.dirty {
|
||||
// We haven't visited this predecessor yet, so it doesn't have
|
||||
// meaningful out facts.
|
||||
edgeFact = fb.l.Ident()
|
||||
} else {
|
||||
edgeFact = pred.out[edge.i]
|
||||
}
|
||||
if first {
|
||||
if debug {
|
||||
log.Printf("propagate to node %d", bi)
|
||||
}
|
||||
in = edgeFact
|
||||
first = false
|
||||
} else {
|
||||
in = fb.merge(in, edgeFact)
|
||||
}
|
||||
if debug {
|
||||
log.Printf(" from node %d: %v", edge.node, edgeFact)
|
||||
}
|
||||
}
|
||||
if first {
|
||||
// No predecessors.
|
||||
if debug {
|
||||
log.Printf("node %d gets initial state", bi)
|
||||
}
|
||||
in = block.in
|
||||
}
|
||||
|
||||
if !block.dirty && fb.l.Equals(in, block.in) {
|
||||
// No change to block input, which means the transfer function
|
||||
// results also won't change from the last time we ran it.
|
||||
if debug {
|
||||
log.Printf(" initial state unchanged: %v", in)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if debug {
|
||||
log.Printf(" new initial state: %v", in)
|
||||
}
|
||||
block.in = in
|
||||
|
||||
// Apply transfer function.
|
||||
predID := fb.nodeMap.Value(bi)
|
||||
i := 0
|
||||
for succNum := range fb.cfg.Out(bi) {
|
||||
edgeFact := fb.transfer(predID, fb.nodeMap.Value(succNum), in)
|
||||
if block.dirty || !fb.l.Equals(block.out[i], edgeFact) {
|
||||
// Out fact changed, so recompute the target block.
|
||||
if debug {
|
||||
log.Printf(" to node %d: %v", succNum, edgeFact)
|
||||
}
|
||||
block.out[i] = edgeFact
|
||||
fb.queue.enqueue(succNum)
|
||||
} else {
|
||||
if debug {
|
||||
log.Printf(" to node %d: no change", succNum)
|
||||
}
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
block.dirty = false
|
||||
}
|
||||
}
|
||||
48
vendor/honnef.co/go/tools/analysis/dfa/dot.go
vendored
Normal file
48
vendor/honnef.co/go/tools/analysis/dfa/dot.go
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
package dfa
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Dot returns a directed graph in [Graphviz] format that represents the finite
|
||||
// join-semilattice ⟨S, ≤⟩. Vertices represent elements in S and edges
|
||||
// represent the ≤ relation between elements. We map from ⟨S, ∨⟩ to ⟨S, ≤⟩ by
|
||||
// computing x ∨ y for all elements in [S]², where x ≤ y iff x ∨ y == y.
|
||||
//
|
||||
// The resulting graph can be filtered through [tred] to compute the transitive
|
||||
// reduction of the graph, the visualisation of which corresponds to the Hasse
|
||||
// diagram of the semilattice.
|
||||
//
|
||||
// [Graphviz]: https://graphviz.org/
|
||||
// [tred]: https://graphviz.org/docs/cli/tred/
|
||||
func Dot[L Semilattice[Elem], Elem any](states []Elem) string {
|
||||
var sb strings.Builder
|
||||
sb.WriteString("digraph{\n")
|
||||
sb.WriteString("rankdir=\"BT\"\n")
|
||||
|
||||
for i, v := range states {
|
||||
if vs, ok := any(v).(fmt.Stringer); ok {
|
||||
fmt.Fprintf(&sb, "n%d [label=%q]\n", i, vs)
|
||||
} else {
|
||||
fmt.Fprintf(&sb, "n%d [label=%q]\n", i, fmt.Sprintf("%v", v))
|
||||
}
|
||||
}
|
||||
|
||||
var l L
|
||||
|
||||
for dx, x := range states {
|
||||
for dy, y := range states {
|
||||
if dx == dy {
|
||||
continue
|
||||
}
|
||||
|
||||
if l.Equals(l.Merge(x, y), y) {
|
||||
fmt.Fprintf(&sb, "n%d -> n%d\n", dx, dy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sb.WriteString("}")
|
||||
return sb.String()
|
||||
}
|
||||
147
vendor/honnef.co/go/tools/analysis/dfa/lattice.go
vendored
Normal file
147
vendor/honnef.co/go/tools/analysis/dfa/lattice.go
vendored
Normal file
@@ -0,0 +1,147 @@
|
||||
// Copyright 2026 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package dfa
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
)
|
||||
|
||||
// A Semilattice describes a bounded semilattice over Elem.
|
||||
// That is, a partial order over values of type Elem, with a binary
|
||||
// Merge operator and an identity element.
|
||||
//
|
||||
// This is typically implemented by a stateless type, and acts as a factory for
|
||||
// lattice elements.
|
||||
type Semilattice[Elem any] interface {
|
||||
// Ident returns the identity element of this lattice, that is the unit of
|
||||
// the Merge operation.
|
||||
Ident() Elem
|
||||
|
||||
// Equals returns whether a and b are the same element.
|
||||
Equals(a, b Elem) bool
|
||||
|
||||
// Merge combines two lattice values, such as the two possible values of a
|
||||
// variable at the end of an if/else statement.
|
||||
//
|
||||
// Merge must satisfy the following identities, where we use ∧ for Merge, =
|
||||
// for Equals, and 𝟏 for Ident:
|
||||
//
|
||||
// - Associativity: x ∧ (y ∧ z) = (x ∧ y) ∧ z
|
||||
// - Commutativity: x ∧ y = y ∧ x
|
||||
// - Idempotency: x ∧ x = x
|
||||
// - Identity: x ∧ 𝟏 = x
|
||||
Merge(a, b Elem) Elem
|
||||
}
|
||||
|
||||
// A MapLattice implements [Semilattice][map[Key]Elem]. The values in the map
|
||||
// are themselves defined by [Semilattice] L.
|
||||
//
|
||||
// Any elements missing from the map are implicitly L's identity element, and
|
||||
// L's identity element never appears as a value in the map.
|
||||
//
|
||||
// For densely numbered keys, consider using [DenseMapLattice] instead.
|
||||
type MapLattice[Key comparable, Elem any, L Semilattice[Elem]] struct {
|
||||
l L
|
||||
}
|
||||
|
||||
func (m MapLattice[Key, Elem, L]) Ident() map[Key]Elem {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m MapLattice[Key, Elem, L]) Equals(a, b map[Key]Elem) bool {
|
||||
return maps.EqualFunc(a, b, m.l.Equals)
|
||||
}
|
||||
|
||||
func (m MapLattice[Key, Elem, L]) Merge(a, b map[Key]Elem) map[Key]Elem {
|
||||
if len(a) == 0 {
|
||||
return b
|
||||
} else if len(b) == 0 {
|
||||
return a
|
||||
}
|
||||
|
||||
// We need to consider the union of keys in a and b.
|
||||
out := make(map[Key]Elem)
|
||||
id := m.l.Ident()
|
||||
for k, av := range a {
|
||||
bv, ok := b[k]
|
||||
if !ok {
|
||||
// Because Merge(x, Ident()) == x, we can skip calling L.Merge.
|
||||
out[k] = av
|
||||
continue
|
||||
}
|
||||
|
||||
w := m.l.Merge(av, bv)
|
||||
if m.l.Equals(w, id) {
|
||||
// In a semilattice, Merge(x, y) = Ident is only possible when x ==
|
||||
// Ident and y == Ident.
|
||||
panic(fmt.Sprintf(
|
||||
"%T is not a semilattice: Merge(%v, %v) returned Ident for non-Ident arguments",
|
||||
m.l, av, bv))
|
||||
}
|
||||
out[k] = w
|
||||
}
|
||||
// We considered keys that are only in a, and in both a and b. Now we just
|
||||
// need to handle keys that are only in b.
|
||||
for k, v2 := range b {
|
||||
if _, ok := a[k]; !ok {
|
||||
out[k] = v2
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// A DenseMapLattice implements [Semilattice][[]Elem]. It is like a [MapLattice]
|
||||
// that is indexed by integers. The values in the map are themselves defined by
|
||||
// [Semilattice] L.
|
||||
//
|
||||
// Unlike [MapLattice], L's identity element may appear as a value in the map,
|
||||
// to allow for gaps in the numbering of keys when the identity element is
|
||||
// Elem's zero value.
|
||||
type DenseMapLattice[Elem any, L Semilattice[Elem]] struct {
|
||||
l L
|
||||
}
|
||||
|
||||
func (s DenseMapLattice[Elem, L]) Ident() []Elem {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s DenseMapLattice[Elem, L]) Equals(a, b []Elem) bool {
|
||||
nmin := min(len(a), len(b))
|
||||
ident := s.l.Ident()
|
||||
|
||||
// Check that up to nmin, all elements in a and b match. If one of a or b
|
||||
// is longer, then its tail nmin:nmax must only contain identity elements.
|
||||
return slices.EqualFunc(a[:nmin], b[:nmin], s.l.Equals) &&
|
||||
!slices.ContainsFunc(a[nmin:], func(e Elem) bool {
|
||||
return !s.l.Equals(e, ident)
|
||||
}) &&
|
||||
!slices.ContainsFunc(b[nmin:], func(e Elem) bool {
|
||||
return !s.l.Equals(e, ident)
|
||||
})
|
||||
}
|
||||
|
||||
func (s DenseMapLattice[Elem, L]) Merge(a, b []Elem) []Elem {
|
||||
if len(a) == 0 {
|
||||
return b
|
||||
} else if len(b) == 0 {
|
||||
return a
|
||||
}
|
||||
out := make([]Elem, max(len(a), len(b)))
|
||||
for k := range max(len(a), len(b)) {
|
||||
av := s.l.Ident()
|
||||
bv := s.l.Ident()
|
||||
if k < len(a) {
|
||||
av = a[k]
|
||||
}
|
||||
if k < len(b) {
|
||||
bv = b[k]
|
||||
}
|
||||
out[k] = s.l.Merge(av, bv)
|
||||
}
|
||||
return out
|
||||
}
|
||||
83
vendor/honnef.co/go/tools/analysis/edit/edit.go
vendored
Normal file
83
vendor/honnef.co/go/tools/analysis/edit/edit.go
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
// Package edit contains helpers for creating suggested fixes.
|
||||
package edit
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"go/ast"
|
||||
"go/format"
|
||||
"go/token"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"honnef.co/go/tools/pattern"
|
||||
)
|
||||
|
||||
// Ranger describes values that have a start and end position.
|
||||
// In most cases these are either ast.Node or manually constructed ranges.
|
||||
type Ranger interface {
|
||||
Pos() token.Pos
|
||||
End() token.Pos
|
||||
}
|
||||
|
||||
// Range implements the Ranger interface.
|
||||
type Range [2]token.Pos
|
||||
|
||||
func (r Range) Pos() token.Pos { return r[0] }
|
||||
func (r Range) End() token.Pos { return r[1] }
|
||||
|
||||
// ReplaceWithString replaces a range with a string.
|
||||
func ReplaceWithString(old Ranger, new string) analysis.TextEdit {
|
||||
return analysis.TextEdit{
|
||||
Pos: old.Pos(),
|
||||
End: old.End(),
|
||||
NewText: []byte(new),
|
||||
}
|
||||
}
|
||||
|
||||
// ReplaceWithNode replaces a range with an AST node.
|
||||
func ReplaceWithNode(fset *token.FileSet, old Ranger, new ast.Node) analysis.TextEdit {
|
||||
buf := &bytes.Buffer{}
|
||||
if err := format.Node(buf, fset, new); err != nil {
|
||||
panic("internal error: " + err.Error())
|
||||
}
|
||||
return analysis.TextEdit{
|
||||
Pos: old.Pos(),
|
||||
End: old.End(),
|
||||
NewText: buf.Bytes(),
|
||||
}
|
||||
}
|
||||
|
||||
// ReplaceWithPattern replaces a range with the result of executing a pattern.
|
||||
func ReplaceWithPattern(fset *token.FileSet, old Ranger, new pattern.Pattern, state pattern.State) analysis.TextEdit {
|
||||
r := pattern.NodeToAST(new.Root, state)
|
||||
buf := &bytes.Buffer{}
|
||||
format.Node(buf, fset, r)
|
||||
return analysis.TextEdit{
|
||||
Pos: old.Pos(),
|
||||
End: old.End(),
|
||||
NewText: buf.Bytes(),
|
||||
}
|
||||
}
|
||||
|
||||
// Delete deletes a range of code.
|
||||
func Delete(old Ranger) analysis.TextEdit {
|
||||
return analysis.TextEdit{
|
||||
Pos: old.Pos(),
|
||||
End: old.End(),
|
||||
NewText: nil,
|
||||
}
|
||||
}
|
||||
|
||||
func Fix(msg string, edits ...analysis.TextEdit) analysis.SuggestedFix {
|
||||
return analysis.SuggestedFix{
|
||||
Message: msg,
|
||||
TextEdits: edits,
|
||||
}
|
||||
}
|
||||
|
||||
// Selector creates a new selector expression.
|
||||
func Selector(x, sel string) *ast.SelectorExpr {
|
||||
return &ast.SelectorExpr{
|
||||
X: &ast.Ident{Name: x},
|
||||
Sel: &ast.Ident{Name: sel},
|
||||
}
|
||||
}
|
||||
154
vendor/honnef.co/go/tools/analysis/facts/deprecated/deprecated.go
vendored
Normal file
154
vendor/honnef.co/go/tools/analysis/facts/deprecated/deprecated.go
vendored
Normal file
@@ -0,0 +1,154 @@
|
||||
package deprecated
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
type IsDeprecated struct{ Msg string }
|
||||
|
||||
func (*IsDeprecated) AFact() {}
|
||||
func (d *IsDeprecated) String() string { return "Deprecated: " + d.Msg }
|
||||
|
||||
type Result struct {
|
||||
Objects map[types.Object]*IsDeprecated
|
||||
Packages map[*types.Package]*IsDeprecated
|
||||
}
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "fact_deprecated",
|
||||
Doc: "Mark deprecated objects",
|
||||
Run: deprecated,
|
||||
FactTypes: []analysis.Fact{(*IsDeprecated)(nil)},
|
||||
ResultType: reflect.TypeFor[Result](),
|
||||
}
|
||||
|
||||
func deprecated(pass *analysis.Pass) (any, error) {
|
||||
var names []*ast.Ident
|
||||
|
||||
extractDeprecatedMessage := func(docs []*ast.CommentGroup) string {
|
||||
for _, doc := range docs {
|
||||
if doc == nil {
|
||||
continue
|
||||
}
|
||||
parts := strings.SplitSeq(doc.Text(), "\n\n")
|
||||
for part := range parts {
|
||||
if !strings.HasPrefix(part, "Deprecated: ") {
|
||||
continue
|
||||
}
|
||||
alt := part[len("Deprecated: "):]
|
||||
alt = strings.Replace(alt, "\n", " ", -1)
|
||||
return alt
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
doDocs := func(names []*ast.Ident, docs []*ast.CommentGroup) {
|
||||
alt := extractDeprecatedMessage(docs)
|
||||
if alt == "" {
|
||||
return
|
||||
}
|
||||
|
||||
for _, name := range names {
|
||||
obj := pass.TypesInfo.ObjectOf(name)
|
||||
pass.ExportObjectFact(obj, &IsDeprecated{alt})
|
||||
}
|
||||
}
|
||||
|
||||
var docs []*ast.CommentGroup
|
||||
for _, f := range pass.Files {
|
||||
docs = append(docs, f.Doc)
|
||||
}
|
||||
if alt := extractDeprecatedMessage(docs); alt != "" {
|
||||
// Don't mark package syscall as deprecated, even though
|
||||
// it is. A lot of people still use it for simple
|
||||
// constants like SIGKILL, and I am not comfortable
|
||||
// telling them to use x/sys for that.
|
||||
if pass.Pkg.Path() != "syscall" {
|
||||
pass.ExportPackageFact(&IsDeprecated{alt})
|
||||
}
|
||||
}
|
||||
|
||||
docs = docs[:0]
|
||||
for _, f := range pass.Files {
|
||||
fn := func(node ast.Node) bool {
|
||||
if node == nil {
|
||||
return true
|
||||
}
|
||||
var ret bool
|
||||
switch node := node.(type) {
|
||||
case *ast.GenDecl:
|
||||
switch node.Tok {
|
||||
case token.TYPE, token.CONST, token.VAR:
|
||||
docs = append(docs, node.Doc)
|
||||
for i := range node.Specs {
|
||||
switch n := node.Specs[i].(type) {
|
||||
case *ast.ValueSpec:
|
||||
names = append(names, n.Names...)
|
||||
case *ast.TypeSpec:
|
||||
names = append(names, n.Name)
|
||||
}
|
||||
}
|
||||
ret = true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
case *ast.FuncDecl:
|
||||
docs = append(docs, node.Doc)
|
||||
names = []*ast.Ident{node.Name}
|
||||
ret = false
|
||||
case *ast.TypeSpec:
|
||||
docs = append(docs, node.Doc)
|
||||
names = []*ast.Ident{node.Name}
|
||||
ret = true
|
||||
case *ast.ValueSpec:
|
||||
docs = append(docs, node.Doc)
|
||||
names = node.Names
|
||||
ret = false
|
||||
case *ast.File:
|
||||
return true
|
||||
case *ast.StructType:
|
||||
for _, field := range node.Fields.List {
|
||||
doDocs(field.Names, []*ast.CommentGroup{field.Doc})
|
||||
}
|
||||
return false
|
||||
case *ast.InterfaceType:
|
||||
for _, field := range node.Methods.List {
|
||||
doDocs(field.Names, []*ast.CommentGroup{field.Doc})
|
||||
}
|
||||
return false
|
||||
default:
|
||||
return false
|
||||
}
|
||||
if len(names) == 0 || len(docs) == 0 {
|
||||
return ret
|
||||
}
|
||||
doDocs(names, docs)
|
||||
|
||||
docs = docs[:0]
|
||||
names = nil
|
||||
return ret
|
||||
}
|
||||
ast.Inspect(f, fn)
|
||||
}
|
||||
|
||||
out := Result{
|
||||
Objects: map[types.Object]*IsDeprecated{},
|
||||
Packages: map[*types.Package]*IsDeprecated{},
|
||||
}
|
||||
|
||||
for _, fact := range pass.AllObjectFacts() {
|
||||
out.Objects[fact.Object] = fact.Fact.(*IsDeprecated)
|
||||
}
|
||||
for _, fact := range pass.AllPackageFacts() {
|
||||
out.Packages[fact.Package] = fact.Fact.(*IsDeprecated)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
20
vendor/honnef.co/go/tools/analysis/facts/directives/directives.go
vendored
Normal file
20
vendor/honnef.co/go/tools/analysis/facts/directives/directives.go
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
package directives
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
)
|
||||
|
||||
func directives(pass *analysis.Pass) (any, error) {
|
||||
return lint.ParseDirectives(pass.Files, pass.Fset), nil
|
||||
}
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "directives",
|
||||
Doc: "extracts linter directives",
|
||||
Run: directives,
|
||||
RunDespiteErrors: true,
|
||||
ResultType: reflect.TypeFor[[]lint.Directive](),
|
||||
}
|
||||
97
vendor/honnef.co/go/tools/analysis/facts/generated/generated.go
vendored
Normal file
97
vendor/honnef.co/go/tools/analysis/facts/generated/generated.go
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
package generated
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
type Generator int
|
||||
|
||||
// A list of known generators we can detect
|
||||
const (
|
||||
Unknown Generator = iota
|
||||
Goyacc
|
||||
Cgo
|
||||
Stringer
|
||||
ProtocGenGo
|
||||
)
|
||||
|
||||
var (
|
||||
// used by cgo before Go 1.11
|
||||
oldCgo = []byte("// Created by cgo - DO NOT EDIT")
|
||||
prefix = []byte("// Code generated ")
|
||||
suffix = []byte(" DO NOT EDIT.")
|
||||
nl = []byte("\n")
|
||||
crnl = []byte("\r\n")
|
||||
)
|
||||
|
||||
func isGenerated(path string) (Generator, bool) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
defer f.Close()
|
||||
br := bufio.NewReader(f)
|
||||
for {
|
||||
s, err := br.ReadBytes('\n')
|
||||
if err != nil && err != io.EOF {
|
||||
return 0, false
|
||||
}
|
||||
s = bytes.TrimSuffix(s, crnl)
|
||||
s = bytes.TrimSuffix(s, nl)
|
||||
if bytes.HasPrefix(s, prefix) && bytes.HasSuffix(s, suffix) {
|
||||
if len(s)-len(suffix) < len(prefix) {
|
||||
return Unknown, true
|
||||
}
|
||||
|
||||
text := string(s[len(prefix) : len(s)-len(suffix)])
|
||||
switch text {
|
||||
case "by goyacc.":
|
||||
return Goyacc, true
|
||||
case "by cmd/cgo;":
|
||||
return Cgo, true
|
||||
case "by protoc-gen-go.":
|
||||
return ProtocGenGo, true
|
||||
}
|
||||
if strings.HasPrefix(text, `by "stringer `) {
|
||||
return Stringer, true
|
||||
}
|
||||
if strings.HasPrefix(text, `by goyacc `) {
|
||||
return Goyacc, true
|
||||
}
|
||||
|
||||
return Unknown, true
|
||||
}
|
||||
if bytes.Equal(s, oldCgo) {
|
||||
return Cgo, true
|
||||
}
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "isgenerated",
|
||||
Doc: "annotate file names that have been code generated",
|
||||
Run: func(pass *analysis.Pass) (any, error) {
|
||||
m := map[string]Generator{}
|
||||
for _, f := range pass.Files {
|
||||
path := pass.Fset.PositionFor(f.Pos(), false).Filename
|
||||
g, ok := isGenerated(path)
|
||||
if ok {
|
||||
m[path] = g
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
},
|
||||
RunDespiteErrors: true,
|
||||
ResultType: reflect.TypeFor[map[string]Generator](),
|
||||
}
|
||||
830
vendor/honnef.co/go/tools/analysis/facts/nilness/nilness.go
vendored
Normal file
830
vendor/honnef.co/go/tools/analysis/facts/nilness/nilness.go
vendored
Normal file
@@ -0,0 +1,830 @@
|
||||
package nilness
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/constant"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"honnef.co/go/tools/analysis/dfa"
|
||||
"honnef.co/go/tools/analysis/dfa/dense"
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/go/types/typeutil"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
|
||||
"golang.org/x/exp/typeparams"
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
// TODO(dh): The analysis is currently entirely forward, which means that for
|
||||
//
|
||||
// x := s[:0]
|
||||
// y := s[:1]
|
||||
// z := s[:0]
|
||||
//
|
||||
// x will have MaybeNil at every program point and s will have MaybeNil before
|
||||
// execution of y, even though executing y without panicing tells us that s has
|
||||
// been non-nil for all 3 instructions.
|
||||
|
||||
type nilnessFact struct {
|
||||
Rets []ValueNilness
|
||||
}
|
||||
|
||||
func (*nilnessFact) AFact() {}
|
||||
func (fact *nilnessFact) String() string {
|
||||
return fmt.Sprintf("nilness: %v", fact.Rets)
|
||||
}
|
||||
|
||||
type ValueNilness struct {
|
||||
// Undefined for non-interface values.
|
||||
// For interface values, whether the stored value may be nil.
|
||||
// Even when Outer == MaybeNil, Inner may still offer precise information
|
||||
// for the cases when Outer is dynamically not nil. For example, {NeverNil,
|
||||
// MaybeNil} states that the interface value might be nil, but if it isn't,
|
||||
// it will definitely contain a non-nil value.
|
||||
Inner Nilness
|
||||
// For non-interface values, whether the value may be nil.
|
||||
// For interface values, whether the interface value may be nil.
|
||||
Outer Nilness
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
m map[*types.Func][]ValueNilness
|
||||
}
|
||||
|
||||
var Analysis = &analysis.Analyzer{
|
||||
Name: "nilness",
|
||||
Doc: "Annotates return values with their nilness",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
FactTypes: []analysis.Fact{(*nilnessFact)(nil)},
|
||||
ResultType: reflect.TypeFor[*Result](),
|
||||
}
|
||||
|
||||
// Nilness returns nilness information for return value ret of fn.
|
||||
func (r *Result) Nilness(fn *types.Func, ret int) ValueNilness {
|
||||
typ := fn.Type().(*types.Signature).Results().At(ret).Type()
|
||||
if !typeutil.IsPointerLike(typ) {
|
||||
return ValueNilness{Outer: NeverNil}
|
||||
}
|
||||
if len(r.m[fn]) == 0 {
|
||||
return ValueNilness{Inner: MaybeNil, Outer: MaybeNil}
|
||||
}
|
||||
|
||||
return normalize(r.m[fn][ret], typ)
|
||||
}
|
||||
|
||||
func normalize(v ValueNilness, typ types.Type) ValueNilness {
|
||||
if v.Inner == 0 || !types.IsInterface(typ) {
|
||||
v.Inner = MaybeNil
|
||||
}
|
||||
if v.Outer == 0 {
|
||||
v.Outer = MaybeNil
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
seen := map[*ir.Function]struct{}{}
|
||||
out := &Result{
|
||||
m: map[*types.Func][]ValueNilness{},
|
||||
}
|
||||
|
||||
// TODO(dh): instead of recursion and giving up on mutual recursion, we
|
||||
// should compute the DFA over the call graph, at least until we have
|
||||
// proper function summaries.
|
||||
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
|
||||
impl(pass, fn, seen)
|
||||
}
|
||||
|
||||
for _, fact := range pass.AllObjectFacts() {
|
||||
out.m[fact.Object.(*types.Func)] = fact.Fact.(*nilnessFact).Rets
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
type Nilness uint8
|
||||
|
||||
const (
|
||||
// The value is never nil.
|
||||
NeverNil Nilness = iota + 1
|
||||
// The value is always nil.
|
||||
AlwaysNil
|
||||
// The value might be nil, but only because of the value of a global
|
||||
// variable.
|
||||
MaybeNilGlobal
|
||||
// The value might be nil.
|
||||
MaybeNil
|
||||
)
|
||||
|
||||
func (n Nilness) String() string {
|
||||
switch n {
|
||||
case 0:
|
||||
return "NoNilness"
|
||||
case NeverNil:
|
||||
return "NeverNil"
|
||||
case AlwaysNil:
|
||||
return "AlwaysNil"
|
||||
case MaybeNilGlobal:
|
||||
return "MaybeNilGlobal"
|
||||
case MaybeNil:
|
||||
return "MaybeNil"
|
||||
default:
|
||||
return "InvalidNilness"
|
||||
}
|
||||
}
|
||||
|
||||
type state struct {
|
||||
cloned bool
|
||||
m []ValueNilness
|
||||
n numbering
|
||||
}
|
||||
|
||||
func (s *state) get(v ir.Value) ValueNilness {
|
||||
if !typeutil.IsPointerLike(v.Type()) {
|
||||
// All non-pointer-like types are always {_ NeverNil}.
|
||||
return ValueNilness{Outer: NeverNil}
|
||||
}
|
||||
num := s.n.number(v)
|
||||
if num < len(s.m) {
|
||||
return s.m[num]
|
||||
}
|
||||
|
||||
switch v.(type) {
|
||||
case *ir.Parameter:
|
||||
return ValueNilness{Inner: MaybeNil, Outer: MaybeNil}
|
||||
case *ir.Builtin:
|
||||
return ValueNilness{Outer: NeverNil}
|
||||
case *ir.FreeVar:
|
||||
return ValueNilness{Inner: MaybeNil, Outer: MaybeNil}
|
||||
case *ir.Function:
|
||||
return ValueNilness{Outer: NeverNil}
|
||||
case *ir.Global:
|
||||
// Globals are addresses, not the values stored in them. The addresses
|
||||
// cannot be nil.
|
||||
return ValueNilness{Outer: NeverNil}
|
||||
}
|
||||
|
||||
return lattice{}.Ident()
|
||||
}
|
||||
|
||||
func (s *state) set(key ir.Value, value ValueNilness) {
|
||||
if !typeutil.IsPointerLike(key.Type()) {
|
||||
// No point in recording state for non-pointer-like types. They're
|
||||
// always {_ NeverNil}.
|
||||
return
|
||||
}
|
||||
|
||||
if value == (lattice{}.Ident()) {
|
||||
// No point in storing the default value.
|
||||
return
|
||||
}
|
||||
num := s.n.number(key)
|
||||
if !s.cloned {
|
||||
if num < len(s.m) && s.m[num] == value {
|
||||
// Don't clone if the value already matches.
|
||||
return
|
||||
}
|
||||
s.cloned = true
|
||||
s.m = slices.Clone(s.m)
|
||||
}
|
||||
if num >= len(s.m) {
|
||||
s.m = append(s.m, make([]ValueNilness, num-len(s.m)+1)...)
|
||||
}
|
||||
s.m[num] = value
|
||||
}
|
||||
|
||||
func (s *state) setInner(key ir.Value, value Nilness) {
|
||||
if !typeutil.IsPointerLike(key.Type()) {
|
||||
return
|
||||
}
|
||||
if value == (lattice{}.Ident().Inner) {
|
||||
return
|
||||
}
|
||||
num := s.n.number(key)
|
||||
if !s.cloned {
|
||||
if num < len(s.m) && s.m[num].Inner == value {
|
||||
// Don't clone if the value already matches.
|
||||
return
|
||||
}
|
||||
s.cloned = true
|
||||
s.m = slices.Clone(s.m)
|
||||
}
|
||||
if num >= len(s.m) {
|
||||
dflt := s.get(key)
|
||||
s.m = append(s.m, make([]ValueNilness, num-len(s.m)+1)...)
|
||||
s.m[num] = dflt
|
||||
}
|
||||
v := s.m[num]
|
||||
v.Inner = value
|
||||
s.m[num] = v
|
||||
}
|
||||
|
||||
func (s *state) setOuter(key ir.Value, value Nilness) {
|
||||
if !typeutil.IsPointerLike(key.Type()) {
|
||||
return
|
||||
}
|
||||
if value == (lattice{}).Ident().Outer {
|
||||
return
|
||||
}
|
||||
num := s.n.number(key)
|
||||
if !s.cloned {
|
||||
if num < len(s.m) && s.m[num].Outer == value {
|
||||
// Don't clone if the value already matches.
|
||||
return
|
||||
}
|
||||
s.cloned = true
|
||||
s.m = slices.Clone(s.m)
|
||||
}
|
||||
if num >= len(s.m) {
|
||||
dflt := s.get(key)
|
||||
s.m = append(s.m, make([]ValueNilness, num-len(s.m)+1)...)
|
||||
s.m[num] = dflt
|
||||
}
|
||||
v := s.m[num]
|
||||
v.Outer = value
|
||||
s.m[num] = v
|
||||
}
|
||||
|
||||
func defaultNilnessForSignature(pass *analysis.Pass, typ *types.Signature) []ValueNilness {
|
||||
n := typ.Results().Len()
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]ValueNilness, n)
|
||||
for i := range n {
|
||||
out[i] = defaultNilness(pass, typ.Results().At(i).Type())
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func defaultNilness(pass *analysis.Pass, typ types.Type) ValueNilness {
|
||||
if typeutil.IsPointerLike(typ) {
|
||||
// IsPointerLike handles type parameters with type sets, too.
|
||||
return ValueNilness{MaybeNil, MaybeNil}
|
||||
} else {
|
||||
return ValueNilness{NeverNil, NeverNil}
|
||||
}
|
||||
}
|
||||
|
||||
func impl(pass *analysis.Pass, fn *ir.Function, seenFns map[*ir.Function]struct{}) []ValueNilness {
|
||||
goto start
|
||||
bailout:
|
||||
return defaultNilnessForSignature(pass, fn.Signature)
|
||||
|
||||
start:
|
||||
if fn.Signature.Results().Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
if fn.Object() == nil {
|
||||
// TODO(dh): support closures
|
||||
goto bailout
|
||||
}
|
||||
if fact := new(nilnessFact); pass.ImportObjectFact(fn.Object(), fact) {
|
||||
return fact.Rets
|
||||
}
|
||||
if fn.Pkg != pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg {
|
||||
goto bailout
|
||||
}
|
||||
if fn.Blocks == nil {
|
||||
goto bailout
|
||||
}
|
||||
if _, ok := seenFns[fn]; ok {
|
||||
// break recursion
|
||||
goto bailout
|
||||
}
|
||||
|
||||
seenFns[fn] = struct{}{}
|
||||
|
||||
anyPointers := false
|
||||
for ret := range fn.Signature.Results().Variables() {
|
||||
if typeutil.IsPointerLike(ret.Type()) {
|
||||
anyPointers = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !anyPointers {
|
||||
goto bailout
|
||||
}
|
||||
|
||||
n := numbering{}
|
||||
|
||||
processBlock := func(from, to *ir.BasicBlock, s state) state {
|
||||
handleReturnValue := func(v ir.Value, call *ir.Call, idx int) {
|
||||
typ := call.Common().Signature().Results().At(idx).Type()
|
||||
if !typeutil.IsPointerLike(typ) {
|
||||
s.setOuter(v, NeverNil)
|
||||
return
|
||||
}
|
||||
|
||||
if callee, ok := call.Call.Value.(*ir.Builtin); ok {
|
||||
switch callee.Name() {
|
||||
case "append":
|
||||
// TODO(dh): if we knew that the varargs had non-zero
|
||||
// length, we'd know that the resulting slice is non-nil.
|
||||
switch an := s.get(call.Call.Args[0]).Outer; an {
|
||||
case MaybeNil, MaybeNilGlobal, NeverNil:
|
||||
s.setOuter(v, an)
|
||||
case AlwaysNil:
|
||||
s.setOuter(v, MaybeNil)
|
||||
}
|
||||
case "UnsafeSlice":
|
||||
// If len is negative, or if ptr is nil and len is not
|
||||
// zero, unsafe.Slice panics. This implies that a non-nil
|
||||
// pointer cannot become nil, and vice versa.
|
||||
s.set(v, s.get(call.Call.Args[0]))
|
||||
case "UnsafeStringData":
|
||||
// TODO(dh): if we had string length information we could
|
||||
// return better information.
|
||||
s.setOuter(v, MaybeNil)
|
||||
case "UnsafeSliceData":
|
||||
// When the slice is non-nil but has zero capacity, the
|
||||
// returned pointer is still non-nil, so we don't have to
|
||||
// worry about that.
|
||||
s.set(v, s.get(call.Call.Args[0]))
|
||||
case "UnsafeAdd":
|
||||
// TODO(dh): a positive addend can never result in a nil pointer.
|
||||
|
||||
// Pointer arithmetic can turn nil pointers into non-nil
|
||||
// ones and vice versa.
|
||||
s.setOuter(v, MaybeNil)
|
||||
case "ssa:deferstack":
|
||||
s.setOuter(v, NeverNil)
|
||||
case "ssa:wrapnilchk":
|
||||
s.setOuter(v, NeverNil)
|
||||
case "recover":
|
||||
s.setOuter(v, MaybeNil)
|
||||
default:
|
||||
panic(fmt.Sprintf("internal error: unhandled builtin %s", callee.Name()))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
callee := call.Common().StaticCallee()
|
||||
if callee == nil {
|
||||
// We don't know which function is being called.
|
||||
s.set(v, ValueNilness{MaybeNil, MaybeNil})
|
||||
return
|
||||
}
|
||||
calleeNilness := impl(pass, callee, seenFns)
|
||||
if len(calleeNilness) > idx {
|
||||
s.set(v, normalize(calleeNilness[idx], typ))
|
||||
} else {
|
||||
s.set(v, ValueNilness{MaybeNil, MaybeNil})
|
||||
}
|
||||
}
|
||||
|
||||
for _, instr := range from.Instrs {
|
||||
// It is tempting to return early when instr is an ir.Value that
|
||||
// doesn't have pointer type. However, instructions like ir.Load
|
||||
// tell us something about the value being operated on.
|
||||
|
||||
switch v := instr.(type) {
|
||||
case *ir.Convert:
|
||||
s.set(v, s.get(v.X))
|
||||
case *ir.SliceToArrayPointer:
|
||||
// Go does not currently allow (*T)(s) where T is a type
|
||||
// parameter with a type set consisting of array types, but it
|
||||
// does allow (T)(s) where T is a type parameter with a type
|
||||
// set consisting of pointers to array types.
|
||||
|
||||
allNonZero := typeutil.All(v.Type(), func(term *types.Term) bool {
|
||||
ptr := term.Type().Underlying().(*types.Pointer).Elem()
|
||||
return typeutil.All(ptr, func(innerTerm *types.Term) bool {
|
||||
return innerTerm.Type().Underlying().(*types.Array).Len() != 0
|
||||
})
|
||||
})
|
||||
|
||||
if allNonZero {
|
||||
// converting a slice to an array pointer of length > 0
|
||||
// panics if the slice is nil
|
||||
s.setOuter(v, NeverNil)
|
||||
s.setOuter(v.X, NeverNil)
|
||||
} else {
|
||||
s.set(v, s.get(v.X))
|
||||
}
|
||||
case *ir.SliceToArray:
|
||||
// Pretty much the same logic as SliceToArrayPointer, minus the
|
||||
// pointer.
|
||||
|
||||
allNonZero := typeutil.All(v.Type(), func(term *types.Term) bool {
|
||||
return term.Type().Underlying().(*types.Array).Len() != 0
|
||||
})
|
||||
|
||||
if allNonZero {
|
||||
// converting a slice to an array of length > 0
|
||||
// panics if the slice is nil
|
||||
s.setOuter(v.X, NeverNil)
|
||||
}
|
||||
case *ir.Slice:
|
||||
if typeutil.All(v.X.Type(), typeutil.IsType[*types.Array]) {
|
||||
// Slicing arrays never results in a nil slice.
|
||||
s.setOuter(v, NeverNil)
|
||||
continue
|
||||
}
|
||||
|
||||
// checkBound returns true if one of the bounds (low, high,
|
||||
// capacity) has non-zero value.
|
||||
checkBound := func(v ir.Value) bool {
|
||||
if v == nil {
|
||||
return false
|
||||
}
|
||||
// TODO(dh): this is where integration with constant
|
||||
// propagation and value range analysis would be useful.
|
||||
if k, ok := v.(*ir.Const); ok {
|
||||
kv, ok := constant.Int64Val(k.Value)
|
||||
return !ok || kv != 0
|
||||
}
|
||||
return false
|
||||
}
|
||||
if checkBound(v.Low) || checkBound(v.High) || checkBound(v.Max) {
|
||||
// One of the indices is non-zero, which means slicing can
|
||||
// only succeed if the slicee is not nil.
|
||||
s.setOuter(v, NeverNil)
|
||||
s.setOuter(v.X, NeverNil)
|
||||
} else {
|
||||
// The new slice is as nilly as the slicee.
|
||||
s.set(v, s.get(v.X))
|
||||
}
|
||||
|
||||
case *ir.If:
|
||||
cond := v.Cond
|
||||
binop, ok := cond.(*ir.BinOp)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
isNil := func(v ir.Value) bool {
|
||||
k, ok := v.(*ir.Const)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return k.Value == nil
|
||||
}
|
||||
var target ir.Value
|
||||
if isNil(binop.X) {
|
||||
target = binop.Y
|
||||
} else if isNil(binop.Y) {
|
||||
target = binop.X
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
op := binop.Op
|
||||
if to != from.Succs[0] {
|
||||
// we're in the false branch, negate op
|
||||
switch op {
|
||||
case token.EQL:
|
||||
op = token.NEQ
|
||||
case token.NEQ:
|
||||
op = token.EQL
|
||||
default:
|
||||
panic(fmt.Sprintf("internal error: unhandled token %v", op))
|
||||
}
|
||||
}
|
||||
switch op {
|
||||
case token.EQL:
|
||||
s.set(target, ValueNilness{AlwaysNil, AlwaysNil})
|
||||
case token.NEQ:
|
||||
s.setOuter(target, NeverNil)
|
||||
default:
|
||||
panic(fmt.Sprintf("internal error: unhandled token %v", op))
|
||||
}
|
||||
|
||||
// TODO(dh): also handle comparison of two non-nil values. The
|
||||
// true branch of neverNil == nilly makes the nilly value neverNil.
|
||||
case *ir.ChangeType:
|
||||
s.set(v, s.get(v.X))
|
||||
case *ir.MultiConvert:
|
||||
s.set(v, s.get(v.X))
|
||||
case *ir.Load:
|
||||
if _, ok := v.X.(*ir.Global); ok {
|
||||
s.setOuter(v, MaybeNilGlobal)
|
||||
} else {
|
||||
s.setOuter(v, MaybeNil)
|
||||
}
|
||||
s.setOuter(v.X, NeverNil)
|
||||
case *ir.FieldAddr:
|
||||
s.setOuter(v.X, NeverNil)
|
||||
s.setOuter(v, NeverNil)
|
||||
case *ir.IndexAddr:
|
||||
s.setOuter(v.X, NeverNil)
|
||||
s.setOuter(v, NeverNil)
|
||||
case *ir.Alloc, *ir.MakeMap, *ir.MakeSlice, *ir.MakeClosure, *ir.MakeChan:
|
||||
s.setOuter(v.(ir.Value), NeverNil)
|
||||
case *ir.MapUpdate:
|
||||
s.setOuter(v.Map, NeverNil)
|
||||
case *ir.Store:
|
||||
s.setOuter(v.Addr, NeverNil)
|
||||
case ir.CallInstruction:
|
||||
// go/defer/calling a nil function fatals/panics
|
||||
if !v.Common().IsInvoke() {
|
||||
s.setOuter(v.Common().Value, NeverNil)
|
||||
}
|
||||
_, ok := v.(*ir.Call)
|
||||
if !ok {
|
||||
// Defer and Go don't produce values
|
||||
continue
|
||||
}
|
||||
if v.Common().Signature().Results().Len() != 1 {
|
||||
// If the called function doesn't return any values then we
|
||||
// don't care about it. If it has more than one return
|
||||
// value, they'll be handled by Extract.
|
||||
continue
|
||||
}
|
||||
|
||||
handleReturnValue(v.(ir.Value), v.(*ir.Call), 0)
|
||||
case *ir.Send:
|
||||
s.setOuter(v.Chan, NeverNil)
|
||||
case *ir.Recv:
|
||||
s.setOuter(v.Chan, NeverNil)
|
||||
s.set(v, ValueNilness{MaybeNil, MaybeNil})
|
||||
case *ir.MakeInterface:
|
||||
s.set(v, ValueNilness{
|
||||
Inner: s.get(v.X).Outer,
|
||||
Outer: NeverNil,
|
||||
})
|
||||
case *ir.ChangeInterface:
|
||||
s.set(v, s.get(v.X))
|
||||
case *ir.TypeAssert:
|
||||
if !v.CommaOk {
|
||||
// The interface value cannot have been nil, or the type
|
||||
// assertion would have panicked.
|
||||
s.setOuter(v.X, NeverNil)
|
||||
|
||||
if types.IsInterface(v.Type()) && !typeparams.IsTypeParam(v.Type()) {
|
||||
// Type asserting to another interface doesn't succeed
|
||||
// if the assertee was nil. It also results in a new
|
||||
// interface value.
|
||||
s.setOuter(v, NeverNil)
|
||||
s.setInner(v, s.get(v.X).Inner)
|
||||
} else {
|
||||
// We've extracted the interface value's inner value.
|
||||
s.setOuter(v, s.get(v.X).Inner)
|
||||
}
|
||||
} else {
|
||||
// In a comma-ok type assertion, the return type is a
|
||||
// tuple. There'll be Extract instructions getting the
|
||||
// individual values, to which we'll attach the nilness
|
||||
// info.
|
||||
}
|
||||
case *ir.TypeSwitch:
|
||||
// Handled in Extract
|
||||
case *ir.MapLookup:
|
||||
if s.get(v.X).Outer == AlwaysNil {
|
||||
s.set(v, ValueNilness{AlwaysNil, AlwaysNil})
|
||||
} else {
|
||||
s.set(v, ValueNilness{MaybeNil, MaybeNil})
|
||||
}
|
||||
case *ir.Field:
|
||||
s.set(v.X, ValueNilness{NeverNil, NeverNil})
|
||||
s.set(v, ValueNilness{MaybeNil, MaybeNil})
|
||||
case *ir.Index:
|
||||
s.set(v.X, ValueNilness{NeverNil, NeverNil})
|
||||
s.set(v, ValueNilness{MaybeNil, MaybeNil})
|
||||
|
||||
case *ir.Extract:
|
||||
switch tuple := v.Tuple.(type) {
|
||||
case *ir.TypeAssert:
|
||||
// When we get here, the type assertion used the comma-ok
|
||||
// form, and we don't yet know anything about the result of
|
||||
// the type assertion.
|
||||
if v.Index == 0 {
|
||||
s.set(v, ValueNilness{MaybeNil, MaybeNil})
|
||||
}
|
||||
|
||||
// TODO(dh): We should set v's nilness in the true and
|
||||
// false branches of checks on the ok value. However, ok can be
|
||||
// used in arbitrary ways, and we're also not set up to handle
|
||||
// relational facts (ok being true or false affects the value
|
||||
// of the other Extract).
|
||||
|
||||
case *ir.Call:
|
||||
handleReturnValue(v, tuple, v.Index)
|
||||
|
||||
case *ir.TypeSwitch:
|
||||
if v.Index == 0 {
|
||||
// Index 0 is an integer and not interesting.
|
||||
continue
|
||||
}
|
||||
idx := v.Index - 1
|
||||
if idx >= len(tuple.Conds) {
|
||||
// Default branch
|
||||
|
||||
// If there is an untyped nil case, then being in the
|
||||
// default branch tells us that the interface value
|
||||
// isn't nil.
|
||||
hasNil := slices.ContainsFunc(tuple.Conds, func(typ types.Type) bool {
|
||||
if typ, ok := typ.(*types.Basic); ok && typ.Kind() == types.UntypedNil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
if hasNil {
|
||||
s.setOuter(tuple.Tag, NeverNil)
|
||||
} else {
|
||||
s.setOuter(tuple.Tag, MaybeNil)
|
||||
}
|
||||
s.setOuter(v, s.get(tuple.Tag).Inner)
|
||||
} else {
|
||||
// There is no Extract for the 'untyped nil' case,
|
||||
// which means that executing any Extract from a type
|
||||
// switch implies that the switched-over value wasn't a
|
||||
// nil interface value.
|
||||
s.setOuter(tuple.Tag, NeverNil)
|
||||
typ := tuple.Conds[idx]
|
||||
if types.IsInterface(typ) && !typeparams.IsTypeParam(typ) {
|
||||
// Succesfully type asserting to an interface type
|
||||
// always produces a non-nil interface value.
|
||||
s.setInner(v, s.get(tuple.Tag).Inner)
|
||||
s.setOuter(v, NeverNil)
|
||||
} else {
|
||||
s.setOuter(v, s.get(tuple.Tag).Inner)
|
||||
}
|
||||
}
|
||||
default:
|
||||
s.set(v, ValueNilness{MaybeNil, MaybeNil})
|
||||
}
|
||||
case *ir.Select:
|
||||
if v.Blocking && len(v.States) == 1 {
|
||||
// If the select doesn't have a default branch and only has
|
||||
// one state, that state's channel cannot have been nil if
|
||||
// we finished execution the select.
|
||||
s.setOuter(v.States[0].Chan, NeverNil)
|
||||
}
|
||||
case *ir.Jump, *ir.BlankStore, *ir.Phi,
|
||||
*ir.Panic, *ir.Return, *ir.RunDefers, *ir.Unreachable, *ir.ConstantSwitch,
|
||||
*ir.UnOp, *ir.BinOp, *ir.CompositeValue, *ir.Range, *ir.Next:
|
||||
default:
|
||||
posn := pass.Fset.PositionFor(v.Pos(), false)
|
||||
panic(fmt.Sprintf("internal error: unhandled type %T at %s", v, posn))
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
processPhis := func(b *ir.BasicBlock, i int, s state) state {
|
||||
for _, instr := range b.Instrs {
|
||||
if instr, ok := instr.(*ir.Phi); ok {
|
||||
s.set(instr, s.get(instr.Edges[i]))
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Populate default state for non-instruction values we encounter. We
|
||||
// cannot defer this logic to state.get because control flow merges use
|
||||
// simple merges of lattice values and won't know about value-specific
|
||||
// defaults.
|
||||
entrys := state{cloned: true, n: n}
|
||||
for _, param := range fn.Params {
|
||||
if typeutil.IsPointerLike(param.Type()) {
|
||||
entrys.set(param, ValueNilness{Inner: MaybeNil, Outer: MaybeNil})
|
||||
} else {
|
||||
// We never track nilness for value types, so they don't have to be
|
||||
// present in the entry state, either.
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(fn.Synthetic, "bound method wrapper") {
|
||||
// This is a bound method and the bound receiver might be nil
|
||||
for _, fvar := range fn.FreeVars {
|
||||
entrys.set(fvar, ValueNilness{Outer: MaybeNil})
|
||||
}
|
||||
} else {
|
||||
// This is a closure, and closed over variables are allocs, which
|
||||
// cannot be nil.
|
||||
for _, fvar := range fn.FreeVars {
|
||||
entrys.set(fvar, ValueNilness{Outer: NeverNil})
|
||||
}
|
||||
}
|
||||
var ops []*ir.Value
|
||||
for _, b := range fn.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
ops = instr.Operands(ops[:0])
|
||||
for _, pop := range ops {
|
||||
if op, ok := (*pop).(*ir.Const); ok && typeutil.IsPointerLike(op.Type()) {
|
||||
// The only constant pointer-like is nil.
|
||||
entrys.set(op, ValueNilness{Inner: AlwaysNil, Outer: AlwaysNil})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
res := dense.Forward[dfa.DenseMapLattice[ValueNilness, lattice]](
|
||||
fn,
|
||||
map[int][]ValueNilness{0: entrys.m},
|
||||
func(fromID, toID int, in []ValueNilness) []ValueNilness {
|
||||
from := fn.Blocks[fromID]
|
||||
to := fn.Blocks[toID]
|
||||
s := state{n: n, m: in}
|
||||
s = processBlock(from, to, s)
|
||||
i := slices.Index(to.Preds, from)
|
||||
s = processPhis(to, i, s)
|
||||
return s.m
|
||||
},
|
||||
)
|
||||
|
||||
retNilness := make([]ValueNilness, fn.Signature.Results().Len())
|
||||
for b := range fn.Returns() {
|
||||
ret := b.Control().(*ir.Return)
|
||||
s := state{n: n, m: res.In(b.Index)}
|
||||
s = processBlock(b, nil, s)
|
||||
for i, res := range ret.Results {
|
||||
retNilness[i] = lattice{}.Merge(retNilness[i], s.get(res))
|
||||
}
|
||||
}
|
||||
|
||||
interesting := false
|
||||
for i := range retNilness {
|
||||
typ := fn.Signature.Results().At(i).Type()
|
||||
if !typeutil.IsPointerLike(typ) {
|
||||
retNilness[i] = ValueNilness{NeverNil, NeverNil}
|
||||
continue
|
||||
}
|
||||
retNilness[i] = normalize(retNilness[i], typ)
|
||||
if retNilness[i] != (ValueNilness{MaybeNil, MaybeNil}) {
|
||||
interesting = true
|
||||
}
|
||||
}
|
||||
|
||||
if interesting {
|
||||
pass.ExportObjectFact(fn.Object(), &nilnessFact{retNilness})
|
||||
}
|
||||
|
||||
return retNilness
|
||||
}
|
||||
|
||||
type lattice struct{}
|
||||
|
||||
var _ dfa.Semilattice[ValueNilness] = lattice{}
|
||||
|
||||
// Equals implements [dfa.Semilattice].
|
||||
func (l lattice) Equals(a, b ValueNilness) bool {
|
||||
return a == b
|
||||
}
|
||||
|
||||
// Ident implements [dfa.Semilattice].
|
||||
func (l lattice) Ident() ValueNilness {
|
||||
return ValueNilness{}
|
||||
}
|
||||
|
||||
var latticeMerge = [5][5]Nilness{
|
||||
0: {
|
||||
0: 0,
|
||||
NeverNil: NeverNil,
|
||||
AlwaysNil: AlwaysNil,
|
||||
MaybeNilGlobal: MaybeNilGlobal,
|
||||
MaybeNil: MaybeNil,
|
||||
},
|
||||
NeverNil: {
|
||||
0: NeverNil,
|
||||
NeverNil: NeverNil,
|
||||
AlwaysNil: MaybeNil,
|
||||
MaybeNilGlobal: MaybeNilGlobal,
|
||||
MaybeNil: MaybeNil,
|
||||
},
|
||||
AlwaysNil: {
|
||||
0: AlwaysNil,
|
||||
NeverNil: MaybeNil,
|
||||
AlwaysNil: AlwaysNil,
|
||||
MaybeNilGlobal: MaybeNil,
|
||||
MaybeNil: MaybeNil,
|
||||
},
|
||||
MaybeNilGlobal: {
|
||||
0: MaybeNilGlobal,
|
||||
NeverNil: MaybeNilGlobal,
|
||||
AlwaysNil: MaybeNil,
|
||||
MaybeNilGlobal: MaybeNilGlobal,
|
||||
MaybeNil: MaybeNil,
|
||||
},
|
||||
MaybeNil: {
|
||||
0: MaybeNil,
|
||||
NeverNil: MaybeNil,
|
||||
AlwaysNil: MaybeNil,
|
||||
MaybeNilGlobal: MaybeNil,
|
||||
MaybeNil: MaybeNil,
|
||||
},
|
||||
}
|
||||
|
||||
// Merge implements [dfa.Semilattice].
|
||||
func (l lattice) Merge(a, b ValueNilness) ValueNilness {
|
||||
return ValueNilness{
|
||||
Inner: latticeMerge[a.Inner][b.Inner],
|
||||
Outer: latticeMerge[a.Outer][b.Outer],
|
||||
}
|
||||
}
|
||||
|
||||
type numbering map[ir.Value]int
|
||||
|
||||
func (n numbering) number(v ir.Value) int {
|
||||
i, ok := n[v]
|
||||
if !ok {
|
||||
i = len(n)
|
||||
n[v] = i
|
||||
}
|
||||
return i
|
||||
}
|
||||
264
vendor/honnef.co/go/tools/analysis/facts/purity/purity.go
vendored
Normal file
264
vendor/honnef.co/go/tools/analysis/facts/purity/purity.go
vendored
Normal file
@@ -0,0 +1,264 @@
|
||||
package purity
|
||||
|
||||
// TODO(dh): we should split this into two facts, one tracking actual purity, and one tracking side-effects. A function
|
||||
// that returns a heap allocation isn't pure, but it may be free of side effects.
|
||||
|
||||
import (
|
||||
"go/types"
|
||||
"reflect"
|
||||
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/go/ir/irutil"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
type IsPure struct{}
|
||||
|
||||
func (*IsPure) AFact() {}
|
||||
func (d *IsPure) String() string { return "is pure" }
|
||||
|
||||
type Result map[*types.Func]*IsPure
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "fact_purity",
|
||||
Doc: "Mark pure functions",
|
||||
Run: purity,
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
FactTypes: []analysis.Fact{(*IsPure)(nil)},
|
||||
ResultType: reflect.TypeFor[Result](),
|
||||
}
|
||||
|
||||
var pureStdlib = map[string]struct{}{
|
||||
"errors.New": {},
|
||||
"fmt.Errorf": {},
|
||||
"fmt.Sprintf": {},
|
||||
"fmt.Sprint": {},
|
||||
"sort.Reverse": {},
|
||||
"strings.Map": {},
|
||||
"strings.Repeat": {},
|
||||
"strings.Replace": {},
|
||||
"strings.Title": {},
|
||||
"strings.ToLower": {},
|
||||
"strings.ToLowerSpecial": {},
|
||||
"strings.ToTitle": {},
|
||||
"strings.ToTitleSpecial": {},
|
||||
"strings.ToUpper": {},
|
||||
"strings.ToUpperSpecial": {},
|
||||
"strings.Trim": {},
|
||||
"strings.TrimFunc": {},
|
||||
"strings.TrimLeft": {},
|
||||
"strings.TrimLeftFunc": {},
|
||||
"strings.TrimPrefix": {},
|
||||
"strings.TrimRight": {},
|
||||
"strings.TrimRightFunc": {},
|
||||
"strings.TrimSpace": {},
|
||||
"strings.TrimSuffix": {},
|
||||
"(*net/http.Request).WithContext": {},
|
||||
"time.Now": {},
|
||||
"time.Parse": {},
|
||||
"time.ParseInLocation": {},
|
||||
"time.Unix": {},
|
||||
"time.UnixMicro": {},
|
||||
"time.UnixMilli": {},
|
||||
"(time.Time).Add": {},
|
||||
"(time.Time).AddDate": {},
|
||||
"(time.Time).After": {},
|
||||
"(time.Time).Before": {},
|
||||
"(time.Time).Clock": {},
|
||||
"(time.Time).Compare": {},
|
||||
"(time.Time).Date": {},
|
||||
"(time.Time).Day": {},
|
||||
"(time.Time).Equal": {},
|
||||
"(time.Time).Format": {},
|
||||
"(time.Time).GoString": {},
|
||||
"(time.Time).GobEncode": {},
|
||||
"(time.Time).Hour": {},
|
||||
"(time.Time).ISOWeek": {},
|
||||
"(time.Time).In": {},
|
||||
"(time.Time).IsDST": {},
|
||||
"(time.Time).IsZero": {},
|
||||
"(time.Time).Local": {},
|
||||
"(time.Time).Location": {},
|
||||
"(time.Time).MarshalBinary": {},
|
||||
"(time.Time).MarshalJSON": {},
|
||||
"(time.Time).MarshalText": {},
|
||||
"(time.Time).Minute": {},
|
||||
"(time.Time).Month": {},
|
||||
"(time.Time).Nanosecond": {},
|
||||
"(time.Time).Round": {},
|
||||
"(time.Time).Second": {},
|
||||
"(time.Time).String": {},
|
||||
"(time.Time).Sub": {},
|
||||
"(time.Time).Truncate": {},
|
||||
"(time.Time).UTC": {},
|
||||
"(time.Time).Unix": {},
|
||||
"(time.Time).UnixMicro": {},
|
||||
"(time.Time).UnixMilli": {},
|
||||
"(time.Time).UnixNano": {},
|
||||
"(time.Time).Weekday": {},
|
||||
"(time.Time).Year": {},
|
||||
"(time.Time).YearDay": {},
|
||||
"(time.Time).Zone": {},
|
||||
"(time.Time).ZoneBounds": {},
|
||||
}
|
||||
|
||||
func purity(pass *analysis.Pass) (any, error) {
|
||||
seen := map[*ir.Function]struct{}{}
|
||||
irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg
|
||||
var check func(fn *ir.Function) (ret bool)
|
||||
check = func(fn *ir.Function) (ret bool) {
|
||||
if fn.Object() == nil {
|
||||
// TODO(dh): support closures
|
||||
return false
|
||||
}
|
||||
if pass.ImportObjectFact(fn.Object(), new(IsPure)) {
|
||||
return true
|
||||
}
|
||||
if fn.Pkg != irpkg {
|
||||
// Function is in another package but wasn't marked as
|
||||
// pure, ergo it isn't pure
|
||||
return false
|
||||
}
|
||||
// Break recursion
|
||||
if _, ok := seen[fn]; ok {
|
||||
return false
|
||||
}
|
||||
|
||||
seen[fn] = struct{}{}
|
||||
defer func() {
|
||||
if ret {
|
||||
pass.ExportObjectFact(fn.Object(), &IsPure{})
|
||||
}
|
||||
}()
|
||||
|
||||
if irutil.IsStub(fn) {
|
||||
return false
|
||||
}
|
||||
|
||||
if _, ok := pureStdlib[fn.Object().(*types.Func).FullName()]; ok {
|
||||
return true
|
||||
}
|
||||
|
||||
if fn.Signature.Results().Len() == 0 {
|
||||
// A function with no return values is empty or is doing some
|
||||
// work we cannot see (for example because of build tags);
|
||||
// don't consider it pure.
|
||||
return false
|
||||
}
|
||||
|
||||
var isBasic func(typ types.Type) bool
|
||||
isBasic = func(typ types.Type) bool {
|
||||
switch u := typ.Underlying().(type) {
|
||||
case *types.Basic:
|
||||
return true
|
||||
case *types.Struct:
|
||||
for field := range u.Fields() {
|
||||
if !isBasic(field.Type()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for _, param := range fn.Params {
|
||||
// TODO(dh): this may not be strictly correct. pure code can, to an extent, operate on non-basic types.
|
||||
if !isBasic(param.Type()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Don't consider external functions pure.
|
||||
if fn.Blocks == nil {
|
||||
return false
|
||||
}
|
||||
checkCall := func(common *ir.CallCommon) bool {
|
||||
if common.IsInvoke() {
|
||||
return false
|
||||
}
|
||||
builtin, ok := common.Value.(*ir.Builtin)
|
||||
if !ok {
|
||||
if common.StaticCallee() != fn {
|
||||
if common.StaticCallee() == nil {
|
||||
return false
|
||||
}
|
||||
if !check(common.StaticCallee()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch builtin.Name() {
|
||||
case "len", "cap":
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var isStackAddr func(ir.Value) bool
|
||||
isStackAddr = func(v ir.Value) bool {
|
||||
switch v := v.(type) {
|
||||
case *ir.Alloc:
|
||||
return !v.Heap
|
||||
case *ir.FieldAddr:
|
||||
return isStackAddr(v.X)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
for _, b := range fn.Blocks {
|
||||
for _, ins := range b.Instrs {
|
||||
switch ins := ins.(type) {
|
||||
case *ir.Call:
|
||||
if !checkCall(ins.Common()) {
|
||||
return false
|
||||
}
|
||||
case *ir.Defer:
|
||||
if !checkCall(&ins.Call) {
|
||||
return false
|
||||
}
|
||||
case *ir.Select:
|
||||
return false
|
||||
case *ir.Send:
|
||||
return false
|
||||
case *ir.Go:
|
||||
return false
|
||||
case *ir.Panic:
|
||||
return false
|
||||
case *ir.Store:
|
||||
if !isStackAddr(ins.Addr) {
|
||||
return false
|
||||
}
|
||||
case *ir.FieldAddr:
|
||||
if !isStackAddr(ins.X) {
|
||||
return false
|
||||
}
|
||||
case *ir.Alloc:
|
||||
// TODO(dh): make use of proper escape analysis
|
||||
if ins.Heap {
|
||||
return false
|
||||
}
|
||||
case *ir.Load:
|
||||
if !isStackAddr(ins.X) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
|
||||
check(fn)
|
||||
}
|
||||
|
||||
out := Result{}
|
||||
for _, fact := range pass.AllObjectFacts() {
|
||||
out[fact.Object.(*types.Func)] = fact.Fact.(*IsPure)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
24
vendor/honnef.co/go/tools/analysis/facts/tokenfile/token.go
vendored
Normal file
24
vendor/honnef.co/go/tools/analysis/facts/tokenfile/token.go
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
package tokenfile
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"reflect"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "tokenfileanalyzer",
|
||||
Doc: "creates a mapping of *token.File to *ast.File",
|
||||
Run: func(pass *analysis.Pass) (any, error) {
|
||||
m := map[*token.File]*ast.File{}
|
||||
for _, af := range pass.Files {
|
||||
tf := pass.Fset.File(af.Pos())
|
||||
m[tf] = af
|
||||
}
|
||||
return m, nil
|
||||
},
|
||||
RunDespiteErrors: true,
|
||||
ResultType: reflect.TypeFor[map[*token.File]*ast.File](),
|
||||
}
|
||||
221
vendor/honnef.co/go/tools/analysis/lint/lint.go
vendored
Normal file
221
vendor/honnef.co/go/tools/analysis/lint/lint.go
vendored
Normal file
@@ -0,0 +1,221 @@
|
||||
// Package lint provides abstractions on top of go/analysis.
|
||||
// These abstractions add extra information to analyzes, such as structured documentation and severities.
|
||||
package lint
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"honnef.co/go/tools/analysis/facts/tokenfile"
|
||||
)
|
||||
|
||||
// Analyzer wraps a go/analysis.Analyzer and provides structured documentation.
|
||||
type Analyzer struct {
|
||||
// The analyzer's documentation. Unlike go/analysis.Analyzer.Doc,
|
||||
// this field is structured, providing access to severity, options
|
||||
// etc.
|
||||
Doc *RawDocumentation
|
||||
Analyzer *analysis.Analyzer
|
||||
}
|
||||
|
||||
func InitializeAnalyzer(a *Analyzer) *Analyzer {
|
||||
a.Analyzer.Doc = a.Doc.Compile().String()
|
||||
a.Analyzer.URL = "https://staticcheck.dev/docs/checks/#" + a.Analyzer.Name
|
||||
a.Analyzer.Requires = append(a.Analyzer.Requires, tokenfile.Analyzer)
|
||||
return a
|
||||
}
|
||||
|
||||
// Severity describes the severity of diagnostics reported by an analyzer.
|
||||
type Severity int
|
||||
|
||||
const (
|
||||
SeverityNone Severity = iota
|
||||
SeverityError
|
||||
SeverityDeprecated
|
||||
SeverityWarning
|
||||
SeverityInfo
|
||||
SeverityHint
|
||||
)
|
||||
|
||||
// MergeStrategy sets how merge mode should behave for diagnostics of an analyzer.
|
||||
type MergeStrategy int
|
||||
|
||||
const (
|
||||
MergeIfAny MergeStrategy = iota
|
||||
MergeIfAll
|
||||
)
|
||||
|
||||
type RawDocumentation struct {
|
||||
Title string
|
||||
Text string
|
||||
Before string
|
||||
After string
|
||||
Since string
|
||||
NonDefault bool
|
||||
Options []string
|
||||
Severity Severity
|
||||
MergeIf MergeStrategy
|
||||
}
|
||||
|
||||
type Documentation struct {
|
||||
Title string
|
||||
Text string
|
||||
|
||||
TitleMarkdown string
|
||||
TextMarkdown string
|
||||
|
||||
Before string
|
||||
After string
|
||||
Since string
|
||||
NonDefault bool
|
||||
Options []string
|
||||
Severity Severity
|
||||
MergeIf MergeStrategy
|
||||
}
|
||||
|
||||
func (doc RawDocumentation) Compile() *Documentation {
|
||||
return &Documentation{
|
||||
Title: strings.TrimSpace(stripMarkdown(doc.Title)),
|
||||
Text: strings.TrimSpace(stripMarkdown(doc.Text)),
|
||||
|
||||
TitleMarkdown: strings.TrimSpace(toMarkdown(doc.Title)),
|
||||
TextMarkdown: strings.TrimSpace(toMarkdown(doc.Text)),
|
||||
|
||||
Before: strings.TrimSpace(doc.Before),
|
||||
After: strings.TrimSpace(doc.After),
|
||||
Since: doc.Since,
|
||||
NonDefault: doc.NonDefault,
|
||||
Options: doc.Options,
|
||||
Severity: doc.Severity,
|
||||
MergeIf: doc.MergeIf,
|
||||
}
|
||||
}
|
||||
|
||||
func toMarkdown(s string) string {
|
||||
return strings.NewReplacer(`\'`, "`", `\"`, "`").Replace(s)
|
||||
}
|
||||
|
||||
func stripMarkdown(s string) string {
|
||||
return strings.NewReplacer(`\'`, "", `\"`, "'").Replace(s)
|
||||
}
|
||||
|
||||
func (doc *Documentation) Format(metadata bool) string {
|
||||
return doc.format(false, metadata)
|
||||
}
|
||||
|
||||
func (doc *Documentation) FormatMarkdown(metadata bool) string {
|
||||
return doc.format(true, metadata)
|
||||
}
|
||||
|
||||
func (doc *Documentation) format(markdown bool, metadata bool) string {
|
||||
b := &strings.Builder{}
|
||||
if markdown {
|
||||
fmt.Fprintf(b, "%s\n\n", doc.TitleMarkdown)
|
||||
if doc.Text != "" {
|
||||
fmt.Fprintf(b, "%s\n\n", doc.TextMarkdown)
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintf(b, "%s\n\n", doc.Title)
|
||||
if doc.Text != "" {
|
||||
fmt.Fprintf(b, "%s\n\n", doc.Text)
|
||||
}
|
||||
}
|
||||
|
||||
if doc.Before != "" {
|
||||
fmt.Fprintln(b, "Before:")
|
||||
fmt.Fprintln(b, "")
|
||||
for line := range strings.SplitSeq(doc.Before, "\n") {
|
||||
fmt.Fprint(b, " ", line, "\n")
|
||||
}
|
||||
fmt.Fprintln(b, "")
|
||||
fmt.Fprintln(b, "After:")
|
||||
fmt.Fprintln(b, "")
|
||||
for line := range strings.SplitSeq(doc.After, "\n") {
|
||||
fmt.Fprint(b, " ", line, "\n")
|
||||
}
|
||||
fmt.Fprintln(b, "")
|
||||
}
|
||||
|
||||
if metadata {
|
||||
fmt.Fprint(b, "Available since\n ")
|
||||
if doc.Since == "" {
|
||||
fmt.Fprint(b, "unreleased")
|
||||
} else {
|
||||
fmt.Fprintf(b, "%s", doc.Since)
|
||||
}
|
||||
if doc.NonDefault {
|
||||
fmt.Fprint(b, ", non-default")
|
||||
}
|
||||
fmt.Fprint(b, "\n")
|
||||
if len(doc.Options) > 0 {
|
||||
fmt.Fprintf(b, "\nOptions\n")
|
||||
for _, opt := range doc.Options {
|
||||
fmt.Fprintf(b, " %s", opt)
|
||||
}
|
||||
fmt.Fprint(b, "\n")
|
||||
}
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (doc *Documentation) String() string {
|
||||
return doc.Format(true)
|
||||
}
|
||||
|
||||
// ExhaustiveTypeSwitch panics when called. It can be used to ensure
|
||||
// that type switches are exhaustive.
|
||||
func ExhaustiveTypeSwitch(v any) {
|
||||
panic(fmt.Sprintf("internal error: unhandled case %T", v))
|
||||
}
|
||||
|
||||
// A directive is a comment of the form '//lint:<command>
|
||||
// [arguments...]'. It represents instructions to the static analysis
|
||||
// tool.
|
||||
type Directive struct {
|
||||
Command string
|
||||
Arguments []string
|
||||
Directive *ast.Comment
|
||||
Node ast.Node
|
||||
}
|
||||
|
||||
func parseDirective(s string) (cmd string, args []string) {
|
||||
if !strings.HasPrefix(s, "//lint:") {
|
||||
return "", nil
|
||||
}
|
||||
s = strings.TrimPrefix(s, "//lint:")
|
||||
fields := strings.Split(s, " ")
|
||||
return fields[0], fields[1:]
|
||||
}
|
||||
|
||||
// ParseDirectives extracts all directives from a list of Go files.
|
||||
func ParseDirectives(files []*ast.File, fset *token.FileSet) []Directive {
|
||||
var dirs []Directive
|
||||
for _, f := range files {
|
||||
// OPT(dh): in our old code, we skip all the comment map work if we
|
||||
// couldn't find any directives, benchmark if that's actually
|
||||
// worth doing
|
||||
cm := ast.NewCommentMap(fset, f, f.Comments)
|
||||
for node, cgs := range cm {
|
||||
for _, cg := range cgs {
|
||||
for _, c := range cg.List {
|
||||
if !strings.HasPrefix(c.Text, "//lint:") {
|
||||
continue
|
||||
}
|
||||
cmd, args := parseDirective(c.Text)
|
||||
d := Directive{
|
||||
Command: cmd,
|
||||
Arguments: args,
|
||||
Directive: c,
|
||||
Node: node,
|
||||
}
|
||||
dirs = append(dirs, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return dirs
|
||||
}
|
||||
280
vendor/honnef.co/go/tools/analysis/report/report.go
vendored
Normal file
280
vendor/honnef.co/go/tools/analysis/report/report.go
vendored
Normal file
@@ -0,0 +1,280 @@
|
||||
package report
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/format"
|
||||
"go/token"
|
||||
"go/version"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/facts/generated"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
ShortRange bool
|
||||
FilterGenerated bool
|
||||
Fixes []analysis.SuggestedFix
|
||||
Related []analysis.RelatedInformation
|
||||
MinimumLanguageVersion string
|
||||
MaximumLanguageVersion string
|
||||
MinimumStdlibVersion string
|
||||
MaximumStdlibVersion string
|
||||
}
|
||||
|
||||
type Option func(*Options)
|
||||
|
||||
func ShortRange() Option {
|
||||
return func(opts *Options) {
|
||||
opts.ShortRange = true
|
||||
}
|
||||
}
|
||||
|
||||
func FilterGenerated() Option {
|
||||
return func(opts *Options) {
|
||||
opts.FilterGenerated = true
|
||||
}
|
||||
}
|
||||
|
||||
func Fixes(fixes ...analysis.SuggestedFix) Option {
|
||||
return func(opts *Options) {
|
||||
opts.Fixes = append(opts.Fixes, fixes...)
|
||||
}
|
||||
}
|
||||
|
||||
func Related(node Positioner, message string) Option {
|
||||
return func(opts *Options) {
|
||||
pos, end, ok := getRange(node, opts.ShortRange)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
r := analysis.RelatedInformation{
|
||||
Pos: pos,
|
||||
End: end,
|
||||
Message: message,
|
||||
}
|
||||
opts.Related = append(opts.Related, r)
|
||||
}
|
||||
}
|
||||
|
||||
func MinimumLanguageVersion(vers string) Option {
|
||||
return func(opts *Options) { opts.MinimumLanguageVersion = vers }
|
||||
}
|
||||
func MaximumLanguageVersion(vers string) Option {
|
||||
return func(opts *Options) { opts.MinimumLanguageVersion = vers }
|
||||
}
|
||||
func MinimumStdlibVersion(vers string) Option {
|
||||
return func(opts *Options) { opts.MinimumStdlibVersion = vers }
|
||||
}
|
||||
func MaximumStdlibVersion(vers string) Option {
|
||||
return func(opts *Options) { opts.MaximumStdlibVersion = vers }
|
||||
}
|
||||
|
||||
type Positioner interface {
|
||||
Pos() token.Pos
|
||||
}
|
||||
|
||||
type fullPositioner interface {
|
||||
Pos() token.Pos
|
||||
End() token.Pos
|
||||
}
|
||||
|
||||
type sourcer interface {
|
||||
Source() ast.Node
|
||||
}
|
||||
|
||||
// shortRange returns the position and end of the main component of an
|
||||
// AST node. For nodes that have no body, the short range is identical
|
||||
// to the node's Pos and End. For nodes that do have a body, the short
|
||||
// range excludes the body.
|
||||
func shortRange(node ast.Node) (pos, end token.Pos) {
|
||||
switch node := node.(type) {
|
||||
case *ast.File:
|
||||
return node.Pos(), node.Name.End()
|
||||
case *ast.CaseClause:
|
||||
return node.Pos(), node.Colon + 1
|
||||
case *ast.CommClause:
|
||||
return node.Pos(), node.Colon + 1
|
||||
case *ast.DeferStmt:
|
||||
return node.Pos(), node.Defer + token.Pos(len("defer"))
|
||||
case *ast.ExprStmt:
|
||||
return shortRange(node.X)
|
||||
case *ast.ForStmt:
|
||||
if node.Post != nil {
|
||||
return node.For, node.Post.End()
|
||||
} else if node.Cond != nil {
|
||||
return node.For, node.Cond.End()
|
||||
} else if node.Init != nil {
|
||||
// +1 to catch the semicolon, for gofmt'ed code
|
||||
return node.Pos(), node.Init.End() + 1
|
||||
} else {
|
||||
return node.Pos(), node.For + token.Pos(len("for"))
|
||||
}
|
||||
case *ast.FuncDecl:
|
||||
return node.Pos(), node.Type.End()
|
||||
case *ast.FuncLit:
|
||||
return node.Pos(), node.Type.End()
|
||||
case *ast.GoStmt:
|
||||
if _, ok := ast.Unparen(node.Call.Fun).(*ast.FuncLit); ok {
|
||||
return node.Pos(), node.Go + token.Pos(len("go"))
|
||||
} else {
|
||||
return node.Pos(), node.End()
|
||||
}
|
||||
case *ast.IfStmt:
|
||||
return node.Pos(), node.Cond.End()
|
||||
case *ast.RangeStmt:
|
||||
return node.Pos(), node.X.End()
|
||||
case *ast.SelectStmt:
|
||||
return node.Pos(), node.Pos() + token.Pos(len("select"))
|
||||
case *ast.SwitchStmt:
|
||||
if node.Tag != nil {
|
||||
return node.Pos(), node.Tag.End()
|
||||
} else if node.Init != nil {
|
||||
// +1 to catch the semicolon, for gofmt'ed code
|
||||
return node.Pos(), node.Init.End() + 1
|
||||
} else {
|
||||
return node.Pos(), node.Pos() + token.Pos(len("switch"))
|
||||
}
|
||||
case *ast.TypeSwitchStmt:
|
||||
return node.Pos(), node.Assign.End()
|
||||
default:
|
||||
return node.Pos(), node.End()
|
||||
}
|
||||
}
|
||||
|
||||
func HasRange(node Positioner) bool {
|
||||
// we don't know if getRange will be called with shortRange set to
|
||||
// true, so make sure that both work.
|
||||
_, _, ok := getRange(node, false)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
_, _, ok = getRange(node, true)
|
||||
return ok
|
||||
}
|
||||
|
||||
func getRange(node Positioner, short bool) (pos, end token.Pos, ok bool) {
|
||||
switch n := node.(type) {
|
||||
case sourcer:
|
||||
s := n.Source()
|
||||
if s == nil {
|
||||
return 0, 0, false
|
||||
}
|
||||
if short {
|
||||
p, e := shortRange(s)
|
||||
return p, e, true
|
||||
}
|
||||
return s.Pos(), s.End(), true
|
||||
case fullPositioner:
|
||||
if short {
|
||||
p, e := shortRange(n)
|
||||
return p, e, true
|
||||
}
|
||||
return n.Pos(), n.End(), true
|
||||
default:
|
||||
return n.Pos(), token.NoPos, true
|
||||
}
|
||||
}
|
||||
|
||||
func Report(pass *analysis.Pass, node Positioner, message string, opts ...Option) {
|
||||
cfg := &Options{}
|
||||
for _, opt := range opts {
|
||||
opt(cfg)
|
||||
}
|
||||
|
||||
langVersion := code.LanguageVersion(pass, node)
|
||||
stdlibVersion := code.StdlibVersion(pass, node)
|
||||
if n := cfg.MaximumLanguageVersion; n != "" && version.Compare(n, langVersion) == -1 {
|
||||
return
|
||||
}
|
||||
if n := cfg.MaximumStdlibVersion; n != "" && version.Compare(n, stdlibVersion) == -1 {
|
||||
return
|
||||
}
|
||||
if n := cfg.MinimumLanguageVersion; n != "" && version.Compare(n, langVersion) == 1 {
|
||||
return
|
||||
}
|
||||
if n := cfg.MinimumStdlibVersion; n != "" && version.Compare(n, stdlibVersion) == 1 {
|
||||
return
|
||||
}
|
||||
|
||||
file := DisplayPosition(pass.Fset, node.Pos()).Filename
|
||||
if cfg.FilterGenerated {
|
||||
m := pass.ResultOf[generated.Analyzer].(map[string]generated.Generator)
|
||||
if _, ok := m[file]; ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
pos, end, ok := getRange(node, cfg.ShortRange)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("no valid position for reporting node %v", node))
|
||||
}
|
||||
d := analysis.Diagnostic{
|
||||
Pos: pos,
|
||||
End: end,
|
||||
Message: message,
|
||||
SuggestedFixes: cfg.Fixes,
|
||||
Related: cfg.Related,
|
||||
}
|
||||
pass.Report(d)
|
||||
}
|
||||
|
||||
func Render(pass *analysis.Pass, x any) string {
|
||||
var buf bytes.Buffer
|
||||
if err := format.Node(&buf, pass.Fset, x); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func RenderArgs(pass *analysis.Pass, args []ast.Expr) string {
|
||||
var ss []string
|
||||
for _, arg := range args {
|
||||
ss = append(ss, Render(pass, arg))
|
||||
}
|
||||
return strings.Join(ss, ", ")
|
||||
}
|
||||
|
||||
func DisplayPosition(fset *token.FileSet, p token.Pos) token.Position {
|
||||
if p == token.NoPos {
|
||||
return token.Position{}
|
||||
}
|
||||
|
||||
// Only use the adjusted position if it points to another Go file.
|
||||
// This means we'll point to the original file for cgo files, but
|
||||
// we won't point to a YACC grammar file.
|
||||
pos := fset.PositionFor(p, false)
|
||||
adjPos := fset.PositionFor(p, true)
|
||||
|
||||
if filepath.Ext(adjPos.Filename) == ".go" {
|
||||
return adjPos
|
||||
}
|
||||
|
||||
return pos
|
||||
}
|
||||
|
||||
func Ordinal(n int) string {
|
||||
suffix := "th"
|
||||
if n < 10 || n > 20 {
|
||||
switch n % 10 {
|
||||
case 0:
|
||||
suffix = "th"
|
||||
case 1:
|
||||
suffix = "st"
|
||||
case 2:
|
||||
suffix = "nd"
|
||||
case 3:
|
||||
suffix = "rd"
|
||||
default:
|
||||
suffix = "th"
|
||||
}
|
||||
}
|
||||
|
||||
return strconv.Itoa(n) + suffix
|
||||
}
|
||||
Reference in New Issue
Block a user