internal/godoc: remove analysis
We have never run this code on golang.org.
Change-Id: Iba632f00ea055308c036f389e3af56ab11781832
Reviewed-on: https://go-review.googlesource.com/c/website/+/293423
Trust: Russ Cox <rsc@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
diff --git a/internal/godoc/analysis/README b/internal/godoc/analysis/README
deleted file mode 100644
index d3e732e..0000000
--- a/internal/godoc/analysis/README
+++ /dev/null
@@ -1,111 +0,0 @@
-
-Type and Pointer Analysis to-do list
-====================================
-
-Alan Donovan <adonovan@google.com>
-
-
-Overall design
---------------
-
-We should re-run the type and pointer analyses periodically,
-as we do with the indexer.
-
-Version skew: how to mitigate the bad effects of stale URLs in old pages?
-We could record the file's length/CRC32/mtime in the go/loader, and
-refuse to decorate it with links unless they match at serving time.
-
-Use the VFS mechanism when (a) enumerating packages and (b) loading
-them. (Requires planned changes to go/loader.)
-
-Future work: shard this using map/reduce for larger corpora.
-
-Testing: how does one test that a web page "looks right"?
-
-
-Bugs
-----
-
-(*ssa.Program).Create requires transitively error-free packages. We
-can make this more robust by making the requirement transitively free
-of "hard" errors; soft errors are fine.
-
-Markup of compiler errors is slightly buggy because they overlap with
-other selections (e.g. Idents). Fix.
-
-
-User Interface
---------------
-
-CALLGRAPH:
-- Add a search box: given a search node, expand path from each entry
- point to it.
-- Cause hovering over a given node to highlight that node, and all
- nodes that are logically identical to it.
-- Initially expand the callgraph trees (but not their toggle divs).
-
-CALLEES:
-- The '(' links are not very discoverable. Highlight them?
-
-Type info:
-- In the source viewer's lower pane, use a toggle div around the
- IMPLEMENTS and METHODSETS lists, like we do in the package view.
- Only expand them initially if short.
-- Include IMPLEMENTS and METHOD SETS information in search index.
-- URLs in IMPLEMENTS/METHOD SETS always link to source, even from the
- package docs view. This makes sense for links to non-exported
- types, but links to exported types and funcs should probably go to
- other package docs.
-- Suppress toggle divs for empty method sets.
-
-Misc:
-- The [X] button in the lower pane is subject to scrolling.
-- Should the lower pane be floating? An iframe?
- When we change document.location by clicking on a link, it will go away.
- How do we prevent that (a la Gmail's chat windows)?
-- Progress/status: for each file, display its analysis status, one of:
- - not in analysis scope
- - type analysis running...
- - type analysis complete
- (+ optionally: there were type errors in this file)
- And if PTA requested:
- - type analysis complete; PTA not attempted due to type errors
- - PTA running...
- - PTA complete
-- Scroll the selection into view, e.g. the vertical center, or better
- still, under the pointer (assuming we have a mouse).
-
-
-More features
--------------
-
-Display the REFERRERS relation? (Useful but potentially large.)
-
-Display the INSTANTIATIONS relation? i.e. given a type T, show the set of
-syntactic constructs that can instantiate it:
- var x T
- x := T{...}
- x = new(T)
- x = make([]T, n)
- etc
- + all INSTANTIATIONS of all S defined as struct{t T} or [n]T
-(Potentially a lot of information.)
-(Add this to guru too.)
-
-
-Optimisations
--------------
-
-Each call to addLink takes a (per-file) lock. The locking is
-fine-grained so server latency isn't terrible, but overall it makes
-the link computation quite slow. Batch update might be better.
-
-Memory usage is now about 1.5GB for GOROOT + go.tools. It used to be 700MB.
-
-Optimize for time and space. The main slowdown is the network I/O
-time caused by an increase in page size of about 3x: about 2x from
-HTML, and 0.7--2.1x from JSON (unindented vs indented). The JSON
-contains a lot of filenames (e.g. 820 copies of 16 distinct
-filenames). 20% of the HTML is L%d spans (now disabled). The HTML
-also contains lots of tooltips for long struct/interface types.
-De-dup or just abbreviate? The actual formatting is very fast.
diff --git a/internal/godoc/analysis/analysis.go b/internal/godoc/analysis/analysis.go
deleted file mode 100644
index 94af35f..0000000
--- a/internal/godoc/analysis/analysis.go
+++ /dev/null
@@ -1,613 +0,0 @@
-// 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/website/internal/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
-}
diff --git a/internal/godoc/analysis/callgraph.go b/internal/godoc/analysis/callgraph.go
deleted file mode 100644
index 492022d..0000000
--- a/internal/godoc/analysis/callgraph.go
+++ /dev/null
@@ -1,351 +0,0 @@
-// 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
-
-// This file computes the CALLERS and CALLEES relations from the call
-// graph. CALLERS/CALLEES information is displayed in the lower pane
-// when a "func" token or ast.CallExpr.Lparen is clicked, respectively.
-
-import (
- "fmt"
- "go/ast"
- "go/token"
- "go/types"
- "log"
- "math/big"
- "sort"
-
- "golang.org/x/tools/go/callgraph"
- "golang.org/x/tools/go/ssa"
-)
-
-// doCallgraph computes the CALLEES and CALLERS relations.
-func (a *analysis) doCallgraph(cg *callgraph.Graph) {
- log.Print("Deleting synthetic nodes...")
- // TODO(adonovan): opt: DeleteSyntheticNodes is asymptotically
- // inefficient and can be (unpredictably) slow.
- cg.DeleteSyntheticNodes()
- log.Print("Synthetic nodes deleted")
-
- // Populate nodes of package call graphs (PCGs).
- for _, n := range cg.Nodes {
- a.pcgAddNode(n.Func)
- }
- // Within each PCG, sort funcs by name.
- for _, pcg := range a.pcgs {
- pcg.sortNodes()
- }
-
- calledFuncs := make(map[ssa.CallInstruction]map[*ssa.Function]bool)
- callingSites := make(map[*ssa.Function]map[ssa.CallInstruction]bool)
- for _, n := range cg.Nodes {
- for _, e := range n.Out {
- if e.Site == nil {
- continue // a call from a synthetic node such as <root>
- }
-
- // Add (site pos, callee) to calledFuncs.
- // (Dynamic calls only.)
- callee := e.Callee.Func
-
- a.pcgAddEdge(n.Func, callee)
-
- if callee.Synthetic != "" {
- continue // call of a package initializer
- }
-
- if e.Site.Common().StaticCallee() == nil {
- // dynamic call
- // (CALLEES information for static calls
- // is computed using SSA information.)
- lparen := e.Site.Common().Pos()
- if lparen != token.NoPos {
- fns := calledFuncs[e.Site]
- if fns == nil {
- fns = make(map[*ssa.Function]bool)
- calledFuncs[e.Site] = fns
- }
- fns[callee] = true
- }
- }
-
- // Add (callee, site) to callingSites.
- fns := callingSites[callee]
- if fns == nil {
- fns = make(map[ssa.CallInstruction]bool)
- callingSites[callee] = fns
- }
- fns[e.Site] = true
- }
- }
-
- // CALLEES.
- log.Print("Callees...")
- for site, fns := range calledFuncs {
- var funcs funcsByPos
- for fn := range fns {
- funcs = append(funcs, fn)
- }
- sort.Sort(funcs)
-
- a.addCallees(site, funcs)
- }
-
- // CALLERS
- log.Print("Callers...")
- for callee, sites := range callingSites {
- pos := funcToken(callee)
- if pos == token.NoPos {
- log.Printf("CALLERS: skipping %s: no pos", callee)
- continue
- }
-
- var this *types.Package // for relativizing names
- if callee.Pkg != nil {
- this = callee.Pkg.Pkg
- }
-
- // Compute sites grouped by parent, with text and URLs.
- sitesByParent := make(map[*ssa.Function]sitesByPos)
- for site := range sites {
- fn := site.Parent()
- sitesByParent[fn] = append(sitesByParent[fn], site)
- }
- var funcs funcsByPos
- for fn := range sitesByParent {
- funcs = append(funcs, fn)
- }
- sort.Sort(funcs)
-
- v := callersJSON{
- Callee: callee.String(),
- Callers: []callerJSON{}, // (JS wants non-nil)
- }
- for _, fn := range funcs {
- caller := callerJSON{
- Func: prettyFunc(this, fn),
- Sites: []anchorJSON{}, // (JS wants non-nil)
- }
- sites := sitesByParent[fn]
- sort.Sort(sites)
- for _, site := range sites {
- pos := site.Common().Pos()
- if pos != token.NoPos {
- caller.Sites = append(caller.Sites, anchorJSON{
- Text: fmt.Sprintf("%d", a.prog.Fset.Position(pos).Line),
- Href: a.posURL(pos, len("(")),
- })
- }
- }
- v.Callers = append(v.Callers, caller)
- }
-
- fi, offset := a.fileAndOffset(pos)
- fi.addLink(aLink{
- start: offset,
- end: offset + len("func"),
- title: fmt.Sprintf("%d callers", len(sites)),
- onclick: fmt.Sprintf("onClickCallers(%d)", fi.addData(v)),
- })
- }
-
- // PACKAGE CALLGRAPH
- log.Print("Package call graph...")
- for pkg, pcg := range a.pcgs {
- // Maps (*ssa.Function).RelString() to index in JSON CALLGRAPH array.
- index := make(map[string]int)
-
- // Treat exported functions (and exported methods of
- // exported named types) as roots even if they aren't
- // actually called from outside the package.
- for i, n := range pcg.nodes {
- if i == 0 || n.fn.Object() == nil || !n.fn.Object().Exported() {
- continue
- }
- recv := n.fn.Signature.Recv()
- if recv == nil || deref(recv.Type()).(*types.Named).Obj().Exported() {
- roots := &pcg.nodes[0].edges
- roots.SetBit(roots, i, 1)
- }
- index[n.fn.RelString(pkg.Pkg)] = i
- }
-
- json := a.pcgJSON(pcg)
-
- // TODO(adonovan): pkg.Path() is not unique!
- // It is possible to declare a non-test package called x_test.
- a.result.pkgInfo(pkg.Pkg.Path()).setCallGraph(json, index)
- }
-}
-
-// addCallees adds client data and links for the facts that site calls fns.
-func (a *analysis) addCallees(site ssa.CallInstruction, fns []*ssa.Function) {
- v := calleesJSON{
- Descr: site.Common().Description(),
- Callees: []anchorJSON{}, // (JS wants non-nil)
- }
- var this *types.Package // for relativizing names
- if p := site.Parent().Package(); p != nil {
- this = p.Pkg
- }
-
- for _, fn := range fns {
- v.Callees = append(v.Callees, anchorJSON{
- Text: prettyFunc(this, fn),
- Href: a.posURL(funcToken(fn), len("func")),
- })
- }
-
- fi, offset := a.fileAndOffset(site.Common().Pos())
- fi.addLink(aLink{
- start: offset,
- end: offset + len("("),
- title: fmt.Sprintf("%d callees", len(v.Callees)),
- onclick: fmt.Sprintf("onClickCallees(%d)", fi.addData(v)),
- })
-}
-
-// -- utilities --------------------------------------------------------
-
-// stable order within packages but undefined across packages.
-type funcsByPos []*ssa.Function
-
-func (a funcsByPos) Less(i, j int) bool { return a[i].Pos() < a[j].Pos() }
-func (a funcsByPos) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
-func (a funcsByPos) Len() int { return len(a) }
-
-type sitesByPos []ssa.CallInstruction
-
-func (a sitesByPos) Less(i, j int) bool { return a[i].Common().Pos() < a[j].Common().Pos() }
-func (a sitesByPos) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
-func (a sitesByPos) Len() int { return len(a) }
-
-func funcToken(fn *ssa.Function) token.Pos {
- switch syntax := fn.Syntax().(type) {
- case *ast.FuncLit:
- return syntax.Type.Func
- case *ast.FuncDecl:
- return syntax.Type.Func
- }
- return token.NoPos
-}
-
-// prettyFunc pretty-prints fn for the user interface.
-// TODO(adonovan): return HTML so we have more markup freedom.
-func prettyFunc(this *types.Package, fn *ssa.Function) string {
- if fn.Parent() != nil {
- return fmt.Sprintf("%s in %s",
- types.TypeString(fn.Signature, types.RelativeTo(this)),
- prettyFunc(this, fn.Parent()))
- }
- if fn.Synthetic != "" && fn.Name() == "init" {
- // (This is the actual initializer, not a declared 'func init').
- if fn.Pkg.Pkg == this {
- return "package initializer"
- }
- return fmt.Sprintf("%q package initializer", fn.Pkg.Pkg.Path())
- }
- return fn.RelString(this)
-}
-
-// -- intra-package callgraph ------------------------------------------
-
-// pcgNode represents a node in the package call graph (PCG).
-type pcgNode struct {
- fn *ssa.Function
- pretty string // cache of prettyFunc(fn)
- edges big.Int // set of callee func indices
-}
-
-// A packageCallGraph represents the intra-package edges of the global call graph.
-// The zeroth node indicates "all external functions".
-type packageCallGraph struct {
- nodeIndex map[*ssa.Function]int // maps func to node index (a small int)
- nodes []*pcgNode // maps node index to node
-}
-
-// sortNodes populates pcg.nodes in name order and updates the nodeIndex.
-func (pcg *packageCallGraph) sortNodes() {
- nodes := make([]*pcgNode, 0, len(pcg.nodeIndex))
- nodes = append(nodes, &pcgNode{fn: nil, pretty: "<external>"})
- for fn := range pcg.nodeIndex {
- nodes = append(nodes, &pcgNode{
- fn: fn,
- pretty: prettyFunc(fn.Pkg.Pkg, fn),
- })
- }
- sort.Sort(pcgNodesByPretty(nodes[1:]))
- for i, n := range nodes {
- pcg.nodeIndex[n.fn] = i
- }
- pcg.nodes = nodes
-}
-
-func (pcg *packageCallGraph) addEdge(caller, callee *ssa.Function) {
- var callerIndex int
- if caller.Pkg == callee.Pkg {
- // intra-package edge
- callerIndex = pcg.nodeIndex[caller]
- if callerIndex < 1 {
- panic(caller)
- }
- }
- edges := &pcg.nodes[callerIndex].edges
- edges.SetBit(edges, pcg.nodeIndex[callee], 1)
-}
-
-func (a *analysis) pcgAddNode(fn *ssa.Function) {
- if fn.Pkg == nil {
- return
- }
- pcg, ok := a.pcgs[fn.Pkg]
- if !ok {
- pcg = &packageCallGraph{nodeIndex: make(map[*ssa.Function]int)}
- a.pcgs[fn.Pkg] = pcg
- }
- pcg.nodeIndex[fn] = -1
-}
-
-func (a *analysis) pcgAddEdge(caller, callee *ssa.Function) {
- if callee.Pkg != nil {
- a.pcgs[callee.Pkg].addEdge(caller, callee)
- }
-}
-
-// pcgJSON returns a new slice of callgraph JSON values.
-func (a *analysis) pcgJSON(pcg *packageCallGraph) []*PCGNodeJSON {
- var nodes []*PCGNodeJSON
- for _, n := range pcg.nodes {
-
- // TODO(adonovan): why is there no good way to iterate
- // over the set bits of a big.Int?
- var callees []int
- nbits := n.edges.BitLen()
- for j := 0; j < nbits; j++ {
- if n.edges.Bit(j) == 1 {
- callees = append(callees, j)
- }
- }
-
- var pos token.Pos
- if n.fn != nil {
- pos = funcToken(n.fn)
- }
- nodes = append(nodes, &PCGNodeJSON{
- Func: anchorJSON{
- Text: n.pretty,
- Href: a.posURL(pos, len("func")),
- },
- Callees: callees,
- })
- }
- return nodes
-}
-
-type pcgNodesByPretty []*pcgNode
-
-func (a pcgNodesByPretty) Less(i, j int) bool { return a[i].pretty < a[j].pretty }
-func (a pcgNodesByPretty) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
-func (a pcgNodesByPretty) Len() int { return len(a) }
diff --git a/internal/godoc/analysis/implements.go b/internal/godoc/analysis/implements.go
deleted file mode 100644
index 5a29579..0000000
--- a/internal/godoc/analysis/implements.go
+++ /dev/null
@@ -1,195 +0,0 @@
-// 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
-
-// This file computes the "implements" relation over all pairs of
-// named types in the program. (The mark-up is done by typeinfo.go.)
-
-// TODO(adonovan): do we want to report implements(C, I) where C and I
-// belong to different packages and at least one is not exported?
-
-import (
- "go/types"
- "sort"
-
- "golang.org/x/tools/go/types/typeutil"
-)
-
-// computeImplements computes the "implements" relation over all pairs
-// of named types in allNamed.
-func computeImplements(cache *typeutil.MethodSetCache, allNamed []*types.Named) map[*types.Named]implementsFacts {
- // Information about a single type's method set.
- type msetInfo struct {
- typ types.Type
- mset *types.MethodSet
- mask1, mask2 uint64
- }
-
- initMsetInfo := func(info *msetInfo, typ types.Type) {
- info.typ = typ
- info.mset = cache.MethodSet(typ)
- for i := 0; i < info.mset.Len(); i++ {
- name := info.mset.At(i).Obj().Name()
- info.mask1 |= 1 << methodBit(name[0])
- info.mask2 |= 1 << methodBit(name[len(name)-1])
- }
- }
-
- // satisfies(T, U) reports whether type T satisfies type U.
- // U must be an interface.
- //
- // Since there are thousands of types (and thus millions of
- // pairs of types) and types.Assignable(T, U) is relatively
- // expensive, we compute assignability directly from the
- // method sets. (At least one of T and U must be an
- // interface.)
- //
- // We use a trick (thanks gri!) related to a Bloom filter to
- // quickly reject most tests, which are false. For each
- // method set, we precompute a mask, a set of bits, one per
- // distinct initial byte of each method name. Thus the mask
- // for io.ReadWriter would be {'R','W'}. AssignableTo(T, U)
- // cannot be true unless mask(T)&mask(U)==mask(U).
- //
- // As with a Bloom filter, we can improve precision by testing
- // additional hashes, e.g. using the last letter of each
- // method name, so long as the subset mask property holds.
- //
- // When analyzing the standard library, there are about 1e6
- // calls to satisfies(), of which 0.6% return true. With a
- // 1-hash filter, 95% of calls avoid the expensive check; with
- // a 2-hash filter, this grows to 98.2%.
- satisfies := func(T, U *msetInfo) bool {
- return T.mask1&U.mask1 == U.mask1 &&
- T.mask2&U.mask2 == U.mask2 &&
- containsAllIdsOf(T.mset, U.mset)
- }
-
- // Information about a named type N, and perhaps also *N.
- type namedInfo struct {
- isInterface bool
- base msetInfo // N
- ptr msetInfo // *N, iff N !isInterface
- }
-
- var infos []namedInfo
-
- // Precompute the method sets and their masks.
- for _, N := range allNamed {
- var info namedInfo
- initMsetInfo(&info.base, N)
- _, info.isInterface = N.Underlying().(*types.Interface)
- if !info.isInterface {
- initMsetInfo(&info.ptr, types.NewPointer(N))
- }
-
- if info.base.mask1|info.ptr.mask1 == 0 {
- continue // neither N nor *N has methods
- }
-
- infos = append(infos, info)
- }
-
- facts := make(map[*types.Named]implementsFacts)
-
- // Test all pairs of distinct named types (T, U).
- // TODO(adonovan): opt: compute (U, T) at the same time.
- for t := range infos {
- T := &infos[t]
- var to, from, fromPtr []types.Type
- for u := range infos {
- if t == u {
- continue
- }
- U := &infos[u]
- switch {
- case T.isInterface && U.isInterface:
- if satisfies(&U.base, &T.base) {
- to = append(to, U.base.typ)
- }
- if satisfies(&T.base, &U.base) {
- from = append(from, U.base.typ)
- }
- case T.isInterface: // U concrete
- if satisfies(&U.base, &T.base) {
- to = append(to, U.base.typ)
- } else if satisfies(&U.ptr, &T.base) {
- to = append(to, U.ptr.typ)
- }
- case U.isInterface: // T concrete
- if satisfies(&T.base, &U.base) {
- from = append(from, U.base.typ)
- } else if satisfies(&T.ptr, &U.base) {
- fromPtr = append(fromPtr, U.base.typ)
- }
- }
- }
-
- // Sort types (arbitrarily) to avoid nondeterminism.
- sort.Sort(typesByString(to))
- sort.Sort(typesByString(from))
- sort.Sort(typesByString(fromPtr))
-
- facts[T.base.typ.(*types.Named)] = implementsFacts{to, from, fromPtr}
- }
-
- return facts
-}
-
-type implementsFacts struct {
- to []types.Type // named or ptr-to-named types assignable to interface T
- from []types.Type // named interfaces assignable from T
- fromPtr []types.Type // named interfaces assignable only from *T
-}
-
-type typesByString []types.Type
-
-func (p typesByString) Len() int { return len(p) }
-func (p typesByString) Less(i, j int) bool { return p[i].String() < p[j].String() }
-func (p typesByString) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
-
-// methodBit returns the index of x in [a-zA-Z], or 52 if not found.
-func methodBit(x byte) uint64 {
- switch {
- case 'a' <= x && x <= 'z':
- return uint64(x - 'a')
- case 'A' <= x && x <= 'Z':
- return uint64(26 + x - 'A')
- }
- return 52 // all other bytes
-}
-
-// containsAllIdsOf reports whether the method identifiers of T are a
-// superset of those in U. If U belongs to an interface type, the
-// result is equal to types.Assignable(T, U), but is cheaper to compute.
-//
-// TODO(gri): make this a method of *types.MethodSet.
-//
-func containsAllIdsOf(T, U *types.MethodSet) bool {
- t, tlen := 0, T.Len()
- u, ulen := 0, U.Len()
- for t < tlen && u < ulen {
- tMeth := T.At(t).Obj()
- uMeth := U.At(u).Obj()
- tId := tMeth.Id()
- uId := uMeth.Id()
- if tId > uId {
- // U has a method T lacks: fail.
- return false
- }
- if tId < uId {
- // T has a method U lacks: ignore it.
- t++
- continue
- }
- // U and T both have a method of this Id. Check types.
- if !types.Identical(tMeth.Type(), uMeth.Type()) {
- return false // type mismatch
- }
- u++
- t++
- }
- return u == ulen
-}
diff --git a/internal/godoc/analysis/json.go b/internal/godoc/analysis/json.go
deleted file mode 100644
index f897618..0000000
--- a/internal/godoc/analysis/json.go
+++ /dev/null
@@ -1,69 +0,0 @@
-// 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
-
-// This file defines types used by client-side JavaScript.
-
-type anchorJSON struct {
- Text string // HTML
- Href string // URL
-}
-
-type commOpJSON struct {
- Op anchorJSON
- Fn string
-}
-
-// JavaScript's onClickComm() expects a commJSON.
-type commJSON struct {
- Ops []commOpJSON
-}
-
-// Indicates one of these forms of fact about a type T:
-// T "is implemented by <ByKind> type <Other>" (ByKind != "", e.g. "array")
-// T "implements <Other>" (ByKind == "")
-type implFactJSON struct {
- ByKind string `json:",omitempty"`
- Other anchorJSON
-}
-
-// Implements facts are grouped by form, for ease of reading.
-type implGroupJSON struct {
- Descr string
- Facts []implFactJSON
-}
-
-// JavaScript's onClickIdent() expects a TypeInfoJSON.
-type TypeInfoJSON struct {
- Name string // type name
- Size, Align int64
- Methods []anchorJSON
- ImplGroups []implGroupJSON
-}
-
-// JavaScript's onClickCallees() expects a calleesJSON.
-type calleesJSON struct {
- Descr string
- Callees []anchorJSON // markup for called function
-}
-
-type callerJSON struct {
- Func string
- Sites []anchorJSON
-}
-
-// JavaScript's onClickCallers() expects a callersJSON.
-type callersJSON struct {
- Callee string
- Callers []callerJSON
-}
-
-// JavaScript's cgAddChild requires a global array of PCGNodeJSON
-// called CALLGRAPH, representing the intra-package call graph.
-// The first element is special and represents "all external callers".
-type PCGNodeJSON struct {
- Func anchorJSON
- Callees []int // indices within CALLGRAPH of nodes called by this one
-}
diff --git a/internal/godoc/analysis/peers.go b/internal/godoc/analysis/peers.go
deleted file mode 100644
index a742f06..0000000
--- a/internal/godoc/analysis/peers.go
+++ /dev/null
@@ -1,154 +0,0 @@
-// 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
-
-// This file computes the channel "peers" relation over all pairs of
-// channel operations in the program. The peers are displayed in the
-// lower pane when a channel operation (make, <-, close) is clicked.
-
-// TODO(adonovan): handle calls to reflect.{Select,Recv,Send,Close} too,
-// then enable reflection in PTA.
-
-import (
- "fmt"
- "go/token"
- "go/types"
-
- "golang.org/x/tools/go/pointer"
- "golang.org/x/tools/go/ssa"
-)
-
-func (a *analysis) doChannelPeers(ptsets map[ssa.Value]pointer.Pointer) {
- addSendRecv := func(j *commJSON, op chanOp) {
- j.Ops = append(j.Ops, commOpJSON{
- Op: anchorJSON{
- Text: op.mode,
- Href: a.posURL(op.pos, op.len),
- },
- Fn: prettyFunc(nil, op.fn),
- })
- }
-
- // Build an undirected bipartite multigraph (binary relation)
- // of MakeChan ops and send/recv/close ops.
- //
- // TODO(adonovan): opt: use channel element types to partition
- // the O(n^2) problem into subproblems.
- aliasedOps := make(map[*ssa.MakeChan][]chanOp)
- opToMakes := make(map[chanOp][]*ssa.MakeChan)
- for _, op := range a.ops {
- // Combine the PT sets from all contexts.
- var makes []*ssa.MakeChan // aliased ops
- ptr, ok := ptsets[op.ch]
- if !ok {
- continue // e.g. channel op in dead code
- }
- for _, label := range ptr.PointsTo().Labels() {
- makechan, ok := label.Value().(*ssa.MakeChan)
- if !ok {
- continue // skip intrinsically-created channels for now
- }
- if makechan.Pos() == token.NoPos {
- continue // not possible?
- }
- makes = append(makes, makechan)
- aliasedOps[makechan] = append(aliasedOps[makechan], op)
- }
- opToMakes[op] = makes
- }
-
- // Now that complete relation is built, build links for ops.
- for _, op := range a.ops {
- v := commJSON{
- Ops: []commOpJSON{}, // (JS wants non-nil)
- }
- ops := make(map[chanOp]bool)
- for _, makechan := range opToMakes[op] {
- v.Ops = append(v.Ops, commOpJSON{
- Op: anchorJSON{
- Text: "made",
- Href: a.posURL(makechan.Pos()-token.Pos(len("make")),
- len("make")),
- },
- Fn: makechan.Parent().RelString(op.fn.Package().Pkg),
- })
- for _, op := range aliasedOps[makechan] {
- ops[op] = true
- }
- }
- for op := range ops {
- addSendRecv(&v, op)
- }
-
- // Add links for each aliased op.
- fi, offset := a.fileAndOffset(op.pos)
- fi.addLink(aLink{
- start: offset,
- end: offset + op.len,
- title: "show channel ops",
- onclick: fmt.Sprintf("onClickComm(%d)", fi.addData(v)),
- })
- }
- // Add links for makechan ops themselves.
- for makechan, ops := range aliasedOps {
- v := commJSON{
- Ops: []commOpJSON{}, // (JS wants non-nil)
- }
- for _, op := range ops {
- addSendRecv(&v, op)
- }
-
- fi, offset := a.fileAndOffset(makechan.Pos())
- fi.addLink(aLink{
- start: offset - len("make"),
- end: offset,
- title: "show channel ops",
- onclick: fmt.Sprintf("onClickComm(%d)", fi.addData(v)),
- })
- }
-}
-
-// -- utilities --------------------------------------------------------
-
-// chanOp abstracts an ssa.Send, ssa.Unop(ARROW), close(), or a SelectState.
-// Derived from cmd/guru/peers.go.
-type chanOp struct {
- ch ssa.Value
- mode string // sent|received|closed
- pos token.Pos
- len int
- fn *ssa.Function
-}
-
-// chanOps returns a slice of all the channel operations in the instruction.
-// Derived from cmd/guru/peers.go.
-func chanOps(instr ssa.Instruction) []chanOp {
- fn := instr.Parent()
- var ops []chanOp
- switch instr := instr.(type) {
- case *ssa.UnOp:
- if instr.Op == token.ARROW {
- // TODO(adonovan): don't assume <-ch; could be 'range ch'.
- ops = append(ops, chanOp{instr.X, "received", instr.Pos(), len("<-"), fn})
- }
- case *ssa.Send:
- ops = append(ops, chanOp{instr.Chan, "sent", instr.Pos(), len("<-"), fn})
- case *ssa.Select:
- for _, st := range instr.States {
- mode := "received"
- if st.Dir == types.SendOnly {
- mode = "sent"
- }
- ops = append(ops, chanOp{st.Chan, mode, st.Pos, len("<-"), fn})
- }
- case ssa.CallInstruction:
- call := instr.Common()
- if blt, ok := call.Value.(*ssa.Builtin); ok && blt.Name() == "close" {
- pos := instr.Common().Pos()
- ops = append(ops, chanOp{call.Args[0], "closed", pos - token.Pos(len("close")), len("close("), fn})
- }
- }
- return ops
-}
diff --git a/internal/godoc/analysis/typeinfo.go b/internal/godoc/analysis/typeinfo.go
deleted file mode 100644
index e57683f..0000000
--- a/internal/godoc/analysis/typeinfo.go
+++ /dev/null
@@ -1,234 +0,0 @@
-// 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
-
-// This file computes the markup for information from go/types:
-// IMPORTS, identifier RESOLUTION, METHOD SETS, size/alignment, and
-// the IMPLEMENTS relation.
-//
-// IMPORTS links connect import specs to the documentation for the
-// imported package.
-//
-// RESOLUTION links referring identifiers to their defining
-// identifier, and adds tooltips for kind and type.
-//
-// METHOD SETS, size/alignment, and the IMPLEMENTS relation are
-// displayed in the lower pane when a type's defining identifier is
-// clicked.
-
-import (
- "fmt"
- "go/types"
- "reflect"
- "strconv"
- "strings"
-
- "golang.org/x/tools/go/loader"
- "golang.org/x/tools/go/types/typeutil"
-)
-
-// TODO(adonovan): audit to make sure it's safe on ill-typed packages.
-
-// TODO(adonovan): use same Sizes as loader.Config.
-var sizes = types.StdSizes{WordSize: 8, MaxAlign: 8}
-
-func (a *analysis) doTypeInfo(info *loader.PackageInfo, implements map[*types.Named]implementsFacts) {
- // We must not assume the corresponding SSA packages were
- // created (i.e. were transitively error-free).
-
- // IMPORTS
- for _, f := range info.Files {
- // Package decl.
- fi, offset := a.fileAndOffset(f.Name.Pos())
- fi.addLink(aLink{
- start: offset,
- end: offset + len(f.Name.Name),
- title: "Package docs for " + info.Pkg.Path(),
- // TODO(adonovan): fix: we're putting the untrusted Path()
- // into a trusted field. What's the appropriate sanitizer?
- href: "/pkg/" + info.Pkg.Path(),
- })
-
- // Import specs.
- for _, imp := range f.Imports {
- // Remove quotes.
- L := int(imp.End()-imp.Path.Pos()) - len(`""`)
- path, _ := strconv.Unquote(imp.Path.Value)
- fi, offset := a.fileAndOffset(imp.Path.Pos())
- fi.addLink(aLink{
- start: offset + 1,
- end: offset + 1 + L,
- title: "Package docs for " + path,
- // TODO(adonovan): fix: we're putting the untrusted path
- // into a trusted field. What's the appropriate sanitizer?
- href: "/pkg/" + path,
- })
- }
- }
-
- // RESOLUTION
- qualifier := types.RelativeTo(info.Pkg)
- for id, obj := range info.Uses {
- // Position of the object definition.
- pos := obj.Pos()
- Len := len(obj.Name())
-
- // Correct the position for non-renaming import specs.
- // import "sync/atomic"
- // ^^^^^^^^^^^
- if obj, ok := obj.(*types.PkgName); ok && id.Name == obj.Imported().Name() {
- // Assume this is a non-renaming import.
- // NB: not true for degenerate renamings: `import foo "foo"`.
- pos++
- Len = len(obj.Imported().Path())
- }
-
- if obj.Pkg() == nil {
- continue // don't mark up built-ins.
- }
-
- fi, offset := a.fileAndOffset(id.NamePos)
- fi.addLink(aLink{
- start: offset,
- end: offset + len(id.Name),
- title: types.ObjectString(obj, qualifier),
- href: a.posURL(pos, Len),
- })
- }
-
- // IMPLEMENTS & METHOD SETS
- for _, obj := range info.Defs {
- if obj, ok := obj.(*types.TypeName); ok {
- if named, ok := obj.Type().(*types.Named); ok {
- a.namedType(named, implements)
- }
- }
- }
-}
-
-func (a *analysis) namedType(T *types.Named, implements map[*types.Named]implementsFacts) {
- obj := T.Obj()
- qualifier := types.RelativeTo(obj.Pkg())
- v := &TypeInfoJSON{
- Name: obj.Name(),
- Size: sizes.Sizeof(T),
- Align: sizes.Alignof(T),
- Methods: []anchorJSON{}, // (JS wants non-nil)
- }
-
- // addFact adds the fact "is implemented by T" (by) or
- // "implements T" (!by) to group.
- addFact := func(group *implGroupJSON, T types.Type, by bool) {
- Tobj := deref(T).(*types.Named).Obj()
- var byKind string
- if by {
- // Show underlying kind of implementing type,
- // e.g. "slice", "array", "struct".
- s := reflect.TypeOf(T.Underlying()).String()
- byKind = strings.ToLower(strings.TrimPrefix(s, "*types."))
- }
- group.Facts = append(group.Facts, implFactJSON{
- ByKind: byKind,
- Other: anchorJSON{
- Href: a.posURL(Tobj.Pos(), len(Tobj.Name())),
- Text: types.TypeString(T, qualifier),
- },
- })
- }
-
- // IMPLEMENTS
- if r, ok := implements[T]; ok {
- if isInterface(T) {
- // "T is implemented by <conc>" ...
- // "T is implemented by <iface>"...
- // "T implements <iface>"...
- group := implGroupJSON{
- Descr: types.TypeString(T, qualifier),
- }
- // Show concrete types first; use two passes.
- for _, sub := range r.to {
- if !isInterface(sub) {
- addFact(&group, sub, true)
- }
- }
- for _, sub := range r.to {
- if isInterface(sub) {
- addFact(&group, sub, true)
- }
- }
- for _, super := range r.from {
- addFact(&group, super, false)
- }
- v.ImplGroups = append(v.ImplGroups, group)
- } else {
- // T is concrete.
- if r.from != nil {
- // "T implements <iface>"...
- group := implGroupJSON{
- Descr: types.TypeString(T, qualifier),
- }
- for _, super := range r.from {
- addFact(&group, super, false)
- }
- v.ImplGroups = append(v.ImplGroups, group)
- }
- if r.fromPtr != nil {
- // "*C implements <iface>"...
- group := implGroupJSON{
- Descr: "*" + types.TypeString(T, qualifier),
- }
- for _, psuper := range r.fromPtr {
- addFact(&group, psuper, false)
- }
- v.ImplGroups = append(v.ImplGroups, group)
- }
- }
- }
-
- // METHOD SETS
- for _, sel := range typeutil.IntuitiveMethodSet(T, &a.prog.MethodSets) {
- meth := sel.Obj().(*types.Func)
- pos := meth.Pos() // may be 0 for error.Error
- v.Methods = append(v.Methods, anchorJSON{
- Href: a.posURL(pos, len(meth.Name())),
- Text: types.SelectionString(sel, qualifier),
- })
- }
-
- // Since there can be many specs per decl, we
- // can't attach the link to the keyword 'type'
- // (as we do with 'func'); we use the Ident.
- fi, offset := a.fileAndOffset(obj.Pos())
- fi.addLink(aLink{
- start: offset,
- end: offset + len(obj.Name()),
- title: fmt.Sprintf("type info for %s", obj.Name()),
- onclick: fmt.Sprintf("onClickTypeInfo(%d)", fi.addData(v)),
- })
-
- // Add info for exported package-level types to the package info.
- if obj.Exported() && isPackageLevel(obj) {
- // TODO(adonovan): Path is not unique!
- // It is possible to declare a non-test package called x_test.
- a.result.pkgInfo(obj.Pkg().Path()).addType(v)
- }
-}
-
-// -- utilities --------------------------------------------------------
-
-func isInterface(T types.Type) bool { return types.IsInterface(T) }
-
-// 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
-}
-
-// isPackageLevel reports whether obj is a package-level object.
-func isPackageLevel(obj types.Object) bool {
- return obj.Pkg().Scope().Lookup(obj.Name()) == obj
-}
diff --git a/internal/godoc/corpus.go b/internal/godoc/corpus.go
index 7bdb9aa..153e132 100644
--- a/internal/godoc/corpus.go
+++ b/internal/godoc/corpus.go
@@ -9,7 +9,6 @@
"sync"
"time"
- "golang.org/x/website/internal/godoc/analysis"
"golang.org/x/website/internal/godoc/util"
"golang.org/x/website/internal/godoc/vfs"
)
@@ -99,9 +98,6 @@
// SearchIndex is the search index in use.
searchIndex util.RWValue
- // Analysis is the result of type and pointer analysis.
- Analysis analysis.Result
-
// flag to check whether a corpus is initialized or not
initMu sync.RWMutex
initDone bool
diff --git a/internal/godoc/godoc.go b/internal/godoc/godoc.go
index e981a1c..2a0c05c 100644
--- a/internal/godoc/godoc.go
+++ b/internal/godoc/godoc.go
@@ -18,7 +18,6 @@
"go/format"
"go/printer"
"go/token"
- htmltemplate "html/template"
"io"
"log"
"os"
@@ -95,11 +94,6 @@
"example_name": p.example_nameFunc,
"example_suffix": p.example_suffixFunc,
- // formatting of analysis information
- "callgraph_html": p.callgraph_htmlFunc,
- "implements_html": p.implements_htmlFunc,
- "methodset_html": p.methodset_htmlFunc,
-
// formatting of Notes
"noteTitle": noteTitle,
@@ -413,12 +407,6 @@
IsMain bool // true for package main
IsFiltered bool // true if results were filtered
- // analysis info
- TypeInfoIndex map[string]int // index of JSON datum for type T (if -analysis=type)
- AnalysisData htmltemplate.JS // array of TypeInfoJSON values
- CallGraph htmltemplate.JS // array of PCGNodeJSON values (if -analysis=pointer)
- CallGraphIndex map[string]int // maps func name to index in CallGraph
-
// directory info
Dirs *DirList // nil if no directory information
DirTime time.Time // directory time stamp
@@ -660,64 +648,6 @@
return suffix
}
-// implements_html returns the "> Implements" toggle for a package-level named type.
-// Its contents are populated from JSON data by client-side JS at load time.
-func (p *Presentation) implements_htmlFunc(info *PageInfo, typeName string) string {
- if p.ImplementsHTML == nil {
- return ""
- }
- index, ok := info.TypeInfoIndex[typeName]
- if !ok {
- return ""
- }
- var buf bytes.Buffer
- err := p.ImplementsHTML.Execute(&buf, struct{ Index int }{index})
- if err != nil {
- log.Print(err)
- }
- return buf.String()
-}
-
-// methodset_html returns the "> Method set" toggle for a package-level named type.
-// Its contents are populated from JSON data by client-side JS at load time.
-func (p *Presentation) methodset_htmlFunc(info *PageInfo, typeName string) string {
- if p.MethodSetHTML == nil {
- return ""
- }
- index, ok := info.TypeInfoIndex[typeName]
- if !ok {
- return ""
- }
- var buf bytes.Buffer
- err := p.MethodSetHTML.Execute(&buf, struct{ Index int }{index})
- if err != nil {
- log.Print(err)
- }
- return buf.String()
-}
-
-// callgraph_html returns the "> Call graph" toggle for a package-level func.
-// Its contents are populated from JSON data by client-side JS at load time.
-func (p *Presentation) callgraph_htmlFunc(info *PageInfo, recv, name string) string {
- if p.CallGraphHTML == nil {
- return ""
- }
- if recv != "" {
- // Format must match (*ssa.Function).RelString().
- name = fmt.Sprintf("(%s).%s", recv, name)
- }
- index, ok := info.CallGraphIndex[name]
- if !ok {
- return ""
- }
- var buf bytes.Buffer
- err := p.CallGraphHTML.Execute(&buf, struct{ Index int }{index})
- if err != nil {
- log.Print(err)
- }
- return buf.String()
-}
-
func noteTitle(note string) string {
return strings.Title(strings.ToLower(note))
}
diff --git a/internal/godoc/page.go b/internal/godoc/page.go
index 5833a89..a892a5a 100644
--- a/internal/godoc/page.go
+++ b/internal/godoc/page.go
@@ -23,7 +23,6 @@
Query string
Body []byte
GoogleCN bool // page is being served from golang.google.cn
- TreeView bool // page needs to contain treeview related js and css
// filled in by ServePage
SearchBox bool
diff --git a/internal/godoc/pres.go b/internal/godoc/pres.go
index fb17a42..bb0057b 100644
--- a/internal/godoc/pres.go
+++ b/internal/godoc/pres.go
@@ -25,13 +25,10 @@
cmdHandler handlerServer
pkgHandler handlerServer
- CallGraphHTML,
DirlistHTML,
ErrorHTML,
ExampleHTML,
GodocHTML,
- ImplementsHTML,
- MethodSetHTML,
PackageHTML,
PackageRootHTML,
SearchHTML,
diff --git a/internal/godoc/server.go b/internal/godoc/server.go
index a8e0e8d..52178ed 100644
--- a/internal/godoc/server.go
+++ b/internal/godoc/server.go
@@ -14,7 +14,6 @@
"go/doc"
"go/token"
htmlpkg "html"
- htmltemplate "html/template"
"io"
"io/ioutil"
"log"
@@ -27,7 +26,6 @@
"text/template"
"time"
- "golang.org/x/website/internal/godoc/analysis"
"golang.org/x/website/internal/godoc/util"
"golang.org/x/website/internal/godoc/vfs"
)
@@ -317,17 +315,6 @@
tabtitle = "Commands"
}
- // Emit JSON array for type information.
- pi := h.c.Analysis.PackageInfo(relpath)
- hasTreeView := len(pi.CallGraph) != 0
- info.CallGraphIndex = pi.CallGraphIndex
- info.CallGraph = htmltemplate.JS(marshalJSON(pi.CallGraph))
- info.AnalysisData = htmltemplate.JS(marshalJSON(pi.Types))
- info.TypeInfoIndex = make(map[string]int)
- for i, ti := range pi.Types {
- info.TypeInfoIndex[ti.Name] = i
- }
-
info.GoogleCN = googleCN(r)
var body []byte
if info.Dirname == "/src" {
@@ -341,7 +328,6 @@
Subtitle: subtitle,
Body: body,
GoogleCN: info.GoogleCN,
- TreeView: hasTreeView,
})
}
@@ -583,20 +569,8 @@
var buf bytes.Buffer
if pathpkg.Ext(abspath) == ".go" {
- // Find markup links for this file (e.g. "/src/fmt/print.go").
- fi := p.Corpus.Analysis.FileInfo(abspath)
- buf.WriteString("<script type='text/javascript'>document.ANALYSIS_DATA = ")
- buf.Write(marshalJSON(fi.Data))
- buf.WriteString(";</script>\n")
-
- if status := p.Corpus.Analysis.Status(); status != "" {
- buf.WriteString("<a href='/lib/godoc/analysis/help.html'>Static analysis features</a> ")
- // TODO(adonovan): show analysis status at per-file granularity.
- fmt.Fprintf(&buf, "<span style='color: grey'>[%s]</span><br/>", htmlpkg.EscapeString(status))
- }
-
buf.WriteString("<pre>")
- formatGoSource(&buf, src, fi.Links, h, s)
+ formatGoSource(&buf, src, h, s)
buf.WriteString("</pre>")
} else {
buf.WriteString("<pre>")
@@ -614,34 +588,18 @@
})
}
-// formatGoSource HTML-escapes Go source text and writes it to w,
-// decorating it with the specified analysis links.
-//
-func formatGoSource(buf *bytes.Buffer, text []byte, links []analysis.Link, pattern string, selection Selection) {
+// formatGoSource HTML-escapes Go source text and writes it to w.
+func formatGoSource(buf *bytes.Buffer, text []byte, pattern string, selection Selection) {
// Emit to a temp buffer so that we can add line anchors at the end.
saved, buf := buf, new(bytes.Buffer)
- var i int
- var link analysis.Link // shared state of the two funcs below
- segmentIter := func() (seg Segment) {
- if i < len(links) {
- link = links[i]
- i++
- seg = Segment{link.Start(), link.End()}
- }
- return
- }
- linkWriter := func(w io.Writer, offs int, start bool) {
- link.Write(w, offs, start)
- }
-
comments := tokenSelection(text, token.COMMENT)
var highlights Selection
if pattern != "" {
highlights = regexpSelection(text, pattern)
}
- FormatSelections(buf, text, linkWriter, segmentIter, selectionTag, comments, highlights, selection)
+ FormatSelections(buf, text, nil, nil, selectionTag, comments, highlights, selection)
// Now copy buf to saved, adding line anchors.