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.

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