github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/tools/oracle/oracle.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  // +build go1.5
     6  
     7  // Package oracle contains the implementation of the oracle tool whose
     8  // command-line is provided by golang.org/x/tools/cmd/oracle.
     9  //
    10  // http://golang.org/s/oracle-design
    11  // http://golang.org/s/oracle-user-manual
    12  //
    13  package oracle // import "golang.org/x/tools/oracle"
    14  
    15  // This file defines oracle.Query, the entry point for the oracle tool.
    16  // The actual executable is defined in cmd/oracle.
    17  
    18  // TODO(adonovan): new queries
    19  // - show all statements that may update the selected lvalue
    20  //   (local, global, field, etc).
    21  // - show all places where an object of type T is created
    22  //   (&T{}, var t T, new(T), new(struct{array [3]T}), etc.
    23  
    24  import (
    25  	"fmt"
    26  	"go/ast"
    27  	"go/build"
    28  	"go/parser"
    29  	"go/token"
    30  	"go/types"
    31  	"io"
    32  	"path/filepath"
    33  
    34  	"golang.org/x/tools/go/ast/astutil"
    35  	"golang.org/x/tools/go/loader"
    36  	"golang.org/x/tools/go/pointer"
    37  	"golang.org/x/tools/go/ssa"
    38  	"golang.org/x/tools/oracle/serial"
    39  )
    40  
    41  type printfFunc func(pos interface{}, format string, args ...interface{})
    42  
    43  // queryResult is the interface of each query-specific result type.
    44  type queryResult interface {
    45  	toSerial(res *serial.Result, fset *token.FileSet)
    46  	display(printf printfFunc)
    47  }
    48  
    49  // A QueryPos represents the position provided as input to a query:
    50  // a textual extent in the program's source code, the AST node it
    51  // corresponds to, and the package to which it belongs.
    52  // Instances are created by parseQueryPos.
    53  type queryPos struct {
    54  	fset       *token.FileSet
    55  	start, end token.Pos           // source extent of query
    56  	path       []ast.Node          // AST path from query node to root of ast.File
    57  	exact      bool                // 2nd result of PathEnclosingInterval
    58  	info       *loader.PackageInfo // type info for the queried package (nil for fastQueryPos)
    59  }
    60  
    61  // TypeString prints type T relative to the query position.
    62  func (qpos *queryPos) typeString(T types.Type) string {
    63  	return types.TypeString(T, types.RelativeTo(qpos.info.Pkg))
    64  }
    65  
    66  // ObjectString prints object obj relative to the query position.
    67  func (qpos *queryPos) objectString(obj types.Object) string {
    68  	return types.ObjectString(obj, types.RelativeTo(qpos.info.Pkg))
    69  }
    70  
    71  // SelectionString prints selection sel relative to the query position.
    72  func (qpos *queryPos) selectionString(sel *types.Selection) string {
    73  	return types.SelectionString(sel, types.RelativeTo(qpos.info.Pkg))
    74  }
    75  
    76  // A Query specifies a single oracle query.
    77  type Query struct {
    78  	Mode  string         // query mode ("callers", etc)
    79  	Pos   string         // query position
    80  	Build *build.Context // package loading configuration
    81  
    82  	// pointer analysis options
    83  	Scope      []string  // main packages in (*loader.Config).FromArgs syntax
    84  	PTALog     io.Writer // (optional) pointer-analysis log file
    85  	Reflection bool      // model reflection soundly (currently slow).
    86  
    87  	// Populated during Run()
    88  	Fset   *token.FileSet
    89  	result queryResult
    90  }
    91  
    92  // Serial returns an instance of serial.Result, which implements the
    93  // {xml,json}.Marshaler interfaces so that query results can be
    94  // serialized as JSON or XML.
    95  //
    96  func (q *Query) Serial() *serial.Result {
    97  	resj := &serial.Result{Mode: q.Mode}
    98  	q.result.toSerial(resj, q.Fset)
    99  	return resj
   100  }
   101  
   102  // WriteTo writes the oracle query result res to out in a compiler diagnostic format.
   103  func (q *Query) WriteTo(out io.Writer) {
   104  	printf := func(pos interface{}, format string, args ...interface{}) {
   105  		fprintf(out, q.Fset, pos, format, args...)
   106  	}
   107  	q.result.display(printf)
   108  }
   109  
   110  // Run runs an oracle query and populates its Fset and Result.
   111  func Run(q *Query) error {
   112  	switch q.Mode {
   113  	case "callees":
   114  		return callees(q)
   115  	case "callers":
   116  		return callers(q)
   117  	case "callstack":
   118  		return callstack(q)
   119  	case "peers":
   120  		return peers(q)
   121  	case "pointsto":
   122  		return pointsto(q)
   123  	case "whicherrs":
   124  		return whicherrs(q)
   125  	case "definition":
   126  		return definition(q)
   127  	case "describe":
   128  		return describe(q)
   129  	case "freevars":
   130  		return freevars(q)
   131  	case "implements":
   132  		return implements(q)
   133  	case "referrers":
   134  		return referrers(q)
   135  	case "what":
   136  		return what(q)
   137  	default:
   138  		return fmt.Errorf("invalid mode: %q", q.Mode)
   139  	}
   140  }
   141  
   142  func setPTAScope(lconf *loader.Config, scope []string) error {
   143  	if len(scope) == 0 {
   144  		return fmt.Errorf("no packages specified for pointer analysis scope")
   145  	}
   146  
   147  	// Determine initial packages for PTA.
   148  	args, err := lconf.FromArgs(scope, true)
   149  	if err != nil {
   150  		return err
   151  	}
   152  	if len(args) > 0 {
   153  		return fmt.Errorf("surplus arguments: %q", args)
   154  	}
   155  	return nil
   156  }
   157  
   158  // Create a pointer.Config whose scope is the initial packages of lprog
   159  // and their dependencies.
   160  func setupPTA(prog *ssa.Program, lprog *loader.Program, ptaLog io.Writer, reflection bool) (*pointer.Config, error) {
   161  	// TODO(adonovan): the body of this function is essentially
   162  	// duplicated in all go/pointer clients.  Refactor.
   163  
   164  	// For each initial package (specified on the command line),
   165  	// if it has a main function, analyze that,
   166  	// otherwise analyze its tests, if any.
   167  	var testPkgs, mains []*ssa.Package
   168  	for _, info := range lprog.InitialPackages() {
   169  		initialPkg := prog.Package(info.Pkg)
   170  
   171  		// Add package to the pointer analysis scope.
   172  		if initialPkg.Func("main") != nil {
   173  			mains = append(mains, initialPkg)
   174  		} else {
   175  			testPkgs = append(testPkgs, initialPkg)
   176  		}
   177  	}
   178  	if testPkgs != nil {
   179  		if p := prog.CreateTestMainPackage(testPkgs...); p != nil {
   180  			mains = append(mains, p)
   181  		}
   182  	}
   183  	if mains == nil {
   184  		return nil, fmt.Errorf("analysis scope has no main and no tests")
   185  	}
   186  	return &pointer.Config{
   187  		Log:        ptaLog,
   188  		Reflection: reflection,
   189  		Mains:      mains,
   190  	}, nil
   191  }
   192  
   193  // importQueryPackage finds the package P containing the
   194  // query position and tells conf to import it.
   195  // It returns the package's path.
   196  func importQueryPackage(pos string, conf *loader.Config) (string, error) {
   197  	fqpos, err := fastQueryPos(pos)
   198  	if err != nil {
   199  		return "", err // bad query
   200  	}
   201  	filename := fqpos.fset.File(fqpos.start).Name()
   202  
   203  	// This will not work for ad-hoc packages
   204  	// such as $GOROOT/src/net/http/triv.go.
   205  	// TODO(adonovan): ensure we report a clear error.
   206  	_, importPath, err := guessImportPath(filename, conf.Build)
   207  	if err != nil {
   208  		return "", err // can't find GOPATH dir
   209  	}
   210  	if importPath == "" {
   211  		return "", fmt.Errorf("can't guess import path from %s", filename)
   212  	}
   213  
   214  	// Check that it's possible to load the queried package.
   215  	// (e.g. oracle tests contain different 'package' decls in same dir.)
   216  	// Keep consistent with logic in loader/util.go!
   217  	cfg2 := *conf.Build
   218  	cfg2.CgoEnabled = false
   219  	bp, err := cfg2.Import(importPath, "", 0)
   220  	if err != nil {
   221  		return "", err // no files for package
   222  	}
   223  
   224  	switch pkgContainsFile(bp, filename) {
   225  	case 'T':
   226  		conf.ImportWithTests(importPath)
   227  	case 'X':
   228  		conf.ImportWithTests(importPath)
   229  		importPath += "_test" // for TypeCheckFuncBodies
   230  	case 'G':
   231  		conf.Import(importPath)
   232  	default:
   233  		return "", fmt.Errorf("package %q doesn't contain file %s",
   234  			importPath, filename)
   235  	}
   236  
   237  	conf.TypeCheckFuncBodies = func(p string) bool { return p == importPath }
   238  
   239  	return importPath, nil
   240  }
   241  
   242  // pkgContainsFile reports whether file was among the packages Go
   243  // files, Test files, eXternal test files, or not found.
   244  func pkgContainsFile(bp *build.Package, filename string) byte {
   245  	for i, files := range [][]string{bp.GoFiles, bp.TestGoFiles, bp.XTestGoFiles} {
   246  		for _, file := range files {
   247  			if sameFile(filepath.Join(bp.Dir, file), filename) {
   248  				return "GTX"[i]
   249  			}
   250  		}
   251  	}
   252  	return 0 // not found
   253  }
   254  
   255  // ParseQueryPos parses the source query position pos and returns the
   256  // AST node of the loaded program lprog that it identifies.
   257  // If needExact, it must identify a single AST subtree;
   258  // this is appropriate for queries that allow fairly arbitrary syntax,
   259  // e.g. "describe".
   260  //
   261  func parseQueryPos(lprog *loader.Program, posFlag string, needExact bool) (*queryPos, error) {
   262  	filename, startOffset, endOffset, err := parsePosFlag(posFlag)
   263  	if err != nil {
   264  		return nil, err
   265  	}
   266  	start, end, err := findQueryPos(lprog.Fset, filename, startOffset, endOffset)
   267  	if err != nil {
   268  		return nil, err
   269  	}
   270  	info, path, exact := lprog.PathEnclosingInterval(start, end)
   271  	if path == nil {
   272  		return nil, fmt.Errorf("no syntax here")
   273  	}
   274  	if needExact && !exact {
   275  		return nil, fmt.Errorf("ambiguous selection within %s", astutil.NodeDescription(path[0]))
   276  	}
   277  	return &queryPos{lprog.Fset, start, end, path, exact, info}, nil
   278  }
   279  
   280  // ---------- Utilities ----------
   281  
   282  // allowErrors causes type errors to be silently ignored.
   283  // (Not suitable if SSA construction follows.)
   284  func allowErrors(lconf *loader.Config) {
   285  	ctxt := *lconf.Build // copy
   286  	ctxt.CgoEnabled = false
   287  	lconf.Build = &ctxt
   288  	lconf.AllowErrors = true
   289  	// AllErrors makes the parser always return an AST instead of
   290  	// bailing out after 10 errors and returning an empty ast.File.
   291  	lconf.ParserMode = parser.AllErrors
   292  	lconf.TypeChecker.Error = func(err error) {}
   293  }
   294  
   295  // ptrAnalysis runs the pointer analysis and returns its result.
   296  func ptrAnalysis(conf *pointer.Config) *pointer.Result {
   297  	result, err := pointer.Analyze(conf)
   298  	if err != nil {
   299  		panic(err) // pointer analysis internal error
   300  	}
   301  	return result
   302  }
   303  
   304  func unparen(e ast.Expr) ast.Expr { return astutil.Unparen(e) }
   305  
   306  // deref returns a pointer's element type; otherwise it returns typ.
   307  func deref(typ types.Type) types.Type {
   308  	if p, ok := typ.Underlying().(*types.Pointer); ok {
   309  		return p.Elem()
   310  	}
   311  	return typ
   312  }
   313  
   314  // fprintf prints to w a message of the form "location: message\n"
   315  // where location is derived from pos.
   316  //
   317  // pos must be one of:
   318  //    - a token.Pos, denoting a position
   319  //    - an ast.Node, denoting an interval
   320  //    - anything with a Pos() method:
   321  //         ssa.Member, ssa.Value, ssa.Instruction, types.Object, pointer.Label, etc.
   322  //    - a QueryPos, denoting the extent of the user's query.
   323  //    - nil, meaning no position at all.
   324  //
   325  // The output format is is compatible with the 'gnu'
   326  // compilation-error-regexp in Emacs' compilation mode.
   327  // TODO(adonovan): support other editors.
   328  //
   329  func fprintf(w io.Writer, fset *token.FileSet, pos interface{}, format string, args ...interface{}) {
   330  	var start, end token.Pos
   331  	switch pos := pos.(type) {
   332  	case ast.Node:
   333  		start = pos.Pos()
   334  		end = pos.End()
   335  	case token.Pos:
   336  		start = pos
   337  		end = start
   338  	case interface {
   339  		Pos() token.Pos
   340  	}:
   341  		start = pos.Pos()
   342  		end = start
   343  	case *queryPos:
   344  		start = pos.start
   345  		end = pos.end
   346  	case nil:
   347  		// no-op
   348  	default:
   349  		panic(fmt.Sprintf("invalid pos: %T", pos))
   350  	}
   351  
   352  	if sp := fset.Position(start); start == end {
   353  		// (prints "-: " for token.NoPos)
   354  		fmt.Fprintf(w, "%s: ", sp)
   355  	} else {
   356  		ep := fset.Position(end)
   357  		// The -1 below is a concession to Emacs's broken use of
   358  		// inclusive (not half-open) intervals.
   359  		// Other editors may not want it.
   360  		// TODO(adonovan): add an -editor=vim|emacs|acme|auto
   361  		// flag; auto uses EMACS=t / VIM=... / etc env vars.
   362  		fmt.Fprintf(w, "%s:%d.%d-%d.%d: ",
   363  			sp.Filename, sp.Line, sp.Column, ep.Line, ep.Column-1)
   364  	}
   365  	fmt.Fprintf(w, format, args...)
   366  	io.WriteString(w, "\n")
   367  }