You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

401 lines
12 KiB

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