github.com/april1989/origin-go-tools@v0.0.32/cmd/guru/guru.go (about)

     1  // Copyright 2014 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  // TODO(adonovan): new queries
     8  // - show all statements that may update the selected lvalue
     9  //   (local, global, field, etc).
    10  // - show all places where an object of type T is created
    11  //   (&T{}, var t T, new(T), new(struct{array [3]T}), etc.
    12  
    13  import (
    14  	"encoding/json"
    15  	"fmt"
    16  	"go/ast"
    17  	"go/build"
    18  	"go/parser"
    19  	"go/token"
    20  	"go/types"
    21  	"io"
    22  	"log"
    23  	"path/filepath"
    24  	"strings"
    25  
    26  	"github.com/april1989/origin-go-tools/go/ast/astutil"
    27  	"github.com/april1989/origin-go-tools/go/buildutil"
    28  	"github.com/april1989/origin-go-tools/go/loader"
    29  	"github.com/april1989/origin-go-tools/go/pointer"
    30  	"github.com/april1989/origin-go-tools/go/ssa"
    31  )
    32  
    33  type printfFunc func(pos interface{}, format string, args ...interface{})
    34  
    35  // A QueryResult is an item of output.  Each query produces a stream of
    36  // query results, calling Query.Output for each one.
    37  type QueryResult interface {
    38  	// JSON returns the QueryResult in JSON form.
    39  	JSON(fset *token.FileSet) []byte
    40  
    41  	// PrintPlain prints the QueryResult in plain text form.
    42  	// The implementation calls printfFunc to print each line of output.
    43  	PrintPlain(printf printfFunc)
    44  }
    45  
    46  // A QueryPos represents the position provided as input to a query:
    47  // a textual extent in the program's source code, the AST node it
    48  // corresponds to, and the package to which it belongs.
    49  // Instances are created by parseQueryPos.
    50  type queryPos struct {
    51  	fset       *token.FileSet
    52  	start, end token.Pos           // source extent of query
    53  	path       []ast.Node          // AST path from query node to root of ast.File
    54  	exact      bool                // 2nd result of PathEnclosingInterval
    55  	info       *loader.PackageInfo // type info for the queried package (nil for fastQueryPos)
    56  }
    57  
    58  // TypeString prints type T relative to the query position.
    59  func (qpos *queryPos) typeString(T types.Type) string {
    60  	return types.TypeString(T, types.RelativeTo(qpos.info.Pkg))
    61  }
    62  
    63  // ObjectString prints object obj relative to the query position.
    64  func (qpos *queryPos) objectString(obj types.Object) string {
    65  	return types.ObjectString(obj, types.RelativeTo(qpos.info.Pkg))
    66  }
    67  
    68  // A Query specifies a single guru query.
    69  type Query struct {
    70  	Pos   string         // query position
    71  	Build *build.Context // package loading configuration
    72  
    73  	// pointer analysis options
    74  	Scope      []string  // main packages in (*loader.Config).FromArgs syntax
    75  	PTALog     io.Writer // (optional) pointer-analysis log file
    76  	Reflection bool      // model reflection soundly (currently slow).
    77  
    78  	// result-printing function, safe for concurrent use
    79  	Output func(*token.FileSet, QueryResult)
    80  }
    81  
    82  // Run runs an guru query and populates its Fset and Result.
    83  func Run(mode string, q *Query) error {
    84  	switch mode {
    85  	case "callees":
    86  		return callees(q)
    87  	case "callers":
    88  		return callers(q)
    89  	case "callstack":
    90  		return callstack(q)
    91  	case "peers":
    92  		return peers(q)
    93  	case "pointsto":
    94  		return pointsto(q)
    95  	case "whicherrs":
    96  		return whicherrs(q)
    97  	case "definition":
    98  		return definition(q)
    99  	case "describe":
   100  		return describe(q)
   101  	case "freevars":
   102  		return freevars(q)
   103  	case "implements":
   104  		return implements(q)
   105  	case "referrers":
   106  		return referrers(q)
   107  	case "what":
   108  		return what(q)
   109  	default:
   110  		return fmt.Errorf("invalid mode: %q", mode)
   111  	}
   112  }
   113  
   114  func setPTAScope(lconf *loader.Config, scope []string) error {
   115  	pkgs := buildutil.ExpandPatterns(lconf.Build, scope)
   116  	if len(pkgs) == 0 {
   117  		return fmt.Errorf("no packages specified for pointer analysis scope")
   118  	}
   119  	// The value of each entry in pkgs is true,
   120  	// giving ImportWithTests (not Import) semantics.
   121  	lconf.ImportPkgs = pkgs
   122  	return nil
   123  }
   124  
   125  // Create a pointer.Config whose scope is the initial packages of lprog
   126  // and their dependencies.
   127  func setupPTA(prog *ssa.Program, lprog *loader.Program, ptaLog io.Writer, reflection bool) (*pointer.Config, error) {
   128  	// For each initial package (specified on the command line),
   129  	// if it has a main function, analyze that,
   130  	// otherwise analyze its tests, if any.
   131  	var mains []*ssa.Package
   132  	for _, info := range lprog.InitialPackages() {
   133  		p := prog.Package(info.Pkg)
   134  
   135  		// Add package to the pointer analysis scope.
   136  		if p.Pkg.Name() == "main" && p.Func("main") != nil {
   137  			mains = append(mains, p)
   138  		} else if main := prog.CreateTestMainPackage(p); main != nil {
   139  			mains = append(mains, main)
   140  		}
   141  	}
   142  	if mains == nil {
   143  		return nil, fmt.Errorf("analysis scope has no main and no tests")
   144  	}
   145  	return &pointer.Config{
   146  		Log:        ptaLog,
   147  		Reflection: reflection,
   148  		Mains:      mains,
   149  	}, nil
   150  }
   151  
   152  // importQueryPackage finds the package P containing the
   153  // query position and tells conf to import it.
   154  // It returns the package's path.
   155  func importQueryPackage(pos string, conf *loader.Config) (string, error) {
   156  	fqpos, err := fastQueryPos(conf.Build, pos)
   157  	if err != nil {
   158  		return "", err // bad query
   159  	}
   160  	filename := fqpos.fset.File(fqpos.start).Name()
   161  
   162  	_, importPath, err := guessImportPath(filename, conf.Build)
   163  	if err != nil {
   164  		// Can't find GOPATH dir.
   165  		// Treat the query file as its own package.
   166  		importPath = "command-line-arguments"
   167  		conf.CreateFromFilenames(importPath, filename)
   168  	} else {
   169  		// Check that it's possible to load the queried package.
   170  		// (e.g. guru tests contain different 'package' decls in same dir.)
   171  		// Keep consistent with logic in loader/util.go!
   172  		cfg2 := *conf.Build
   173  		cfg2.CgoEnabled = false
   174  		bp, err := cfg2.Import(importPath, "", 0)
   175  		if err != nil {
   176  			return "", err // no files for package
   177  		}
   178  
   179  		switch pkgContainsFile(bp, filename) {
   180  		case 'T':
   181  			conf.ImportWithTests(importPath)
   182  		case 'X':
   183  			conf.ImportWithTests(importPath)
   184  			importPath += "_test" // for TypeCheckFuncBodies
   185  		case 'G':
   186  			conf.Import(importPath)
   187  		default:
   188  			// This happens for ad-hoc packages like
   189  			// $GOROOT/src/net/http/triv.go.
   190  			return "", fmt.Errorf("package %q doesn't contain file %s",
   191  				importPath, filename)
   192  		}
   193  	}
   194  
   195  	conf.TypeCheckFuncBodies = func(p string) bool { return p == importPath }
   196  
   197  	return importPath, nil
   198  }
   199  
   200  // pkgContainsFile reports whether file was among the packages Go
   201  // files, Test files, eXternal test files, or not found.
   202  func pkgContainsFile(bp *build.Package, filename string) byte {
   203  	for i, files := range [][]string{bp.GoFiles, bp.TestGoFiles, bp.XTestGoFiles} {
   204  		for _, file := range files {
   205  			if sameFile(filepath.Join(bp.Dir, file), filename) {
   206  				return "GTX"[i]
   207  			}
   208  		}
   209  	}
   210  	return 0 // not found
   211  }
   212  
   213  // ParseQueryPos parses the source query position pos and returns the
   214  // AST node of the loaded program lprog that it identifies.
   215  // If needExact, it must identify a single AST subtree;
   216  // this is appropriate for queries that allow fairly arbitrary syntax,
   217  // e.g. "describe".
   218  //
   219  func parseQueryPos(lprog *loader.Program, pos string, needExact bool) (*queryPos, error) {
   220  	filename, startOffset, endOffset, err := parsePos(pos)
   221  	if err != nil {
   222  		return nil, err
   223  	}
   224  
   225  	// Find the named file among those in the loaded program.
   226  	var file *token.File
   227  	lprog.Fset.Iterate(func(f *token.File) bool {
   228  		if sameFile(filename, f.Name()) {
   229  			file = f
   230  			return false // done
   231  		}
   232  		return true // continue
   233  	})
   234  	if file == nil {
   235  		return nil, fmt.Errorf("file %s not found in loaded program", filename)
   236  	}
   237  
   238  	start, end, err := fileOffsetToPos(file, startOffset, endOffset)
   239  	if err != nil {
   240  		return nil, err
   241  	}
   242  	info, path, exact := lprog.PathEnclosingInterval(start, end)
   243  	if path == nil {
   244  		return nil, fmt.Errorf("no syntax here")
   245  	}
   246  	if needExact && !exact {
   247  		return nil, fmt.Errorf("ambiguous selection within %s", astutil.NodeDescription(path[0]))
   248  	}
   249  	return &queryPos{lprog.Fset, start, end, path, exact, info}, nil
   250  }
   251  
   252  // ---------- Utilities ----------
   253  
   254  // loadWithSoftErrors calls lconf.Load, suppressing "soft" errors.  (See Go issue 16530.)
   255  // TODO(adonovan): Once the loader has an option to allow soft errors,
   256  // replace calls to loadWithSoftErrors with loader calls with that parameter.
   257  func loadWithSoftErrors(lconf *loader.Config) (*loader.Program, error) {
   258  	lconf.AllowErrors = true
   259  
   260  	// Ideally we would just return conf.Load() here, but go/types
   261  	// reports certain "soft" errors that gc does not (Go issue 14596).
   262  	// As a workaround, we set AllowErrors=true and then duplicate
   263  	// the loader's error checking but allow soft errors.
   264  	// It would be nice if the loader API permitted "AllowErrors: soft".
   265  	prog, err := lconf.Load()
   266  	if err != nil {
   267  		return nil, err
   268  	}
   269  	var errpkgs []string
   270  	// Report hard errors in indirectly imported packages.
   271  	for _, info := range prog.AllPackages {
   272  		if containsHardErrors(info.Errors) {
   273  			errpkgs = append(errpkgs, info.Pkg.Path())
   274  		} else {
   275  			// Enable SSA construction for packages containing only soft errors.
   276  			info.TransitivelyErrorFree = true
   277  		}
   278  	}
   279  	if errpkgs != nil {
   280  		var more string
   281  		if len(errpkgs) > 3 {
   282  			more = fmt.Sprintf(" and %d more", len(errpkgs)-3)
   283  			errpkgs = errpkgs[:3]
   284  		}
   285  		return nil, fmt.Errorf("couldn't load packages due to errors: %s%s",
   286  			strings.Join(errpkgs, ", "), more)
   287  	}
   288  	return prog, err
   289  }
   290  
   291  func containsHardErrors(errors []error) bool {
   292  	for _, err := range errors {
   293  		if err, ok := err.(types.Error); ok && err.Soft {
   294  			continue
   295  		}
   296  		return true
   297  	}
   298  	return false
   299  }
   300  
   301  // allowErrors causes type errors to be silently ignored.
   302  // (Not suitable if SSA construction follows.)
   303  func allowErrors(lconf *loader.Config) {
   304  	ctxt := *lconf.Build // copy
   305  	ctxt.CgoEnabled = false
   306  	lconf.Build = &ctxt
   307  	lconf.AllowErrors = true
   308  	// AllErrors makes the parser always return an AST instead of
   309  	// bailing out after 10 errors and returning an empty ast.File.
   310  	lconf.ParserMode = parser.AllErrors
   311  	lconf.TypeChecker.Error = func(err error) {}
   312  }
   313  
   314  // ptrAnalysis runs the pointer analysis and returns its result.
   315  func ptrAnalysis(conf *pointer.Config) *pointer.Result {
   316  	result, err := pointer.Analyze(conf)
   317  	if err != nil {
   318  		panic(err) // pointer analysis internal error
   319  	}
   320  	return result
   321  }
   322  
   323  func unparen(e ast.Expr) ast.Expr { return astutil.Unparen(e) }
   324  
   325  // deref returns a pointer's element type; otherwise it returns typ.
   326  func deref(typ types.Type) types.Type {
   327  	if p, ok := typ.Underlying().(*types.Pointer); ok {
   328  		return p.Elem()
   329  	}
   330  	return typ
   331  }
   332  
   333  // fprintf prints to w a message of the form "location: message\n"
   334  // where location is derived from pos.
   335  //
   336  // pos must be one of:
   337  //    - a token.Pos, denoting a position
   338  //    - an ast.Node, denoting an interval
   339  //    - anything with a Pos() method:
   340  //         ssa.Member, ssa.Value, ssa.Instruction, types.Object, pointer.Label, etc.
   341  //    - a QueryPos, denoting the extent of the user's query.
   342  //    - nil, meaning no position at all.
   343  //
   344  // The output format is is compatible with the 'gnu'
   345  // compilation-error-regexp in Emacs' compilation mode.
   346  //
   347  func fprintf(w io.Writer, fset *token.FileSet, pos interface{}, format string, args ...interface{}) {
   348  	var start, end token.Pos
   349  	switch pos := pos.(type) {
   350  	case ast.Node:
   351  		start = pos.Pos()
   352  		end = pos.End()
   353  	case token.Pos:
   354  		start = pos
   355  		end = start
   356  	case *types.PkgName:
   357  		// The Pos of most PkgName objects does not coincide with an identifier,
   358  		// so we suppress the usual start+len(name) heuristic for types.Objects.
   359  		start = pos.Pos()
   360  		end = start
   361  	case types.Object:
   362  		start = pos.Pos()
   363  		end = start + token.Pos(len(pos.Name())) // heuristic
   364  	case interface {
   365  		Pos() token.Pos
   366  	}:
   367  		start = pos.Pos()
   368  		end = start
   369  	case *queryPos:
   370  		start = pos.start
   371  		end = pos.end
   372  	case nil:
   373  		// no-op
   374  	default:
   375  		panic(fmt.Sprintf("invalid pos: %T", pos))
   376  	}
   377  
   378  	if sp := fset.Position(start); start == end {
   379  		// (prints "-: " for token.NoPos)
   380  		fmt.Fprintf(w, "%s: ", sp)
   381  	} else {
   382  		ep := fset.Position(end)
   383  		// The -1 below is a concession to Emacs's broken use of
   384  		// inclusive (not half-open) intervals.
   385  		// Other editors may not want it.
   386  		// TODO(adonovan): add an -editor=vim|emacs|acme|auto
   387  		// flag; auto uses EMACS=t / VIM=... / etc env vars.
   388  		fmt.Fprintf(w, "%s:%d.%d-%d.%d: ",
   389  			sp.Filename, sp.Line, sp.Column, ep.Line, ep.Column-1)
   390  	}
   391  	fmt.Fprintf(w, format, args...)
   392  	io.WriteString(w, "\n")
   393  }
   394  
   395  func toJSON(x interface{}) []byte {
   396  	b, err := json.MarshalIndent(x, "", "\t")
   397  	if err != nil {
   398  		log.Fatalf("JSON error: %v", err)
   399  	}
   400  	return b
   401  }