 bf27481efd
			
		
	
	
		bf27481efd
		
			
		
	
	
	
	
		
			
			full diff: https://github.com/dominikh/go-tools/compare/2019.2.3...2020.1.3 Also updates tests to accomodate updated rules: --- FAIL: TestSourcesFromTestdataWithIssuesDir/staticcheck.go (0.43s) linters_test.go:137: [run --disable-all --print-issued-lines=false --print-linter-name=false --out-format=line-number --max-same-issues=10 -Estaticcheck --no-config testdata/staticcheck.go] linters_test.go:33: Error Trace: linters_test.go:33 linters_test.go:138 linters_test.go:53 Error: Received unexpected error: staticcheck.go:11: no match for `self-assignment of x to x` vs ["SA4006: this value of `x` is never used"] in: staticcheck.go:11:2: SA4006: this value of `x` is never used unmatched errors staticcheck.go:11:2: SA4006: this value of `x` is never used Test: TestSourcesFromTestdataWithIssuesDir/staticcheck.go Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
		
			
				
	
	
		
			1979 lines
		
	
	
		
			54 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			1979 lines
		
	
	
		
			54 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package unused
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"go/ast"
 | |
| 	"go/token"
 | |
| 	"go/types"
 | |
| 	"io"
 | |
| 	"strings"
 | |
| 	"sync"
 | |
| 	"sync/atomic"
 | |
| 
 | |
| 	"golang.org/x/tools/go/analysis"
 | |
| 	"honnef.co/go/tools/code"
 | |
| 	"honnef.co/go/tools/go/types/typeutil"
 | |
| 	"honnef.co/go/tools/internal/passes/buildir"
 | |
| 	"honnef.co/go/tools/ir"
 | |
| 	"honnef.co/go/tools/lint"
 | |
| )
 | |
| 
 | |
| // The graph we construct omits nodes along a path that do not
 | |
| // contribute any new information to the solution. For example, the
 | |
| // full graph for a function with a receiver would be Func ->
 | |
| // Signature -> Var -> Type. However, since signatures cannot be
 | |
| // unused, and receivers are always considered used, we can compact
 | |
| // the graph down to Func -> Type. This makes the graph smaller, but
 | |
| // harder to debug.
 | |
| 
 | |
| // TODO(dh): conversions between structs mark fields as used, but the
 | |
| // conversion itself isn't part of that subgraph. even if the function
 | |
| // containing the conversion is unused, the fields will be marked as
 | |
| // used.
 | |
| 
 | |
| // TODO(dh): we cannot observe function calls in assembly files.
 | |
| 
 | |
| /*
 | |
| 
 | |
| - packages use:
 | |
|   - (1.1) exported named types (unless in package main)
 | |
|   - (1.2) exported functions (unless in package main)
 | |
|   - (1.3) exported variables (unless in package main)
 | |
|   - (1.4) exported constants (unless in package main)
 | |
|   - (1.5) init functions
 | |
|   - (1.6) functions exported to cgo
 | |
|   - (1.7) the main function iff in the main package
 | |
|   - (1.8) symbols linked via go:linkname
 | |
| 
 | |
| - named types use:
 | |
|   - (2.1) exported methods
 | |
|   - (2.2) the type they're based on
 | |
|   - (2.3) all their aliases. we can't easily track uses of aliases
 | |
|     because go/types turns them into uses of the aliased types. assume
 | |
|     that if a type is used, so are all of its aliases.
 | |
|   - (2.4) the pointer type. this aids with eagerly implementing
 | |
|     interfaces. if a method that implements an interface is defined on
 | |
|     a pointer receiver, and the pointer type is never used, but the
 | |
|     named type is, then we still want to mark the method as used.
 | |
| 
 | |
| - variables and constants use:
 | |
|   - their types
 | |
| 
 | |
| - functions use:
 | |
|   - (4.1) all their arguments, return parameters and receivers
 | |
|   - (4.2) anonymous functions defined beneath them
 | |
|   - (4.3) closures and bound methods.
 | |
|     this implements a simplified model where a function is used merely by being referenced, even if it is never called.
 | |
|     that way we don't have to keep track of closures escaping functions.
 | |
|   - (4.4) functions they return. we assume that someone else will call the returned function
 | |
|   - (4.5) functions/interface methods they call
 | |
|   - types they instantiate or convert to
 | |
|   - (4.7) fields they access
 | |
|   - (4.8) types of all instructions
 | |
|   - (4.9) package-level variables they assign to iff in tests (sinks for benchmarks)
 | |
| 
 | |
| - conversions use:
 | |
|   - (5.1) when converting between two equivalent structs, the fields in
 | |
|     either struct use each other. the fields are relevant for the
 | |
|     conversion, but only if the fields are also accessed outside the
 | |
|     conversion.
 | |
|   - (5.2) when converting to or from unsafe.Pointer, mark all fields as used.
 | |
| 
 | |
| - structs use:
 | |
|   - (6.1) fields of type NoCopy sentinel
 | |
|   - (6.2) exported fields
 | |
|   - (6.3) embedded fields that help implement interfaces (either fully implements it, or contributes required methods) (recursively)
 | |
|   - (6.4) embedded fields that have exported methods (recursively)
 | |
|   - (6.5) embedded structs that have exported fields (recursively)
 | |
| 
 | |
| - (7.1) field accesses use fields
 | |
| - (7.2) fields use their types
 | |
| 
 | |
| - (8.0) How we handle interfaces:
 | |
|   - (8.1) We do not technically care about interfaces that only consist of
 | |
|     exported methods. Exported methods on concrete types are always
 | |
|     marked as used.
 | |
|   - Any concrete type implements all known interfaces. Even if it isn't
 | |
|     assigned to any interfaces in our code, the user may receive a value
 | |
|     of the type and expect to pass it back to us through an interface.
 | |
| 
 | |
|     Concrete types use their methods that implement interfaces. If the
 | |
|     type is used, it uses those methods. Otherwise, it doesn't. This
 | |
|     way, types aren't incorrectly marked reachable through the edge
 | |
|     from method to type.
 | |
| 
 | |
|   - (8.3) All interface methods are marked as used, even if they never get
 | |
|     called. This is to accommodate sum types (unexported interface
 | |
|     method that must exist but never gets called.)
 | |
| 
 | |
|   - (8.4) All embedded interfaces are marked as used. This is an
 | |
|     extension of 8.3, but we have to explicitly track embedded
 | |
|     interfaces because in a chain C->B->A, B wouldn't be marked as
 | |
|     used by 8.3 just because it contributes A's methods to C.
 | |
| 
 | |
| - Inherent uses:
 | |
|   - thunks and other generated wrappers call the real function
 | |
|   - (9.2) variables use their types
 | |
|   - (9.3) types use their underlying and element types
 | |
|   - (9.4) conversions use the type they convert to
 | |
|   - (9.5) instructions use their operands
 | |
|   - (9.6) instructions use their operands' types
 | |
|   - (9.7) variable _reads_ use variables, writes do not, except in tests
 | |
|   - (9.8) runtime functions that may be called from user code via the compiler
 | |
| 
 | |
| 
 | |
| - const groups:
 | |
|   (10.1) if one constant out of a block of constants is used, mark all
 | |
|   of them used. a lot of the time, unused constants exist for the sake
 | |
|   of completeness. See also
 | |
|   https://github.com/dominikh/go-tools/issues/365
 | |
| 
 | |
| 
 | |
| - (11.1) anonymous struct types use all their fields. we cannot
 | |
|   deduplicate struct types, as that leads to order-dependent
 | |
|   reportings. we can't not deduplicate struct types while still
 | |
|   tracking fields, because then each instance of the unnamed type in
 | |
|   the data flow chain will get its own fields, causing false
 | |
|   positives. Thus, we only accurately track fields of named struct
 | |
|   types, and assume that unnamed struct types use all their fields.
 | |
| 
 | |
| 
 | |
| - Differences in whole program mode:
 | |
|   - (e2) types aim to implement all exported interfaces from all packages
 | |
|   - (e3) exported identifiers aren't automatically used. for fields and
 | |
|     methods this poses extra issues due to reflection. We assume
 | |
|     that all exported fields are used. We also maintain a list of
 | |
|     known reflection-based method callers.
 | |
| 
 | |
| */
 | |
| 
 | |
