614 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			614 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Go
		
	
	
	
// Copyright 2014 The Go Authors. All rights reserved.
 | 
						|
// Use of this source code is governed by a BSD-style
 | 
						|
// license that can be found in the LICENSE file.
 | 
						|
 | 
						|
// Package analysis performs type and pointer analysis
 | 
						|
// and generates mark-up for the Go source view.
 | 
						|
//
 | 
						|
// The Run method populates a Result object by running type and
 | 
						|
// (optionally) pointer analysis.  The Result object is thread-safe
 | 
						|
// and at all times may be accessed by a serving thread, even as it is
 | 
						|
// progressively populated as analysis facts are derived.
 | 
						|
//
 | 
						|
// The Result is a mapping from each godoc file URL
 | 
						|
// (e.g. /src/fmt/print.go) to information about that file.  The
 | 
						|
// information is a list of HTML markup links and a JSON array of
 | 
						|
// structured data values.  Some of the links call client-side
 | 
						|
// JavaScript functions that index this array.
 | 
						|
//
 | 
						|
// The analysis computes mark-up for the following relations:
 | 
						|
//
 | 
						|
// IMPORTS: for each ast.ImportSpec, the package that it denotes.
 | 
						|
//
 | 
						|
// RESOLUTION: for each ast.Ident, its kind and type, and the location
 | 
						|
// of its definition.
 | 
						|
//
 | 
						|
// METHOD SETS, IMPLEMENTS: for each ast.Ident defining a named type,
 | 
						|
// its method-set, the set of interfaces it implements or is
 | 
						|
// implemented by, and its size/align values.
 | 
						|
//
 | 
						|
// CALLERS, CALLEES: for each function declaration ('func' token), its
 | 
						|
// callers, and for each call-site ('(' token), its callees.
 | 
						|
//
 | 
						|
// CALLGRAPH: the package docs include an interactive viewer for the
 | 
						|
// intra-package call graph of "fmt".
 | 
						|
//
 | 
						|
// CHANNEL PEERS: for each channel operation make/<-/close, the set of
 | 
						|
// other channel ops that alias the same channel(s).
 | 
						|
//
 | 
						|
// ERRORS: for each locus of a frontend (scanner/parser/type) error, the
 | 
						|
// location is highlighted in red and hover text provides the compiler
 | 
						|
// error message.
 | 
						|
//
 | 
						|
package analysis // import "golang.org/x/tools/godoc/analysis"
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"go/build"
 | 
						|
	"go/scanner"
 | 
						|
	"go/token"
 | 
						|
	"go/types"
 | 
						|
	"html"
 | 
						|
	"io"
 | 
						|
	"log"
 | 
						|
	"os"
 | 
						|
	"path/filepath"
 | 
						|
	"sort"
 | 
						|
	"strings"
 | 
						|
	"sync"
 | 
						|
 | 
						|
	"golang.org/x/tools/go/loader"
 | 
						|
	"golang.org/x/tools/go/pointer"
 | 
						|
	"golang.org/x/tools/go/ssa"
 | 
						|
	"golang.org/x/tools/go/ssa/ssautil"
 | 
						|
)
 | 
						|
 | 
						|
// -- links ------------------------------------------------------------
 | 
						|
 | 
						|
// A Link is an HTML decoration of the bytes [Start, End) of a file.
 | 
						|
// Write is called before/after those bytes to emit the mark-up.
 | 
						|
type Link interface {
 | 
						|
	Start() int
 | 
						|
	End() int
 | 
						|
	Write(w io.Writer, _ int, start bool) // the godoc.LinkWriter signature
 | 
						|
}
 | 
						|
 | 
						|
// An <a> element.
 | 
						|
type aLink struct {
 | 
						|
	start, end int    // =godoc.Segment
 | 
						|
	title      string // hover text
 | 
						|
	onclick    string // JS code (NB: trusted)
 | 
						|
	href       string // URL     (NB: trusted)
 | 
						|
}
 | 
						|
 | 
						|
func (a aLink) Start() int { return a.start }
 | 
						|
func (a aLink) End() int   { return a.end }
 | 
						|
