PageRenderTime 51ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/go/gotour/src/code.google.com/p/go.tools/oracle/oracle.go

https://github.com/zatkin/programming
Go | 557 lines | 309 code | 61 blank | 187 comment | 75 complexity | bab373d254c51a9128ee1f90a2e1a0dc MD5 | raw file
Possible License(s): GPL-3.0, BSD-3-Clause, Apache-2.0, MIT
  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 oracle contains the implementation of the oracle tool whose
  5. // command-line is provided by code.google.com/p/go.tools/cmd/oracle.
  6. //
  7. // http://golang.org/s/oracle-design
  8. // http://golang.org/s/oracle-user-manual
  9. //
  10. package oracle
  11. // This file defines oracle.Query, the entry point for the oracle tool.
  12. // The actual executable is defined in cmd/oracle.
  13. // TODO(adonovan): new queries
  14. // - show all statements that may update the selected lvalue
  15. // (local, global, field, etc).
  16. // - show all places where an object of type T is created
  17. // (&T{}, var t T, new(T), new(struct{array [3]T}), etc.
  18. // ORACLE CONTROL FLOW
  19. //
  20. // The Oracle is somewhat convoluted due to the need to support two
  21. // very different use-cases, "one-shot" and "long running", and to do
  22. // so quickly.
  23. //
  24. // The cmd/oracle tool issues "one-shot" queries via the exported
  25. // Query function, which creates an Oracle to answer a single query.
  26. // newOracle consults the 'needs' flags of the query mode and the
  27. // package containing the query to avoid doing more work than it needs
  28. // (loading, parsing, type checking, SSA construction).
  29. //
  30. // The Pythia tool (github.com/fzipp/pythia) is an example of a "long
  31. // running" tool. It calls New() and then loops, calling
  32. // ParseQueryPos and (*Oracle).Query to handle each incoming HTTP
  33. // query. Since New cannot see which queries will follow, it must
  34. // load, parse, type-check and SSA-build the entire transitive closure
  35. // of the analysis scope, retaining full debug information and all
  36. // typed ASTs.
  37. //
  38. // TODO(adonovan): experiment with inverting the control flow by
  39. // making each mode consist of two functions: a "one-shot setup"
  40. // function and the existing "impl" function. The one-shot setup
  41. // function would do all of the work of Query and newOracle,
  42. // specialized to each mode, calling library utilities for the common
  43. // things. This would give it more control over "scope reduction".
  44. // Long running tools would not call the one-shot setup function but
  45. // would have their own setup function equivalent to the existing
  46. // 'needsAll' flow path.
  47. import (
  48. "fmt"
  49. "go/ast"
  50. "go/build"
  51. "go/token"
  52. "io"
  53. "code.google.com/p/go.tools/astutil"
  54. "code.google.com/p/go.tools/go/loader"
  55. "code.google.com/p/go.tools/go/pointer"
  56. "code.google.com/p/go.tools/go/ssa"
  57. "code.google.com/p/go.tools/go/types"
  58. "code.google.com/p/go.tools/oracle/serial"
  59. )
  60. // An Oracle holds the program state required for one or more queries.
  61. type Oracle struct {
  62. fset *token.FileSet // file set [all queries]
  63. prog *ssa.Program // the SSA program [needSSA]
  64. ptaConfig pointer.Config // pointer analysis configuration [needPTA]
  65. typeInfo map[*types.Package]*loader.PackageInfo // type info for all ASTs in the program [needRetainTypeInfo]
  66. }
  67. // A set of bits indicating the analytical requirements of each mode.
  68. //
  69. // Typed ASTs for the whole program are always constructed
  70. // transiently; they are retained only for the queried package unless
  71. // needRetainTypeInfo is set.
  72. const (
  73. needPos = 1 << iota // needs a position
  74. needExactPos // needs an exact AST selection; implies needPos
  75. needRetainTypeInfo // needs to retain type info for all ASTs in the program
  76. needSSA // needs ssa.Packages for whole program
  77. needSSADebug // needs debug info for ssa.Packages
  78. needPTA = needSSA // needs pointer analysis
  79. needAll = -1 // needs everything (e.g. a sequence of queries)
  80. )
  81. type modeInfo struct {
  82. name string
  83. needs int
  84. impl func(*Oracle, *QueryPos) (queryResult, error)
  85. }
  86. var modes = []*modeInfo{
  87. // Pointer analyses, whole program:
  88. {"callees", needPTA | needExactPos, callees},
  89. {"callers", needPTA | needPos, callers},
  90. {"callgraph", needPTA, doCallgraph},
  91. {"callstack", needPTA | needPos, callstack},
  92. {"peers", needPTA | needSSADebug | needPos, peers},
  93. {"pointsto", needPTA | needSSADebug | needExactPos, pointsto},
  94. // Type-based, modular analyses:
  95. {"definition", needPos, definition},
  96. {"describe", needExactPos, describe},
  97. {"freevars", needPos, freevars},
  98. // Type-based, whole-program analyses:
  99. {"implements", needRetainTypeInfo | needPos, implements},
  100. {"referrers", needRetainTypeInfo | needPos, referrers},
  101. }
  102. func findMode(mode string) *modeInfo {
  103. for _, m := range modes {
  104. if m.name == mode {
  105. return m
  106. }
  107. }
  108. return nil
  109. }
  110. type printfFunc func(pos interface{}, format string, args ...interface{})
  111. // queryResult is the interface of each query-specific result type.
  112. type queryResult interface {
  113. toSerial(res *serial.Result, fset *token.FileSet)
  114. display(printf printfFunc)
  115. }
  116. // A QueryPos represents the position provided as input to a query:
  117. // a textual extent in the program's source code, the AST node it
  118. // corresponds to, and the package to which it belongs.
  119. // Instances are created by ParseQueryPos.
  120. //
  121. type QueryPos struct {
  122. fset *token.FileSet
  123. start, end token.Pos // source extent of query
  124. path []ast.Node // AST path from query node to root of ast.File
  125. exact bool // 2nd result of PathEnclosingInterval
  126. info *loader.PackageInfo // type info for the queried package (nil for fastQueryPos)
  127. }
  128. // TypeString prints type T relative to the query position.
  129. func (qpos *QueryPos) TypeString(T types.Type) string {
  130. return types.TypeString(qpos.info.Pkg, T)
  131. }
  132. // ObjectString prints object obj relative to the query position.
  133. func (qpos *QueryPos) ObjectString(obj types.Object) string {
  134. return types.ObjectString(qpos.info.Pkg, obj)
  135. }
  136. // SelectionString prints selection sel relative to the query position.
  137. func (qpos *QueryPos) SelectionString(sel *types.Selection) string {
  138. return types.SelectionString(qpos.info.Pkg, sel)
  139. }
  140. // A Result encapsulates the result of an oracle.Query.
  141. type Result struct {
  142. fset *token.FileSet
  143. q queryResult // the query-specific result
  144. mode string // query mode
  145. warnings []pointer.Warning // pointer analysis warnings (TODO(adonovan): fix: never populated!)
  146. }
  147. // Serial returns an instance of serial.Result, which implements the
  148. // {xml,json}.Marshaler interfaces so that query results can be
  149. // serialized as JSON or XML.
  150. //
  151. func (res *Result) Serial() *serial.Result {
  152. resj := &serial.Result{Mode: res.mode}
  153. res.q.toSerial(resj, res.fset)
  154. for _, w := range res.warnings {
  155. resj.Warnings = append(resj.Warnings, serial.PTAWarning{
  156. Pos: res.fset.Position(w.Pos).String(),
  157. Message: w.Message,
  158. })
  159. }
  160. return resj
  161. }
  162. // Query runs a single oracle query.
  163. //
  164. // args specify the main package in (*loader.Config).FromArgs syntax.
  165. // mode is the query mode ("callers", etc).
  166. // ptalog is the (optional) pointer-analysis log file.
  167. // buildContext is the go/build configuration for locating packages.
  168. // reflection determines whether to model reflection soundly (currently slow).
  169. //
  170. // Clients that intend to perform multiple queries against the same
  171. // analysis scope should use this pattern instead:
  172. //
  173. // conf := loader.Config{Build: buildContext, SourceImports: true}
  174. // ... populate config, e.g. conf.FromArgs(args) ...
  175. // iprog, err := conf.Load()
  176. // if err != nil { ... }
  177. // o, err := oracle.New(iprog, nil, false)
  178. // if err != nil { ... }
  179. // for ... {
  180. // qpos, err := oracle.ParseQueryPos(imp, pos, needExact)
  181. // if err != nil { ... }
  182. //
  183. // res, err := o.Query(mode, qpos)
  184. // if err != nil { ... }
  185. //
  186. // // use res
  187. // }
  188. //
  189. // TODO(adonovan): the ideal 'needsExact' parameter for ParseQueryPos
  190. // depends on the query mode; how should we expose this?
  191. //
  192. func Query(args []string, mode, pos string, ptalog io.Writer, buildContext *build.Context, reflection bool) (*Result, error) {
  193. if mode == "what" {
  194. // Bypass package loading, type checking, SSA construction.
  195. return what(pos, buildContext)
  196. }
  197. minfo := findMode(mode)
  198. if minfo == nil {
  199. return nil, fmt.Errorf("invalid mode type: %q", mode)
  200. }
  201. conf := loader.Config{Build: buildContext, SourceImports: true}
  202. // Determine initial packages.
  203. args, err := conf.FromArgs(args, true)
  204. if err != nil {
  205. return nil, err
  206. }
  207. if len(args) > 0 {
  208. return nil, fmt.Errorf("surplus arguments: %q", args)
  209. }
  210. // For queries needing only a single typed package,
  211. // reduce the analysis scope to that package.
  212. if minfo.needs&(needSSA|needRetainTypeInfo) == 0 {
  213. reduceScope(pos, &conf)
  214. }
  215. // TODO(adonovan): report type errors to the user via Serial
  216. // types, not stderr?
  217. // conf.TypeChecker.Error = func(err error) {
  218. // E := err.(types.Error)
  219. // fmt.Fprintf(os.Stderr, "%s: %s\n", E.Fset.Position(E.Pos), E.Msg)
  220. // }
  221. // Load/parse/type-check the program.
  222. iprog, err := conf.Load()
  223. if err != nil {
  224. return nil, err
  225. }
  226. o, err := newOracle(iprog, ptalog, minfo.needs, reflection)
  227. if err != nil {
  228. return nil, err
  229. }
  230. qpos, err := ParseQueryPos(iprog, pos, minfo.needs&needExactPos != 0)
  231. if err != nil && minfo.needs&(needPos|needExactPos) != 0 {
  232. return nil, err
  233. }
  234. // SSA is built and we have the QueryPos.
  235. // Release the other ASTs and type info to the GC.
  236. iprog = nil
  237. return o.query(minfo, qpos)
  238. }
  239. // reduceScope is called for one-shot queries that need only a single
  240. // typed package. It attempts to guess the query package from pos and
  241. // reduce the analysis scope (set of loaded packages) to just that one
  242. // plus (the exported parts of) its dependencies. It leaves its
  243. // arguments unchanged on failure.
  244. //
  245. // TODO(adonovan): this is a real mess... but it's fast.
  246. //
  247. func reduceScope(pos string, conf *loader.Config) {
  248. fqpos, err := fastQueryPos(pos)
  249. if err != nil {
  250. return // bad query
  251. }
  252. // TODO(adonovan): fix: this gives the wrong results for files
  253. // in non-importable packages such as tests and ad-hoc packages
  254. // specified as a list of files (incl. the oracle's tests).
  255. _, importPath, err := guessImportPath(fqpos.fset.File(fqpos.start).Name(), conf.Build)
  256. if err != nil {
  257. return // can't find GOPATH dir
  258. }
  259. if importPath == "" {
  260. return
  261. }
  262. // Check that it's possible to load the queried package.
  263. // (e.g. oracle tests contain different 'package' decls in same dir.)
  264. // Keep consistent with logic in loader/util.go!
  265. cfg2 := *conf.Build
  266. cfg2.CgoEnabled = false
  267. bp, err := cfg2.Import(importPath, "", 0)
  268. if err != nil {
  269. return // no files for package
  270. }
  271. // Check that the queried file appears in the package:
  272. // it might be a '// +build ignore' from an ad-hoc main
  273. // package, e.g. $GOROOT/src/pkg/net/http/triv.go.
  274. if !pkgContainsFile(bp, fqpos.fset.File(fqpos.start).Name()) {
  275. return // not found
  276. }
  277. conf.TypeCheckFuncBodies = func(p string) bool { return p == importPath }
  278. // Ignore packages specified on command line.
  279. conf.CreatePkgs = nil
  280. conf.ImportPkgs = nil
  281. // Instead load just the one containing the query position
  282. // (and possibly its corresponding tests/production code).
  283. // TODO(adonovan): set 'augment' based on which file list
  284. // contains
  285. _ = conf.ImportWithTests(importPath) // ignore error
  286. }
  287. func pkgContainsFile(bp *build.Package, filename string) bool {
  288. for _, files := range [][]string{bp.GoFiles, bp.TestGoFiles, bp.XTestGoFiles} {
  289. for _, file := range files {
  290. if sameFile(file, filename) {
  291. return true
  292. }
  293. }
  294. }
  295. return false
  296. }
  297. // New constructs a new Oracle that can be used for a sequence of queries.
  298. //
  299. // iprog specifies the program to analyze.
  300. // ptalog is the (optional) pointer-analysis log file.
  301. // reflection determines whether to model reflection soundly (currently slow).
  302. //
  303. func New(iprog *loader.Program, ptalog io.Writer, reflection bool) (*Oracle, error) {
  304. return newOracle(iprog, ptalog, needAll, reflection)
  305. }
  306. func newOracle(iprog *loader.Program, ptalog io.Writer, needs int, reflection bool) (*Oracle, error) {
  307. o := &Oracle{fset: iprog.Fset}
  308. // Retain type info for all ASTs in the program.
  309. if needs&needRetainTypeInfo != 0 {
  310. o.typeInfo = iprog.AllPackages
  311. }
  312. // Create SSA package for the initial packages and their dependencies.
  313. if needs&needSSA != 0 {
  314. var mode ssa.BuilderMode
  315. if needs&needSSADebug != 0 {
  316. mode |= ssa.GlobalDebug
  317. }
  318. prog := ssa.Create(iprog, mode)
  319. // For each initial package (specified on the command line),
  320. // if it has a main function, analyze that,
  321. // otherwise analyze its tests, if any.
  322. var testPkgs, mains []*ssa.Package
  323. for _, info := range iprog.InitialPackages() {
  324. initialPkg := prog.Package(info.Pkg)
  325. // Add package to the pointer analysis scope.
  326. if initialPkg.Func("main") != nil {
  327. mains = append(mains, initialPkg)
  328. } else {
  329. testPkgs = append(testPkgs, initialPkg)
  330. }
  331. }
  332. if testPkgs != nil {
  333. if p := prog.CreateTestMainPackage(testPkgs...); p != nil {
  334. mains = append(mains, p)
  335. }
  336. }
  337. if mains == nil {
  338. return nil, fmt.Errorf("analysis scope has no main and no tests")
  339. }
  340. o.ptaConfig.Log = ptalog
  341. o.ptaConfig.Reflection = reflection
  342. o.ptaConfig.Mains = mains
  343. o.prog = prog
  344. }
  345. return o, nil
  346. }
  347. // Query runs the query of the specified mode and selection.
  348. //
  349. // TODO(adonovan): fix: this function does not currently support the
  350. // "what" query, which needs to access the go/build.Context.
  351. //
  352. func (o *Oracle) Query(mode string, qpos *QueryPos) (*Result, error) {
  353. minfo := findMode(mode)
  354. if minfo == nil {
  355. return nil, fmt.Errorf("invalid mode type: %q", mode)
  356. }
  357. return o.query(minfo, qpos)
  358. }
  359. func (o *Oracle) query(minfo *modeInfo, qpos *QueryPos) (*Result, error) {
  360. // Clear out residue of previous query (for long-running clients).
  361. o.ptaConfig.Queries = nil
  362. o.ptaConfig.IndirectQueries = nil
  363. res := &Result{
  364. mode: minfo.name,
  365. fset: o.fset,
  366. }
  367. var err error
  368. res.q, err = minfo.impl(o, qpos)
  369. if err != nil {
  370. return nil, err
  371. }
  372. return res, nil
  373. }
  374. // ParseQueryPos parses the source query position pos.
  375. // If needExact, it must identify a single AST subtree;
  376. // this is appropriate for queries that allow fairly arbitrary syntax,
  377. // e.g. "describe".
  378. //
  379. func ParseQueryPos(iprog *loader.Program, posFlag string, needExact bool) (*QueryPos, error) {
  380. filename, startOffset, endOffset, err := parsePosFlag(posFlag)
  381. if err != nil {
  382. return nil, err
  383. }
  384. start, end, err := findQueryPos(iprog.Fset, filename, startOffset, endOffset)
  385. if err != nil {
  386. return nil, err
  387. }
  388. info, path, exact := iprog.PathEnclosingInterval(start, end)
  389. if path == nil {
  390. return nil, fmt.Errorf("no syntax here")
  391. }
  392. if needExact && !exact {
  393. return nil, fmt.Errorf("ambiguous selection within %s", astutil.NodeDescription(path[0]))
  394. }
  395. return &QueryPos{iprog.Fset, start, end, path, exact, info}, nil
  396. }
  397. // WriteTo writes the oracle query result res to out in a compiler diagnostic format.
  398. func (res *Result) WriteTo(out io.Writer) {
  399. printf := func(pos interface{}, format string, args ...interface{}) {
  400. fprintf(out, res.fset, pos, format, args...)
  401. }
  402. res.q.display(printf)
  403. // Print warnings after the main output.
  404. if res.warnings != nil {
  405. fmt.Fprintln(out, "\nPointer analysis warnings:")
  406. for _, w := range res.warnings {
  407. printf(w.Pos, "warning: "+w.Message)
  408. }
  409. }
  410. }
  411. // ---------- Utilities ----------
  412. // buildSSA constructs the SSA representation of Go-source function bodies.
  413. // Not needed in simpler modes, e.g. freevars.
  414. //
  415. func buildSSA(o *Oracle) {
  416. o.prog.BuildAll()
  417. }
  418. // ptrAnalysis runs the pointer analysis and returns its result.
  419. func ptrAnalysis(o *Oracle) *pointer.Result {
  420. result, err := pointer.Analyze(&o.ptaConfig)
  421. if err != nil {
  422. panic(err) // pointer analysis internal error
  423. }
  424. return result
  425. }
  426. // unparen returns e with any enclosing parentheses stripped.
  427. func unparen(e ast.Expr) ast.Expr {
  428. for {
  429. p, ok := e.(*ast.ParenExpr)
  430. if !ok {
  431. break
  432. }
  433. e = p.X
  434. }
  435. return e
  436. }
  437. // deref returns a pointer's element type; otherwise it returns typ.
  438. func deref(typ types.Type) types.Type {
  439. if p, ok := typ.Underlying().(*types.Pointer); ok {
  440. return p.Elem()
  441. }
  442. return typ
  443. }
  444. // fprintf prints to w a message of the form "location: message\n"
  445. // where location is derived from pos.
  446. //
  447. // pos must be one of:
  448. // - a token.Pos, denoting a position
  449. // - an ast.Node, denoting an interval
  450. // - anything with a Pos() method:
  451. // ssa.Member, ssa.Value, ssa.Instruction, types.Object, pointer.Label, etc.
  452. // - a QueryPos, denoting the extent of the user's query.
  453. // - nil, meaning no position at all.
  454. //
  455. // The output format is is compatible with the 'gnu'
  456. // compilation-error-regexp in Emacs' compilation mode.
  457. // TODO(adonovan): support other editors.
  458. //
  459. func fprintf(w io.Writer, fset *token.FileSet, pos interface{}, format string, args ...interface{}) {
  460. var start, end token.Pos
  461. switch pos := pos.(type) {
  462. case ast.Node:
  463. start = pos.Pos()
  464. end = pos.End()
  465. case token.Pos:
  466. start = pos
  467. end = start
  468. case interface {
  469. Pos() token.Pos
  470. }:
  471. start = pos.Pos()
  472. end = start
  473. case *QueryPos:
  474. start = pos.start
  475. end = pos.end
  476. case nil:
  477. // no-op
  478. default:
  479. panic(fmt.Sprintf("invalid pos: %T", pos))
  480. }
  481. if sp := fset.Position(start); start == end {
  482. // (prints "-: " for token.NoPos)
  483. fmt.Fprintf(w, "%s: ", sp)
  484. } else {
  485. ep := fset.Position(end)
  486. // The -1 below is a concession to Emacs's broken use of
  487. // inclusive (not half-open) intervals.
  488. // Other editors may not want it.
  489. // TODO(adonovan): add an -editor=vim|emacs|acme|auto
  490. // flag; auto uses EMACS=t / VIM=... / etc env vars.
  491. fmt.Fprintf(w, "%s:%d.%d-%d.%d: ",
  492. sp.Filename, sp.Line, sp.Column, ep.Line, ep.Column-1)
  493. }
  494. fmt.Fprintf(w, format, args...)
  495. io.WriteString(w, "\n")
  496. }