| func assert(b bool) {
 | |
| 	if !b {
 | |
| 		panic("failed assertion")
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func typString(obj types.Object) string {
 | |
| 	switch obj := obj.(type) {
 | |
| 	case *types.Func:
 | |
| 		return "func"
 | |
| 	case *types.Var:
 | |
| 		if obj.IsField() {
 | |
| 			return "field"
 | |
| 		}
 | |
| 		return "var"
 | |
| 	case *types.Const:
 | |
| 		return "const"
 | |
| 	case *types.TypeName:
 | |
| 		return "type"
 | |
| 	default:
 | |
| 		return "identifier"
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // /usr/lib/go/src/runtime/proc.go:433:6: func badmorestackg0 is unused (U1000)
 | |
| 
 | |
| // Functions defined in the Go runtime that may be called through
 | |
| // compiler magic or via assembly.
 | |
| var runtimeFuncs = map[string]bool{
 | |
| 	// The first part of the list is copied from
 | |
| 	// cmd/compile/internal/gc/builtin.go, var runtimeDecls
 | |
| 	"newobject":            true,
 | |
| 	"panicindex":           true,
 | |
| 	"panicslice":           true,
 | |
| 	"panicdivide":          true,
 | |
| 	"panicmakeslicelen":    true,
 | |
| 	"throwinit":            true,
 | |
| 	"panicwrap":            true,
 | |
| 	"gopanic":              true,
 | |
| 	"gorecover":            true,
 | |
| 	"goschedguarded":       true,
 | |
| 	"printbool":            true,
 | |
| 	"printfloat":           true,
 | |
| 	"printint":             true,
 | |
| 	"printhex":             true,
 | |
| 	"printuint":            true,
 | |
| 	"printcomplex":         true,
 | |
| 	"printstring":          true,
 | |
| 	"printpointer":         true,
 | |
| 	"printiface":           true,
 | |
| 	"printeface":           true,
 | |
| 	"printslice":           true,
 | |
| 	"printnl":              true,
 | |
| 	"printsp":              true,
 | |
| 	"printlock":            true,
 | |
| 	"printunlock":          true,
 | |
| 	"concatstring2":        true,
 | |
| 	"concatstring3":        true,
 | |
| 	"concatstring4":        true,
 | |
| 	"concatstring5":        true,
 | |
| 	"concatstrings":        true,
 | |
| 	"cmpstring":            true,
 | |
| 	"intstring":            true,
 | |
| 	"slicebytetostring":    true,
 | |
| 	"slicebytetostringtmp": true,
 | |
| 	"slicerunetostring":    true,
 | |
| 	"stringtoslicebyte":    true,
 | |
| 	"stringtoslicerune":    true,
 | |
| 	"slicecopy":            true,
 | |
| 	"slicestringcopy":      true,
 | |
| 	"decoderune":           true,
 | |
| 	"countrunes":           true,
 | |
| 	"convI2I":              true,
 | |
| 	"convT16":              true,
 | |
| 	"convT32":              true,
 | |
| 	"convT64":              true,
 | |
| 	"convTstring":          true,
 | |
| 	"convTslice":           true,
 | |
| 	"convT2E":              true,
 | |
| 	"convT2Enoptr":         true,
 | |
| 	"convT2I":              true,
 | |
| 	"convT2Inoptr":         true,
 | |
| 	"assertE2I":            true,
 | |
| 	"assertE2I2":           true,
 | |
| 	"assertI2I":            true,
 | |
| 	"assertI2I2":           true,
 | |
| 	"panicdottypeE":        true,
 | |
| 	"panicdottypeI":        true,
 | |
| 	"panicnildottype":      true,
 | |
| 	"ifaceeq":              true,
 | |
| 	"efaceeq":              true,
 | |
| 	"fastrand":             true,
 | |
| 	"makemap64":            true,
 | |
| 	"makemap":              true,
 | |
| 	"makemap_small":        true,
 | |
| 	"mapaccess1":           true,
 | |
| 	"mapaccess1_fast32":    true,
 | |
| 	"mapaccess1_fast64":    true,
 | |
| 	"mapaccess1_faststr":   true,
 | |
| 	"mapaccess1_fat":       true,
 | |
| 	"mapaccess2":           true,
 | |
| 	"mapaccess2_fast32":    true,
 | |
| 	"mapaccess2_fast64":    true,
 | |
| 	"mapaccess2_faststr":   true,
 | |
| 	"mapaccess2_fat":       true,
 | |
| 	"mapassign":            true,
 | |
| 	"mapassign_fast32":     true,
 | |
| 	"mapassign_fast32ptr":  true,
 | |
| 	"mapassign_fast64":     true,
 | |
| 	"mapassign_fast64ptr":  true,
 | |
| 	"mapassign_faststr":    true,
 | |
| 	"mapiterinit":          true,
 | |
| 	"mapdelete":            true,
 | |
| 	"mapdelete_fast32":     true,
 | |
| 	"mapdelete_fast64":     true,
 | |
| 	"mapdelete_faststr":    true,
 | |
| 	"mapiternext":          true,
 | |
| 	"mapclear":             true,
 | |
| 	"makechan64":           true,
 | |
| 	"makechan":             true,
 | |
| 	"chanrecv1":            true,
 | |
| 	"chanrecv2":            true,
 | |
| 	"chansend1":            true,
 | |
| 	"closechan":            true,
 | |
| 	"writeBarrier":         true,
 | |
| 	"typedmemmove":         true,
 | |
| 	"typedmemclr":          true,
 | |
| 	"typedslicecopy":       true,
 | |
| 	"selectnbsend":         true,
 | |
| 	"selectnbrecv":         true,
 | |
| 	"selectnbrecv2":        true,
 | |
| 	"selectsetpc":          true,
 | |
| 	"selectgo":             true,
 | |
| 	"block":                true,
 | |
| 	"makeslice":            true,
 | |
| 	"makeslice64":          true,
 | |
| 	"growslice":            true,
 | |
| 	"memmove":              true,
 | |
| 	"memclrNoHeapPointers": true,
 | |
| 	"memclrHasPointers":    true,
 | |
| 	"memequal":             true,
 | |
| 	"memequal8":            true,
 | |
| 	"memequal16":           true,
 | |
| 	"memequal32":           true,
 | |
| 	"memequal64":           true,
 | |
| 	"memequal128":          true,
 | |
| 	"int64div":             true,
 | |
| 	"uint64div":            true,
 | |
| 	"int64mod":             true,
 | |
| 	"uint64mod":            true,
 | |
| 	"float64toint64":       true,
 | |
| 	"float64touint64":      true,
 | |
| 	"float64touint32":      true,
 | |
| 	"int64tofloat64":       true,
 | |
| 	"uint64tofloat64":      true,
 | |
| 	"uint32tofloat64":      true,
 | |
| 	"complex128div":        true,
 | |
| 	"racefuncenter":        true,
 | |
| 	"racefuncenterfp":      true,
 | |
| 	"racefuncexit":         true,
 | |
| 	"raceread":             true,
 | |
| 	"racewrite":            true,
 | |
| 	"racereadrange":        true,
 | |
| 	"racewriterange":       true,
 | |
| 	"msanread":             true,
 | |
| 	"msanwrite":            true,
 | |
| 	"x86HasPOPCNT":         true,
 | |
| 	"x86HasSSE41":          true,
 | |
| 	"arm64HasATOMICS":      true,
 | |
| 
 | |
| 	// The second part of the list is extracted from assembly code in
 | |
| 	// the standard library, with the exception of the runtime package itself
 | |
| 	"abort":                 true,
 | |
| 	"aeshashbody":           true,
 | |
| 	"args":                  true,
 | |
| 	"asminit":               true,
 | |
| 	"badctxt":               true,
 | |
| 	"badmcall2":             true,
 | |
| 	"badmcall":              true,
 | |
| 	"badmorestackg0":        true,
 | |
| 	"badmorestackgsignal":   true,
 | |
| 	"badsignal2":            true,
 | |
| 	"callbackasm1":          true,
 | |
| 	"callCfunction":         true,
 | |
| 	"cgocallback_gofunc":    true,
 | |
| 	"cgocallbackg":          true,
 | |
| 	"checkgoarm":            true,
 | |
| 	"check":                 true,
 | |
| 	"debugCallCheck":        true,
 | |
| 	"debugCallWrap":         true,
 | |
| 	"emptyfunc":             true,
 | |
| 	"entersyscall":          true,
 | |
| 	"exit":                  true,
 | |
| 	"exits":                 true,
 | |
| 	"exitsyscall":           true,
 | |
| 	"externalthreadhandler": true,
 | |
| 	"findnull":              true,
 | |
| 	"goexit1":               true,
 | |
| 	"gostring":              true,
 | |
| 	"i386_set_ldt":          true,
 | |
| 	"_initcgo":              true,
 | |
| 	"init_thread_tls":       true,
 | |
| 	"ldt0setup":             true,
 | |
| 	"libpreinit":            true,
 | |
| 	"load_g":                true,
 | |
| 	"morestack":             true,
 | |
| 	"mstart":                true,
 | |
| 	"nacl_sysinfo":          true,
 | |
| 	"nanotimeQPC":           true,
 | |
| 	"nanotime":              true,
 | |
| 	"newosproc0":            true,
 | |
| 	"newproc":               true,
 | |
| 	"newstack":              true,
 | |
| 	"noted":                 true,
 | |
| 	"nowQPC":                true,
 | |
| 	"osinit":                true,
 | |
| 	"printf":                true,
 | |
| 	"racecallback":          true,
 | |
| 	"reflectcallmove":       true,
 | |
| 	"reginit":               true,
 | |
| 	"rt0_go":                true,
 | |
| 	"save_g":                true,
 | |
| 	"schedinit":             true,
 | |
| 	"setldt":                true,
 | |
| 	"settls":                true,
 | |
| 	"sighandler":            true,
 | |
| 	"sigprofNonGo":          true,
 | |
| 	"sigtrampgo":            true,
 | |
| 	"_sigtramp":             true,
 | |
| 	"sigtramp":              true,
 | |
| 	"stackcheck":            true,
 | |
| 	"syscall_chdir":         true,
 | |
| 	"syscall_chroot":        true,
 | |
| 	"syscall_close":         true,
 | |
| 	"syscall_dup2":          true,
 | |
| 	"syscall_execve":        true,
 | |
| 	"syscall_exit":          true,
 | |
| 	"syscall_fcntl":         true,
 | |
| 	"syscall_forkx":         true,
 | |
| 	"syscall_gethostname":   true,
 | |
| 	"syscall_getpid":        true,
 | |
| 	"syscall_ioctl":         true,
 | |
| 	"syscall_pipe":          true,
 | |
| 	"syscall_rawsyscall6":   true,
 | |
| 	"syscall_rawSyscall6":   true,
 | |
| 	"syscall_rawsyscall":    true,
 | |
| 	"syscall_RawSyscall":    true,
 | |
| 	"syscall_rawsysvicall6": true,
 | |
| 	"syscall_setgid":        true,
 | |
| 	"syscall_setgroups":     true,
 | |
| 	"syscall_setpgid":       true,
 | |
| 	"syscall_setsid":        true,
 | |
| 	"syscall_setuid":        true,
 | |
| 	"syscall_syscall6":      true,
 | |
| 	"syscall_syscall":       true,
 | |
| 	"syscall_Syscall":       true,
 | |
| 	"syscall_sysvicall6":    true,
 | |
| 	"syscall_wait4":         true,
 | |
| 	"syscall_write":         true,
 | |
| 	"traceback":             true,
 | |
| 	"tstart":                true,
 | |
| 	"usplitR0":              true,
 | |
| 	"wbBufFlush":            true,
 | |
| 	"write":                 true,
 | |
| }
 | |
| 
 | |
| type pkg struct {
 | |
| 	Fset       *token.FileSet
 | |
| 	Files      []*ast.File
 | |
| 	Pkg        *types.Package
 | |
| 	TypesInfo  *types.Info
 | |
| 	TypesSizes types.Sizes
 | |
| 	IR         *ir.Package
 | |
| 	SrcFuncs   []*ir.Function
 | |
| }
 | |
| 
 | |
| type Checker struct {
 | |
| 	WholeProgram bool
 | |
| 	Debug        io.Writer
 | |
| 
 | |
| 	mu              sync.Mutex
 | |
| 	initialPackages map[*types.Package]struct{}
 | |
| 	allPackages     map[*types.Package]struct{}
 | |
| 	graph           *Graph
 | |
| }
 | |
| 
 | |
| func NewChecker(wholeProgram bool) *Checker {
 | |
| 	return &Checker{
 | |
| 		initialPackages: map[*types.Package]struct{}{},
 | |
| 		allPackages:     map[*types.Package]struct{}{},
 | |
| 		WholeProgram:    wholeProgram,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (c *Checker) Analyzer() *analysis.Analyzer {
 | |
| 	name := "U1000"
 | |
| 	if c.WholeProgram {
 | |
| 		name = "U1001"
 | |
| 	}
 | |
| 	return &analysis.Analyzer{
 | |
| 		Name:     name,
 | |
| 		Doc:      "Unused code",
 | |
| 		Run:      c.Run,
 | |
| 		Requires: []*analysis.Analyzer{buildir.Analyzer},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (c *Checker) Run(pass *analysis.Pass) (interface{}, error) {
 | |
| 	c.mu.Lock()
 | |
| 	if c.graph == nil {
 | |
| 		c.graph = NewGraph()
 | |
| 		c.graph.wholeProgram = c.WholeProgram
 | |
| 		c.graph.fset = pass.Fset
 | |
| 	}
 | |
| 
 | |
| 	var visit func(pkg *types.Package)
 | |
| 	visit = func(pkg *types.Package) {
 | |
| 		if _, ok := c.allPackages[pkg]; ok {
 | |
| 			return
 | |
| 		}
 | |
| 		c.allPackages[pkg] = struct{}{}
 | |
| 		for _, imp := range pkg.Imports() {
 | |
| 			visit(imp)
 | |
| 		}
 | |
| 	}
 | |
| 	visit(pass.Pkg)
 | |
| 
 | |
| 	c.initialPackages[pass.Pkg] = struct{}{}
 | |
| 	c.mu.Unlock()
 | |
| 
 | |
| 	irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR)
 | |
| 	pkg := &pkg{
 | |
| 		Fset:       pass.Fset,
 | |
| 		Files:      pass.Files,
 | |
| 		Pkg:        pass.Pkg,
 | |
| 		TypesInfo:  pass.TypesInfo,
 | |
| 		TypesSizes: pass.TypesSizes,
 | |
| 		IR:         irpkg.Pkg,
 | |
| 		SrcFuncs:   irpkg.SrcFuncs,
 | |
| 	}
 | |
| 
 | |
| 	c.processPkg(c.graph, pkg)
 | |
| 
 | |
| 	return nil, nil
 | |
| }
 | |
| 
 | |
| func (c *Checker) ProblemObject(fset *token.FileSet, obj types.Object) lint.Problem {
 | |
| 	name := obj.Name()
 | |
| 	if sig, ok := obj.Type().(*types.Signature); ok && sig.Recv() != nil {
 | |
| 		switch sig.Recv().Type().(type) {
 | |
| 		case *types.Named, *types.Pointer:
 | |
| 			typ := types.TypeString(sig.Recv().Type(), func(*types.Package) string { return "" })
 | |
| 			if len(typ) > 0 && typ[0] == '*' {
 | |
| 				name = fmt.Sprintf("(%s).%s", typ, obj.Name())
 | |
| 			} else if len(typ) > 0 {
 | |
| 				name = fmt.Sprintf("%s.%s", typ, obj.Name())
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	checkName := "U1000"
 | |
| 	if c.WholeProgram {
 | |
| 		checkName = "U1001"
 | |
| 	}
 | |
| 	return lint.Problem{
 | |
| 		Pos:     lint.DisplayPosition(fset, obj.Pos()),
 | |
| 		Message: fmt.Sprintf("%s %s is unused", typString(obj), name),
 | |
| 		Check:   checkName,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (c *Checker) Result() []types.Object {
 | |
| 	out := c.results()
 | |
| 
 | |
| 	out2 := make([]types.Object, 0, len(out))
 | |
| 	for _, v := range out {
 | |
| 		if _, ok := c.initialPackages[v.Pkg()]; !ok {
 | |
| 			continue
 | |
| 		}
 | |
| 		out2 = append(out2, v)
 | |
| 	}
 | |
| 
 | |
| 	return out2
 | |
| }
 | |
| 
 | |
| func (c *Checker) debugf(f string, v ...interface{}) {
 | |
| 	if c.Debug != nil {
 | |
| 		fmt.Fprintf(c.Debug, f, v...)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (graph *Graph) quieten(node *Node) {
 | |
| 	if node.seen {
 | |
| 		return
 | |
| 	}
 | |
| 	switch obj := node.obj.(type) {
 | |
| 	case *types.Named:
 | |
| 		for i := 0; i < obj.NumMethods(); i++ {
 | |
| 			m := obj.Method(i)
 | |
| 			if node, ok := graph.nodeMaybe(m); ok {
 | |
| 				node.quiet = true
 | |
| 			}
 | |
| 		}
 | |
| 	case *types.Struct:
 | |
| 		for i := 0; i < obj.NumFields(); i++ {
 | |
| 			if node, ok := graph.nodeMaybe(obj.Field(i)); ok {
 | |
| 				node.quiet = true
 | |
| 			}
 | |
| 		}
 | |
| 	case *types.Interface:
 | |
| 		for i := 0; i < obj.NumExplicitMethods(); i++ {
 | |
| 			m := obj.ExplicitMethod(i)
 | |
| 			if node, ok := graph.nodeMaybe(m); ok {
 | |
| 				node.quiet = true
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (c *Checker) results() []types.Object {
 | |
| 	if c.graph == nil {
 | |
| 		// We never analyzed any packages
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	var out []types.Object
 | |
| 
 | |
| 	if c.WholeProgram {
 | |
| 		var ifaces []*types.Interface
 | |
| 		var notIfaces []types.Type
 | |
| 
 | |
| 		// implement as many interfaces as possible
 | |
| 		c.graph.seenTypes.Iterate(func(t types.Type, _ interface{}) {
 | |
| 			switch t := t.(type) {
 | |
| 			case *types.Interface:
 | |
| 				if t.NumMethods() > 0 {
 | |
| 					ifaces = append(ifaces, t)
 | |
| 				}
 | |
| 			default:
 | |
| 				if _, ok := t.Underlying().(*types.Interface); !ok {
 | |
| 					notIfaces = append(notIfaces, t)
 | |
| 				}
 | |
| 			}
 | |
| 		})
 | |
| 
 | |
| 		for pkg := range c.allPackages {
 | |
| 			for _, iface := range interfacesFromExportData(pkg) {
 | |
| 				if iface.NumMethods() > 0 {
 | |
| 					ifaces = append(ifaces, iface)
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		ctx := &context{
 | |
| 			g:         c.graph,
 | |
| 			seenTypes: &c.graph.seenTypes,
 | |
| 		}
 | |
| 		// (8.0) handle interfaces
 | |
| 		// (e2) types aim to implement all exported interfaces from all packages
 | |
| 		for _, t := range notIfaces {
 | |
| 			// OPT(dh): it is unfortunate that we do not have access
 | |
| 			// to a populated method set at this point.
 | |
| 			ms := types.NewMethodSet(t)
 | |
| 			for _, iface := range ifaces {
 | |
| 				if sels, ok := c.graph.implements(t, iface, ms); ok {
 | |
| 					for _, sel := range sels {
 | |
| 						c.graph.useMethod(ctx, t, sel, t, edgeImplements)
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if c.Debug != nil {
 | |
| 		debugNode := func(node *Node) {
 | |
| 			if node.obj == nil {
 | |
| 				c.debugf("n%d [label=\"Root\"];\n", node.id)
 | |
| 			} else {
 | |
| 				c.debugf("n%d [label=%q];\n", node.id, fmt.Sprintf("(%T) %s", node.obj, node.obj))
 | |
| 			}
 | |
| 			for _, e := range node.used {
 | |
| 				for i := edgeKind(1); i < 64; i++ {
 | |
| 					if e.kind.is(1 << i) {
 | |
| 						c.debugf("n%d -> n%d [label=%q];\n", node.id, e.node.id, edgeKind(1<<i))
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		c.debugf("digraph{\n")
 | |
| 		debugNode(c.graph.Root)
 | |
| 		for _, v := range c.graph.Nodes {
 | |
| 			debugNode(v)
 | |
| 		}
 | |
| 		c.graph.TypeNodes.Iterate(func(key types.Type, value interface{}) {
 | |
| 			debugNode(value.(*Node))
 | |
| 		})
 | |
| 
 | |
| 		c.debugf("}\n")
 | |
| 	}
 | |
| 
 | |
| 	c.graph.color(c.graph.Root)
 | |
| 	// if a node is unused, don't report any of the node's
 | |
| 	// children as unused. for example, if a function is unused,
 | |
| 	// don't flag its receiver. if a named type is unused, don't
 | |
| 	// flag its methods.
 | |
| 
 | |
| 	for _, v := range c.graph.Nodes {
 | |
| 		c.graph.quieten(v)
 | |
| 	}
 | |
| 	c.graph.TypeNodes.Iterate(func(_ types.Type, value interface{}) {
 | |
| 		c.graph.quieten(value.(*Node))
 | |
| 	})
 | |
| 
 | |
| 	report := func(node *Node) {
 | |
| 		if node.seen {
 | |
| 			return
 | |
| 		}
 | |
| 		if node.quiet {
 | |
| 			c.debugf("n%d [color=purple];\n", node.id)
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		c.debugf("n%d [color=red];\n", node.id)
 | |
| 		switch obj := node.obj.(type) {
 | |
| 		case *types.Var:
 | |
| 			// don't report unnamed variables (interface embedding)
 | |
| 			if obj.Name() != "" || obj.IsField() {
 | |
| 				out = append(out, obj)
 | |
| 			}
 | |
| 			return
 | |
| 		case types.Object:
 | |
| 			if obj.Name() != "_" {
 | |
| 				out = append(out, obj)
 | |
| 			}
 | |
| 			return
 | |
| 		}
 | |
| 		c.debugf("n%d [color=gray];\n", node.id)
 | |
| 	}
 | |
| 	for _, v := range c.graph.Nodes {
 | |
| 		report(v)
 | |
| 	}
 | |
| 	c.graph.TypeNodes.Iterate(func(_ types.Type, value interface{}) {
 | |
| 		report(value.(*Node))
 | |
| 	})
 | |
| 
 | |
| 	return out
 | |
| }
 | |
| 
 | |
| func (c *Checker) processPkg(graph *Graph, pkg *pkg) {
 | |
| 	if pkg.Pkg.Path() == "unsafe" {
 | |
| 		return
 | |
| 	}
 | |
| 	graph.entry(pkg)
 | |
| }
 | |
| 
 | |
| func objNodeKeyFor(fset *token.FileSet, obj types.Object) objNodeKey {
 | |
| 	var kind objType
 | |
| 	switch obj.(type) {
 | |
| 	case *types.PkgName:
 | |
| 		kind = otPkgName
 | |
| 	case *types.Const:
 | |
| 		kind = otConst
 | |
| 	case *types.TypeName:
 | |
| 		kind = otTypeName
 | |
| 	case *types.Var:
 | |
| 		kind = otVar
 | |
| 	case *types.Func:
 | |
| 		kind = otFunc
 | |
| 	case *types.Label:
 | |
| 		kind = otLabel
 | |
| 	case *types.Builtin:
 | |
| 		kind = otBuiltin
 | |
| 	case *types.Nil:
 | |
| 		kind = otNil
 | |
| 	default:
 | |
| 		panic(fmt.Sprintf("unreachable: %T", obj))
 | |
| 	}
 | |
| 
 | |
| 	position := fset.PositionFor(obj.Pos(), false)
 | |
| 	position.Column = 0
 | |
| 	position.Offset = 0
 | |
| 	return objNodeKey{
 | |
| 		position: position,
 | |
| 		kind:     kind,
 | |
| 		name:     obj.Name(),
 | |
| 	}
 | |
| }
 | |
| 
 | |
| type objType uint8
 | |
| 
 | |
| const (
 | |
| 	otPkgName objType = iota
 | |
| 	otConst
 | |
| 	otTypeName
 | |
| 	otVar
 | |
| 	otFunc
 | |
| 	otLabel
 | |
| 	otBuiltin
 | |
| 	otNil
 | |
| )
 | |
| 
 | |
| // An objNodeKey describes a types.Object node in the graph.
 | |
| //
 | |
| // Due to test variants we may end up with multiple instances of the
 | |
| // same object, which is why we have to deduplicate based on their
 | |
| // source position. And because export data lacks column information,
 | |
| // we also have to incorporate the object's string representation in
 | |
| // the key.
 | |
| //
 | |
| // Previously we used the object's full string representation
 | |
| // (types.ObjectString), but that causes a significant amount of
 | |
| // allocations. Currently we're using the object's type and name, in
 | |
| // the hope that it is impossible for two objects to have the same
 | |
| // type, name and file position.
 | |
| type objNodeKey struct {
 | |
| 	position token.Position
 | |
| 	kind     objType
 | |
| 	name     string
 | |
| }
 | |
| 
 | |
| type Graph struct {
 | |
| 	// accessed atomically
 | |
| 	nodeOffset uint64
 | |
| 
 | |
| 	// Safe for concurrent use
 | |
| 	fset      *token.FileSet
 | |
| 	Root      *Node
 | |
| 	seenTypes typeutil.Map
 | |
| 
 | |
| 	// read-only
 | |
| 	wholeProgram bool
 | |
| 
 | |
| 	// need synchronisation
 | |
| 	mu        sync.Mutex
 | |
| 	TypeNodes typeutil.Map
 | |
| 	Nodes     map[interface{}]*Node
 | |
| 	objNodes  map[objNodeKey]*Node
 | |
| }
 | |
| 
 | |
| type context struct {
 | |
| 	g           *Graph
 | |
| 	pkg         *pkg
 | |
| 	seenFns     map[string]struct{}
 | |
| 	seenTypes   *typeutil.Map
 | |
| 	nodeCounter uint64
 | |
| }
 | |
| 
 | |
| func NewGraph() *Graph {
 | |
| 	g := &Graph{
 | |
| 		Nodes:    map[interface{}]*Node{},
 | |
| 		objNodes: map[objNodeKey]*Node{},
 | |
| 	}
 | |
| 	g.Root = g.newNode(&context{}, nil)
 | |
| 	return g
 | |
| }
 | |
| 
 | |
| func (g *Graph) color(root *Node) {
 | |
| 	if root.seen {
 | |
| 		return
 | |
| 	}
 | |
| 	root.seen = true
 | |
| 	for _, e := range root.used {
 | |
| 		g.color(e.node)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| type ConstGroup struct {
 | |
| 	// give the struct a size to get unique pointers
 | |
| 	_ byte
 | |
| }
 | |
| 
 | |
| func (ConstGroup) String() string { return "const group" }
 | |
| 
 | |
| type edge struct {
 | |
| 	node *Node
 | |
| 	kind edgeKind
 | |
| }
 | |
| 
 | |
| type Node struct {
 | |
| 	obj interface{}
 | |
| 	id  uint64
 | |
| 
 | |
| 	mu   sync.Mutex
 | |
| 	used []edge
 | |
| 
 | |
| 	// set during final graph walk if node is reachable
 | |
| 	seen bool
 | |
| 	// a parent node (e.g. the struct type containing a field) is
 | |
| 	// already unused, don't report children
 | |
| 	quiet bool
 | |
| }
 | |
| 
 | |
| func (g *Graph) nodeMaybe(obj types.Object) (*Node, bool) {
 | |
| 	g.mu.Lock()
 | |
| 	defer g.mu.Unlock()
 | |
| 	if node, ok := g.Nodes[obj]; ok {
 | |
| 		return node, true
 | |
| 	}
 | |
| 	return nil, false
 | |
| }
 | |
| 
 | |
| func (g *Graph) node(ctx *context, obj interface{}) (node *Node, new bool) {
 | |
| 	g.mu.Lock()
 | |
| 	defer g.mu.Unlock()
 | |
| 	switch obj := obj.(type) {
 | |
| 	case types.Type:
 | |
| 		if v := g.TypeNodes.At(obj); v != nil {
 | |
| 			return v.(*Node), false
 | |
| 		}
 | |
| 		node := g.newNode(ctx, obj)
 | |
| 		g.TypeNodes.Set(obj, node)
 | |
| 		return node, true
 | |
| 	case types.Object:
 | |
| 		if node, ok := g.Nodes[obj]; ok {
 | |
| 			return node, false
 | |
| 		}
 | |
| 
 | |
| 		key := objNodeKeyFor(g.fset, obj)
 | |
| 		if onode, ok := g.objNodes[key]; ok {
 | |
| 			return onode, false
 | |
| 		}
 | |
| 
 | |
| 		node = g.newNode(ctx, obj)
 | |
| 		g.Nodes[obj] = node
 | |
| 		g.objNodes[key] = node
 | |
| 		return node, true
 | |
| 	default:
 | |
| 		if node, ok := g.Nodes[obj]; ok {
 | |
| 			return node, false
 | |
| 		}
 | |
| 
 | |
| 		node = g.newNode(ctx, obj)
 | |
| 		g.Nodes[obj] = node
 | |
| 		return node, true
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (g *Graph) newNode(ctx *context, obj interface{}) *Node {
 | |
| 	ctx.nodeCounter++
 | |
| 	return &Node{
 | |
| 		obj: obj,
 | |
| 		id:  ctx.nodeCounter,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (n *Node) use(node *Node, kind edgeKind) {
 | |
| 	n.mu.Lock()
 | |
| 	defer n.mu.Unlock()
 | |
| 	assert(node != nil)
 | |
| 	n.used = append(n.used, edge{node: node, kind: kind})
 | |
| }
 | |
| 
 | |
| // isIrrelevant reports whether an object's presence in the graph is
 | |
| // of any relevance. A lot of objects will never have outgoing edges,
 | |
| // nor meaningful incoming ones. Examples are basic types and empty
 | |
| // signatures, among many others.
 | |
| //
 | |
| // Dropping these objects should have no effect on correctness, but
 | |
| // may improve performance. It also helps with debugging, as it
 | |
| // greatly reduces the size of the graph.
 | |
| func isIrrelevant(obj interface{}) bool {
 | |
| 	if obj, ok := obj.(types.Object); ok {
 | |
| 		switch obj := obj.(type) {
 | |
| 		case *types.Var:
 | |
| 			if obj.IsField() {
 | |
| 				// We need to track package fields
 | |
| 				return false
 | |
| 			}
 | |
| 			if obj.Pkg() != nil && obj.Parent() == obj.Pkg().Scope() {
 | |
| 				// We need to track package-level variables
 | |
| 				return false
 | |
| 			}
 | |
| 			return isIrrelevant(obj.Type())
 | |
| 		default:
 | |
| 			return false
 | |
| 		}
 | |
| 	}
 | |
| 	if T, ok := obj.(types.Type); ok {
 | |
| 		switch T := T.(type) {
 | |
| 		case *types.Array:
 | |
| 			return isIrrelevant(T.Elem())
 | |
| 		case *types.Slice:
 | |
| 			return isIrrelevant(T.Elem())
 | |
| 		case *types.Basic:
 | |
| 			return true
 | |
| 		case *types.Tuple:
 | |
| 			for i := 0; i < T.Len(); i++ {
 | |
| 				if !isIrrelevant(T.At(i).Type()) {
 | |
| 					return false
 | |
| 				}
 | |
| 			}
 | |
| 			return true
 | |
| 		case *types.Signature:
 | |
| 			if T.Recv() != nil {
 | |
| 				return false
 | |
| 			}
 | |
| 			for i := 0; i < T.Params().Len(); i++ {
 | |
| 				if !isIrrelevant(T.Params().At(i)) {
 | |
| 					return false
 | |
| 				}
 | |
| 			}
 | |
| 			for i := 0; i < T.Results().Len(); i++ {
 | |
| 				if !isIrrelevant(T.Results().At(i)) {
 | |
| 					return false
 | |
| 				}
 | |
| 			}
 | |
| 			return true
 | |
| 		case *types.Interface:
 | |
| 			return T.NumMethods() == 0 && T.NumEmbeddeds() == 0
 | |
| 		case *types.Pointer:
 | |
| 			return isIrrelevant(T.Elem())
 | |
| 		case *types.Map:
 | |
| 			return isIrrelevant(T.Key()) && isIrrelevant(T.Elem())
 | |
| 		case *types.Struct:
 | |
| 			return T.NumFields() == 0
 | |
| 		case *types.Chan:
 | |
| 			return isIrrelevant(T.Elem())
 | |
| 		default:
 | |
| 			return false
 | |
| 		}
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| func (ctx *context) see(obj interface{}) *Node {
 | |
| 	if isIrrelevant(obj) {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	assert(obj != nil)
 | |
| 	// add new node to graph
 | |
| 	node, _ := ctx.g.node(ctx, obj)
 | |
| 	return node
 | |
| }
 | |
| 
 | |
| func (ctx *context) use(used, by interface{}, kind edgeKind) {
 | |
| 	if isIrrelevant(used) {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	assert(used != nil)
 | |
| 	if obj, ok := by.(types.Object); ok && obj.Pkg() != nil {
 | |
| 		if !ctx.g.wholeProgram && obj.Pkg() != ctx.pkg.Pkg {
 | |
| 			return
 | |
| 		}
 | |
| 	}
 | |
| 	usedNode, new := ctx.g.node(ctx, used)
 | |
| 	assert(!new)
 | |
| 	if by == nil {
 | |
| 		ctx.g.Root.use(usedNode, kind)
 | |
| 	} else {
 | |
| 		byNode, new := ctx.g.node(ctx, by)
 | |
| 		assert(!new)
 | |
| 		byNode.use(usedNode, kind)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (ctx *context) seeAndUse(used, by interface{}, kind edgeKind) *Node {
 | |
| 	node := ctx.see(used)
 | |
| 	ctx.use(used, by, kind)
 | |
| 	return node
 | |
| }
 | |
| 
 | |
| // trackExportedIdentifier reports whether obj should be considered
 | |
| // used due to being exported, checking various conditions that affect
 | |
| // the decision.
 | |
| func (g *Graph) trackExportedIdentifier(ctx *context, obj types.Object) bool {
 | |
| 	if !obj.Exported() {
 | |
| 		// object isn't exported, the question is moot
 | |
| 		return false
 | |
| 	}
 | |
| 	path := g.fset.Position(obj.Pos()).Filename
 | |
| 	if g.wholeProgram {
 | |
| 		// Example functions without "Output:" comments aren't being
 | |
| 		// run and thus don't show up in the graph.
 | |
| 		if strings.HasSuffix(path, "_test.go") && strings.HasPrefix(obj.Name(), "Example") {
 | |
| 			return true
 | |
| 		}
 | |
| 		// whole program mode tracks exported identifiers accurately
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	if ctx.pkg.Pkg.Name() == "main" && !strings.HasSuffix(path, "_test.go") {
 | |
| 		// exported identifiers in package main can't be imported.
 | |
| 		// However, test functions can be called, and xtest packages
 | |
| 		// even have access to exported identifiers.
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	if strings.HasSuffix(path, "_test.go") {
 | |
| 		if strings.HasPrefix(obj.Name(), "Test") ||
 | |
| 			strings.HasPrefix(obj.Name(), "Benchmark") ||
 | |
| 			strings.HasPrefix(obj.Name(), "Example") {
 | |
| 			return true
 | |
| 		}
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| func (g *Graph) entry(pkg *pkg) {
 | |
| 	no := atomic.AddUint64(&g.nodeOffset, 1)
 | |
| 	ctx := &context{
 | |
| 		g:           g,
 | |
| 		pkg:         pkg,
 | |
| 		nodeCounter: no * 1e9,
 | |
| 		seenFns:     map[string]struct{}{},
 | |
| 	}
 | |
| 	if g.wholeProgram {
 | |
| 		ctx.seenTypes = &g.seenTypes
 | |
| 	} else {
 | |
| 		ctx.seenTypes = &typeutil.Map{}
 | |
| 	}
 | |
| 
 | |
| 	scopes := map[*types.Scope]*ir.Function{}
 | |
| 	for _, fn := range pkg.SrcFuncs {
 | |
| 		if fn.Object() != nil {
 | |
| 			scope := fn.Object().(*types.Func).Scope()
 | |
| 			scopes[scope] = fn
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for _, f := range pkg.Files {
 | |
| 		for _, cg := range f.Comments {
 | |
| 			for _, c := range cg.List {
 | |
| 				if strings.HasPrefix(c.Text, "//go:linkname ") {
 | |
| 					// FIXME(dh): we're looking at all comments. The
 | |
| 					// compiler only looks at comments in the
 | |
| 					// left-most column. The intention probably is to
 | |
| 					// only look at top-level comments.
 | |
| 
 | |
| 					// (1.8) packages use symbols linked via go:linkname
 | |
| 					fields := strings.Fields(c.Text)
 | |
| 					if len(fields) == 3 {
 | |
| 						if m, ok := pkg.IR.Members[fields[1]]; ok {
 | |
| 							var obj types.Object
 | |
| 							switch m := m.(type) {
 | |
| 							case *ir.Global:
 | |
| 								obj = m.Object()
 | |
| 							case *ir.Function:
 | |
| 								obj = m.Object()
 | |
| 							default:
 | |
| 								panic(fmt.Sprintf("unhandled type: %T", m))
 | |
| 							}
 | |
| 							assert(obj != nil)
 | |
| 							ctx.seeAndUse(obj, nil, edgeLinkname)
 | |
| 						}
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	surroundingFunc := func(obj types.Object) *ir.Function {
 | |
| 		scope := obj.Parent()
 | |
| 		for scope != nil {
 | |
| 			if fn := scopes[scope]; fn != nil {
 | |
| 				return fn
 | |
| 			}
 | |
| 			scope = scope.Parent()
 | |
| 		}
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	// IR form won't tell us about locally scoped types that aren't
 | |
| 	// being used. Walk the list of Defs to get all named types.
 | |
| 	//
 | |
| 	// IR form also won't tell us about constants; use Defs and Uses
 | |
| 	// to determine which constants exist and which are being used.
 | |
| 	for _, obj := range pkg.TypesInfo.Defs {
 | |
| 		switch obj := obj.(type) {
 | |
| 		case *types.TypeName:
 | |
| 			// types are being handled by walking the AST
 | |
| 		case *types.Const:
 | |
| 			ctx.see(obj)
 | |
| 			fn := surroundingFunc(obj)
 | |
| 			if fn == nil && g.trackExportedIdentifier(ctx, obj) {
 | |
| 				// (1.4) packages use exported constants (unless in package main)
 | |
| 				ctx.use(obj, nil, edgeExportedConstant)
 | |
| 			}
 | |
| 			g.typ(ctx, obj.Type(), nil)
 | |
| 			ctx.seeAndUse(obj.Type(), obj, edgeType)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Find constants being used inside functions, find sinks in tests
 | |
| 	for _, fn := range pkg.SrcFuncs {
 | |
| 		if fn.Object() != nil {
 | |
| 			ctx.see(fn.Object())
 | |
| 		}
 | |
| 		node := fn.Source()
 | |
| 		if node == nil {
 | |
| 			continue
 | |
| 		}
 | |
| 		ast.Inspect(node, func(node ast.Node) bool {
 | |
| 			switch node := node.(type) {
 | |
| 			case *ast.Ident:
 | |
| 				obj, ok := pkg.TypesInfo.Uses[node]
 | |
| 				if !ok {
 | |
| 					return true
 | |
| 				}
 | |
| 				switch obj := obj.(type) {
 | |
| 				case *types.Const:
 | |
| 					ctx.seeAndUse(obj, owningObject(fn), edgeUsedConstant)
 | |
| 				}
 | |
| 			case *ast.AssignStmt:
 | |
| 				for _, expr := range node.Lhs {
 | |
| 					ident, ok := expr.(*ast.Ident)
 | |
| 					if !ok {
 | |
| 						continue
 | |
| 					}
 | |
| 					obj := pkg.TypesInfo.ObjectOf(ident)
 | |
| 					if obj == nil {
 | |
| 						continue
 | |
| 					}
 | |
| 					path := g.fset.File(obj.Pos()).Name()
 | |
| 					if strings.HasSuffix(path, "_test.go") {
 | |
| 						if obj.Parent() != nil && obj.Parent().Parent() != nil && obj.Parent().Parent().Parent() == nil {
 | |
| 							// object's scope is the package, whose
 | |
| 							// parent is the file, whose parent is nil
 | |
| 
 | |
| 							// (4.9) functions use package-level variables they assign to iff in tests (sinks for benchmarks)
 | |
| 							// (9.7) variable _reads_ use variables, writes do not, except in tests
 | |
| 							ctx.seeAndUse(obj, owningObject(fn), edgeTestSink)
 | |
| 						}
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			return true
 | |
| 		})
 | |
| 	}
 | |
| 	// Find constants being used in non-function contexts
 | |
| 	for _, obj := range pkg.TypesInfo.Uses {
 | |
| 		_, ok := obj.(*types.Const)
 | |
| 		if !ok {
 | |
| 			continue
 | |
| 		}
 | |
| 		ctx.seeAndUse(obj, nil, edgeUsedConstant)
 | |
| 	}
 | |
| 
 | |
| 	var fns []*types.Func
 | |
| 	var fn *types.Func
 | |
| 	var stack []ast.Node
 | |
| 	for _, f := range pkg.Files {
 | |
| 		ast.Inspect(f, func(n ast.Node) bool {
 | |
| 			if n == nil {
 | |
| 				pop := stack[len(stack)-1]
 | |
| 				stack = stack[:len(stack)-1]
 | |
| 				if _, ok := pop.(*ast.FuncDecl); ok {
 | |
| 					fns = fns[:len(fns)-1]
 | |
| 					if len(fns) == 0 {
 | |
| 						fn = nil
 | |
| 					} else {
 | |
| 						fn = fns[len(fns)-1]
 | |
| 					}
 | |
| 				}
 | |
| 				return true
 | |
| 			}
 | |
| 			stack = append(stack, n)
 | |
| 			switch n := n.(type) {
 | |
| 			case *ast.FuncDecl:
 | |
| 				fn = pkg.TypesInfo.ObjectOf(n.Name).(*types.Func)
 | |
| 				fns = append(fns, fn)
 | |
| 				ctx.see(fn)
 | |
| 			case *ast.GenDecl:
 | |
| 				switch n.Tok {
 | |
| 				case token.CONST:
 | |
| 					groups := code.GroupSpecs(pkg.Fset, n.Specs)
 | |
| 					for _, specs := range groups {
 | |
| 						if len(specs) > 1 {
 | |
| 							cg := &ConstGroup{}
 | |
| 							ctx.see(cg)
 | |
| 							for _, spec := range specs {
 | |
| 								for _, name := range spec.(*ast.ValueSpec).Names {
 | |
| 									obj := pkg.TypesInfo.ObjectOf(name)
 | |
| 									// (10.1) const groups
 | |
| 									ctx.seeAndUse(obj, cg, edgeConstGroup)
 | |
| 									ctx.use(cg, obj, edgeConstGroup)
 | |
| 								}
 | |
| 							}
 | |
| 						}
 | |
| 					}
 | |
| 				case token.VAR:
 | |
| 					for _, spec := range n.Specs {
 | |
| 						v := spec.(*ast.ValueSpec)
 | |
| 						for _, name := range v.Names {
 | |
| 							T := pkg.TypesInfo.TypeOf(name)
 | |
| 							if fn != nil {
 | |
| 								ctx.seeAndUse(T, fn, edgeVarDecl)
 | |
| 							} else {
 | |
| 								// TODO(dh): we likely want to make
 | |
| 								// the type used by the variable, not
 | |
| 								// the package containing the
 | |
| 								// variable. But then we have to take
 | |
| 								// special care of blank identifiers.
 | |
| 								ctx.seeAndUse(T, nil, edgeVarDecl)
 | |
| 							}
 | |
| 							g.typ(ctx, T, nil)
 | |
| 						}
 | |
| 					}
 | |
| 				case token.TYPE:
 | |
| 					for _, spec := range n.Specs {
 | |
| 						// go/types doesn't provide a way to go from a
 | |
| 						// types.Named to the named type it was based on
 | |
| 						// (the t1 in type t2 t1). Therefore we walk the
 | |
| 						// AST and process GenDecls.
 | |
| 						//
 | |
| 						// (2.2) named types use the type they're based on
 | |
| 						v := spec.(*ast.TypeSpec)
 | |
| 						T := pkg.TypesInfo.TypeOf(v.Type)
 | |
| 						obj := pkg.TypesInfo.ObjectOf(v.Name)
 | |
| 						ctx.see(obj)
 | |
| 						ctx.see(T)
 | |
| 						ctx.use(T, obj, edgeType)
 | |
| 						g.typ(ctx, obj.Type(), nil)
 | |
| 						g.typ(ctx, T, nil)
 | |
| 
 | |
| 						if v.Assign != 0 {
 | |
| 							aliasFor := obj.(*types.TypeName).Type()
 | |
| 							// (2.3) named types use all their aliases. we can't easily track uses of aliases
 | |
| 							if isIrrelevant(aliasFor) {
 | |
| 								// We do not track the type this is an
 | |
| 								// alias for (for example builtins), so
 | |
| 								// just mark the alias used.
 | |
| 								//
 | |
| 								// FIXME(dh): what about aliases declared inside functions?
 | |
| 								ctx.use(obj, nil, edgeAlias)
 | |
| 							} else {
 | |
| 								ctx.see(aliasFor)
 | |
| 								ctx.seeAndUse(obj, aliasFor, edgeAlias)
 | |
| 							}
 | |
| 						}
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 			return true
 | |
| 		})
 | |
| 	}
 | |
| 
 | |
| 	for _, m := range pkg.IR.Members {
 | |
| 		switch m := m.(type) {
 | |
| 		case *ir.NamedConst:
 | |
| 			// nothing to do, we collect all constants from Defs
 | |
| 		case *ir.Global:
 | |
| 			if m.Object() != nil {
 | |
| 				ctx.see(m.Object())
 | |
| 				if g.trackExportedIdentifier(ctx, m.Object()) {
 | |
| 					// (1.3) packages use exported variables (unless in package main)
 | |
| 					ctx.use(m.Object(), nil, edgeExportedVariable)
 | |
| 				}
 | |
| 			}
 | |
| 		case *ir.Function:
 | |
| 			mObj := owningObject(m)
 | |
| 			if mObj != nil {
 | |
| 				ctx.see(mObj)
 | |
| 			}
 | |
| 			//lint:ignore SA9003 handled implicitly
 | |
| 			if m.Name() == "init" {
 | |
| 				// (1.5) packages use init functions
 | |
| 				//
 | |
| 				// This is handled implicitly. The generated init
 | |
| 				// function has no object, thus everything in it will
 | |
| 				// be owned by the package.
 | |
| 			}
 | |
| 			// This branch catches top-level functions, not methods.
 | |
| 			if m.Object() != nil && g.trackExportedIdentifier(ctx, m.Object()) {
 | |
| 				// (1.2) packages use exported functions (unless in package main)
 | |
| 				ctx.use(mObj, nil, edgeExportedFunction)
 | |
| 			}
 | |
| 			if m.Name() == "main" && pkg.Pkg.Name() == "main" {
 | |
| 				// (1.7) packages use the main function iff in the main package
 | |
| 				ctx.use(mObj, nil, edgeMainFunction)
 | |
| 			}
 | |
| 			if pkg.Pkg.Path() == "runtime" && runtimeFuncs[m.Name()] {
 | |
| 				// (9.8) runtime functions that may be called from user code via the compiler
 | |
| 				ctx.use(mObj, nil, edgeRuntimeFunction)
 | |
| 			}
 | |
| 			if m.Source() != nil {
 | |
| 				doc := m.Source().(*ast.FuncDecl).Doc
 | |
| 				if doc != nil {
 | |
| 					for _, cmt := range doc.List {
 | |
| 						if strings.HasPrefix(cmt.Text, "//go:cgo_export_") {
 | |
| 							// (1.6) packages use functions exported to cgo
 | |
| 							ctx.use(mObj, nil, edgeCgoExported)
 | |
| 						}
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 			g.function(ctx, m)
 | |
| 		case *ir.Type:
 | |
| 			if m.Object() != nil {
 | |
| 				ctx.see(m.Object())
 | |
| 				if g.trackExportedIdentifier(ctx, m.Object()) {
 | |
| 					// (1.1) packages use exported named types (unless in package main)
 | |
| 					ctx.use(m.Object(), nil, edgeExportedType)
 | |
| 				}
 | |
| 			}
 | |
| 			g.typ(ctx, m.Type(), nil)
 | |
| 		default:
 | |
| 			panic(fmt.Sprintf("unreachable: %T", m))
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if !g.wholeProgram {
 | |
| 		// When not in whole program mode we reset seenTypes after each package,
 | |
| 		// which means g.seenTypes only contains types of
 | |
| 		// interest to us. In whole program mode, we're better off
 | |
| 		// processing all interfaces at once, globally, both for
 | |
| 		// performance reasons and because in whole program mode we
 | |
| 		// actually care about all interfaces, not just the subset
 | |
| 		// that has unexported methods.
 | |
| 
 | |
| 		var ifaces []*types.Interface
 | |
| 		var notIfaces []types.Type
 | |
| 
 | |
| 		ctx.seenTypes.Iterate(func(t types.Type, _ interface{}) {
 | |
| 			switch t := t.(type) {
 | |
| 			case *types.Interface:
 | |
| 				// OPT(dh): (8.1) we only need interfaces that have unexported methods
 | |
| 				ifaces = append(ifaces, t)
 | |
| 			default:
 | |
| 				if _, ok := t.Underlying().(*types.Interface); !ok {
 | |
| 					notIfaces = append(notIfaces, t)
 | |
| 				}
 | |
| 			}
 | |
| 		})
 | |
| 
 | |
| 		// (8.0) handle interfaces
 | |
| 		for _, t := range notIfaces {
 | |
| 			ms := pkg.IR.Prog.MethodSets.MethodSet(t)
 | |
| 			for _, iface := range ifaces {
 | |
| 				if sels, ok := g.implements(t, iface, ms); ok {
 | |
| 					for _, sel := range sels {
 | |
| 						g.useMethod(ctx, t, sel, t, edgeImplements)
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (g *Graph) useMethod(ctx *context, t types.Type, sel *types.Selection, by interface{}, kind edgeKind) {
 | |
| 	obj := sel.Obj()
 | |
| 	path := sel.Index()
 | |
| 	assert(obj != nil)
 | |
| 	if len(path) > 1 {
 | |
| 		base := code.Dereference(t).Underlying().(*types.Struct)
 | |
| 		for _, idx := range path[:len(path)-1] {
 | |
| 			next := base.Field(idx)
 | |
| 			// (6.3) structs use embedded fields that help implement interfaces
 | |
| 			ctx.see(base)
 | |
| 			ctx.seeAndUse(next, base, edgeProvidesMethod)
 | |
| 			base, _ = code.Dereference(next.Type()).Underlying().(*types.Struct)
 | |
| 		}
 | |
| 	}
 | |
| 	ctx.seeAndUse(obj, by, kind)
 | |
| }
 | |
| 
 | |
| func owningObject(fn *ir.Function) types.Object {
 | |
| 	if fn.Object() != nil {
 | |
| 		return fn.Object()
 | |
| 	}
 | |
| 	if fn.Parent() != nil {
 | |
| 		return owningObject(fn.Parent())
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (g *Graph) function(ctx *context, fn *ir.Function) {
 | |
| 	if fn.Package() != nil && fn.Package() != ctx.pkg.IR {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	name := fn.RelString(nil)
 | |
| 	if _, ok := ctx.seenFns[name]; ok {
 | |
| 		return
 | |
| 	}
 | |
| 	ctx.seenFns[name] = struct{}{}
 | |
| 
 | |
| 	// (4.1) functions use all their arguments, return parameters and receivers
 | |
| 	g.signature(ctx, fn.Signature, owningObject(fn))
 | |
| 	g.instructions(ctx, fn)
 | |
| 	for _, anon := range fn.AnonFuncs {
 | |
| 		// (4.2) functions use anonymous functions defined beneath them
 | |
| 		//
 | |
| 		// This fact is expressed implicitly. Anonymous functions have
 | |
| 		// no types.Object, so their owner is the surrounding
 | |
| 		// function.
 | |
| 		g.function(ctx, anon)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (g *Graph) typ(ctx *context, t types.Type, parent types.Type) {
 | |
| 	if g.wholeProgram {
 | |
| 		g.mu.Lock()
 | |
| 	}
 | |
| 	if ctx.seenTypes.At(t) != nil {
 | |
| 		if g.wholeProgram {
 | |
| 			g.mu.Unlock()
 | |
| 		}
 | |
| 		return
 | |
| 	}
 | |
| 	if g.wholeProgram {
 | |
| 		g.mu.Unlock()
 | |
| 	}
 | |
| 	if t, ok := t.(*types.Named); ok && t.Obj().Pkg() != nil {
 | |
| 		if t.Obj().Pkg() != ctx.pkg.Pkg {
 | |
| 			return
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if g.wholeProgram {
 | |
| 		g.mu.Lock()
 | |
| 	}
 | |
| 	ctx.seenTypes.Set(t, struct{}{})
 | |
| 	if g.wholeProgram {
 | |
| 		g.mu.Unlock()
 | |
| 	}
 | |
| 	if isIrrelevant(t) {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	ctx.see(t)
 | |
| 	switch t := t.(type) {
 | |
| 	case *types.Struct:
 | |
| 		for i := 0; i < t.NumFields(); i++ {
 | |
| 			ctx.see(t.Field(i))
 | |
| 			if t.Field(i).Exported() {
 | |
| 				// (6.2) structs use exported fields
 | |
| 				ctx.use(t.Field(i), t, edgeExportedField)
 | |
| 			} else if t.Field(i).Name() == "_" {
 | |
| 				ctx.use(t.Field(i), t, edgeBlankField)
 | |
| 			} else if isNoCopyType(t.Field(i).Type()) {
 | |
| 				// (6.1) structs use fields of type NoCopy sentinel
 | |
| 				ctx.use(t.Field(i), t, edgeNoCopySentinel)
 | |
| 			} else if parent == nil {
 | |
| 				// (11.1) anonymous struct types use all their fields.
 | |
| 				ctx.use(t.Field(i), t, edgeAnonymousStruct)
 | |
| 			}
 | |
| 			if t.Field(i).Anonymous() {
 | |
| 				// (e3) exported identifiers aren't automatically used.
 | |
| 				if !g.wholeProgram {
 | |
| 					// does the embedded field contribute exported methods to the method set?
 | |
| 					T := t.Field(i).Type()
 | |
| 					if _, ok := T.Underlying().(*types.Pointer); !ok {
 | |
| 						// An embedded field is addressable, so check
 | |
| 						// the pointer type to get the full method set
 | |
| 						T = types.NewPointer(T)
 | |
| 					}
 | |
| 					ms := ctx.pkg.IR.Prog.MethodSets.MethodSet(T)
 | |
| 					for j := 0; j < ms.Len(); j++ {
 | |
| 						if ms.At(j).Obj().Exported() {
 | |
| 							// (6.4) structs use embedded fields that have exported methods (recursively)
 | |
| 							ctx.use(t.Field(i), t, edgeExtendsExportedMethodSet)
 | |
| 							break
 | |
| 						}
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				seen := map[*types.Struct]struct{}{}
 | |
| 				var hasExportedField func(t types.Type) bool
 | |
| 				hasExportedField = func(T types.Type) bool {
 | |
| 					t, ok := code.Dereference(T).Underlying().(*types.Struct)
 | |
| 					if !ok {
 | |
| 						return false
 | |
| 					}
 | |
| 					if _, ok := seen[t]; ok {
 | |
| 						return false
 | |
| 					}
 | |
| 					seen[t] = struct{}{}
 | |
| 					for i := 0; i < t.NumFields(); i++ {
 | |
| 						field := t.Field(i)
 | |
| 						if field.Exported() {
 | |
| 							return true
 | |
| 						}
 | |
| 						if field.Embedded() && hasExportedField(field.Type()) {
 | |
| 							return true
 | |
| 						}
 | |
| 					}
 | |
| 					return false
 | |
| 				}
 | |
| 				// does the embedded field contribute exported fields?
 | |
| 				if hasExportedField(t.Field(i).Type()) {
 | |
| 					// (6.5) structs use embedded structs that have exported fields (recursively)
 | |
| 					ctx.use(t.Field(i), t, edgeExtendsExportedFields)
 | |
| 				}
 | |
| 
 | |
| 			}
 | |
| 			g.variable(ctx, t.Field(i))
 | |
| 		}
 | |
| 	case *types.Basic:
 | |
| 		// Nothing to do
 | |
| 	case *types.Named:
 | |
| 		// (9.3) types use their underlying and element types
 | |
| 		ctx.seeAndUse(t.Underlying(), t, edgeUnderlyingType)
 | |
| 		ctx.seeAndUse(t.Obj(), t, edgeTypeName)
 | |
| 		ctx.seeAndUse(t, t.Obj(), edgeNamedType)
 | |
| 
 | |
| 		// (2.4) named types use the pointer type
 | |
| 		if _, ok := t.Underlying().(*types.Interface); !ok && t.NumMethods() > 0 {
 | |
| 			ctx.seeAndUse(types.NewPointer(t), t, edgePointerType)
 | |
| 		}
 | |
| 
 | |
| 		for i := 0; i < t.NumMethods(); i++ {
 | |
| 			ctx.see(t.Method(i))
 | |
| 			// don't use trackExportedIdentifier here, we care about
 | |
| 			// all exported methods, even in package main or in tests.
 | |
| 			if t.Method(i).Exported() && !g.wholeProgram {
 | |
| 				// (2.1) named types use exported methods
 | |
| 				ctx.use(t.Method(i), t, edgeExportedMethod)
 | |
| 			}
 | |
| 			g.function(ctx, ctx.pkg.IR.Prog.FuncValue(t.Method(i)))
 | |
| 		}
 | |
| 
 | |
| 		g.typ(ctx, t.Underlying(), t)
 | |
| 	case *types.Slice:
 | |
| 		// (9.3) types use their underlying and element types
 | |
| 		ctx.seeAndUse(t.Elem(), t, edgeElementType)
 | |
| 		g.typ(ctx, t.Elem(), nil)
 | |
| 	case *types.Map:
 | |
| 		// (9.3) types use their underlying and element types
 | |
| 		ctx.seeAndUse(t.Elem(), t, edgeElementType)
 | |
| 		// (9.3) types use their underlying and element types
 | |
| 		ctx.seeAndUse(t.Key(), t, edgeKeyType)
 | |
| 		g.typ(ctx, t.Elem(), nil)
 | |
| 		g.typ(ctx, t.Key(), nil)
 | |
| 	case *types.Signature:
 | |
| 		g.signature(ctx, t, nil)
 | |
| 	case *types.Interface:
 | |
| 		for i := 0; i < t.NumMethods(); i++ {
 | |
| 			m := t.Method(i)
 | |
| 			// (8.3) All interface methods are marked as used
 | |
| 			ctx.seeAndUse(m, t, edgeInterfaceMethod)
 | |
| 			ctx.seeAndUse(m.Type().(*types.Signature), m, edgeSignature)
 | |
| 			g.signature(ctx, m.Type().(*types.Signature), nil)
 | |
| 		}
 | |
| 		for i := 0; i < t.NumEmbeddeds(); i++ {
 | |
| 			tt := t.EmbeddedType(i)
 | |
| 			// (8.4) All embedded interfaces are marked as used
 | |
| 			ctx.seeAndUse(tt, t, edgeEmbeddedInterface)
 | |
| 		}
 | |
| 	case *types.Array:
 | |
| 		// (9.3) types use their underlying and element types
 | |
| 		ctx.seeAndUse(t.Elem(), t, edgeElementType)
 | |
| 		g.typ(ctx, t.Elem(), nil)
 | |
| 	case *types.Pointer:
 | |
| 		// (9.3) types use their underlying and element types
 | |
| 		ctx.seeAndUse(t.Elem(), t, edgeElementType)
 | |
| 		g.typ(ctx, t.Elem(), nil)
 | |
| 	case *types.Chan:
 | |
| 		// (9.3) types use their underlying and element types
 | |
| 		ctx.seeAndUse(t.Elem(), t, edgeElementType)
 | |
| 		g.typ(ctx, t.Elem(), nil)
 | |
| 	case *types.Tuple:
 | |
| 		for i := 0; i < t.Len(); i++ {
 | |
| 			// (9.3) types use their underlying and element types
 | |
| 			ctx.seeAndUse(t.At(i).Type(), t, edgeTupleElement|edgeType)
 | |
| 			g.typ(ctx, t.At(i).Type(), nil)
 | |
| 		}
 | |
| 	default:
 | |
| 		panic(fmt.Sprintf("unreachable: %T", t))
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (g *Graph) variable(ctx *context, v *types.Var) {
 | |
| 	// (9.2) variables use their types
 | |
| 	ctx.seeAndUse(v.Type(), v, edgeType)
 | |
| 	g.typ(ctx, v.Type(), nil)
 | |
| }
 | |
| 
 | |
| func (g *Graph) signature(ctx *context, sig *types.Signature, fn types.Object) {
 | |
| 	var user interface{} = fn
 | |
| 	if fn == nil {
 | |
| 		user = sig
 | |
| 		ctx.see(sig)
 | |
| 	}
 | |
| 	if sig.Recv() != nil {
 | |
| 		ctx.seeAndUse(sig.Recv().Type(), user, edgeReceiver|edgeType)
 | |
| 		g.typ(ctx, sig.Recv().Type(), nil)
 | |
| 	}
 | |
| 	for i := 0; i < sig.Params().Len(); i++ {
 | |
| 		param := sig.Params().At(i)
 | |
| 		ctx.seeAndUse(param.Type(), user, edgeFunctionArgument|edgeType)
 | |
| 		g.typ(ctx, param.Type(), nil)
 | |
| 	}
 | |
| 	for i := 0; i < sig.Results().Len(); i++ {
 | |
| 		param := sig.Results().At(i)
 | |
| 		ctx.seeAndUse(param.Type(), user, edgeFunctionResult|edgeType)
 | |
| 		g.typ(ctx, param.Type(), nil)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (g *Graph) instructions(ctx *context, fn *ir.Function) {
 | |
| 	fnObj := owningObject(fn)
 | |
| 	for _, b := range fn.Blocks {
 | |
| 		for _, instr := range b.Instrs {
 | |
| 			ops := instr.Operands(nil)
 | |
| 			switch instr.(type) {
 | |
| 			case *ir.Store:
 | |
| 				// (9.7) variable _reads_ use variables, writes do not
 | |
| 				ops = ops[1:]
 | |
| 			case *ir.DebugRef:
 | |
| 				ops = nil
 | |
| 			}
 | |
| 			for _, arg := range ops {
 | |
| 				walkPhi(*arg, func(v ir.Value) {
 | |
| 					switch v := v.(type) {
 | |
| 					case *ir.Function:
 | |
| 						// (4.3) functions use closures and bound methods.
 | |
| 						// (4.5) functions use functions they call
 | |
| 						// (9.5) instructions use their operands
 | |
| 						// (4.4) functions use functions they return. we assume that someone else will call the returned function
 | |
| 						if owningObject(v) != nil {
 | |
| 							ctx.seeAndUse(owningObject(v), fnObj, edgeInstructionOperand)
 | |
| 						}
 | |
| 						g.function(ctx, v)
 | |
| 					case *ir.Const:
 | |
| 						// (9.6) instructions use their operands' types
 | |
| 						ctx.seeAndUse(v.Type(), fnObj, edgeType)
 | |
| 						g.typ(ctx, v.Type(), nil)
 | |
| 					case *ir.Global:
 | |
| 						if v.Object() != nil {
 | |
| 							// (9.5) instructions use their operands
 | |
| 							ctx.seeAndUse(v.Object(), fnObj, edgeInstructionOperand)
 | |
| 						}
 | |
| 					}
 | |
| 				})
 | |
| 			}
 | |
| 			if v, ok := instr.(ir.Value); ok {
 | |
| 				if _, ok := v.(*ir.Range); !ok {
 | |
| 					// See https://github.com/golang/go/issues/19670
 | |
| 
 | |
| 					// (4.8) instructions use their types
 | |
| 					// (9.4) conversions use the type they convert to
 | |
| 					ctx.seeAndUse(v.Type(), fnObj, edgeType)
 | |
| 					g.typ(ctx, v.Type(), nil)
 | |
| 				}
 | |
| 			}
 | |
| 			switch instr := instr.(type) {
 | |
| 			case *ir.Field:
 | |
| 				st := instr.X.Type().Underlying().(*types.Struct)
 | |
| 				field := st.Field(instr.Field)
 | |
| 				// (4.7) functions use fields they access
 | |
| 				ctx.seeAndUse(field, fnObj, edgeFieldAccess)
 | |
| 			case *ir.FieldAddr:
 | |
| 				st := code.Dereference(instr.X.Type()).Underlying().(*types.Struct)
 | |
| 				field := st.Field(instr.Field)
 | |
| 				// (4.7) functions use fields they access
 | |
| 				ctx.seeAndUse(field, fnObj, edgeFieldAccess)
 | |
| 			case *ir.Store:
 | |
| 				// nothing to do, handled generically by operands
 | |
| 			case *ir.Call:
 | |
| 				c := instr.Common()
 | |
| 				if !c.IsInvoke() {
 | |
| 					// handled generically as an instruction operand
 | |
| 
 | |
| 					if g.wholeProgram {
 | |
| 						// (e3) special case known reflection-based method callers
 | |
| 						switch code.CallName(c) {
 | |
| 						case "net/rpc.Register", "net/rpc.RegisterName", "(*net/rpc.Server).Register", "(*net/rpc.Server).RegisterName":
 | |
| 							var arg ir.Value
 | |
| 							switch code.CallName(c) {
 | |
| 							case "net/rpc.Register":
 | |
| 								arg = c.Args[0]
 | |
| 							case "net/rpc.RegisterName":
 | |
| 								arg = c.Args[1]
 | |
| 							case "(*net/rpc.Server).Register":
 | |
| 								arg = c.Args[1]
 | |
| 							case "(*net/rpc.Server).RegisterName":
 | |
| 								arg = c.Args[2]
 | |
| 							}
 | |
| 							walkPhi(arg, func(v ir.Value) {
 | |
| 								if v, ok := v.(*ir.MakeInterface); ok {
 | |
| 									walkPhi(v.X, func(vv ir.Value) {
 | |
| 										ms := ctx.pkg.IR.Prog.MethodSets.MethodSet(vv.Type())
 | |
| 										for i := 0; i < ms.Len(); i++ {
 | |
| 											if ms.At(i).Obj().Exported() {
 | |
| 												g.useMethod(ctx, vv.Type(), ms.At(i), fnObj, edgeNetRPCRegister)
 | |
| 											}
 | |
| 										}
 | |
| 									})
 | |
| 								}
 | |
| 							})
 | |
| 						}
 | |
| 					}
 | |
| 				} else {
 | |
| 					// (4.5) functions use functions/interface methods they call
 | |
| 					ctx.seeAndUse(c.Method, fnObj, edgeInterfaceCall)
 | |
| 				}
 | |
| 			case *ir.Return:
 | |
| 				// nothing to do, handled generically by operands
 | |
| 			case *ir.ChangeType:
 | |
| 				// conversion type handled generically
 | |
| 
 | |
| 				s1, ok1 := code.Dereference(instr.Type()).Underlying().(*types.Struct)
 | |
| 				s2, ok2 := code.Dereference(instr.X.Type()).Underlying().(*types.Struct)
 | |
| 				if ok1 && ok2 {
 | |
| 					// Converting between two structs. The fields are
 | |
| 					// relevant for the conversion, but only if the
 | |
| 					// fields are also used outside of the conversion.
 | |
| 					// Mark fields as used by each other.
 | |
| 
 | |
| 					assert(s1.NumFields() == s2.NumFields())
 | |
| 					for i := 0; i < s1.NumFields(); i++ {
 | |
| 						ctx.see(s1.Field(i))
 | |
| 						ctx.see(s2.Field(i))
 | |
| 						// (5.1) when converting between two equivalent structs, the fields in
 | |
| 						// either struct use each other. the fields are relevant for the
 | |
| 						// conversion, but only if the fields are also accessed outside the
 | |
| 						// conversion.
 | |
| 						ctx.seeAndUse(s1.Field(i), s2.Field(i), edgeStructConversion)
 | |
| 						ctx.seeAndUse(s2.Field(i), s1.Field(i), edgeStructConversion)
 | |
| 					}
 | |
| 				}
 | |
| 			case *ir.MakeInterface:
 | |
| 				// nothing to do, handled generically by operands
 | |
| 			case *ir.Slice:
 | |
| 				// nothing to do, handled generically by operands
 | |
| 			case *ir.RunDefers:
 | |
| 				// nothing to do, the deferred functions are already marked use by defering them.
 | |
| 			case *ir.Convert:
 | |
| 				// to unsafe.Pointer
 | |
| 				if typ, ok := instr.Type().(*types.Basic); ok && typ.Kind() == types.UnsafePointer {
 | |
| 					if ptr, ok := instr.X.Type().Underlying().(*types.Pointer); ok {
 | |
| 						if st, ok := ptr.Elem().Underlying().(*types.Struct); ok {
 | |
| 							for i := 0; i < st.NumFields(); i++ {
 | |
| 								// (5.2) when converting to or from unsafe.Pointer, mark all fields as used.
 | |
| 								ctx.seeAndUse(st.Field(i), fnObj, edgeUnsafeConversion)
 | |
| 							}
 | |
| 						}
 | |
| 					}
 | |
| 				}
 | |
| 				// from unsafe.Pointer
 | |
| 				if typ, ok := instr.X.Type().(*types.Basic); ok && typ.Kind() == types.UnsafePointer {
 | |
| 					if ptr, ok := instr.Type().Underlying().(*types.Pointer); ok {
 | |
| 						if st, ok := ptr.Elem().Underlying().(*types.Struct); ok {
 | |
| 							for i := 0; i < st.NumFields(); i++ {
 | |
| 								// (5.2) when converting to or from unsafe.Pointer, mark all fields as used.
 | |
| 								ctx.seeAndUse(st.Field(i), fnObj, edgeUnsafeConversion)
 | |
| 							}
 | |
| 						}
 | |
| 					}
 | |
| 				}
 | |
| 			case *ir.TypeAssert:
 | |
| 				// nothing to do, handled generically by instruction
 | |
| 				// type (possibly a tuple, which contains the asserted
 | |
| 				// to type). redundantly handled by the type of
 | |
| 				// ir.Extract, too
 | |
| 			case *ir.MakeClosure:
 | |
| 				// nothing to do, handled generically by operands
 | |
| 			case *ir.Alloc:
 | |
| 				// nothing to do
 | |
| 			case *ir.UnOp:
 | |
| 				// nothing to do
 | |
| 			case *ir.BinOp:
 | |
| 				// nothing to do
 | |
| 			case *ir.If:
 | |
| 				// nothing to do
 | |
| 			case *ir.Jump:
 | |
| 				// nothing to do
 | |
| 			case *ir.Unreachable:
 | |
| 				// nothing to do
 | |
| 			case *ir.IndexAddr:
 | |
| 				// nothing to do
 | |
| 			case *ir.Extract:
 | |
| 				// nothing to do
 | |
| 			case *ir.Panic:
 | |
| 				// nothing to do
 | |
| 			case *ir.DebugRef:
 | |
| 				// nothing to do
 | |
| 			case *ir.BlankStore:
 | |
| 				// nothing to do
 | |
| 			case *ir.Phi:
 | |
| 				// nothing to do
 | |
| 			case *ir.Sigma:
 | |
| 				// nothing to do
 | |
| 			case *ir.MakeMap:
 | |
| 				// nothing to do
 | |
| 			case *ir.MapUpdate:
 | |
| 				// nothing to do
 | |
| 			case *ir.MapLookup:
 | |
| 				// nothing to do
 | |
| 			case *ir.StringLookup:
 | |
| 				// nothing to do
 | |
| 			case *ir.MakeSlice:
 | |
| 				// nothing to do
 | |
| 			case *ir.Send:
 | |
| 				// nothing to do
 | |
| 			case *ir.MakeChan:
 | |
| 				// nothing to do
 | |
| 			case *ir.Range:
 | |
| 				// nothing to do
 | |
| 			case *ir.Next:
 | |
| 				// nothing to do
 | |
| 			case *ir.Index:
 | |
| 				// nothing to do
 | |
| 			case *ir.Select:
 | |
| 				// nothing to do
 | |
| 			case *ir.ChangeInterface:
 | |
| 				// nothing to do
 | |
| 			case *ir.Load:
 | |
| 				// nothing to do
 | |
| 			case *ir.Go:
 | |
| 				// nothing to do
 | |
| 			case *ir.Defer:
 | |
| 				// nothing to do
 | |
| 			case *ir.Parameter:
 | |
| 				// nothing to do
 | |
| 			case *ir.Const:
 | |
| 				// nothing to do
 | |
| 			case *ir.Recv:
 | |
| 				// nothing to do
 | |
| 			case *ir.TypeSwitch:
 | |
| 				// nothing to do
 | |
| 			case *ir.ConstantSwitch:
 | |
| 				// nothing to do
 | |
| 			default:
 | |
| 				panic(fmt.Sprintf("unreachable: %T", instr))
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // isNoCopyType reports whether a type represents the NoCopy sentinel
 | |
| // type. The NoCopy type is a named struct with no fields and exactly
 | |
| // one method `func Lock()` that is empty.
 | |
| //
 | |
| // FIXME(dh): currently we're not checking that the function body is
 | |
| // empty.
 | |
| func isNoCopyType(typ types.Type) bool {
 | |
| 	st, ok := typ.Underlying().(*types.Struct)
 | |
| 	if !ok {
 | |
| 		return false
 | |
| 	}
 | |
| 	if st.NumFields() != 0 {
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	named, ok := typ.(*types.Named)
 | |
| 	if !ok {
 | |
| 		return false
 | |
| 	}
 | |
| 	if named.NumMethods() != 1 {
 | |
| 		return false
 | |
| 	}
 | |
| 	meth := named.Method(0)
 | |
| 	if meth.Name() != "Lock" {
 | |
| 		return false
 | |
| 	}
 | |
| 	sig := meth.Type().(*types.Signature)
 | |
| 	if sig.Params().Len() != 0 || sig.Results().Len() != 0 {
 | |
| 		return false
 | |
| 	}
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| func walkPhi(v ir.Value, fn func(v ir.Value)) {
 | |
| 	phi, ok := v.(*ir.Phi)
 | |
| 	if !ok {
 | |
| 		fn(v)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	seen := map[ir.Value]struct{}{}
 | |
| 	var impl func(v *ir.Phi)
 | |
| 	impl = func(v *ir.Phi) {
 | |
| 		if _, ok := seen[v]; ok {
 | |
| 			return
 | |
| 		}
 | |
| 		seen[v] = struct{}{}
 | |
| 		for _, e := range v.Edges {
 | |
| 			if ev, ok := e.(*ir.Phi); ok {
 | |
| 				impl(ev)
 | |
| 			} else {
 | |
| 				fn(e)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	impl(phi)
 | |
| }
 | |
| 
 | |
| func interfacesFromExportData(pkg *types.Package) []*types.Interface {
 | |
| 	var out []*types.Interface
 | |
| 	scope := pkg.Scope()
 | |
| 	for _, name := range scope.Names() {
 | |
| 		obj := scope.Lookup(name)
 | |
| 		out = append(out, interfacesFromObject(obj)...)
 | |
| 	}
 | |
| 	return out
 | |
| }
 | |
| 
 | |
| func interfacesFromObject(obj types.Object) []*types.Interface {
 | |
| 	var out []*types.Interface
 | |
| 	switch obj := obj.(type) {
 | |
| 	case *types.Func:
 | |
| 		sig := obj.Type().(*types.Signature)
 | |
| 		for i := 0; i < sig.Results().Len(); i++ {
 | |
| 			out = append(out, interfacesFromObject(sig.Results().At(i))...)
 | |
| 		}
 | |
| 		for i := 0; i < sig.Params().Len(); i++ {
 | |
| 			out = append(out, interfacesFromObject(sig.Params().At(i))...)
 | |
| 		}
 | |
| 	case *types.TypeName:
 | |
| 		if named, ok := obj.Type().(*types.Named); ok {
 | |
| 			for i := 0; i < named.NumMethods(); i++ {
 | |
| 				out = append(out, interfacesFromObject(named.Method(i))...)
 | |
| 			}
 | |
| 
 | |
| 			if iface, ok := named.Underlying().(*types.Interface); ok {
 | |
| 				out = append(out, iface)
 | |
| 			}
 | |
| 		}
 | |
| 	case *types.Var:
 | |
| 		// No call to Underlying here. We want unnamed interfaces
 | |
| 		// only. Named interfaces are gotten directly from the
 | |
| 		// package's scope.
 | |
| 		if iface, ok := obj.Type().(*types.Interface); ok {
 | |
| 			out = append(out, iface)
 | |
| 		}
 | |
| 	case *types.Const:
 | |
| 	case *types.Builtin:
 | |
| 	default:
 | |
| 		panic(fmt.Sprintf("unhandled type: %T", obj))
 | |
| 	}
 | |
| 	return out
 | |
| }
 |