1065 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			1065 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package unused // import "github.com/golangci/go-tools/unused"
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"go/ast"
 | |
| 	"go/token"
 | |
| 	"go/types"
 | |
| 	"io"
 | |
| 	"path/filepath"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/golangci/go-tools/lint"
 | |
| 
 | |
| 	"golang.org/x/tools/go/loader"
 | |
| 	"golang.org/x/tools/go/types/typeutil"
 | |
| )
 | |
| 
 | |
| func NewLintChecker(c *Checker) *LintChecker {
 | |
| 	l := &LintChecker{
 | |
| 		c: c,
 | |
| 	}
 | |
| 	return l
 | |
| }
 | |
| 
 | |
| type LintChecker struct {
 | |
| 	c *Checker
 | |
| }
 | |
| 
 | |
| func (*LintChecker) Name() string   { return "unused" }
 | |
| func (*LintChecker) Prefix() string { return "U" }
 | |
| 
 | |
| func (l *LintChecker) Init(*lint.Program) {}
 | |
| func (l *LintChecker) Funcs() map[string]lint.Func {
 | |
| 	return map[string]lint.Func{
 | |
| 		"U1000": l.Lint,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 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:
 | |
| 		// log.Printf("%T", obj)
 | |
| 		return "identifier"
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (l *LintChecker) Lint(j *lint.Job) {
 | |
| 	unused := l.c.Check(j.Program.Prog)
 | |
| 	for _, u := range unused {
 | |
| 		name := u.Obj.Name()
 | |
| 		if sig, ok := u.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, u.Obj.Name())
 | |
| 				} else if len(typ) > 0 {
 | |
| 					name = fmt.Sprintf("%s.%s", typ, u.Obj.Name())
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		j.Errorf(u.Obj, "%s %s is unused", typString(u.Obj), name)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| type graph struct {
 | |
| 	roots []*graphNode
 | |
| 	nodes map[interface{}]*graphNode
 | |
| }
 | |
| 
 | |
| func (g *graph) markUsedBy(obj, usedBy interface{}) {
 | |
| 	objNode := g.getNode(obj)
 | |
| 	usedByNode := g.getNode(usedBy)
 | |
| 	if objNode.obj == usedByNode.obj {
 | |
| 		return
 | |
| 	}
 | |
| 	usedByNode.uses[objNode] = struct{}{}
 | |
| }
 | |
| 
 | |
| var labelCounter = 1
 | |
| 
 | |
| func (g *graph) getNode(obj interface{}) *graphNode {
 | |
| 	for {
 | |
| 		if pt, ok := obj.(*types.Pointer); ok {
 | |
| 			obj = pt.Elem()
 | |
| 		} else {
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	_, ok := g.nodes[obj]
 | |
| 	if !ok {
 | |
| 		g.addObj(obj)
 | |
| 	}
 | |
| 
 | |
| 	return g.nodes[obj]
 | |
| }
 | |
| 
 | |
| func (g *graph) addObj(obj interface{}) {
 | |
| 	if pt, ok := obj.(*types.Pointer); ok {
 | |
| 		obj = pt.Elem()
 | |
| 	}
 | |
| 	node := &graphNode{obj: obj, uses: make(map[*graphNode]struct{}), n: labelCounter}
 | |
| 	g.nodes[obj] = node
 | |
| 	labelCounter++
 | |
| 
 | |
| 	if obj, ok := obj.(*types.Struct); ok {
 | |
| 		n := obj.NumFields()
 | |
| 		for i := 0; i < n; i++ {
 | |
| 			field := obj.Field(i)
 | |
| 			g.markUsedBy(obj, field)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| type graphNode struct {
 | |
| 	obj   interface{}
 | |
| 	uses  map[*graphNode]struct{}
 | |
| 	used  bool
 | |
| 	quiet bool
 | |
| 	n     int
 | |
| }
 | |
| 
 | |
| type CheckMode int
 | |
| 
 | |
| const (
 | |
| 	CheckConstants CheckMode = 1 << iota
 | |
| 	CheckFields
 | |
| 	CheckFunctions
 | |
| 	CheckTypes
 | |
| 	CheckVariables
 | |
| 
 | |
| 	CheckAll = CheckConstants | CheckFields | CheckFunctions | CheckTypes | CheckVariables
 | |
| )
 | |
| 
 | |
| type Unused struct {
 | |
| 	Obj      types.Object
 | |
| 	Position token.Position
 | |
| }
 | |
| 
 | |
| type Checker struct {
 | |
| 	Mode               CheckMode
 | |
| 	WholeProgram       bool
 | |
| 	ConsiderReflection bool
 | |
| 	Debug              io.Writer
 | |
| 
 | |
| 	graph *graph
 | |
| 
 | |
| 	msCache      typeutil.MethodSetCache
 | |
| 	lprog        *loader.Program
 | |
| 	topmostCache map[*types.Scope]*types.Scope
 | |
| 	interfaces   []*types.Interface
 | |
| }
 | |
| 
 | |
| func NewChecker(mode CheckMode) *Checker {
 | |
| 	return &Checker{
 | |
| 		Mode: mode,
 | |
| 		graph: &graph{
 | |
| 			nodes: make(map[interface{}]*graphNode),
 | |
| 		},
 | |
| 		topmostCache: make(map[*types.Scope]*types.Scope),
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (c *Checker) checkConstants() bool { return (c.Mode & CheckConstants) > 0 }
 | |
| func (c *Checker) checkFields() bool    { return (c.Mode & CheckFields) > 0 }
 | |
| func (c *Checker) checkFunctions() bool { return (c.Mode & CheckFunctions) > 0 }
 | |
| func (c *Checker) checkTypes() bool     { return (c.Mode & CheckTypes) > 0 }
 | |
| func (c *Checker) checkVariables() bool { return (c.Mode & CheckVariables) > 0 }
 | |
| 
 | |
| func (c *Checker) markFields(typ types.Type) {
 | |
| 	structType, ok := typ.Underlying().(*types.Struct)
 | |
| 	if !ok {
 | |
| 		return
 | |
| 	}
 | |
| 	n := structType.NumFields()
 | |
| 	for i := 0; i < n; i++ {
 | |
| 		field := structType.Field(i)
 | |
| 		c.graph.markUsedBy(field, typ)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| type Error struct {
 | |
| 	Errors map[string][]error
 | |
| }
 | |
| 
 | |
| func (e Error) Error() string {
 | |
| 	return fmt.Sprintf("errors in %d packages", len(e.Errors))
 | |
| }
 | |
| 
 | |
| func (c *Checker) Check(lprog *loader.Program) []Unused {
 | |
| 	var unused []Unused
 | |
| 	c.lprog = lprog
 | |
| 	if c.WholeProgram {
 | |
| 		c.findExportedInterfaces()
 | |
| 	}
 | |
| 	for _, pkg := range c.lprog.InitialPackages() {
 | |
| 		c.processDefs(pkg)
 | |
| 		c.processUses(pkg)
 | |
| 		c.processTypes(pkg)
 | |
| 		c.processSelections(pkg)
 | |
| 		c.processAST(pkg)
 | |
| 	}
 | |
| 
 | |
| 	for _, node := range c.graph.nodes {
 | |
| 		obj, ok := node.obj.(types.Object)
 | |
| 		if !ok {
 | |
| 			continue
 | |
| 		}
 | |
| 		typNode, ok := c.graph.nodes[obj.Type()]
 | |
| 		if !ok {
 | |
| 			continue
 | |
| 		}
 | |
| 		node.uses[typNode] = struct{}{}
 | |
| 	}
 | |
| 
 | |
| 	roots := map[*graphNode]struct{}{}
 | |
| 	for _, root := range c.graph.roots {
 | |
| 		roots[root] = struct{}{}
 | |
| 	}
 | |
| 	markNodesUsed(roots)
 | |
| 	c.markNodesQuiet()
 | |
| 
 | |
| 	if c.Debug != nil {
 | |
| 		c.printDebugGraph(c.Debug)
 | |
| 	}
 | |
| 
 | |
| 	for _, node := range c.graph.nodes {
 | |
| 		if node.used || node.quiet {
 | |
| 			continue
 | |
| 		}
 | |
| 		obj, ok := node.obj.(types.Object)
 | |
| 		if !ok {
 | |
| 			continue
 | |
| 		}
 | |
| 		found := false
 | |
| 		if !false {
 | |
| 			for _, pkg := range c.lprog.InitialPackages() {
 | |
| 				if pkg.Pkg == obj.Pkg() {
 | |
| 					found = true
 | |
| 					break
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		if !found {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		pos := c.lprog.Fset.Position(obj.Pos())
 | |
| 		if pos.Filename == "" || filepath.Base(pos.Filename) == "C" {
 | |
| 			continue
 | |
| 		}
 | |
| 		generated := false
 | |
| 		for _, file := range c.lprog.Package(obj.Pkg().Path()).Files {
 | |
| 			if c.lprog.Fset.Position(file.Pos()).Filename != pos.Filename {
 | |
| 				continue
 | |
| 			}
 | |
| 			if len(file.Comments) > 0 {
 | |
| 				generated = isGenerated(file.Comments[0].Text())
 | |
| 			}
 | |
| 			break
 | |
| 		}
 | |
| 		if generated {
 | |
| 			continue
 | |
| 		}
 | |
| 		unused = append(unused, Unused{Obj: obj, Position: pos})
 | |
| 	}
 | |
| 	return unused
 | |
| }
 | |
| 
 | |
| // 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 (c *Checker) useNoCopyFields(typ types.Type) {
 | |
| 	if st, ok := typ.Underlying().(*types.Struct); ok {
 | |
| 		n := st.NumFields()
 | |
| 		for i := 0; i < n; i++ {
 | |
| 			field := st.Field(i)
 | |
| 			if isNoCopyType(field.Type()) {
 | |
| 				c.graph.markUsedBy(field, typ)
 | |
| 				c.graph.markUsedBy(field.Type().(*types.Named).Method(0), field.Type())
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (c *Checker) useExportedFields(typ types.Type) {
 | |
| 	if st, ok := typ.Underlying().(*types.Struct); ok {
 | |
| 		n := st.NumFields()
 | |
| 		for i := 0; i < n; i++ {
 | |
| 			field := st.Field(i)
 | |
| 			if field.Exported() {
 | |
| 				c.graph.markUsedBy(field, typ)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (c *Checker) useExportedMethods(typ types.Type) {
 | |
| 	named, ok := typ.(*types.Named)
 | |
| 	if !ok {
 | |
| 		return
 | |
| 	}
 | |
| 	ms := typeutil.IntuitiveMethodSet(named, &c.msCache)
 | |
| 	for i := 0; i < len(ms); i++ {
 | |
| 		meth := ms[i].Obj()
 | |
| 		if meth.Exported() {
 | |
| 			c.graph.markUsedBy(meth, typ)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	st, ok := named.Underlying().(*types.Struct)
 | |
| 	if !ok {
 | |
| 		return
 | |
| 	}
 | |
| 	n := st.NumFields()
 | |
| 	for i := 0; i < n; i++ {
 | |
| 		field := st.Field(i)
 | |
| 		if !field.Anonymous() {
 | |
| 			continue
 | |
| 		}
 | |
| 		ms := typeutil.IntuitiveMethodSet(field.Type(), &c.msCache)
 | |
| 		for j := 0; j < len(ms); j++ {
 | |
| 			if ms[j].Obj().Exported() {
 | |
| 				c.graph.markUsedBy(field, typ)
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (c *Checker) processDefs(pkg *loader.PackageInfo) {
 | |
| 	for _, obj := range pkg.Defs {
 | |
| 		if obj == nil {
 | |
| 			continue
 | |
| 		}
 | |
| 		c.graph.getNode(obj)
 | |
| 
 | |
| 		if obj, ok := obj.(*types.TypeName); ok {
 | |
| 			c.graph.markUsedBy(obj.Type().Underlying(), obj.Type())
 | |
| 			c.graph.markUsedBy(obj.Type(), obj) // TODO is this needed?
 | |
| 			c.graph.markUsedBy(obj, obj.Type())
 | |
| 
 | |
| 			// We mark all exported fields as used. For normal
 | |
| 			// operation, we have to. The user may use these fields
 | |
| 			// without us knowing.
 | |
| 			//
 | |
| 			// TODO(dh): In whole-program mode, however, we mark them
 | |
| 			// as used because of reflection (such as JSON
 | |
| 			// marshaling). Strictly speaking, we would only need to
 | |
| 			// mark them used if an instance of the type was
 | |
| 			// accessible via an interface value.
 | |
| 			if !c.WholeProgram || c.ConsiderReflection {
 | |
| 				c.useExportedFields(obj.Type())
 | |
| 			}
 | |
| 
 | |
| 			// TODO(dh): Traditionally we have not marked all exported
 | |
| 			// methods as exported, even though they're strictly
 | |
| 			// speaking accessible through reflection. We've done that
 | |
| 			// because using methods just via reflection is rare, and
 | |
| 			// not worth the false negatives. With the new -reflect
 | |
| 			// flag, however, we should reconsider that choice.
 | |
| 			if !c.WholeProgram {
 | |
| 				c.useExportedMethods(obj.Type())
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		switch obj := obj.(type) {
 | |
| 		case *types.Var, *types.Const, *types.Func, *types.TypeName:
 | |
| 			if obj.Exported() {
 | |
| 				// Exported variables and constants use their types,
 | |
| 				// even if there's no expression using them in the
 | |
| 				// checked program.
 | |
| 				//
 | |
| 				// Also operates on funcs and type names, but that's
 | |
| 				// irrelevant/redundant.
 | |
| 				c.graph.markUsedBy(obj.Type(), obj)
 | |
| 			}
 | |
| 			if obj.Name() == "_" {
 | |
| 				node := c.graph.getNode(obj)
 | |
| 				node.quiet = true
 | |
| 				scope := c.topmostScope(pkg.Pkg.Scope().Innermost(obj.Pos()), pkg.Pkg)
 | |
| 				if scope == pkg.Pkg.Scope() {
 | |
| 					c.graph.roots = append(c.graph.roots, node)
 | |
| 				} else {
 | |
| 					c.graph.markUsedBy(obj, scope)
 | |
| 				}
 | |
| 			} else {
 | |
| 				// Variables declared in functions are used. This is
 | |
| 				// done so that arguments and return parameters are
 | |
| 				// always marked as used.
 | |
| 				if _, ok := obj.(*types.Var); ok {
 | |
| 					if obj.Parent() != obj.Pkg().Scope() && obj.Parent() != nil {
 | |
| 						c.graph.markUsedBy(obj, c.topmostScope(obj.Parent(), obj.Pkg()))
 | |
| 						c.graph.markUsedBy(obj.Type(), obj)
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if fn, ok := obj.(*types.Func); ok {
 | |
| 			// A function uses its signature
 | |
| 			c.graph.markUsedBy(fn, fn.Type())
 | |
| 
 | |
| 			// A function uses its return types
 | |
| 			sig := fn.Type().(*types.Signature)
 | |
| 			res := sig.Results()
 | |
| 			n := res.Len()
 | |
| 			for i := 0; i < n; i++ {
 | |
| 				c.graph.markUsedBy(res.At(i).Type(), fn)
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if obj, ok := obj.(interface {
 | |
| 			Scope() *types.Scope
 | |
| 			Pkg() *types.Package
 | |
| 		}); ok {
 | |
| 			scope := obj.Scope()
 | |
| 			c.graph.markUsedBy(c.topmostScope(scope, obj.Pkg()), obj)
 | |
| 		}
 | |
| 
 | |
| 		if c.isRoot(obj) {
 | |
| 			node := c.graph.getNode(obj)
 | |
| 			c.graph.roots = append(c.graph.roots, node)
 | |
| 			if obj, ok := obj.(*types.PkgName); ok {
 | |
| 				scope := obj.Pkg().Scope()
 | |
| 				c.graph.markUsedBy(scope, obj)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (c *Checker) processUses(pkg *loader.PackageInfo) {
 | |
| 	for ident, usedObj := range pkg.Uses {
 | |
| 		if _, ok := usedObj.(*types.PkgName); ok {
 | |
| 			continue
 | |
| 		}
 | |
| 		pos := ident.Pos()
 | |
| 		scope := pkg.Pkg.Scope().Innermost(pos)
 | |
| 		scope = c.topmostScope(scope, pkg.Pkg)
 | |
| 		if scope != pkg.Pkg.Scope() {
 | |
| 			c.graph.markUsedBy(usedObj, scope)
 | |
| 		}
 | |
| 
 | |
| 		switch usedObj.(type) {
 | |
| 		case *types.Var, *types.Const:
 | |
| 			c.graph.markUsedBy(usedObj.Type(), usedObj)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (c *Checker) findExportedInterfaces() {
 | |
| 	c.interfaces = []*types.Interface{types.Universe.Lookup("error").Type().(*types.Named).Underlying().(*types.Interface)}
 | |
| 	var pkgs []*loader.PackageInfo
 | |
| 	if c.WholeProgram {
 | |
| 		for _, pkg := range c.lprog.AllPackages {
 | |
| 			pkgs = append(pkgs, pkg)
 | |
| 		}
 | |
| 	} else {
 | |
| 		pkgs = c.lprog.InitialPackages()
 | |
| 	}
 | |
| 
 | |
| 	for _, pkg := range pkgs {
 | |
| 		for _, tv := range pkg.Types {
 | |
| 			iface, ok := tv.Type.(*types.Interface)
 | |
| 			if !ok {
 | |
| 				continue
 | |
| 			}
 | |
| 			if iface.NumMethods() == 0 {
 | |
| 				continue
 | |
| 			}
 | |
| 			c.interfaces = append(c.interfaces, iface)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (c *Checker) processTypes(pkg *loader.PackageInfo) {
 | |
| 	named := map[*types.Named]*types.Pointer{}
 | |
| 	var interfaces []*types.Interface
 | |
| 	for _, tv := range pkg.Types {
 | |
| 		if typ, ok := tv.Type.(interface {
 | |
| 			Elem() types.Type
 | |
| 		}); ok {
 | |
| 			c.graph.markUsedBy(typ.Elem(), typ)
 | |
| 		}
 | |
| 
 | |
| 		switch obj := tv.Type.(type) {
 | |
| 		case *types.Named:
 | |
| 			named[obj] = types.NewPointer(obj)
 | |
| 			c.graph.markUsedBy(obj, obj.Underlying())
 | |
| 			c.graph.markUsedBy(obj.Underlying(), obj)
 | |
| 		case *types.Interface:
 | |
| 			if obj.NumMethods() > 0 {
 | |
| 				interfaces = append(interfaces, obj)
 | |
| 			}
 | |
| 		case *types.Struct:
 | |
| 			c.useNoCopyFields(obj)
 | |
| 			if pkg.Pkg.Name() != "main" && !c.WholeProgram {
 | |
| 				c.useExportedFields(obj)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Pretend that all types are meant to implement as many
 | |
| 	// interfaces as possible.
 | |
| 	//
 | |
| 	// TODO(dh): For normal operations, that's the best we can do, as
 | |
| 	// we have no idea what external users will do with our types. In
 | |
| 	// whole-program mode, we could be more conservative, in two ways:
 | |
| 	// 1) Only consider interfaces if a type has been assigned to one
 | |
| 	// 2) Use SSA and flow analysis and determine the exact set of
 | |
| 	// interfaces that is relevant.
 | |
| 	fn := func(iface *types.Interface) {
 | |
| 		for obj, objPtr := range named {
 | |
| 			if !types.Implements(obj, iface) && !types.Implements(objPtr, iface) {
 | |
| 				continue
 | |
| 			}
 | |
| 			ifaceMethods := make(map[string]struct{}, iface.NumMethods())
 | |
| 			n := iface.NumMethods()
 | |
| 			for i := 0; i < n; i++ {
 | |
| 				meth := iface.Method(i)
 | |
| 				ifaceMethods[meth.Name()] = struct{}{}
 | |
| 			}
 | |
| 			for _, obj := range []types.Type{obj, objPtr} {
 | |
| 				ms := c.msCache.MethodSet(obj)
 | |
| 				n := ms.Len()
 | |
| 				for i := 0; i < n; i++ {
 | |
| 					sel := ms.At(i)
 | |
| 					meth := sel.Obj().(*types.Func)
 | |
| 					_, found := ifaceMethods[meth.Name()]
 | |
| 					if !found {
 | |
| 						continue
 | |
| 					}
 | |
| 					c.graph.markUsedBy(meth.Type().(*types.Signature).Recv().Type(), obj) // embedded receiver
 | |
| 					if len(sel.Index()) > 1 {
 | |
| 						f := getField(obj, sel.Index()[0])
 | |
| 						c.graph.markUsedBy(f, obj) // embedded receiver
 | |
| 					}
 | |
| 					c.graph.markUsedBy(meth, obj)
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for _, iface := range interfaces {
 | |
| 		fn(iface)
 | |
| 	}
 | |
| 	for _, iface := range c.interfaces {
 | |
| 		fn(iface)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (c *Checker) processSelections(pkg *loader.PackageInfo) {
 | |
| 	fn := func(expr *ast.SelectorExpr, sel *types.Selection, offset int) {
 | |
| 		scope := pkg.Pkg.Scope().Innermost(expr.Pos())
 | |
| 		c.graph.markUsedBy(expr.X, c.topmostScope(scope, pkg.Pkg))
 | |
| 		c.graph.markUsedBy(sel.Obj(), expr.X)
 | |
| 		if len(sel.Index()) > 1 {
 | |
| 			typ := sel.Recv()
 | |
| 			indices := sel.Index()
 | |
| 			for _, idx := range indices[:len(indices)-offset] {
 | |
| 				obj := getField(typ, idx)
 | |
| 				typ = obj.Type()
 | |
| 				c.graph.markUsedBy(obj, expr.X)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for expr, sel := range pkg.Selections {
 | |
| 		switch sel.Kind() {
 | |
| 		case types.FieldVal:
 | |
| 			fn(expr, sel, 0)
 | |
| 		case types.MethodVal:
 | |
| 			fn(expr, sel, 1)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func dereferenceType(typ types.Type) types.Type {
 | |
| 	if typ, ok := typ.(*types.Pointer); ok {
 | |
| 		return typ.Elem()
 | |
| 	}
 | |
| 	return typ
 | |
| }
 | |
| 
 | |
| // processConversion marks fields as used if they're part of a type conversion.
 | |
| func (c *Checker) processConversion(pkg *loader.PackageInfo, node ast.Node) {
 | |
| 	if node, ok := node.(*ast.CallExpr); ok {
 | |
| 		callTyp := pkg.TypeOf(node.Fun)
 | |
| 		var typDst *types.Struct
 | |
| 		var ok bool
 | |
| 		switch typ := callTyp.(type) {
 | |
| 		case *types.Named:
 | |
| 			typDst, ok = typ.Underlying().(*types.Struct)
 | |
| 		case *types.Pointer:
 | |
| 			typDst, ok = typ.Elem().Underlying().(*types.Struct)
 | |
| 		default:
 | |
| 			return
 | |
| 		}
 | |
| 		if !ok {
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		if typ, ok := pkg.TypeOf(node.Args[0]).(*types.Basic); ok && typ.Kind() == types.UnsafePointer {
 | |
| 			// This is an unsafe conversion. Assume that all the
 | |
| 			// fields are relevant (they are, because of memory
 | |
| 			// layout)
 | |
| 			n := typDst.NumFields()
 | |
| 			for i := 0; i < n; i++ {
 | |
| 				c.graph.markUsedBy(typDst.Field(i), typDst)
 | |
| 			}
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		typSrc, ok := dereferenceType(pkg.TypeOf(node.Args[0])).Underlying().(*types.Struct)
 | |
| 		if !ok {
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		// When we convert from type t1 to t2, were t1 and t2 are
 | |
| 		// structs, all fields are relevant, as otherwise the
 | |
| 		// conversion would fail.
 | |
| 		//
 | |
| 		// We mark t2's fields as used by t1's fields, and vice
 | |
| 		// versa. That way, if no code actually refers to a field
 | |
| 		// in either type, it's still correctly marked as unused.
 | |
| 		// If a field is used in either struct, it's implicitly
 | |
| 		// relevant in the other one, too.
 | |
| 		//
 | |
| 		// It works in a similar way for conversions between types
 | |
| 		// of two packages, only that the extra information in the
 | |
| 		// graph is redundant unless we're in whole program mode.
 | |
| 		n := typDst.NumFields()
 | |
| 		for i := 0; i < n; i++ {
 | |
| 			fDst := typDst.Field(i)
 | |
| 			fSrc := typSrc.Field(i)
 | |
| 			c.graph.markUsedBy(fDst, fSrc)
 | |
| 			c.graph.markUsedBy(fSrc, fDst)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // processCompositeLiteral marks fields as used if the struct is used
 | |
| // in a composite literal.
 | |
| func (c *Checker) processCompositeLiteral(pkg *loader.PackageInfo, node ast.Node) {
 | |
| 	// XXX how does this actually work? wouldn't it match t{}?
 | |
| 	if node, ok := node.(*ast.CompositeLit); ok {
 | |
| 		typ := pkg.TypeOf(node)
 | |
| 		if _, ok := typ.(*types.Named); ok {
 | |
| 			typ = typ.Underlying()
 | |
| 		}
 | |
| 		if _, ok := typ.(*types.Struct); !ok {
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		if isBasicStruct(node.Elts) {
 | |
| 			c.markFields(typ)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // processCgoExported marks functions as used if they're being
 | |
| // exported to cgo.
 | |
| func (c *Checker) processCgoExported(pkg *loader.PackageInfo, node ast.Node) {
 | |
| 	if node, ok := node.(*ast.FuncDecl); ok {
 | |
| 		if node.Doc == nil {
 | |
| 			return
 | |
| 		}
 | |
| 		for _, cmt := range node.Doc.List {
 | |
| 			if !strings.HasPrefix(cmt.Text, "//go:cgo_export_") {
 | |
| 				return
 | |
| 			}
 | |
| 			obj := pkg.ObjectOf(node.Name)
 | |
| 			c.graph.roots = append(c.graph.roots, c.graph.getNode(obj))
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (c *Checker) processVariableDeclaration(pkg *loader.PackageInfo, node ast.Node) {
 | |
| 	if decl, ok := node.(*ast.GenDecl); ok {
 | |
| 		for _, spec := range decl.Specs {
 | |
| 			spec, ok := spec.(*ast.ValueSpec)
 | |
| 			if !ok {
 | |
| 				continue
 | |
| 			}
 | |
| 			for i, name := range spec.Names {
 | |
| 				if i >= len(spec.Values) {
 | |
| 					break
 | |
| 				}
 | |
| 				value := spec.Values[i]
 | |
| 				fn := func(node ast.Node) bool {
 | |
| 					if node3, ok := node.(*ast.Ident); ok {
 | |
| 						obj := pkg.ObjectOf(node3)
 | |
| 						if _, ok := obj.(*types.PkgName); ok {
 | |
| 							return true
 | |
| 						}
 | |
| 						c.graph.markUsedBy(obj, pkg.ObjectOf(name))
 | |
| 					}
 | |
| 					return true
 | |
| 				}
 | |
| 				ast.Inspect(value, fn)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (c *Checker) processArrayConstants(pkg *loader.PackageInfo, node ast.Node) {
 | |
| 	if decl, ok := node.(*ast.ArrayType); ok {
 | |
| 		ident, ok := decl.Len.(*ast.Ident)
 | |
| 		if !ok {
 | |
| 			return
 | |
| 		}
 | |
| 		c.graph.markUsedBy(pkg.ObjectOf(ident), pkg.TypeOf(decl))
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (c *Checker) processKnownReflectMethodCallers(pkg *loader.PackageInfo, node ast.Node) {
 | |
| 	call, ok := node.(*ast.CallExpr)
 | |
| 	if !ok {
 | |
| 		return
 | |
| 	}
 | |
| 	sel, ok := call.Fun.(*ast.SelectorExpr)
 | |
| 	if !ok {
 | |
| 		return
 | |
| 	}
 | |
| 	if types.TypeString(pkg.TypeOf(sel.X), nil) != "*net/rpc.Server" {
 | |
| 		x, ok := sel.X.(*ast.Ident)
 | |
| 		if !ok {
 | |
| 			return
 | |
| 		}
 | |
| 		pkgname, ok := pkg.ObjectOf(x).(*types.PkgName)
 | |
| 		if !ok {
 | |
| 			return
 | |
| 		}
 | |
| 		if pkgname.Imported().Path() != "net/rpc" {
 | |
| 			return
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	var arg ast.Expr
 | |
| 	switch sel.Sel.Name {
 | |
| 	case "Register":
 | |
| 		if len(call.Args) != 1 {
 | |
| 			return
 | |
| 		}
 | |
| 		arg = call.Args[0]
 | |
| 	case "RegisterName":
 | |
| 		if len(call.Args) != 2 {
 | |
| 			return
 | |
| 		}
 | |
| 		arg = call.Args[1]
 | |
| 	}
 | |
| 	typ := pkg.TypeOf(arg)
 | |
| 	ms := types.NewMethodSet(typ)
 | |
| 	for i := 0; i < ms.Len(); i++ {
 | |
| 		c.graph.markUsedBy(ms.At(i).Obj(), typ)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (c *Checker) processAST(pkg *loader.PackageInfo) {
 | |
| 	fn := func(node ast.Node) bool {
 | |
| 		c.processConversion(pkg, node)
 | |
| 		c.processKnownReflectMethodCallers(pkg, node)
 | |
| 		c.processCompositeLiteral(pkg, node)
 | |
| 		c.processCgoExported(pkg, node)
 | |
| 		c.processVariableDeclaration(pkg, node)
 | |
| 		c.processArrayConstants(pkg, node)
 | |
| 		return true
 | |
| 	}
 | |
| 	for _, file := range pkg.Files {
 | |
| 		ast.Inspect(file, fn)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func isBasicStruct(elts []ast.Expr) bool {
 | |
| 	for _, elt := range elts {
 | |
| 		if _, ok := elt.(*ast.KeyValueExpr); !ok {
 | |
| 			return true
 | |
| 		}
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| func isPkgScope(obj types.Object) bool {
 | |
| 	return obj.Parent() == obj.Pkg().Scope()
 | |
| }
 | |
| 
 | |
| func isMain(obj types.Object) bool {
 | |
| 	if obj.Pkg().Name() != "main" {
 | |
| 		return false
 | |
| 	}
 | |
| 	if obj.Name() != "main" {
 | |
| 		return false
 | |
| 	}
 | |
| 	if !isPkgScope(obj) {
 | |
| 		return false
 | |
| 	}
 | |
| 	if !isFunction(obj) {
 | |
| 		return false
 | |
| 	}
 | |
| 	if isMethod(obj) {
 | |
| 		return false
 | |
| 	}
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| func isFunction(obj types.Object) bool {
 | |
| 	_, ok := obj.(*types.Func)
 | |
| 	return ok
 | |
| }
 | |
| 
 | |
| func isMethod(obj types.Object) bool {
 | |
| 	if !isFunction(obj) {
 | |
| 		return false
 | |
| 	}
 | |
| 	return obj.(*types.Func).Type().(*types.Signature).Recv() != nil
 | |
| }
 | |
| 
 | |
| func isVariable(obj types.Object) bool {
 | |
| 	_, ok := obj.(*types.Var)
 | |
| 	return ok
 | |
| }
 | |
| 
 | |
| func isConstant(obj types.Object) bool {
 | |
| 	_, ok := obj.(*types.Const)
 | |
| 	return ok
 | |
| }
 | |
| 
 | |
| func isType(obj types.Object) bool {
 | |
| 	_, ok := obj.(*types.TypeName)
 | |
| 	return ok
 | |
| }
 | |
| 
 | |
| func isField(obj types.Object) bool {
 | |
| 	if obj, ok := obj.(*types.Var); ok && obj.IsField() {
 | |
| 		return true
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| func (c *Checker) checkFlags(v interface{}) bool {
 | |
| 	obj, ok := v.(types.Object)
 | |
| 	if !ok {
 | |
| 		return false
 | |
| 	}
 | |
| 	if isFunction(obj) && !c.checkFunctions() {
 | |
| 		return false
 | |
| 	}
 | |
| 	if isVariable(obj) && !c.checkVariables() {
 | |
| 		return false
 | |
| 	}
 | |
| 	if isConstant(obj) && !c.checkConstants() {
 | |
| 		return false
 | |
| 	}
 | |
| 	if isType(obj) && !c.checkTypes() {
 | |
| 		return false
 | |
| 	}
 | |
| 	if isField(obj) && !c.checkFields() {
 | |
| 		return false
 | |
| 	}
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| func (c *Checker) isRoot(obj types.Object) bool {
 | |
| 	// - in local mode, main, init, tests, and non-test, non-main exported are roots
 | |
| 	// - in global mode (not yet implemented), main, init and tests are roots
 | |
| 
 | |
| 	if _, ok := obj.(*types.PkgName); ok {
 | |
| 		return true
 | |
| 	}
 | |
| 
 | |
| 	if isMain(obj) || (isFunction(obj) && !isMethod(obj) && obj.Name() == "init") {
 | |
| 		return true
 | |
| 	}
 | |
| 	if obj.Exported() {
 | |
| 		f := c.lprog.Fset.Position(obj.Pos()).Filename
 | |
| 		if strings.HasSuffix(f, "_test.go") {
 | |
| 			return strings.HasPrefix(obj.Name(), "Test") ||
 | |
| 				strings.HasPrefix(obj.Name(), "Benchmark") ||
 | |
| 				strings.HasPrefix(obj.Name(), "Example")
 | |
| 		}
 | |
| 
 | |
| 		// Package-level are used, except in package main
 | |
| 		if isPkgScope(obj) && obj.Pkg().Name() != "main" && !c.WholeProgram {
 | |
| 			return true
 | |
| 		}
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| func markNodesUsed(nodes map[*graphNode]struct{}) {
 | |
| 	for node := range nodes {
 | |
| 		wasUsed := node.used
 | |
| 		node.used = true
 | |
| 		if !wasUsed {
 | |
| 			markNodesUsed(node.uses)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (c *Checker) markNodesQuiet() {
 | |
| 	for _, node := range c.graph.nodes {
 | |
| 		if node.used {
 | |
| 			continue
 | |
| 		}
 | |
| 		if obj, ok := node.obj.(types.Object); ok && !c.checkFlags(obj) {
 | |
| 			node.quiet = true
 | |
| 			continue
 | |
| 		}
 | |
| 		c.markObjQuiet(node.obj)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (c *Checker) markObjQuiet(obj interface{}) {
 | |
| 	switch obj := obj.(type) {
 | |
| 	case *types.Named:
 | |
| 		n := obj.NumMethods()
 | |
| 		for i := 0; i < n; i++ {
 | |
| 			meth := obj.Method(i)
 | |
| 			node := c.graph.getNode(meth)
 | |
| 			node.quiet = true
 | |
| 			c.markObjQuiet(meth.Scope())
 | |
| 		}
 | |
| 	case *types.Struct:
 | |
| 		n := obj.NumFields()
 | |
| 		for i := 0; i < n; i++ {
 | |
| 			field := obj.Field(i)
 | |
| 			c.graph.nodes[field].quiet = true
 | |
| 		}
 | |
| 	case *types.Func:
 | |
| 		c.markObjQuiet(obj.Scope())
 | |
| 	case *types.Scope:
 | |
| 		if obj == nil {
 | |
| 			return
 | |
| 		}
 | |
| 		if obj.Parent() == types.Universe {
 | |
| 			return
 | |
| 		}
 | |
| 		for _, name := range obj.Names() {
 | |
| 			v := obj.Lookup(name)
 | |
| 			if n, ok := c.graph.nodes[v]; ok {
 | |
| 				n.quiet = true
 | |
| 			}
 | |
| 		}
 | |
| 		n := obj.NumChildren()
 | |
| 		for i := 0; i < n; i++ {
 | |
| 			c.markObjQuiet(obj.Child(i))
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func getField(typ types.Type, idx int) *types.Var {
 | |
| 	switch obj := typ.(type) {
 | |
| 	case *types.Pointer:
 | |
| 		return getField(obj.Elem(), idx)
 | |
| 	case *types.Named:
 | |
| 		switch v := obj.Underlying().(type) {
 | |
| 		case *types.Struct:
 | |
| 			return v.Field(idx)
 | |
| 		case *types.Pointer:
 | |
| 			return getField(v.Elem(), idx)
 | |
| 		default:
 | |
| 			panic(fmt.Sprintf("unexpected type %s", typ))
 | |
| 		}
 | |
| 	case *types.Struct:
 | |
| 		return obj.Field(idx)
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (c *Checker) topmostScope(scope *types.Scope, pkg *types.Package) (ret *types.Scope) {
 | |
| 	if top, ok := c.topmostCache[scope]; ok {
 | |
| 		return top
 | |
| 	}
 | |
| 	defer func() {
 | |
| 		c.topmostCache[scope] = ret
 | |
| 	}()
 | |
| 	if scope == pkg.Scope() {
 | |
| 		return scope
 | |
| 	}
 | |
| 	if scope.Parent().Parent() == pkg.Scope() {
 | |
| 		return scope
 | |
| 	}
 | |
| 	return c.topmostScope(scope.Parent(), pkg)
 | |
| }
 | |
| 
 | |
| func (c *Checker) printDebugGraph(w io.Writer) {
 | |
| 	fmt.Fprintln(w, "digraph {")
 | |
| 	fmt.Fprintln(w, "n0 [label = roots]")
 | |
| 	for _, node := range c.graph.nodes {
 | |
| 		s := fmt.Sprintf("%s (%T)", node.obj, node.obj)
 | |
| 		s = strings.Replace(s, "\n", "", -1)
 | |
| 		s = strings.Replace(s, `"`, "", -1)
 | |
| 		fmt.Fprintf(w, `n%d [label = %q]`, node.n, s)
 | |
| 		color := "black"
 | |
| 		switch {
 | |
| 		case node.used:
 | |
| 			color = "green"
 | |
| 		case node.quiet:
 | |
| 			color = "orange"
 | |
| 		case !c.checkFlags(node.obj):
 | |
| 			color = "purple"
 | |
| 		default:
 | |
| 			color = "red"
 | |
| 		}
 | |
| 		fmt.Fprintf(w, "[color = %s]", color)
 | |
| 		fmt.Fprintln(w)
 | |
| 	}
 | |
| 
 | |
| 	for _, node1 := range c.graph.nodes {
 | |
| 		for node2 := range node1.uses {
 | |
| 			fmt.Fprintf(w, "n%d -> n%d\n", node1.n, node2.n)
 | |
| 		}
 | |
| 	}
 | |
| 	for _, root := range c.graph.roots {
 | |
| 		fmt.Fprintf(w, "n0 -> n%d\n", root.n)
 | |
| 	}
 | |
| 	fmt.Fprintln(w, "}")
 | |
| }
 | |
| 
 | |
| func isGenerated(comment string) bool {
 | |
| 	return strings.Contains(comment, "Code generated by") ||
 | |
| 		strings.Contains(comment, "DO NOT EDIT")
 | |
| }
 | 