func (a aLink) Write(w io.Writer, _ int, start bool) {
 | 
						|
	if start {
 | 
						|
		fmt.Fprintf(w, `<a title='%s'`, html.EscapeString(a.title))
 | 
						|
		if a.onclick != "" {
 | 
						|
			fmt.Fprintf(w, ` onclick='%s'`, html.EscapeString(a.onclick))
 | 
						|
		}
 | 
						|
		if a.href != "" {
 | 
						|
			// TODO(adonovan): I think that in principle, a.href must first be
 | 
						|
			// url.QueryEscape'd, but if I do that, a leading slash becomes "%2F",
 | 
						|
			// which causes the browser to treat the path as relative, not absolute.
 | 
						|
			// WTF?
 | 
						|
			fmt.Fprintf(w, ` href='%s'`, html.EscapeString(a.href))
 | 
						|
		}
 | 
						|
		fmt.Fprintf(w, ">")
 | 
						|
	} else {
 | 
						|
		fmt.Fprintf(w, "</a>")
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// An <a class='error'> element.
 | 
						|
type errorLink struct {
 | 
						|
	start int
 | 
						|
	msg   string
 | 
						|
}
 | 
						|
 | 
						|
func (e errorLink) Start() int { return e.start }
 | 
						|
func (e errorLink) End() int   { return e.start + 1 }
 | 
						|
 | 
						|
func (e errorLink) Write(w io.Writer, _ int, start bool) {
 | 
						|
	// <span> causes havoc, not sure why, so use <a>.
 | 
						|
	if start {
 | 
						|
		fmt.Fprintf(w, `<a class='error' title='%s'>`, html.EscapeString(e.msg))
 | 
						|
	} else {
 | 
						|
		fmt.Fprintf(w, "</a>")
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// -- fileInfo ---------------------------------------------------------
 | 
						|
 | 
						|
// FileInfo holds analysis information for the source file view.
 | 
						|
// Clients must not mutate it.
 | 
						|
type FileInfo struct {
 | 
						|
	Data  []interface{} // JSON serializable values
 | 
						|
	Links []Link        // HTML link markup
 | 
						|
}
 | 
						|
 | 
						|
// A fileInfo is the server's store of hyperlinks and JSON data for a
 | 
						|
// particular file.
 | 
						|
type fileInfo struct {
 | 
						|
	mu        sync.Mutex
 | 
						|
	data      []interface{} // JSON objects
 | 
						|
	links     []Link
 | 
						|
	sorted    bool
 | 
						|
	hasErrors bool // TODO(adonovan): surface this in the UI
 | 
						|
}
 | 
						|
 | 
						|
// addLink adds a link to the Go source file fi.
 | 
						|
func (fi *fileInfo) addLink(link Link) {
 | 
						|
	fi.mu.Lock()
 | 
						|
	fi.links = append(fi.links, link)
 | 
						|
	fi.sorted = false
 | 
						|
	if _, ok := link.(errorLink); ok {
 | 
						|
		fi.hasErrors = true
 | 
						|
	}
 | 
						|
	fi.mu.Unlock()
 | 
						|
}
 | 
						|
 | 
						|
// addData adds the structured value x to the JSON data for the Go
 | 
						|
// source file fi.  Its index is returned.
 | 
						|
func (fi *fileInfo) addData(x interface{}) int {
 | 
						|
	fi.mu.Lock()
 | 
						|
	index := len(fi.data)
 | 
						|
	fi.data = append(fi.data, x)
 | 
						|
	fi.mu.Unlock()
 | 
						|
	return index
 | 
						|
}
 | 
						|
 | 
						|
// get returns the file info in external form.
 | 
						|
// Callers must not mutate its fields.
 | 
						|
func (fi *fileInfo) get() FileInfo {
 | 
						|
	var r FileInfo
 | 
						|
	// Copy slices, to avoid races.
 | 
						|
	fi.mu.Lock()
 | 
						|
	r.Data = append(r.Data, fi.data...)
 | 
						|
	if !fi.sorted {
 | 
						|
		sort.Sort(linksByStart(fi.links))
 | 
						|
		fi.sorted = true
 | 
						|
	}
 | 
						|
	r.Links = append(r.Links, fi.links...)
 | 
						|
	fi.mu.Unlock()
 | 
						|
	return r
 | 
						|
}
 | 
						|
 | 
						|
// PackageInfo holds analysis information for the package view.
 | 
						|
// Clients must not mutate it.
 | 
						|
type PackageInfo struct {
 | 
						|
	CallGraph      []*PCGNodeJSON
 | 
						|
	CallGraphIndex map[string]int
 | 
						|
	Types          []*TypeInfoJSON
 | 
						|
}
 | 
						|
 | 
						|
type pkgInfo struct {
 | 
						|
	mu             sync.Mutex
 | 
						|
	callGraph      []*PCGNodeJSON
 | 
						|
	callGraphIndex map[string]int  // keys are (*ssa.Function).RelString()
 | 
						|
	types          []*TypeInfoJSON // type info for exported types
 | 
						|
}
 | 
						|
 | 
						|
func (pi *pkgInfo) setCallGraph(callGraph []*PCGNodeJSON, callGraphIndex map[string]int) {
 | 
						|
	pi.mu.Lock()
 | 
						|
	pi.callGraph = callGraph
 | 
						|
	pi.callGraphIndex = callGraphIndex
 | 
						|
	pi.mu.Unlock()
 | 
						|
}
 | 
						|
 | 
						|
func (pi *pkgInfo) addType(t *TypeInfoJSON) {
 | 
						|
	pi.mu.Lock()
 | 
						|
	pi.types = append(pi.types, t)
 | 
						|
	pi.mu.Unlock()
 | 
						|
}
 | 
						|
 | 
						|
// get returns the package info in external form.
 | 
						|
// Callers must not mutate its fields.
 | 
						|
func (pi *pkgInfo) get() PackageInfo {
 | 
						|
	var r PackageInfo
 | 
						|
	// Copy slices, to avoid races.
 | 
						|
	pi.mu.Lock()
 | 
						|
	r.CallGraph = append(r.CallGraph, pi.callGraph...)
 | 
						|
	r.CallGraphIndex = pi.callGraphIndex
 | 
						|
	r.Types = append(r.Types, pi.types...)
 | 
						|
	pi.mu.Unlock()
 | 
						|
	return r
 | 
						|
}
 | 
						|
 | 
						|
// -- Result -----------------------------------------------------------
 | 
						|
 | 
						|
// Result contains the results of analysis.
 | 
						|
// The result contains a mapping from filenames to a set of HTML links
 | 
						|
// and JavaScript data referenced by the links.
 | 
						|
type Result struct {
 | 
						|
	mu        sync.Mutex           // guards maps (but not their contents)
 | 
						|
	status    string               // global analysis status
 | 
						|
	fileInfos map[string]*fileInfo // keys are godoc file URLs
 | 
						|
	pkgInfos  map[string]*pkgInfo  // keys are import paths
 | 
						|
}
 | 
						|
 | 
						|
// fileInfo returns the fileInfo for the specified godoc file URL,
 | 
						|
// constructing it as needed.  Thread-safe.
 | 
						|
func (res *Result) fileInfo(url string) *fileInfo {
 | 
						|
	res.mu.Lock()
 | 
						|
	fi, ok := res.fileInfos[url]
 | 
						|
	if !ok {
 | 
						|
		if res.fileInfos == nil {
 | 
						|
			res.fileInfos = make(map[string]*fileInfo)
 | 
						|
		}
 | 
						|
		fi = new(fileInfo)
 | 
						|
		res.fileInfos[url] = fi
 | 
						|
	}
 | 
						|
	res.mu.Unlock()
 | 
						|
	return fi
 | 
						|
}
 | 
						|
 | 
						|
// Status returns a human-readable description of the current analysis status.
 | 
						|
func (res *Result) Status() string {
 | 
						|
	res.mu.Lock()
 | 
						|
	defer res.mu.Unlock()
 | 
						|
	return res.status
 | 
						|
}
 | 
						|
 | 
						|
func (res *Result) setStatusf(format string, args ...interface{}) {
 | 
						|
	res.mu.Lock()
 | 
						|
	res.status = fmt.Sprintf(format, args...)
 | 
						|
	log.Printf(format, args...)
 | 
						|
	res.mu.Unlock()
 | 
						|
}
 | 
						|
 | 
						|
// FileInfo returns new slices containing opaque JSON values and the
 | 
						|
// HTML link markup for the specified godoc file URL.  Thread-safe.
 | 
						|
// Callers must not mutate the elements.
 | 
						|
// It returns "zero" if no data is available.
 | 
						|
//
 | 
						|
func (res *Result) FileInfo(url string) (fi FileInfo) {
 | 
						|
	return res.fileInfo(url).get()
 | 
						|
}
 | 
						|
 | 
						|
// pkgInfo returns the pkgInfo for the specified import path,
 | 
						|
// constructing it as needed.  Thread-safe.
 | 
						|
func (res *Result) pkgInfo(importPath string) *pkgInfo {
 | 
						|
	res.mu.Lock()
 | 
						|
	pi, ok := res.pkgInfos[importPath]
 | 
						|
	if !ok {
 | 
						|
		if res.pkgInfos == nil {
 | 
						|
			res.pkgInfos = make(map[string]*pkgInfo)
 | 
						|
		}
 | 
						|
		pi = new(pkgInfo)
 | 
						|
		res.pkgInfos[importPath] = pi
 | 
						|
	}
 | 
						|
	res.mu.Unlock()
 | 
						|
	return pi
 | 
						|
}
 | 
						|
 | 
						|
// PackageInfo returns new slices of JSON values for the callgraph and
 | 
						|
// type info for the specified package.  Thread-safe.
 | 
						|
// Callers must not mutate its fields.
 | 
						|
// PackageInfo returns "zero" if no data is available.
 | 
						|
//
 | 
						|
func (res *Result) PackageInfo(importPath string) PackageInfo {
 | 
						|
	return res.pkgInfo(importPath).get()
 | 
						|
}
 | 
						|
 | 
						|
// -- analysis ---------------------------------------------------------
 | 
						|
 | 
						|
type analysis struct {
 | 
						|
	result    *Result
 | 
						|
	prog      *ssa.Program
 | 
						|
	ops       []chanOp       // all channel ops in program
 | 
						|
	allNamed  []*types.Named // all "defined" (formerly "named") types in the program
 | 
						|
	ptaConfig pointer.Config
 | 
						|
	path2url  map[string]string // maps openable path to godoc file URL (/src/fmt/print.go)
 | 
						|
	pcgs      map[*ssa.Package]*packageCallGraph
 | 
						|
}
 | 
						|
 | 
						|
// fileAndOffset returns the file and offset for a given pos.
 | 
						|
func (a *analysis) fileAndOffset(pos token.Pos) (fi *fileInfo, offset int) {
 | 
						|
	return a.fileAndOffsetPosn(a.prog.Fset.Position(pos))
 | 
						|
}
 | 
						|
 | 
						|
// fileAndOffsetPosn returns the file and offset for a given position.
 | 
						|
func (a *analysis) fileAndOffsetPosn(posn token.Position) (fi *fileInfo, offset int) {
 | 
						|
	url := a.path2url[posn.Filename]
 | 
						|
	return a.result.fileInfo(url), posn.Offset
 | 
						|
}
 | 
						|
 | 
						|
// posURL returns the URL of the source extent [pos, pos+len).
 | 
						|
func (a *analysis) posURL(pos token.Pos, len int) string {
 | 
						|
	if pos == token.NoPos {
 | 
						|
		return ""
 | 
						|
	}
 | 
						|
	posn := a.prog.Fset.Position(pos)
 | 
						|
	url := a.path2url[posn.Filename]
 | 
						|
	return fmt.Sprintf("%s?s=%d:%d#L%d",
 | 
						|
		url, posn.Offset, posn.Offset+len, posn.Line)
 | 
						|
}
 | 
						|
 | 
						|
// ----------------------------------------------------------------------
 | 
						|
 | 
						|
// Run runs program analysis and computes the resulting markup,
 | 
						|
// populating *result in a thread-safe manner, first with type
 | 
						|
// information then later with pointer analysis information if
 | 
						|
// enabled by the pta flag.
 | 
						|
//
 | 
						|
func Run(pta bool, result *Result) {
 | 
						|
	conf := loader.Config{
 | 
						|
		AllowErrors: true,
 | 
						|
	}
 | 
						|
 | 
						|
	// Silence the default error handler.
 | 
						|
	// Don't print all errors; we'll report just
 | 
						|
	// one per errant package later.
 | 
						|
	conf.TypeChecker.Error = func(e error) {}
 | 
						|
 | 
						|
	var roots, args []string // roots[i] ends with os.PathSeparator
 | 
						|
 | 
						|
	// Enumerate packages in $GOROOT.
 | 
						|
	root := filepath.Join(build.Default.GOROOT, "src") + string(os.PathSeparator)
 | 
						|
	roots = append(roots, root)
 | 
						|
	args = allPackages(root)
 | 
						|
	log.Printf("GOROOT=%s: %s\n", root, args)
 | 
						|
 | 
						|
	// Enumerate packages in $GOPATH.
 | 
						|
	for i, dir := range filepath.SplitList(build.Default.GOPATH) {
 | 
						|
		root := filepath.Join(dir, "src") + string(os.PathSeparator)
 | 
						|
		roots = append(roots, root)
 | 
						|
		pkgs := allPackages(root)
 | 
						|
		log.Printf("GOPATH[%d]=%s: %s\n", i, root, pkgs)
 | 
						|
		args = append(args, pkgs...)
 | 
						|
	}
 | 
						|
 | 
						|
	// Uncomment to make startup quicker during debugging.
 | 
						|
	//args = []string{"golang.org/x/tools/cmd/godoc"}
 | 
						|
	//args = []string{"fmt"}
 | 
						|
 | 
						|
	if _, err := conf.FromArgs(args, true); err != nil {
 | 
						|
		// TODO(adonovan): degrade gracefully, not fail totally.
 | 
						|
		// (The crippling case is a parse error in an external test file.)
 | 
						|
		result.setStatusf("Analysis failed: %s.", err) // import error
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	result.setStatusf("Loading and type-checking packages...")
 | 
						|
	iprog, err := conf.Load()
 | 
						|
	if iprog != nil {
 | 
						|
		// Report only the first error of each package.
 | 
						|
		for _, info := range iprog.AllPackages {
 | 
						|
			for _, err := range info.Errors {
 | 
						|
				fmt.Fprintln(os.Stderr, err)
 | 
						|
				break
 | 
						|
			}
 | 
						|
		}
 | 
						|
		log.Printf("Loaded %d packages.", len(iprog.AllPackages))
 | 
						|
	}
 | 
						|
	if err != nil {
 | 
						|
		result.setStatusf("Loading failed: %s.\n", err)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	// Create SSA-form program representation.
 | 
						|
	// Only the transitively error-free packages are used.
 | 
						|
	prog := ssautil.CreateProgram(iprog, ssa.GlobalDebug)
 | 
						|
 | 
						|
	// Create a "testmain" package for each package with tests.
 | 
						|
	for _, pkg := range prog.AllPackages() {
 | 
						|
		if testmain := prog.CreateTestMainPackage(pkg); testmain != nil {
 | 
						|
			log.Printf("Adding tests for %s", pkg.Pkg.Path())
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Build SSA code for bodies of all functions in the whole program.
 | 
						|
	result.setStatusf("Constructing SSA form...")
 | 
						|
	prog.Build()
 | 
						|
	log.Print("SSA construction complete")
 | 
						|
 | 
						|
	a := analysis{
 | 
						|
		result: result,
 | 
						|
		prog:   prog,
 | 
						|
		pcgs:   make(map[*ssa.Package]*packageCallGraph),
 | 
						|
	}
 | 
						|
 | 
						|
	// Build a mapping from openable filenames to godoc file URLs,
 | 
						|
	// i.e. "/src/" plus path relative to GOROOT/src or GOPATH[i]/src.
 | 
						|
	a.path2url = make(map[string]string)
 | 
						|
	for _, info := range iprog.AllPackages {
 | 
						|
	nextfile:
 | 
						|
		for _, f := range info.Files {
 | 
						|
			if f.Pos() == 0 {
 | 
						|
				continue // e.g. files generated by cgo
 | 
						|
			}
 | 
						|
			abs := iprog.Fset.File(f.Pos()).Name()
 | 
						|
			// Find the root to which this file belongs.
 | 
						|
			for _, root := range roots {
 | 
						|
				rel := strings.TrimPrefix(abs, root)
 | 
						|
				if len(rel) < len(abs) {
 | 
						|
					a.path2url[abs] = "/src/" + filepath.ToSlash(rel)
 | 
						|
					continue nextfile
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			log.Printf("Can't locate file %s (package %q) beneath any root",
 | 
						|
				abs, info.Pkg.Path())
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Add links for scanner, parser, type-checker errors.
 | 
						|
	// TODO(adonovan): fix: these links can overlap with
 | 
						|
	// identifier markup, causing the renderer to emit some
 | 
						|
	// characters twice.
 | 
						|
	errors := make(map[token.Position][]string)
 | 
						|
	for _, info := range iprog.AllPackages {
 | 
						|
		for _, err := range info.Errors {
 | 
						|
			switch err := err.(type) {
 | 
						|
			case types.Error:
 | 
						|
				posn := a.prog.Fset.Position(err.Pos)
 | 
						|
				errors[posn] = append(errors[posn], err.Msg)
 | 
						|
			case scanner.ErrorList:
 | 
						|
				for _, e := range err {
 | 
						|
					errors[e.Pos] = append(errors[e.Pos], e.Msg)
 | 
						|
				}
 | 
						|
			default:
 | 
						|
				log.Printf("Package %q has error (%T) without position: %v\n",
 | 
						|
					info.Pkg.Path(), err, err)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	for posn, errs := range errors {
 | 
						|
		fi, offset := a.fileAndOffsetPosn(posn)
 | 
						|
		fi.addLink(errorLink{
 | 
						|
			start: offset,
 | 
						|
			msg:   strings.Join(errs, "\n"),
 | 
						|
		})
 | 
						|
	}
 | 
						|
 | 
						|
	// ---------- type-based analyses ----------
 | 
						|
 | 
						|
	// Compute the all-pairs IMPLEMENTS relation.
 | 
						|
	// Collect all named types, even local types
 | 
						|
	// (which can have methods via promotion)
 | 
						|
	// and the built-in "error".
 | 
						|
	errorType := types.Universe.Lookup("error").Type().(*types.Named)
 | 
						|
	a.allNamed = append(a.allNamed, errorType)
 | 
						|
	for _, info := range iprog.AllPackages {
 | 
						|
		for _, obj := range info.Defs {
 | 
						|
			if obj, ok := obj.(*types.TypeName); ok {
 | 
						|
				if named, ok := obj.Type().(*types.Named); ok {
 | 
						|
					a.allNamed = append(a.allNamed, named)
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	log.Print("Computing implements relation...")
 | 
						|
	facts := computeImplements(&a.prog.MethodSets, a.allNamed)
 | 
						|
 | 
						|
	// Add the type-based analysis results.
 | 
						|
	log.Print("Extracting type info...")
 | 
						|
	for _, info := range iprog.AllPackages {
 | 
						|
		a.doTypeInfo(info, facts)
 | 
						|
	}
 | 
						|
 | 
						|
	a.visitInstrs(pta)
 | 
						|
 | 
						|
	result.setStatusf("Type analysis complete.")
 | 
						|
 | 
						|
	if pta {
 | 
						|
		mainPkgs := ssautil.MainPackages(prog.AllPackages())
 | 
						|
		log.Print("Transitively error-free main packages: ", mainPkgs)
 | 
						|
		a.pointer(mainPkgs)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// visitInstrs visits all SSA instructions in the program.
 | 
						|
func (a *analysis) visitInstrs(pta bool) {
 | 
						|
	log.Print("Visit instructions...")
 | 
						|
	for fn := range ssautil.AllFunctions(a.prog) {
 | 
						|
		for _, b := range fn.Blocks {
 | 
						|
			for _, instr := range b.Instrs {
 | 
						|
				// CALLEES (static)
 | 
						|
				// (Dynamic calls require pointer analysis.)
 | 
						|
				//
 | 
						|
				// We use the SSA representation to find the static callee,
 | 
						|
				// since in many cases it does better than the
 | 
						|
				// types.Info.{Refs,Selection} information.  For example:
 | 
						|
				//
 | 
						|
				//   defer func(){}()      // static call to anon function
 | 
						|
				//   f := func(){}; f()    // static call to anon function
 | 
						|
				//   f := fmt.Println; f() // static call to named function
 | 
						|
				//
 | 
						|
				// The downside is that we get no static callee information
 | 
						|
				// for packages that (transitively) contain errors.
 | 
						|
				if site, ok := instr.(ssa.CallInstruction); ok {
 | 
						|
					if callee := site.Common().StaticCallee(); callee != nil {
 | 
						|
						// TODO(adonovan): callgraph: elide wrappers.
 | 
						|
						// (Do static calls ever go to wrappers?)
 | 
						|
						if site.Common().Pos() != token.NoPos {
 | 
						|
							a.addCallees(site, []*ssa.Function{callee})
 | 
						|
						}
 | 
						|
					}
 | 
						|
				}
 | 
						|
 | 
						|
				if !pta {
 | 
						|
					continue
 | 
						|
				}
 | 
						|
 | 
						|
				// CHANNEL PEERS
 | 
						|
				// Collect send/receive/close instructions in the whole ssa.Program.
 | 
						|
				for _, op := range chanOps(instr) {
 | 
						|
					a.ops = append(a.ops, op)
 | 
						|
					a.ptaConfig.AddQuery(op.ch) // add channel ssa.Value to PTA query
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	log.Print("Visit instructions complete")
 | 
						|
}
 | 
						|
 | 
						|
// pointer runs the pointer analysis.
 | 
						|
func (a *analysis) pointer(mainPkgs []*ssa.Package) {
 | 
						|
	// Run the pointer analysis and build the complete callgraph.
 | 
						|
	a.ptaConfig.Mains = mainPkgs
 | 
						|
	a.ptaConfig.BuildCallGraph = true
 | 
						|
	a.ptaConfig.Reflection = false // (for now)
 | 
						|
 | 
						|
	a.result.setStatusf("Pointer analysis running...")
 | 
						|
 | 
						|
	ptares, err := pointer.Analyze(&a.ptaConfig)
 | 
						|
	if err != nil {
 | 
						|
		// If this happens, it indicates a bug.
 | 
						|
		a.result.setStatusf("Pointer analysis failed: %s.", err)
 | 
						|
		return
 | 
						|
	}
 | 
						|
	log.Print("Pointer analysis complete.")
 | 
						|
 | 
						|
	// Add the results of pointer analysis.
 | 
						|
 | 
						|
	a.result.setStatusf("Computing channel peers...")
 | 
						|
	a.doChannelPeers(ptares.Queries)
 | 
						|
	a.result.setStatusf("Computing dynamic call graph edges...")
 | 
						|
	a.doCallgraph(ptares.CallGraph)
 | 
						|
 | 
						|
	a.result.setStatusf("Analysis complete.")
 | 
						|
}
 | 
						|
 | 
						|
type linksByStart []Link
 | 
						|
 | 
						|
func (a linksByStart) Less(i, j int) bool { return a[i].Start() < a[j].Start() }
 | 
						|
func (a linksByStart) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
 | 
						|
func (a linksByStart) Len() int           { return len(a) }
 | 
						|
 | 
						|
// allPackages returns a new sorted slice of all packages beneath the
 | 
						|
// specified package root directory, e.g. $GOROOT/src or $GOPATH/src.
 | 
						|
// Derived from from go/ssa/stdlib_test.go
 | 
						|
// root must end with os.PathSeparator.
 | 
						|
//
 | 
						|
// TODO(adonovan): use buildutil.AllPackages when the tree thaws.
 | 
						|
func allPackages(root string) []string {
 | 
						|
	var pkgs []string
 | 
						|
	filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
 | 
						|
		if info == nil {
 | 
						|
			return nil // non-existent root directory?
 | 
						|
		}
 | 
						|
		if !info.IsDir() {
 | 
						|
			return nil // not a directory
 | 
						|
		}
 | 
						|
		// Prune the search if we encounter any of these names:
 | 
						|
		base := filepath.Base(path)
 | 
						|
		if base == "testdata" || strings.HasPrefix(base, ".") {
 | 
						|
			return filepath.SkipDir
 | 
						|
		}
 | 
						|
		pkg := filepath.ToSlash(strings.TrimPrefix(path, root))
 | 
						|
		switch pkg {
 | 
						|
		case "builtin":
 | 
						|
			return filepath.SkipDir
 | 
						|
		case "":
 | 
						|
			return nil // ignore root of tree
 | 
						|
		}
 | 
						|
		pkgs = append(pkgs, pkg)
 | 
						|
		return nil
 | 
						|
	})
 | 
						|
	return pkgs
 | 
						|
}
 |