|  | // 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 main | 
|  |  | 
|  | // TODO(adonovan): new queries | 
|  | // - show all statements that may update the selected lvalue | 
|  | //   (local, global, field, etc). | 
|  | // - show all places where an object of type T is created | 
|  | //   (&T{}, var t T, new(T), new(struct{array [3]T}), etc. | 
|  |  | 
|  | import ( | 
|  | "encoding/json" | 
|  | "fmt" | 
|  | "go/ast" | 
|  | "go/build" | 
|  | "go/parser" | 
|  | "go/token" | 
|  | "go/types" | 
|  | "io" | 
|  | "log" | 
|  | "path/filepath" | 
|  | "strings" | 
|  |  | 
|  | "golang.org/x/tools/go/ast/astutil" | 
|  | "golang.org/x/tools/go/buildutil" | 
|  | "golang.org/x/tools/go/loader" | 
|  | "golang.org/x/tools/go/pointer" | 
|  | "golang.org/x/tools/go/ssa" | 
|  | ) | 
|  |  | 
|  | type printfFunc func(pos interface{}, format string, args ...interface{}) | 
|  |  | 
|  | // A QueryResult is an item of output.  Each query produces a stream of | 
|  | // query results, calling Query.Output for each one. | 
|  | type QueryResult interface { | 
|  | // JSON returns the QueryResult in JSON form. | 
|  | JSON(fset *token.FileSet) []byte | 
|  |  | 
|  | // PrintPlain prints the QueryResult in plain text form. | 
|  | // The implementation calls printfFunc to print each line of output. | 
|  | PrintPlain(printf printfFunc) | 
|  | } | 
|  |  | 
|  | // A QueryPos represents the position provided as input to a query: | 
|  | // a textual extent in the program's source code, the AST node it | 
|  | // corresponds to, and the package to which it belongs. | 
|  | // Instances are created by parseQueryPos. | 
|  | type queryPos struct { | 
|  | fset       *token.FileSet | 
|  | start, end token.Pos           // source extent of query | 
|  | path       []ast.Node          // AST path from query node to root of ast.File | 
|  | exact      bool                // 2nd result of PathEnclosingInterval | 
|  | info       *loader.PackageInfo // type info for the queried package (nil for fastQueryPos) | 
|  | } | 
|  |  | 
|  | // TypeString prints type T relative to the query position. | 
|  | func (qpos *queryPos) typeString(T types.Type) string { | 
|  | return types.TypeString(T, types.RelativeTo(qpos.info.Pkg)) | 
|  | } | 
|  |  | 
|  | // ObjectString prints object obj relative to the query position. | 
|  | func (qpos *queryPos) objectString(obj types.Object) string { | 
|  | return types.ObjectString(obj, types.RelativeTo(qpos.info.Pkg)) | 
|  | } | 
|  |  | 
|  | // A Query specifies a single guru query. | 
|  | type Query struct { | 
|  | Pos   string         // query position | 
|  | Build *build.Context // package loading configuration | 
|  |  | 
|  | // pointer analysis options | 
|  | Scope      []string  // main packages in (*loader.Config).FromArgs syntax | 
|  | PTALog     io.Writer // (optional) pointer-analysis log file | 
|  | Reflection bool      // model reflection soundly (currently slow). | 
|  |  | 
|  | // result-printing function, safe for concurrent use | 
|  | Output func(*token.FileSet, QueryResult) | 
|  | } | 
|  |  | 
|  | // Run runs an guru query and populates its Fset and Result. | 
|  | func Run(mode string, q *Query) error { | 
|  | switch mode { | 
|  | case "callees": | 
|  | return callees(q) | 
|  | case "callers": | 
|  | return callers(q) | 
|  | case "callstack": | 
|  | return callstack(q) | 
|  | case "peers": | 
|  | return peers(q) | 
|  | case "pointsto": | 
|  | return pointsto(q) | 
|  | case "whicherrs": | 
|  | return whicherrs(q) | 
|  | case "definition": | 
|  | return definition(q) | 
|  | case "describe": | 
|  | return describe(q) | 
|  | case "freevars": | 
|  | return freevars(q) | 
|  | case "implements": | 
|  | return implements(q) | 
|  | case "referrers": | 
|  | return referrers(q) | 
|  | case "what": | 
|  | return what(q) | 
|  | default: | 
|  | return fmt.Errorf("invalid mode: %q", mode) | 
|  | } | 
|  | } | 
|  |  | 
|  | func setPTAScope(lconf *loader.Config, scope []string) error { | 
|  | pkgs := buildutil.ExpandPatterns(lconf.Build, scope) | 
|  | if len(pkgs) == 0 { | 
|  | return fmt.Errorf("no packages specified for pointer analysis scope") | 
|  | } | 
|  | // The value of each entry in pkgs is true, | 
|  | // giving ImportWithTests (not Import) semantics. | 
|  | lconf.ImportPkgs = pkgs | 
|  | return nil | 
|  | } | 
|  |  | 
|  | // Create a pointer.Config whose scope is the initial packages of lprog | 
|  | // and their dependencies. | 
|  | func setupPTA(prog *ssa.Program, lprog *loader.Program, ptaLog io.Writer, reflection bool) (*pointer.Config, error) { | 
|  | // For each initial package (specified on the command line), | 
|  | // if it has a main function, analyze that, | 
|  | // otherwise analyze its tests, if any. | 
|  | var mains []*ssa.Package | 
|  | for _, info := range lprog.InitialPackages() { | 
|  | p := prog.Package(info.Pkg) | 
|  |  | 
|  | // Add package to the pointer analysis scope. | 
|  | if p.Pkg.Name() == "main" && p.Func("main") != nil { | 
|  | mains = append(mains, p) | 
|  | } else if main := prog.CreateTestMainPackage(p); main != nil { | 
|  | mains = append(mains, main) | 
|  | } | 
|  | } | 
|  | if mains == nil { | 
|  | return nil, fmt.Errorf("analysis scope has no main and no tests") | 
|  | } | 
|  | return &pointer.Config{ | 
|  | Log:        ptaLog, | 
|  | Reflection: reflection, | 
|  | Mains:      mains, | 
|  | }, nil | 
|  | } | 
|  |  | 
|  | // importQueryPackage finds the package P containing the | 
|  | // query position and tells conf to import it. | 
|  | // It returns the package's path. | 
|  | func importQueryPackage(pos string, conf *loader.Config) (string, error) { | 
|  | fqpos, err := fastQueryPos(conf.Build, pos) | 
|  | if err != nil { | 
|  | return "", err // bad query | 
|  | } | 
|  | filename := fqpos.fset.File(fqpos.start).Name() | 
|  |  | 
|  | _, importPath, err := guessImportPath(filename, conf.Build) | 
|  | if err != nil { | 
|  | // Can't find GOPATH dir. | 
|  | // Treat the query file as its own package. | 
|  | importPath = "command-line-arguments" | 
|  | conf.CreateFromFilenames(importPath, filename) | 
|  | } else { | 
|  | // Check that it's possible to load the queried package. | 
|  | // (e.g. guru tests contain different 'package' decls in same dir.) | 
|  | // Keep consistent with logic in loader/util.go! | 
|  | cfg2 := *conf.Build | 
|  | cfg2.CgoEnabled = false | 
|  | bp, err := cfg2.Import(importPath, "", 0) | 
|  | if err != nil { | 
|  | return "", err // no files for package | 
|  | } | 
|  |  | 
|  | switch pkgContainsFile(bp, filename) { | 
|  | case 'T': | 
|  | conf.ImportWithTests(importPath) | 
|  | case 'X': | 
|  | conf.ImportWithTests(importPath) | 
|  | importPath += "_test" // for TypeCheckFuncBodies | 
|  | case 'G': | 
|  | conf.Import(importPath) | 
|  | default: | 
|  | // This happens for ad-hoc packages like | 
|  | // $GOROOT/src/net/http/triv.go. | 
|  | return "", fmt.Errorf("package %q doesn't contain file %s", | 
|  | importPath, filename) | 
|  | } | 
|  | } | 
|  |  | 
|  | conf.TypeCheckFuncBodies = func(p string) bool { return p == importPath } | 
|  |  | 
|  | return importPath, nil | 
|  | } | 
|  |  | 
|  | // pkgContainsFile reports whether file was among the packages Go | 
|  | // files, Test files, eXternal test files, or not found. | 
|  | func pkgContainsFile(bp *build.Package, filename string) byte { | 
|  | for i, files := range [][]string{bp.GoFiles, bp.TestGoFiles, bp.XTestGoFiles} { | 
|  | for _, file := range files { | 
|  | if sameFile(filepath.Join(bp.Dir, file), filename) { | 
|  | return "GTX"[i] | 
|  | } | 
|  | } | 
|  | } | 
|  | return 0 // not found | 
|  | } | 
|  |  | 
|  | // ParseQueryPos parses the source query position pos and returns the | 
|  | // AST node of the loaded program lprog that it identifies. | 
|  | // If needExact, it must identify a single AST subtree; | 
|  | // this is appropriate for queries that allow fairly arbitrary syntax, | 
|  | // e.g. "describe". | 
|  | // | 
|  | func parseQueryPos(lprog *loader.Program, pos string, needExact bool) (*queryPos, error) { | 
|  | filename, startOffset, endOffset, err := parsePos(pos) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  |  | 
|  | // Find the named file among those in the loaded program. | 
|  | var file *token.File | 
|  | lprog.Fset.Iterate(func(f *token.File) bool { | 
|  | if sameFile(filename, f.Name()) { | 
|  | file = f | 
|  | return false // done | 
|  | } | 
|  | return true // continue | 
|  | }) | 
|  | if file == nil { | 
|  | return nil, fmt.Errorf("file %s not found in loaded program", filename) | 
|  | } | 
|  |  | 
|  | start, end, err := fileOffsetToPos(file, startOffset, endOffset) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  | info, path, exact := lprog.PathEnclosingInterval(start, end) | 
|  | if path == nil { | 
|  | return nil, fmt.Errorf("no syntax here") | 
|  | } | 
|  | if needExact && !exact { | 
|  | return nil, fmt.Errorf("ambiguous selection within %s", astutil.NodeDescription(path[0])) | 
|  | } | 
|  | return &queryPos{lprog.Fset, start, end, path, exact, info}, nil | 
|  | } | 
|  |  | 
|  | // ---------- Utilities ---------- | 
|  |  | 
|  | // loadWithSoftErrors calls lconf.Load, suppressing "soft" errors.  (See Go issue 16530.) | 
|  | // TODO(adonovan): Once the loader has an option to allow soft errors, | 
|  | // replace calls to loadWithSoftErrors with loader calls with that parameter. | 
|  | func loadWithSoftErrors(lconf *loader.Config) (*loader.Program, error) { | 
|  | lconf.AllowErrors = true | 
|  |  | 
|  | // Ideally we would just return conf.Load() here, but go/types | 
|  | // reports certain "soft" errors that gc does not (Go issue 14596). | 
|  | // As a workaround, we set AllowErrors=true and then duplicate | 
|  | // the loader's error checking but allow soft errors. | 
|  | // It would be nice if the loader API permitted "AllowErrors: soft". | 
|  | prog, err := lconf.Load() | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  | var errpkgs []string | 
|  | // Report hard errors in indirectly imported packages. | 
|  | for _, info := range prog.AllPackages { | 
|  | if containsHardErrors(info.Errors) { | 
|  | errpkgs = append(errpkgs, info.Pkg.Path()) | 
|  | } else { | 
|  | // Enable SSA construction for packages containing only soft errors. | 
|  | info.TransitivelyErrorFree = true | 
|  | } | 
|  | } | 
|  | if errpkgs != nil { | 
|  | var more string | 
|  | if len(errpkgs) > 3 { | 
|  | more = fmt.Sprintf(" and %d more", len(errpkgs)-3) | 
|  | errpkgs = errpkgs[:3] | 
|  | } | 
|  | return nil, fmt.Errorf("couldn't load packages due to errors: %s%s", | 
|  | strings.Join(errpkgs, ", "), more) | 
|  | } | 
|  | return prog, err | 
|  | } | 
|  |  | 
|  | func containsHardErrors(errors []error) bool { | 
|  | for _, err := range errors { | 
|  | if err, ok := err.(types.Error); ok && err.Soft { | 
|  | continue | 
|  | } | 
|  | return true | 
|  | } | 
|  | return false | 
|  | } | 
|  |  | 
|  | // allowErrors causes type errors to be silently ignored. | 
|  | // (Not suitable if SSA construction follows.) | 
|  | func allowErrors(lconf *loader.Config) { | 
|  | ctxt := *lconf.Build // copy | 
|  | ctxt.CgoEnabled = false | 
|  | lconf.Build = &ctxt | 
|  | lconf.AllowErrors = true | 
|  | // AllErrors makes the parser always return an AST instead of | 
|  | // bailing out after 10 errors and returning an empty ast.File. | 
|  | lconf.ParserMode = parser.AllErrors | 
|  | lconf.TypeChecker.Error = func(err error) {} | 
|  | } | 
|  |  | 
|  | // ptrAnalysis runs the pointer analysis and returns its result. | 
|  | func ptrAnalysis(conf *pointer.Config) *pointer.Result { | 
|  | result, err := pointer.Analyze(conf) | 
|  | if err != nil { | 
|  | panic(err) // pointer analysis internal error | 
|  | } | 
|  | return result | 
|  | } | 
|  |  | 
|  | func unparen(e ast.Expr) ast.Expr { return astutil.Unparen(e) } | 
|  |  | 
|  | // deref returns a pointer's element type; otherwise it returns typ. | 
|  | func deref(typ types.Type) types.Type { | 
|  | if p, ok := typ.Underlying().(*types.Pointer); ok { | 
|  | return p.Elem() | 
|  | } | 
|  | return typ | 
|  | } | 
|  |  | 
|  | // fprintf prints to w a message of the form "location: message\n" | 
|  | // where location is derived from pos. | 
|  | // | 
|  | // pos must be one of: | 
|  | //    - a token.Pos, denoting a position | 
|  | //    - an ast.Node, denoting an interval | 
|  | //    - anything with a Pos() method: | 
|  | //         ssa.Member, ssa.Value, ssa.Instruction, types.Object, pointer.Label, etc. | 
|  | //    - a QueryPos, denoting the extent of the user's query. | 
|  | //    - nil, meaning no position at all. | 
|  | // | 
|  | // The output format is is compatible with the 'gnu' | 
|  | // compilation-error-regexp in Emacs' compilation mode. | 
|  | // | 
|  | func fprintf(w io.Writer, fset *token.FileSet, pos interface{}, format string, args ...interface{}) { | 
|  | var start, end token.Pos | 
|  | switch pos := pos.(type) { | 
|  | case ast.Node: | 
|  | start = pos.Pos() | 
|  | end = pos.End() | 
|  | case token.Pos: | 
|  | start = pos | 
|  | end = start | 
|  | case *types.PkgName: | 
|  | // The Pos of most PkgName objects does not coincide with an identifier, | 
|  | // so we suppress the usual start+len(name) heuristic for types.Objects. | 
|  | start = pos.Pos() | 
|  | end = start | 
|  | case types.Object: | 
|  | start = pos.Pos() | 
|  | end = start + token.Pos(len(pos.Name())) // heuristic | 
|  | case interface { | 
|  | Pos() token.Pos | 
|  | }: | 
|  | start = pos.Pos() | 
|  | end = start | 
|  | case *queryPos: | 
|  | start = pos.start | 
|  | end = pos.end | 
|  | case nil: | 
|  | // no-op | 
|  | default: | 
|  | panic(fmt.Sprintf("invalid pos: %T", pos)) | 
|  | } | 
|  |  | 
|  | if sp := fset.Position(start); start == end { | 
|  | // (prints "-: " for token.NoPos) | 
|  | fmt.Fprintf(w, "%s: ", sp) | 
|  | } else { | 
|  | ep := fset.Position(end) | 
|  | // The -1 below is a concession to Emacs's broken use of | 
|  | // inclusive (not half-open) intervals. | 
|  | // Other editors may not want it. | 
|  | // TODO(adonovan): add an -editor=vim|emacs|acme|auto | 
|  | // flag; auto uses EMACS=t / VIM=... / etc env vars. | 
|  | fmt.Fprintf(w, "%s:%d.%d-%d.%d: ", | 
|  | sp.Filename, sp.Line, sp.Column, ep.Line, ep.Column-1) | 
|  | } | 
|  | fmt.Fprintf(w, format, args...) | 
|  | io.WriteString(w, "\n") | 
|  | } | 
|  |  | 
|  | func toJSON(x interface{}) []byte { | 
|  | b, err := json.MarshalIndent(x, "", "\t") | 
|  | if err != nil { | 
|  | log.Fatalf("JSON error: %v", err) | 
|  | } | 
|  | return b | 
|  | } |