Update go version
This commit is contained in:
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](),
|
||||
}
|
||||
Reference in New Issue
Block a user