github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/tools/godoc/analysis/analysis14.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 analysis performs type and pointer analysis
     8  // and generates mark-up for the Go source view.
     9  //
    10  // The Run method populates a Result object by running type and
    11  // (optionally) pointer analysis.  The Result object is thread-safe
    12  // and at all times may be accessed by a serving thread, even as it is
    13  // progressively populated as analysis facts are derived.
    14  //
    15  // The Result is a mapping from each godoc file URL
    16  // (e.g. /src/fmt/print.go) to information about that file.  The
    17  // information is a list of HTML markup links and a JSON array of
    18  // structured data values.  Some of the links call client-side
    19  // JavaScript functions that index this array.
    20  //
    21  // The analysis computes mark-up for the following relations:
    22  //
    23  // IMPORTS: for each ast.ImportSpec, the package that it denotes.
    24  //
    25  // RESOLUTION: for each ast.Ident, its kind and type, and the location
    26  // of its definition.
    27  //
    28  // METHOD SETS, IMPLEMENTS: for each ast.Ident defining a named type,
    29  // its method-set, the set of interfaces it implements or is
    30  // implemented by, and its size/align values.
    31  //
    32  // CALLERS, CALLEES: for each function declaration ('func' token), its
    33  // callers, and for each call-site ('(' token), its callees.
    34  //
    35  // CALLGRAPH: the package docs include an interactive viewer for the
    36  // intra-package call graph of "fmt".
    37  //
    38  // CHANNEL PEERS: for each channel operation make/<-/close, the set of
    39  // other channel ops that alias the same channel(s).
    40  //
    41  // ERRORS: for each locus of a frontend (scanner/parser/type) error, the
    42  // location is highlighted in red and hover text provides the compiler
    43  // error message.
    44  //
    45  package analysis // import "golang.org/x/tools/godoc/analysis"
    46  
    47  import (
    48  	"fmt"
    49  	"go/build"
    50  	"go/scanner"
    51  	"go/token"
    52  	"html"
    53  	"io"
    54  	"log"
    55  	"os"
    56  	"path/filepath"
    57  	"runtime"
    58  	"sort"
    59  	"strings"
    60  	"sync"
    61  
    62  	"golang.org/x/tools/go/exact"
    63  	"golang.org/x/tools/go/loader"
    64  	"golang.org/x/tools/go/pointer"
    65  	"golang.org/x/tools/go/ssa"
    66  	"golang.org/x/tools/go/ssa/ssautil"
    67  	"golang.org/x/tools/go/types"
    68  )
    69  
    70  // -- links ------------------------------------------------------------
    71  
    72  // A Link is an HTML decoration of the bytes [Start, End) of a file.
    73  // Write is called before/after those bytes to emit the mark-up.
    74  type Link interface {
    75  	Start() int
    76  	End() int
    77  	Write(w io.Writer, _ int, start bool) // the godoc.LinkWriter signature
    78  }
    79  
    80  // An <a> element.
    81  type aLink struct {
    82  	start, end int    // =godoc.Segment
    83  	title      string // hover text
    84  	onclick    string // JS code (NB: trusted)
    85  	href       string // URL     (NB: trusted)
    86  }
    87  
    88  func (a aLink) Start() int { return a.start }
    89  func (a aLink) End() int   { return a.end }
    90  func (a aLink) Write(w io.Writer, _ int, start bool) {
    91  	if start {
    92  		fmt.Fprintf(w, `<a title='%s'`, html.EscapeString(a.title))
    93  		if a.onclick != "" {
    94  			fmt.Fprintf(w, ` onclick='%s'`, html.EscapeString(a.onclick))
    95  		}
    96  		if a.href != "" {
    97  			// TODO(adonovan): I think that in principle, a.href must first be
    98  			// url.QueryEscape'd, but if I do that, a leading slash becomes "%2F",
    99  			// which causes the browser to treat the path as relative, not absolute.
   100  			// WTF?
   101  			fmt.Fprintf(w, ` href='%s'`, html.EscapeString(a.href))
   102  		}
   103  		fmt.Fprintf(w, ">")
   104  	} else {
   105  		fmt.Fprintf(w, "</a>")
   106  	}
   107  }
   108  
   109  // An <a class='error'> element.
   110  type errorLink struct {
   111  	start int
   112  	msg   string
   113  }
   114  
   115  func (e errorLink) Start() int { return e.start }
   116  func (e errorLink) End() int   { return e.start + 1 }
   117  
   118  func (e errorLink) Write(w io.Writer, _ int, start bool) {
   119  	// <span> causes havoc, not sure why, so use <a>.
   120  	if start {
   121  		fmt.Fprintf(w, `<a class='error' title='%s'>`, html.EscapeString(e.msg))
   122  	} else {
   123  		fmt.Fprintf(w, "</a>")
   124  	}
   125  }
   126  
   127  // -- fileInfo ---------------------------------------------------------
   128  
   129  // FileInfo holds analysis information for the source file view.
   130  // Clients must not mutate it.
   131  type FileInfo struct {
   132  	Data  []interface{} // JSON serializable values
   133  	Links []Link        // HTML link markup
   134  }
   135  
   136  // A fileInfo is the server's store of hyperlinks and JSON data for a
   137  // particular file.
   138  type fileInfo struct {
   139  	mu        sync.Mutex
   140  	data      []interface{} // JSON objects
   141  	links     []Link
   142  	sorted    bool
   143  	hasErrors bool // TODO(adonovan): surface this in the UI
   144  }
   145  
   146  // addLink adds a link to the Go source file fi.
   147  func (fi *fileInfo) addLink(link Link) {
   148  	fi.mu.Lock()
   149  	fi.links = append(fi.links, link)
   150  	fi.sorted = false
   151  	if _, ok := link.(errorLink); ok {
   152  		fi.hasErrors = true
   153  	}
   154  	fi.mu.Unlock()
   155  }
   156  
   157  // addData adds the structured value x to the JSON data for the Go
   158  // source file fi.  Its index is returned.
   159  func (fi *fileInfo) addData(x interface{}) int {
   160  	fi.mu.Lock()
   161  	index := len(fi.data)
   162  	fi.data = append(fi.data, x)
   163  	fi.mu.Unlock()
   164  	return index
   165  }
   166  
   167  // get returns the file info in external form.
   168  // Callers must not mutate its fields.
   169  func (fi *fileInfo) get() FileInfo {
   170  	var r FileInfo
   171  	// Copy slices, to avoid races.
   172  	fi.mu.Lock()
   173  	r.Data = append(r.Data, fi.data...)
   174  	if !fi.sorted {
   175  		sort.Sort(linksByStart(fi.links))
   176  		fi.sorted = true
   177  	}
   178  	r.Links = append(r.Links, fi.links...)
   179  	fi.mu.Unlock()
   180  	return r
   181  }
   182  
   183  // PackageInfo holds analysis information for the package view.
   184  // Clients must not mutate it.
   185  type PackageInfo struct {
   186  	CallGraph      []*PCGNodeJSON
   187  	CallGraphIndex map[string]int
   188  	Types          []*TypeInfoJSON
   189  }
   190  
   191  type pkgInfo struct {
   192  	mu             sync.Mutex
   193  	callGraph      []*PCGNodeJSON
   194  	callGraphIndex map[string]int  // keys are (*ssa.Function).RelString()
   195  	types          []*TypeInfoJSON // type info for exported types
   196  }
   197  
   198  func (pi *pkgInfo) setCallGraph(callGraph []*PCGNodeJSON, callGraphIndex map[string]int) {
   199  	pi.mu.Lock()
   200  	pi.callGraph = callGraph
   201  	pi.callGraphIndex = callGraphIndex
   202  	pi.mu.Unlock()
   203  }
   204  
   205  func (pi *pkgInfo) addType(t *TypeInfoJSON) {
   206  	pi.mu.Lock()
   207  	pi.types = append(pi.types, t)
   208  	pi.mu.Unlock()
   209  }
   210  
   211  // get returns the package info in external form.
   212  // Callers must not mutate its fields.
   213  func (pi *pkgInfo) get() PackageInfo {
   214  	var r PackageInfo
   215  	// Copy slices, to avoid races.
   216  	pi.mu.Lock()
   217  	r.CallGraph = append(r.CallGraph, pi.callGraph...)
   218  	r.CallGraphIndex = pi.callGraphIndex
   219  	r.Types = append(r.Types, pi.types...)
   220  	pi.mu.Unlock()
   221  	return r
   222  }
   223  
   224  // -- Result -----------------------------------------------------------
   225  
   226  // Result contains the results of analysis.
   227  // The result contains a mapping from filenames to a set of HTML links
   228  // and JavaScript data referenced by the links.
   229  type Result struct {
   230  	mu        sync.Mutex           // guards maps (but not their contents)
   231  	status    string               // global analysis status
   232  	fileInfos map[string]*fileInfo // keys are godoc file URLs
   233  	pkgInfos  map[string]*pkgInfo  // keys are import paths
   234  }
   235  
   236  // fileInfo returns the fileInfo for the specified godoc file URL,
   237  // constructing it as needed.  Thread-safe.
   238  func (res *Result) fileInfo(url string) *fileInfo {
   239  	res.mu.Lock()
   240  	fi, ok := res.fileInfos[url]
   241  	if !ok {
   242  		if res.fileInfos == nil {
   243  			res.fileInfos = make(map[string]*fileInfo)
   244  		}
   245  		fi = new(fileInfo)
   246  		res.fileInfos[url] = fi
   247  	}
   248  	res.mu.Unlock()
   249  	return fi
   250  }
   251  
   252  // Status returns a human-readable description of the current analysis status.
   253  func (res *Result) Status() string {
   254  	res.mu.Lock()
   255  	defer res.mu.Unlock()
   256  	return res.status
   257  }
   258  
   259  func (res *Result) setStatusf(format string, args ...interface{}) {
   260  	res.mu.Lock()
   261  	res.status = fmt.Sprintf(format, args...)
   262  	log.Printf(format, args...)
   263  	res.mu.Unlock()
   264  }
   265  
   266  // FileInfo returns new slices containing opaque JSON values and the
   267  // HTML link markup for the specified godoc file URL.  Thread-safe.
   268  // Callers must not mutate the elements.
   269  // It returns "zero" if no data is available.
   270  //
   271  func (res *Result) FileInfo(url string) (fi FileInfo) {
   272  	return res.fileInfo(url).get()
   273  }
   274  
   275  // pkgInfo returns the pkgInfo for the specified import path,
   276  // constructing it as needed.  Thread-safe.
   277  func (res *Result) pkgInfo(importPath string) *pkgInfo {
   278  	res.mu.Lock()
   279  	pi, ok := res.pkgInfos[importPath]
   280  	if !ok {
   281  		if res.pkgInfos == nil {
   282  			res.pkgInfos = make(map[string]*pkgInfo)
   283  		}
   284  		pi = new(pkgInfo)
   285  		res.pkgInfos[importPath] = pi
   286  	}
   287  	res.mu.Unlock()
   288  	return pi
   289  }
   290  
   291  // PackageInfo returns new slices of JSON values for the callgraph and
   292  // type info for the specified package.  Thread-safe.
   293  // Callers must not mutate its fields.
   294  // PackageInfo returns "zero" if no data is available.
   295  //
   296  func (res *Result) PackageInfo(importPath string) PackageInfo {
   297  	return res.pkgInfo(importPath).get()
   298  }
   299  
   300  // -- analysis ---------------------------------------------------------
   301  
   302  type analysis struct {
   303  	result    *Result
   304  	prog      *ssa.Program
   305  	ops       []chanOp       // all channel ops in program
   306  	allNamed  []*types.Named // all named types in the program
   307  	ptaConfig pointer.Config
   308  	path2url  map[string]string // maps openable path to godoc file URL (/src/fmt/print.go)
   309  	pcgs      map[*ssa.Package]*packageCallGraph
   310  }
   311  
   312  // fileAndOffset returns the file and offset for a given pos.
   313  func (a *analysis) fileAndOffset(pos token.Pos) (fi *fileInfo, offset int) {
   314  	return a.fileAndOffsetPosn(a.prog.Fset.Position(pos))
   315  }
   316  
   317  // fileAndOffsetPosn returns the file and offset for a given position.
   318  func (a *analysis) fileAndOffsetPosn(posn token.Position) (fi *fileInfo, offset int) {
   319  	url := a.path2url[posn.Filename]
   320  	return a.result.fileInfo(url), posn.Offset
   321  }
   322  
   323  // posURL returns the URL of the source extent [pos, pos+len).
   324  func (a *analysis) posURL(pos token.Pos, len int) string {
   325  	if pos == token.NoPos {
   326  		return ""
   327  	}
   328  	posn := a.prog.Fset.Position(pos)
   329  	url := a.path2url[posn.Filename]
   330  	return fmt.Sprintf("%s?s=%d:%d#L%d",
   331  		url, posn.Offset, posn.Offset+len, posn.Line)
   332  }
   333  
   334  // ----------------------------------------------------------------------
   335  
   336  // Run runs program analysis and computes the resulting markup,
   337  // populating *result in a thread-safe manner, first with type
   338  // information then later with pointer analysis information if
   339  // enabled by the pta flag.
   340  //
   341  func Run(pta bool, result *Result) {
   342  	conf := loader.Config{
   343  		AllowErrors: true,
   344  	}
   345  
   346  	// Silence the default error handler.
   347  	// Don't print all errors; we'll report just
   348  	// one per errant package later.
   349  	conf.TypeChecker.Error = func(e error) {}
   350  
   351  	var roots, args []string // roots[i] ends with os.PathSeparator
   352  
   353  	// Enumerate packages in $GOROOT.
   354  	root := filepath.Join(runtime.GOROOT(), "src") + string(os.PathSeparator)
   355  	roots = append(roots, root)
   356  	args = allPackages(root)
   357  	log.Printf("GOROOT=%s: %s\n", root, args)
   358  
   359  	// Enumerate packages in $GOPATH.
   360  	for i, dir := range filepath.SplitList(build.Default.GOPATH) {
   361  		root := filepath.Join(dir, "src") + string(os.PathSeparator)
   362  		roots = append(roots, root)
   363  		pkgs := allPackages(root)
   364  		log.Printf("GOPATH[%d]=%s: %s\n", i, root, pkgs)
   365  		args = append(args, pkgs...)
   366  	}
   367  
   368  	// Uncomment to make startup quicker during debugging.
   369  	//args = []string{"golang.org/x/tools/cmd/godoc"}
   370  	//args = []string{"fmt"}
   371  
   372  	if _, err := conf.FromArgs(args, true); err != nil {
   373  		// TODO(adonovan): degrade gracefully, not fail totally.
   374  		// (The crippling case is a parse error in an external test file.)
   375  		result.setStatusf("Analysis failed: %s.", err) // import error
   376  		return
   377  	}
   378  
   379  	result.setStatusf("Loading and type-checking packages...")
   380  	iprog, err := conf.Load()
   381  	if iprog != nil {
   382  		// Report only the first error of each package.
   383  		for _, info := range iprog.AllPackages {
   384  			for _, err := range info.Errors {
   385  				fmt.Fprintln(os.Stderr, err)
   386  				break
   387  			}
   388  		}
   389  		log.Printf("Loaded %d packages.", len(iprog.AllPackages))
   390  	}
   391  	if err != nil {
   392  		result.setStatusf("Loading failed: %s.\n", err)
   393  		return
   394  	}
   395  
   396  	// Create SSA-form program representation.
   397  	// Only the transitively error-free packages are used.
   398  	prog := ssautil.CreateProgram(iprog, ssa.GlobalDebug)
   399  
   400  	// Compute the set of main packages, including testmain.
   401  	allPackages := prog.AllPackages()
   402  	var mainPkgs []*ssa.Package
   403  	if testmain := prog.CreateTestMainPackage(allPackages...); testmain != nil {
   404  		mainPkgs = append(mainPkgs, testmain)
   405  		if p := testmain.Const("packages"); p != nil {
   406  			log.Printf("Tested packages: %v", exact.StringVal(p.Value.Value))
   407  		}
   408  	}
   409  	for _, pkg := range allPackages {
   410  		if pkg.Pkg.Name() == "main" && pkg.Func("main") != nil {
   411  			mainPkgs = append(mainPkgs, pkg)
   412  		}
   413  	}
   414  	log.Print("Transitively error-free main packages: ", mainPkgs)
   415  
   416  	// Build SSA code for bodies of all functions in the whole program.
   417  	result.setStatusf("Constructing SSA form...")
   418  	prog.Build()
   419  	log.Print("SSA construction complete")
   420  
   421  	a := analysis{
   422  		result: result,
   423  		prog:   prog,
   424  		pcgs:   make(map[*ssa.Package]*packageCallGraph),
   425  	}
   426  
   427  	// Build a mapping from openable filenames to godoc file URLs,
   428  	// i.e. "/src/" plus path relative to GOROOT/src or GOPATH[i]/src.
   429  	a.path2url = make(map[string]string)
   430  	for _, info := range iprog.AllPackages {
   431  	nextfile:
   432  		for _, f := range info.Files {
   433  			if f.Pos() == 0 {
   434  				continue // e.g. files generated by cgo
   435  			}
   436  			abs := iprog.Fset.File(f.Pos()).Name()
   437  			// Find the root to which this file belongs.
   438  			for _, root := range roots {
   439  				rel := strings.TrimPrefix(abs, root)
   440  				if len(rel) < len(abs) {
   441  					a.path2url[abs] = "/src/" + filepath.ToSlash(rel)
   442  					continue nextfile
   443  				}
   444  			}
   445  
   446  			log.Printf("Can't locate file %s (package %q) beneath any root",
   447  				abs, info.Pkg.Path())
   448  		}
   449  	}
   450  
   451  	// Add links for scanner, parser, type-checker errors.
   452  	// TODO(adonovan): fix: these links can overlap with
   453  	// identifier markup, causing the renderer to emit some
   454  	// characters twice.
   455  	errors := make(map[token.Position][]string)
   456  	for _, info := range iprog.AllPackages {
   457  		for _, err := range info.Errors {
   458  			switch err := err.(type) {
   459  			case types.Error:
   460  				posn := a.prog.Fset.Position(err.Pos)
   461  				errors[posn] = append(errors[posn], err.Msg)
   462  			case scanner.ErrorList:
   463  				for _, e := range err {
   464  					errors[e.Pos] = append(errors[e.Pos], e.Msg)
   465  				}
   466  			default:
   467  				log.Printf("Package %q has error (%T) without position: %v\n",
   468  					info.Pkg.Path(), err, err)
   469  			}
   470  		}
   471  	}
   472  	for posn, errs := range errors {
   473  		fi, offset := a.fileAndOffsetPosn(posn)
   474  		fi.addLink(errorLink{
   475  			start: offset,
   476  			msg:   strings.Join(errs, "\n"),
   477  		})
   478  	}
   479  
   480  	// ---------- type-based analyses ----------
   481  
   482  	// Compute the all-pairs IMPLEMENTS relation.
   483  	// Collect all named types, even local types
   484  	// (which can have methods via promotion)
   485  	// and the built-in "error".
   486  	errorType := types.Universe.Lookup("error").Type().(*types.Named)
   487  	a.allNamed = append(a.allNamed, errorType)
   488  	for _, info := range iprog.AllPackages {
   489  		for _, obj := range info.Defs {
   490  			if obj, ok := obj.(*types.TypeName); ok {
   491  				a.allNamed = append(a.allNamed, obj.Type().(*types.Named))
   492  			}
   493  		}
   494  	}
   495  	log.Print("Computing implements relation...")
   496  	facts := computeImplements(&a.prog.MethodSets, a.allNamed)
   497  
   498  	// Add the type-based analysis results.
   499  	log.Print("Extracting type info...")
   500  	for _, info := range iprog.AllPackages {
   501  		a.doTypeInfo(info, facts)
   502  	}
   503  
   504  	a.visitInstrs(pta)
   505  
   506  	result.setStatusf("Type analysis complete.")
   507  
   508  	if pta {
   509  		a.pointer(mainPkgs)
   510  	}
   511  }
   512  
   513  // visitInstrs visits all SSA instructions in the program.
   514  func (a *analysis) visitInstrs(pta bool) {
   515  	log.Print("Visit instructions...")
   516  	for fn := range ssautil.AllFunctions(a.prog) {
   517  		for _, b := range fn.Blocks {
   518  			for _, instr := range b.Instrs {
   519  				// CALLEES (static)
   520  				// (Dynamic calls require pointer analysis.)
   521  				//
   522  				// We use the SSA representation to find the static callee,
   523  				// since in many cases it does better than the
   524  				// types.Info.{Refs,Selection} information.  For example:
   525  				//
   526  				//   defer func(){}()      // static call to anon function
   527  				//   f := func(){}; f()    // static call to anon function
   528  				//   f := fmt.Println; f() // static call to named function
   529  				//
   530  				// The downside is that we get no static callee information
   531  				// for packages that (transitively) contain errors.
   532  				if site, ok := instr.(ssa.CallInstruction); ok {
   533  					if callee := site.Common().StaticCallee(); callee != nil {
   534  						// TODO(adonovan): callgraph: elide wrappers.
   535  						// (Do static calls ever go to wrappers?)
   536  						if site.Common().Pos() != token.NoPos {
   537  							a.addCallees(site, []*ssa.Function{callee})
   538  						}
   539  					}
   540  				}
   541  
   542  				if !pta {
   543  					continue
   544  				}
   545  
   546  				// CHANNEL PEERS
   547  				// Collect send/receive/close instructions in the whole ssa.Program.
   548  				for _, op := range chanOps(instr) {
   549  					a.ops = append(a.ops, op)
   550  					a.ptaConfig.AddQuery(op.ch) // add channel ssa.Value to PTA query
   551  				}
   552  			}
   553  		}
   554  	}
   555  	log.Print("Visit instructions complete")
   556  }
   557  
   558  // pointer runs the pointer analysis.
   559  func (a *analysis) pointer(mainPkgs []*ssa.Package) {
   560  	// Run the pointer analysis and build the complete callgraph.
   561  	a.ptaConfig.Mains = mainPkgs
   562  	a.ptaConfig.BuildCallGraph = true
   563  	a.ptaConfig.Reflection = false // (for now)
   564  
   565  	a.result.setStatusf("Pointer analysis running...")
   566  
   567  	ptares, err := pointer.Analyze(&a.ptaConfig)
   568  	if err != nil {
   569  		// If this happens, it indicates a bug.
   570  		a.result.setStatusf("Pointer analysis failed: %s.", err)
   571  		return
   572  	}
   573  	log.Print("Pointer analysis complete.")
   574  
   575  	// Add the results of pointer analysis.
   576  
   577  	a.result.setStatusf("Computing channel peers...")
   578  	a.doChannelPeers(ptares.Queries)
   579  	a.result.setStatusf("Computing dynamic call graph edges...")
   580  	a.doCallgraph(ptares.CallGraph)
   581  
   582  	a.result.setStatusf("Analysis complete.")
   583  }
   584  
   585  type linksByStart []Link
   586  
   587  func (a linksByStart) Less(i, j int) bool { return a[i].Start() < a[j].Start() }
   588  func (a linksByStart) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
   589  func (a linksByStart) Len() int           { return len(a) }
   590  
   591  // allPackages returns a new sorted slice of all packages beneath the
   592  // specified package root directory, e.g. $GOROOT/src or $GOPATH/src.
   593  // Derived from from go/ssa/stdlib_test.go
   594  // root must end with os.PathSeparator.
   595  //
   596  // TODO(adonovan): use buildutil.AllPackages when the tree thaws.
   597  func allPackages(root string) []string {
   598  	var pkgs []string
   599  	filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
   600  		if info == nil {
   601  			return nil // non-existent root directory?
   602  		}
   603  		if !info.IsDir() {
   604  			return nil // not a directory
   605  		}
   606  		// Prune the search if we encounter any of these names:
   607  		base := filepath.Base(path)
   608  		if base == "testdata" || strings.HasPrefix(base, ".") {
   609  			return filepath.SkipDir
   610  		}
   611  		pkg := filepath.ToSlash(strings.TrimPrefix(path, root))
   612  		switch pkg {
   613  		case "builtin":
   614  			return filepath.SkipDir
   615  		case "":
   616  			return nil // ignore root of tree
   617  		}
   618  		pkgs = append(pkgs, pkg)
   619  		return nil
   620  	})
   621  	return pkgs
   622  }