aboutsummaryrefslogtreecommitdiff
path: root/vendor/honnef.co/go/tools/unused
diff options
context:
space:
mode:
authorJoseph Richey <joerichey94@gmail.com>2018-02-11 20:34:07 -0800
committerJoseph Richey <joerichey94@gmail.com>2018-02-11 20:34:07 -0800
commit23b8c7b4eab0375b3d59cf4b2a1f3d7356515f95 (patch)
tree6ec67f8d6b15420c3870d2b7c43b2b636fbb8349 /vendor/honnef.co/go/tools/unused
parentfff13ea9041a3945e36d5f002c3c0a1e0e93c825 (diff)
vendor: include source for tools
This change vendors the source for all our build, formatting, and linting tools. Generated by running "dep ensure".
Diffstat (limited to 'vendor/honnef.co/go/tools/unused')
-rw-r--r--vendor/honnef.co/go/tools/unused/unused.go1064
1 files changed, 1064 insertions, 0 deletions
diff --git a/vendor/honnef.co/go/tools/unused/unused.go b/vendor/honnef.co/go/tools/unused/unused.go
new file mode 100644
index 0000000..21889e8
--- /dev/null
+++ b/vendor/honnef.co/go/tools/unused/unused.go
@@ -0,0 +1,1064 @@
+package unused // import "honnef.co/go/tools/unused"
+
+import (
+ "fmt"
+ "go/ast"
+ "go/token"
+ "go/types"
+ "io"
+ "path/filepath"
+ "strings"
+
+ "honnef.co/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")
+}