go.tools/oracle: an oracle that answers questions about Go source code.
+ Tests. + Emacs integration. + Emacs integration test. + very rudimentary Vim integration. Needs some love from a Vim user. TODO (in follow-ups): - More tests would be good. We'll need to make the output order deterministic in more places. - Documentation. R=gri, crawshaw, dominik.honnef CC=golang-dev https://golang.org/cl/9502043
This commit is contained in:
parent
be2647ec01
commit
e08d89f3ed
|
|
@ -0,0 +1,50 @@
|
||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Simple test of Go oracle/Emacs integration.
|
||||||
|
# Requires that GOROOT and GOPATH are set.
|
||||||
|
# Side effect: builds and installs oracle in $GOROOT.
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
[ -z "$GOROOT" ] && { echo "Error: GOROOT is unset." >&2; exit 1; }
|
||||||
|
[ -z "$GOPATH" ] && { echo "Error: GOPATH is unset." >&2; exit 1; }
|
||||||
|
|
||||||
|
log=/tmp/$(basename $0)-$$.log
|
||||||
|
thisdir=$(dirname $0)
|
||||||
|
|
||||||
|
function die() {
|
||||||
|
echo "Error: $@."
|
||||||
|
cat $log
|
||||||
|
exit 1
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
trap "rm -f $log" EXIT
|
||||||
|
|
||||||
|
# Build and install oracle.
|
||||||
|
go get code.google.com/p/go.tools/cmd/oracle || die "'go get' failed"
|
||||||
|
mv -f $GOPATH/bin/oracle $GOROOT/bin/
|
||||||
|
$GOROOT/bin/oracle >$log 2>&1 || true # (prints usage and exits 1)
|
||||||
|
grep -q "Usage: oracle" $log || die "$GOROOT/bin/oracle not installed"
|
||||||
|
|
||||||
|
|
||||||
|
# Run Emacs, set the scope to the oracle tool itself,
|
||||||
|
# load ./main.go, and describe the "fmt" import.
|
||||||
|
emacs --batch --no-splash --no-window-system --no-init \
|
||||||
|
--load $GOROOT/misc/emacs/go-mode.el \
|
||||||
|
--load $thisdir/oracle.el \
|
||||||
|
--eval '
|
||||||
|
(progn
|
||||||
|
(setq go-oracle-scope "code.google.com/p/go.tools/cmd/oracle")
|
||||||
|
(find-file "'$thisdir'/main.go")
|
||||||
|
(search-forward "\"fmt\"")
|
||||||
|
(backward-char)
|
||||||
|
(go-oracle-describe)
|
||||||
|
(princ (with-current-buffer "*go-oracle*"
|
||||||
|
(buffer-substring-no-properties (point-min) (point-max))))
|
||||||
|
(kill-emacs 0))
|
||||||
|
' main.go >$log 2>&1 || die "emacs command failed"
|
||||||
|
|
||||||
|
# Check that Println is mentioned.
|
||||||
|
grep -q "fmt/print.go.*func Println" $log || die "didn't find expected lines in log; got:"
|
||||||
|
|
||||||
|
echo "PASS"
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
// oracle: a tool for answering questions about Go source code.
|
||||||
|
//
|
||||||
|
// Each query prints its results to the standard output in an
|
||||||
|
// editor-friendly format. Currently this is just text in a generic
|
||||||
|
// compiler diagnostic format, but in future we could provide
|
||||||
|
// sexpr/json/python formats for the raw data so that editors can
|
||||||
|
// provide more sophisticated UIs.
|
||||||
|
//
|
||||||
|
// Every line of output is of the form "pos: text", where pos = "-" if unknown.
|
||||||
|
//
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"runtime/pprof"
|
||||||
|
|
||||||
|
"code.google.com/p/go.tools/oracle"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO(adonovan): use a format that permits spaces in filenames, and
|
||||||
|
// doesn't require shell quoting.
|
||||||
|
var posFlag = flag.String("pos", "",
|
||||||
|
"Filename and offset or extent of a syntax element about which to query, "+
|
||||||
|
"e.g. 'foo.go 123-456', 'bar.go 123'.")
|
||||||
|
|
||||||
|
var modeFlag = flag.String("mode", "",
|
||||||
|
"Mode of query to perform: callers, callees, callstack, callgraph, describe.")
|
||||||
|
|
||||||
|
var ptalogFlag = flag.String("ptalog", "pta.log",
|
||||||
|
"Location of the points-to analysis log file, or empty to disable logging.")
|
||||||
|
|
||||||
|
const usage = `Go source code oracle.
|
||||||
|
Usage: oracle [<flag> ...] [<file.go> ...] [<arg> ...]
|
||||||
|
Use -help flag to display options.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
% oracle -pos 'hello.go 123' hello.go
|
||||||
|
% oracle -pos 'hello.go 123 456' hello.go
|
||||||
|
`
|
||||||
|
|
||||||
|
var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
|
||||||
|
|
||||||
|
// TODO(adonovan): the caller must---before go/build.init
|
||||||
|
// runs---specify CGO_ENABLED=0, which entails the "!cgo" go/build
|
||||||
|
// tag, preferring (dummy) Go to native C implementations of
|
||||||
|
// cgoLookupHost et al.
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// If $GOMAXPROCS isn't set, use the full capacity of the machine.
|
||||||
|
// For small machines, use at least 4 threads.
|
||||||
|
if os.Getenv("GOMAXPROCS") == "" {
|
||||||
|
n := runtime.NumCPU()
|
||||||
|
if n < 4 {
|
||||||
|
n = 4
|
||||||
|
}
|
||||||
|
runtime.GOMAXPROCS(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
args := flag.Args()
|
||||||
|
|
||||||
|
if len(args) == 0 {
|
||||||
|
fmt.Fprint(os.Stderr, usage)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up points-to analysis log file.
|
||||||
|
var ptalog io.Writer
|
||||||
|
if *ptalogFlag != "" {
|
||||||
|
if f, err := os.Create(*ptalogFlag); err != nil {
|
||||||
|
log.Fatalf(err.Error())
|
||||||
|
} else {
|
||||||
|
buf := bufio.NewWriter(f)
|
||||||
|
ptalog = buf
|
||||||
|
defer func() {
|
||||||
|
buf.Flush()
|
||||||
|
f.Close()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Profiling support.
|
||||||
|
if *cpuprofile != "" {
|
||||||
|
f, err := os.Create(*cpuprofile)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
pprof.StartCPUProfile(f)
|
||||||
|
defer pprof.StopCPUProfile()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := oracle.Main(args, *modeFlag, *posFlag, ptalog, os.Stdout, nil); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,180 @@
|
||||||
|
;;;
|
||||||
|
;;; Integration of the Go 'oracle' analysis tool into Emacs.
|
||||||
|
;;;
|
||||||
|
;;; To install the Go oracle, run:
|
||||||
|
;;; % export GOROOT=... GOPATH=...
|
||||||
|
;;; % go get code.google.com/p/go.tools/cmd/oracle
|
||||||
|
;;; % mv $GOPATH/bin/oracle $GOROOT/bin/
|
||||||
|
;;;
|
||||||
|
;;; Load this file into Emacs and set go-oracle-scope to your
|
||||||
|
;;; configuration. Then, find a file of Go source code, select an
|
||||||
|
;;; expression of interest, and press F4 (for "describe") or run one
|
||||||
|
;;; of the other go-oracle-xxx commands.
|
||||||
|
;;;
|
||||||
|
;;; TODO(adonovan): simplify installation and configuration by making
|
||||||
|
;;; oracle a subcommand of 'go tool'.
|
||||||
|
|
||||||
|
(require 'compile)
|
||||||
|
(require 'go-mode)
|
||||||
|
(require 'cl)
|
||||||
|
|
||||||
|
(defgroup go-oracle nil
|
||||||
|
"Options specific to the Go oracle."
|
||||||
|
:group 'go)
|
||||||
|
|
||||||
|
(defcustom go-oracle-command (concat (car (go-root-and-paths)) "/bin/oracle")
|
||||||
|
"The Go oracle command; the default is $GOROOT/bin/oracle."
|
||||||
|
:type 'string
|
||||||
|
:group 'go-oracle)
|
||||||
|
|
||||||
|
(defcustom go-oracle-scope ""
|
||||||
|
"The scope of the analysis. See `go-oracle-set-scope'."
|
||||||
|
:type 'string
|
||||||
|
:group 'go-oracle)
|
||||||
|
|
||||||
|
(defvar go-oracle--scope-history
|
||||||
|
nil
|
||||||
|
"History of values supplied to `go-oracle-set-scope'.")
|
||||||
|
|
||||||
|
(defun go-oracle-set-scope ()
|
||||||
|
"Sets the scope for the Go oracle, prompting the user to edit the
|
||||||
|
previous scope.
|
||||||
|
|
||||||
|
The scope specifies a set of arguments, separated by spaces.
|
||||||
|
It may be:
|
||||||
|
1) a set of packages whose main() functions will be analyzed.
|
||||||
|
2) a list of *.go filenames; they will treated like as a single
|
||||||
|
package (see #3).
|
||||||
|
3) a single package whose main() function and/or Test* functions
|
||||||
|
will be analyzed.
|
||||||
|
|
||||||
|
In the common case, this is similar to the argument(s) you would
|
||||||
|
specify to 'go build'."
|
||||||
|
(interactive)
|
||||||
|
(let ((scope (read-from-minibuffer "Go oracle scope: "
|
||||||
|
go-oracle-scope
|
||||||
|
nil
|
||||||
|
nil
|
||||||
|
'go-oracle--scope-history)))
|
||||||
|
(if (string-equal "" scope)
|
||||||
|
(error "You must specify a non-empty scope for the Go oracle"))
|
||||||
|
(setq go-oracle-scope scope)))
|
||||||
|
|
||||||
|
(defun go-oracle--run (mode)
|
||||||
|
"Run the Go oracle in the specified MODE, passing it the
|
||||||
|
selected region of the current buffer. Process the output to
|
||||||
|
replace each file name with a small hyperlink. Display the
|
||||||
|
result."
|
||||||
|
(if (not buffer-file-name)
|
||||||
|
(error "Cannot use oracle on a buffer without a file name"))
|
||||||
|
;; It's not sufficient to save a modified buffer since if
|
||||||
|
;; gofmt-before-save is on the before-save-hook, saving will
|
||||||
|
;; disturb the selected region.
|
||||||
|
(if (buffer-modified-p)
|
||||||
|
(error "Please save the buffer before invoking go-oracle"))
|
||||||
|
(if (string-equal "" go-oracle-scope)
|
||||||
|
(go-oracle-set-scope))
|
||||||
|
(let* ((filename (file-truename buffer-file-name))
|
||||||
|
(pos (if (use-region-p)
|
||||||
|
(format "%s-%s"
|
||||||
|
(1- (go--position-bytes (region-beginning)))
|
||||||
|
(1- (go--position-bytes (region-end))))
|
||||||
|
(format "%s" (1- (position-bytes (point))))))
|
||||||
|
;; This would be simpler if we could just run 'go tool oracle'.
|
||||||
|
(env-vars (go-root-and-paths))
|
||||||
|
(goroot-env (concat "GOROOT=" (car env-vars)))
|
||||||
|
(gopath-env (concat "GOPATH=" (mapconcat #'identity (cdr env-vars) ":"))))
|
||||||
|
(with-current-buffer (get-buffer-create "*go-oracle*")
|
||||||
|
(setq buffer-read-only nil)
|
||||||
|
(erase-buffer)
|
||||||
|
(insert "Go Oracle\n")
|
||||||
|
(let ((args (append (list go-oracle-command nil t nil
|
||||||
|
"-ptalog=" ; avoid writing the huge log
|
||||||
|
(format "-pos=%s %s" filename pos)
|
||||||
|
(format "-mode=%s" mode))
|
||||||
|
(split-string go-oracle-scope " " t))))
|
||||||
|
;; Log the command to *Messages*, for debugging.
|
||||||
|
(message "Command: %s:" args)
|
||||||
|
(message nil) ; clears/shrinks minibuffer
|
||||||
|
|
||||||
|
(message "Running oracle...")
|
||||||
|
;; Use dynamic binding to modify/restore the environment
|
||||||
|
(let ((process-environment (list* goroot-env gopath-env "CGO_ENABLED=0" process-environment)))
|
||||||
|
(apply #'call-process args)))
|
||||||
|
(insert "\n")
|
||||||
|
(compilation-mode)
|
||||||
|
(setq compilation-error-screen-columns nil)
|
||||||
|
(let ((w (display-buffer (current-buffer))))
|
||||||
|
(balance-windows)
|
||||||
|
(shrink-window-if-larger-than-buffer w)
|
||||||
|
(set-window-point w (point-min)))
|
||||||
|
|
||||||
|
;; Hide the file/line info to save space.
|
||||||
|
;; Replace each with a little widget.
|
||||||
|
;; compilation-mode + this loop = slooow.
|
||||||
|
;; TODO(adonovan): have oracle give us an S-expression
|
||||||
|
;; and we'll do the markup directly.
|
||||||
|
(let ((buffer-read-only nil)
|
||||||
|
(p 1))
|
||||||
|
(while (not (null p))
|
||||||
|
(let ((np (compilation-next-single-property-change p 'compilation-message)))
|
||||||
|
(message "Post-processing link (%d%%)" (/ (* p 100) (point-max)))
|
||||||
|
(if np
|
||||||
|
(when (equal (line-number-at-pos p) (line-number-at-pos np))
|
||||||
|
;; np is (typically) the space following ":"; consume it too.
|
||||||
|
(put-text-property p np 'display "▶")
|
||||||
|
(goto-char np)
|
||||||
|
(insert " ")))
|
||||||
|
(setq p np)))
|
||||||
|
(message nil)))))
|
||||||
|
|
||||||
|
(defun go-oracle-callees ()
|
||||||
|
"Show possible callees of the function call at the current point."
|
||||||
|
(interactive)
|
||||||
|
(go-oracle--run "callees"))
|
||||||
|
|
||||||
|
(defun go-oracle-callers ()
|
||||||
|
"Show the set of callers of the function containing the current point."
|
||||||
|
(interactive)
|
||||||
|
(go-oracle--run "callers"))
|
||||||
|
|
||||||
|
(defun go-oracle-callgraph ()
|
||||||
|
"Show the callgraph of the current program."
|
||||||
|
(interactive)
|
||||||
|
(go-oracle--run "callgraph"))
|
||||||
|
|
||||||
|
(defun go-oracle-callstack ()
|
||||||
|
"Show an arbitrary path from a root of the call graph to the
|
||||||
|
function containing the current point."
|
||||||
|
(interactive)
|
||||||
|
(go-oracle--run "callstack"))
|
||||||
|
|
||||||
|
(defun go-oracle-describe ()
|
||||||
|
"Describe the expression at the current point."
|
||||||
|
(interactive)
|
||||||
|
(go-oracle--run "describe"))
|
||||||
|
|
||||||
|
(defun go-oracle-implements ()
|
||||||
|
"Describe the 'implements' relation for types in the package
|
||||||
|
containing the current point."
|
||||||
|
(interactive)
|
||||||
|
(go-oracle--run "implements"))
|
||||||
|
|
||||||
|
(defun go-oracle-freevars ()
|
||||||
|
"Enumerate the free variables of the current selection."
|
||||||
|
(interactive)
|
||||||
|
(go-oracle--run "freevars"))
|
||||||
|
|
||||||
|
(defun go-oracle-channel-peers ()
|
||||||
|
"Enumerate the set of possible corresponding sends/receives for
|
||||||
|
this channel receive/send operation."
|
||||||
|
(interactive)
|
||||||
|
(go-oracle--run "peers"))
|
||||||
|
|
||||||
|
;; TODO(adonovan): don't mutate the keymap; just document how users
|
||||||
|
;; can do this themselves. But that means freezing the API, so don't
|
||||||
|
;; do that yet; wait till v1.0.
|
||||||
|
(add-hook 'go-mode-hook
|
||||||
|
#'(lambda () (local-set-key (kbd "<f4>") #'go-oracle-describe)))
|
||||||
|
|
||||||
|
(provide 'go-oracle)
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
" -*- text -*-
|
||||||
|
" oracle.vim -- Vim integration for the Go oracle.
|
||||||
|
"
|
||||||
|
" Load with (e.g.) :source oracle.vim
|
||||||
|
" Call with (e.g.) :GoOracleDescribe
|
||||||
|
" while cursor or selection is over syntax of interest.
|
||||||
|
" Run :copen to show the quick-fix file.
|
||||||
|
"
|
||||||
|
" This is an absolutely rudimentary integration of the Go Oracle into
|
||||||
|
" Vim's quickfix mechanism and it needs a number of usability
|
||||||
|
" improvements before it can be practically useful to Vim users.
|
||||||
|
" Voluntary contributions welcomed!
|
||||||
|
"
|
||||||
|
" TODO(adonovan):
|
||||||
|
" - prompt/save the buffer if modified.
|
||||||
|
" - reject buffers with no filename.
|
||||||
|
" - hide all filenames in quickfix buffer.
|
||||||
|
|
||||||
|
|
||||||
|
" Users should customize this to their analysis scope, e.g. main package(s).
|
||||||
|
let s:scope = "/home/adonovan/go3/got/d.go"
|
||||||
|
|
||||||
|
" The path to the Go oracle executable.
|
||||||
|
let s:go_oracle = "$GOROOT/bin/oracle"
|
||||||
|
|
||||||
|
" Enable Vim to recognize GNU-style 'file:line.col-line.col: message' format.
|
||||||
|
set errorformat+=%f:%l.%c-%*[0-9].%*[0-9]:\ %m
|
||||||
|
|
||||||
|
func! s:RunOracle(mode) abort
|
||||||
|
let s:pos = line2byte(line("."))+col(".")
|
||||||
|
let s:errfile = tempname()
|
||||||
|
let s:cmd = printf("!%s -ptalog= -mode=%s '-pos=%s %d' %s >%s",
|
||||||
|
\ s:go_oracle, a:mode, bufname(""), s:pos, s:scope, s:errfile)
|
||||||
|
execute s:cmd
|
||||||
|
execute "cfile " . s:errfile
|
||||||
|
endfun
|
||||||
|
|
||||||
|
" Describe the expression at the current point.
|
||||||
|
command! GoOracleDescribe
|
||||||
|
\ call s:RunOracle("describe")
|
||||||
|
|
||||||
|
" Show possible callees of the function call at the current point.
|
||||||
|
command! GoOracleCallees
|
||||||
|
\ call s:RunOracle("callees")
|
||||||
|
|
||||||
|
" Show the set of callers of the function containing the current point.
|
||||||
|
command! GoOracleCallers
|
||||||
|
\ call s:RunOracle("callers")
|
||||||
|
|
||||||
|
" Show the callgraph of the current program.
|
||||||
|
command! GoOracleCallgraph
|
||||||
|
\ call s:RunOracle("callgraph")
|
||||||
|
|
||||||
|
" Describe the 'implements' relation for types in the
|
||||||
|
" package containing the current point.
|
||||||
|
command! GoOracleImplements
|
||||||
|
\ call s:RunOracle("implements")
|
||||||
|
|
||||||
|
" Enumerate the set of possible corresponding sends/receives for
|
||||||
|
" this channel receive/send operation.
|
||||||
|
command! GoOracleChannelPeers
|
||||||
|
\ call s:RunOracle("peers")
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
package oracle
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/ast"
|
||||||
|
|
||||||
|
"code.google.com/p/go.tools/go/types"
|
||||||
|
"code.google.com/p/go.tools/pointer"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Callees reports the possible callees of the function call site
|
||||||
|
// identified by the specified source location.
|
||||||
|
//
|
||||||
|
// TODO(adonovan): if a callee is a wrapper, show the callee's callee.
|
||||||
|
//
|
||||||
|
func callees(o *oracle) (queryResult, error) {
|
||||||
|
// Determine the enclosing call for the specified position.
|
||||||
|
var call *ast.CallExpr
|
||||||
|
for _, n := range o.queryPath {
|
||||||
|
if call, _ = n.(*ast.CallExpr); call != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if call == nil {
|
||||||
|
return nil, o.errorf(o.queryPath[0], "there is no function call here")
|
||||||
|
}
|
||||||
|
// TODO(adonovan): issue an error if the call is "too far
|
||||||
|
// away" from the current selection, as this most likely is
|
||||||
|
// not what the user intended.
|
||||||
|
|
||||||
|
// Reject type conversions.
|
||||||
|
if o.queryPkgInfo.IsType(call.Fun) {
|
||||||
|
return nil, o.errorf(call, "this is a type conversion, not a function call")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reject calls to built-ins.
|
||||||
|
if b, ok := o.queryPkgInfo.TypeOf(call.Fun).(*types.Builtin); ok {
|
||||||
|
return nil, o.errorf(call, "this is a call to the built-in '%s' operator", b.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
buildSSA(o)
|
||||||
|
|
||||||
|
// Compute the subgraph of the callgraph for callsite(s)
|
||||||
|
// arising from 'call'. There may be more than one if its
|
||||||
|
// enclosing function was treated context-sensitively.
|
||||||
|
// (Or zero if it was in dead code.)
|
||||||
|
//
|
||||||
|
// The presence of a key indicates this call site is
|
||||||
|
// interesting even if the value is nil.
|
||||||
|
querySites := make(map[pointer.CallSite][]pointer.CallGraphNode)
|
||||||
|
o.config.CallSite = func(site pointer.CallSite) {
|
||||||
|
if site.Pos() == call.Lparen {
|
||||||
|
// Not a no-op! Ensures key is
|
||||||
|
// present even if value is nil:
|
||||||
|
querySites[site] = querySites[site]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
o.config.Call = func(site pointer.CallSite, caller, callee pointer.CallGraphNode) {
|
||||||
|
if targets, ok := querySites[site]; ok {
|
||||||
|
querySites[site] = append(targets, callee)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ptrAnalysis(o)
|
||||||
|
|
||||||
|
return &calleesResult{
|
||||||
|
call: call,
|
||||||
|
querySites: querySites,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type calleesResult struct {
|
||||||
|
call *ast.CallExpr
|
||||||
|
querySites map[pointer.CallSite][]pointer.CallGraphNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *calleesResult) display(o *oracle) {
|
||||||
|
// Print the set of discovered call edges.
|
||||||
|
if len(r.querySites) == 0 {
|
||||||
|
// e.g. it appears within "if false {...}" or within a dead function.
|
||||||
|
o.printf(r.call.Lparen, "this call site is unreachable in this analysis")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(adonovan): sort, to ensure test determinism.
|
||||||
|
// TODO(adonovan): compute union of callees across all contexts.
|
||||||
|
for site, callees := range r.querySites {
|
||||||
|
if callees == nil {
|
||||||
|
// dynamic call on a provably nil func/interface
|
||||||
|
o.printf(site, "%s on nil value", site.Description())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(adonovan): sort, to ensure test determinism.
|
||||||
|
o.printf(site, "this %s dispatches to:", site.Description())
|
||||||
|
for _, callee := range callees {
|
||||||
|
o.printf(callee.Func(), "\t%s", callee.Func())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
package oracle
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.google.com/p/go.tools/pointer"
|
||||||
|
"code.google.com/p/go.tools/ssa"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Callers reports the possible callers of the function
|
||||||
|
// immediately enclosing the specified source location.
|
||||||
|
//
|
||||||
|
// TODO(adonovan): if a caller is a wrapper, show the caller's caller.
|
||||||
|
//
|
||||||
|
func callers(o *oracle) (queryResult, error) {
|
||||||
|
pkg := o.prog.Package(o.queryPkgInfo.Pkg)
|
||||||
|
if pkg == nil {
|
||||||
|
return nil, o.errorf(o.queryPath[0], "no SSA package")
|
||||||
|
}
|
||||||
|
if !ssa.HasEnclosingFunction(pkg, o.queryPath) {
|
||||||
|
return nil, o.errorf(o.queryPath[0], "this position is not inside a function")
|
||||||
|
}
|
||||||
|
|
||||||
|
buildSSA(o)
|
||||||
|
|
||||||
|
target := ssa.EnclosingFunction(pkg, o.queryPath)
|
||||||
|
if target == nil {
|
||||||
|
return nil, o.errorf(o.queryPath[0], "no SSA function built for this location (dead code?)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the pointer analysis, recording each
|
||||||
|
// call found to originate from target.
|
||||||
|
var calls []callersCall
|
||||||
|
o.config.Call = func(site pointer.CallSite, caller, callee pointer.CallGraphNode) {
|
||||||
|
if callee.Func() == target {
|
||||||
|
calls = append(calls, callersCall{site, caller})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
root := ptrAnalysis(o)
|
||||||
|
|
||||||
|
return &callersResult{
|
||||||
|
target: target,
|
||||||
|
root: root,
|
||||||
|
calls: calls,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type callersResult struct {
|
||||||
|
target *ssa.Function
|
||||||
|
root pointer.CallGraphNode
|
||||||
|
calls []callersCall
|
||||||
|
}
|
||||||
|
|
||||||
|
type callersCall struct {
|
||||||
|
site pointer.CallSite
|
||||||
|
caller pointer.CallGraphNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *callersResult) display(o *oracle) {
|
||||||
|
if r.calls == nil {
|
||||||
|
o.printf(r.target, "%s is not reachable in this program.", r.target)
|
||||||
|
} else {
|
||||||
|
o.printf(r.target, "%s is called from these %d sites:", r.target, len(r.calls))
|
||||||
|
// TODO(adonovan): sort, to ensure test determinism.
|
||||||
|
for _, call := range r.calls {
|
||||||
|
if call.caller == r.root {
|
||||||
|
o.printf(r.target, "the root of the call graph")
|
||||||
|
} else {
|
||||||
|
o.printf(call.site, "\t%s from %s",
|
||||||
|
call.site.Description(), call.caller.Func())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
package oracle
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.google.com/p/go.tools/pointer"
|
||||||
|
)
|
||||||
|
|
||||||
|
// callgraph displays the entire callgraph of the current program.
|
||||||
|
//
|
||||||
|
// Nodes may be seem to appear multiple times due to (limited)
|
||||||
|
// context sensitivity.
|
||||||
|
//
|
||||||
|
// TODO(adonovan): add options for restricting the display to a region
|
||||||
|
// of interest: function, package, subgraph, dirtree, etc.
|
||||||
|
//
|
||||||
|
func callgraph(o *oracle) (queryResult, error) {
|
||||||
|
buildSSA(o)
|
||||||
|
|
||||||
|
// Run the pointer analysis and build the complete callgraph.
|
||||||
|
callgraph := make(pointer.CallGraph)
|
||||||
|
o.config.Call = callgraph.AddEdge
|
||||||
|
root := ptrAnalysis(o)
|
||||||
|
|
||||||
|
return &callgraphResult{
|
||||||
|
root: root,
|
||||||
|
callgraph: callgraph,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type callgraphResult struct {
|
||||||
|
root pointer.CallGraphNode
|
||||||
|
callgraph pointer.CallGraph
|
||||||
|
|
||||||
|
numbering map[pointer.CallGraphNode]int // used by display
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *callgraphResult) print(o *oracle, cgn pointer.CallGraphNode, indent int) {
|
||||||
|
if n := r.numbering[cgn]; n == 0 {
|
||||||
|
n = 1 + len(r.numbering)
|
||||||
|
r.numbering[cgn] = n
|
||||||
|
o.printf(cgn.Func(), "%d\t%s%s", n, strings.Repeat(" ", indent), cgn.Func())
|
||||||
|
for callee := range r.callgraph[cgn] {
|
||||||
|
r.print(o, callee, indent+1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
o.printf(cgn.Func(), "\t%s%s (%d)", strings.Repeat(" ", indent), cgn.Func(), n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *callgraphResult) display(o *oracle) {
|
||||||
|
o.printf(nil, `
|
||||||
|
Below is a call graph of the entire program.
|
||||||
|
The numbered nodes form a spanning tree.
|
||||||
|
Non-numbered nodes indicate back- or cross-edges to the node whose
|
||||||
|
number follows in parentheses.
|
||||||
|
Some nodes may appear multiple times due to context-sensitive
|
||||||
|
treatment of some calls.
|
||||||
|
`)
|
||||||
|
|
||||||
|
r.numbering = make(map[pointer.CallGraphNode]int)
|
||||||
|
r.print(o, r.root, 0)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
package oracle
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.google.com/p/go.tools/pointer"
|
||||||
|
"code.google.com/p/go.tools/ssa"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Callstack displays an arbitrary path from a root of the callgraph
|
||||||
|
// to the function at the current position.
|
||||||
|
//
|
||||||
|
// The information may be misleading in a context-insensitive
|
||||||
|
// analysis. e.g. the call path X->Y->Z might be infeasible if Y never
|
||||||
|
// calls Z when it is called from X. TODO(adonovan): think about UI.
|
||||||
|
//
|
||||||
|
// TODO(adonovan): permit user to specify a starting point other than
|
||||||
|
// the analysis root.
|
||||||
|
//
|
||||||
|
func callstack(o *oracle) (queryResult, error) {
|
||||||
|
pkg := o.prog.Package(o.queryPkgInfo.Pkg)
|
||||||
|
if pkg == nil {
|
||||||
|
return nil, o.errorf(o.queryPath[0], "no SSA package")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ssa.HasEnclosingFunction(pkg, o.queryPath) {
|
||||||
|
return nil, o.errorf(o.queryPath[0], "this position is not inside a function")
|
||||||
|
}
|
||||||
|
|
||||||
|
buildSSA(o)
|
||||||
|
|
||||||
|
target := ssa.EnclosingFunction(pkg, o.queryPath)
|
||||||
|
if target == nil {
|
||||||
|
return nil, o.errorf(o.queryPath[0],
|
||||||
|
"no SSA function built for this location (dead code?)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the pointer analysis and build the complete call graph.
|
||||||
|
callgraph := make(pointer.CallGraph)
|
||||||
|
o.config.Call = callgraph.AddEdge
|
||||||
|
root := ptrAnalysis(o)
|
||||||
|
|
||||||
|
return &callstackResult{
|
||||||
|
target: target,
|
||||||
|
root: root,
|
||||||
|
callgraph: callgraph,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type callstackResult struct {
|
||||||
|
target *ssa.Function
|
||||||
|
root pointer.CallGraphNode
|
||||||
|
callgraph pointer.CallGraph
|
||||||
|
|
||||||
|
seen map[pointer.CallGraphNode]bool // used by display
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *callstackResult) search(o *oracle, cgn pointer.CallGraphNode) bool {
|
||||||
|
if !r.seen[cgn] {
|
||||||
|
r.seen[cgn] = true
|
||||||
|
if cgn.Func() == r.target {
|
||||||
|
o.printf(o, "Found a call path from root to %s", r.target)
|
||||||
|
o.printf(r.target, "%s", r.target)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for callee, site := range r.callgraph[cgn] {
|
||||||
|
if r.search(o, callee) {
|
||||||
|
o.printf(site, "%s from %s", site.Description(), cgn.Func())
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *callstackResult) display(o *oracle) {
|
||||||
|
// Show only an arbitrary path from a root to the current function.
|
||||||
|
// We use depth-first search.
|
||||||
|
|
||||||
|
r.seen = make(map[pointer.CallGraphNode]bool)
|
||||||
|
|
||||||
|
for toplevel := range r.callgraph[r.root] {
|
||||||
|
if r.search(o, toplevel) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
o.printf(r.target, "%s is unreachable in this analysis scope", r.target)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,715 @@
|
||||||
|
package oracle
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/token"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.google.com/p/go.tools/go/types"
|
||||||
|
"code.google.com/p/go.tools/importer"
|
||||||
|
"code.google.com/p/go.tools/pointer"
|
||||||
|
"code.google.com/p/go.tools/ssa"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO(adonovan): all printed sets must be sorted to ensure test determinism.
|
||||||
|
|
||||||
|
// describe describes the syntax node denoted by the query position,
|
||||||
|
// including:
|
||||||
|
// - its syntactic category
|
||||||
|
// - the location of the definition of its referent (for identifiers)
|
||||||
|
// - its type and method set (for an expression or type expression)
|
||||||
|
// - its points-to set (for a pointer-like expression)
|
||||||
|
// - its concrete types (for an interface expression) and their points-to sets.
|
||||||
|
//
|
||||||
|
func describe(o *oracle) (queryResult, error) {
|
||||||
|
if false { // debugging
|
||||||
|
o.printf(o.queryPath[0], "you selected: %s %s",
|
||||||
|
importer.NodeDescription(o.queryPath[0]), pathToString2(o.queryPath))
|
||||||
|
}
|
||||||
|
|
||||||
|
path, action := findInterestingNode(o.queryPkgInfo, o.queryPath)
|
||||||
|
switch action {
|
||||||
|
case actionExpr:
|
||||||
|
return describeValue(o, path)
|
||||||
|
|
||||||
|
case actionType:
|
||||||
|
return describeType(o, path)
|
||||||
|
|
||||||
|
case actionPackage:
|
||||||
|
return describePackage(o, path)
|
||||||
|
|
||||||
|
case actionStmt:
|
||||||
|
return describeStmt(o, path)
|
||||||
|
|
||||||
|
case actionUnknown:
|
||||||
|
return &describeUnknownResult{path[0]}, nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic(action) // unreachable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type describeUnknownResult struct {
|
||||||
|
node ast.Node
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *describeUnknownResult) display(o *oracle) {
|
||||||
|
// Nothing much to say about misc syntax.
|
||||||
|
o.printf(r.node, "%s", importer.NodeDescription(r.node))
|
||||||
|
}
|
||||||
|
|
||||||
|
type action int
|
||||||
|
|
||||||
|
const (
|
||||||
|
actionUnknown action = iota // None of the below
|
||||||
|
actionExpr // FuncDecl, true Expr or Ident(types.{Const,Var})
|
||||||
|
actionType // type Expr or Ident(types.TypeName).
|
||||||
|
actionStmt // Stmt or Ident(types.Label)
|
||||||
|
actionPackage // Ident(types.Package) or ImportSpec
|
||||||
|
)
|
||||||
|
|
||||||
|
// findInterestingNode classifies the syntax node denoted by path as one of:
|
||||||
|
// - an expression, part of an expression or a reference to a constant
|
||||||
|
// or variable;
|
||||||
|
// - a type, part of a type, or a reference to a named type;
|
||||||
|
// - a statement, part of a statement, or a label referring to a statement;
|
||||||
|
// - part of a package declaration or import spec.
|
||||||
|
// - none of the above.
|
||||||
|
// and returns the most "interesting" associated node, which may be
|
||||||
|
// the same node, an ancestor or a descendent.
|
||||||
|
//
|
||||||
|
func findInterestingNode(pkginfo *importer.PackageInfo, path []ast.Node) ([]ast.Node, action) {
|
||||||
|
// TODO(adonovan): integrate with go/types/stdlib_test.go and
|
||||||
|
// apply this to every AST node we can find to make sure it
|
||||||
|
// doesn't crash.
|
||||||
|
|
||||||
|
// TODO(adonovan): audit for ParenExpr safety, esp. since we
|
||||||
|
// traverse up and down.
|
||||||
|
|
||||||
|
// TODO(adonovan): if the users selects the "." in
|
||||||
|
// "fmt.Fprintf()", they'll get an ambiguous selection error;
|
||||||
|
// we won't even reach here. Can we do better?
|
||||||
|
|
||||||
|
// TODO(adonovan): describing a field within 'type T struct {...}'
|
||||||
|
// describes the (anonymous) struct type and concludes "no methods". Fix.
|
||||||
|
|
||||||
|
for len(path) > 0 {
|
||||||
|
switch n := path[0].(type) {
|
||||||
|
case *ast.GenDecl:
|
||||||
|
if len(n.Specs) == 1 {
|
||||||
|
// Descend to sole {Import,Type,Value}Spec child.
|
||||||
|
path = append([]ast.Node{n.Specs[0]}, path...)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return path, actionUnknown // uninteresting
|
||||||
|
|
||||||
|
case *ast.FuncDecl:
|
||||||
|
// Descend to function name.
|
||||||
|
path = append([]ast.Node{n.Name}, path...)
|
||||||
|
continue
|
||||||
|
|
||||||
|
case *ast.ImportSpec:
|
||||||
|
return path, actionPackage
|
||||||
|
|
||||||
|
case *ast.ValueSpec:
|
||||||
|
if len(n.Names) == 1 {
|
||||||
|
// Descend to sole Ident child.
|
||||||
|
path = append([]ast.Node{n.Names[0]}, path...)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return path, actionUnknown // uninteresting
|
||||||
|
|
||||||
|
case *ast.TypeSpec:
|
||||||
|
// Descend to type name.
|
||||||
|
path = append([]ast.Node{n.Name}, path...)
|
||||||
|
continue
|
||||||
|
|
||||||
|
case ast.Stmt:
|
||||||
|
return path, actionStmt
|
||||||
|
|
||||||
|
case *ast.ArrayType,
|
||||||
|
*ast.StructType,
|
||||||
|
*ast.FuncType,
|
||||||
|
*ast.InterfaceType,
|
||||||
|
*ast.MapType,
|
||||||
|
*ast.ChanType:
|
||||||
|
return path, actionType
|
||||||
|
|
||||||
|
case *ast.Comment, *ast.CommentGroup, *ast.File, *ast.KeyValueExpr, *ast.CommClause:
|
||||||
|
return path, actionUnknown // uninteresting
|
||||||
|
|
||||||
|
case *ast.Ellipsis:
|
||||||
|
// Continue to enclosing node.
|
||||||
|
// e.g. [...]T in ArrayType
|
||||||
|
// f(x...) in CallExpr
|
||||||
|
// f(x...T) in FuncType
|
||||||
|
|
||||||
|
case *ast.Field:
|
||||||
|
// TODO(adonovan): this needs more thought,
|
||||||
|
// since fields can be so many things.
|
||||||
|
if len(n.Names) == 1 {
|
||||||
|
// Descend to sole Ident child.
|
||||||
|
path = append([]ast.Node{n.Names[0]}, path...)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Zero names (e.g. anon field in struct)
|
||||||
|
// or multiple field or param names:
|
||||||
|
// continue to enclosing field list.
|
||||||
|
|
||||||
|
case *ast.FieldList:
|
||||||
|
// Continue to enclosing node:
|
||||||
|
// {Struct,Func,Interface}Type or FuncDecl.
|
||||||
|
|
||||||
|
case *ast.BasicLit:
|
||||||
|
if _, ok := path[1].(*ast.ImportSpec); ok {
|
||||||
|
return path[1:], actionPackage
|
||||||
|
}
|
||||||
|
return path, actionExpr
|
||||||
|
|
||||||
|
case *ast.SelectorExpr:
|
||||||
|
if pkginfo.ObjectOf(n.Sel) == nil {
|
||||||
|
// Is this reachable?
|
||||||
|
return path, actionUnknown
|
||||||
|
}
|
||||||
|
// Descend to .Sel child.
|
||||||
|
path = append([]ast.Node{n.Sel}, path...)
|
||||||
|
continue
|
||||||
|
|
||||||
|
case *ast.Ident:
|
||||||
|
switch obj := pkginfo.ObjectOf(n).(type) {
|
||||||
|
case *types.Package:
|
||||||
|
return path, actionPackage
|
||||||
|
|
||||||
|
case *types.Const:
|
||||||
|
return path, actionExpr
|
||||||
|
|
||||||
|
case *types.Label:
|
||||||
|
return path, actionStmt
|
||||||
|
|
||||||
|
case *types.TypeName:
|
||||||
|
return path, actionType
|
||||||
|
|
||||||
|
case *types.Var:
|
||||||
|
// For x in 'struct {x T}', return struct type, for now.
|
||||||
|
if _, ok := path[1].(*ast.Field); ok {
|
||||||
|
_ = path[2].(*ast.FieldList) // assertion
|
||||||
|
if _, ok := path[3].(*ast.StructType); ok {
|
||||||
|
return path[3:], actionType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path, actionExpr
|
||||||
|
|
||||||
|
case *types.Func:
|
||||||
|
// For f in 'interface {f()}', return the interface type, for now.
|
||||||
|
if _, ok := path[1].(*ast.Field); ok {
|
||||||
|
_ = path[2].(*ast.FieldList) // assertion
|
||||||
|
if _, ok := path[3].(*ast.InterfaceType); ok {
|
||||||
|
return path[3:], actionType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For reference to built-in function, return enclosing call.
|
||||||
|
if _, ok := obj.Type().(*types.Builtin); ok {
|
||||||
|
// Ascend to enclosing function call.
|
||||||
|
path = path[1:]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return path, actionExpr
|
||||||
|
}
|
||||||
|
|
||||||
|
// No object.
|
||||||
|
switch path[1].(type) {
|
||||||
|
case *ast.SelectorExpr:
|
||||||
|
// Return enclosing selector expression.
|
||||||
|
return path[1:], actionExpr
|
||||||
|
|
||||||
|
case *ast.Field:
|
||||||
|
// TODO(adonovan): test this.
|
||||||
|
// e.g. all f in:
|
||||||
|
// struct { f, g int }
|
||||||
|
// interface { f() }
|
||||||
|
// func (f T) method(f, g int) (f, g bool)
|
||||||
|
//
|
||||||
|
// switch path[3].(type) {
|
||||||
|
// case *ast.FuncDecl:
|
||||||
|
// case *ast.StructType:
|
||||||
|
// case *ast.InterfaceType:
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return path[1:], actionExpr
|
||||||
|
//
|
||||||
|
// Unclear what to do with these.
|
||||||
|
// Struct.Fields -- field
|
||||||
|
// Interface.Methods -- field
|
||||||
|
// FuncType.{Params.Results} -- actionExpr
|
||||||
|
// FuncDecl.Recv -- actionExpr
|
||||||
|
|
||||||
|
case *ast.ImportSpec:
|
||||||
|
// TODO(adonovan): fix: why no package object? go/types bug?
|
||||||
|
return path[1:], actionPackage
|
||||||
|
|
||||||
|
default:
|
||||||
|
// e.g. blank identifier (go/types bug?)
|
||||||
|
// or y in "switch y := x.(type)" (go/types bug?)
|
||||||
|
fmt.Printf("unknown reference %s in %T\n", n, path[1])
|
||||||
|
return path, actionUnknown
|
||||||
|
}
|
||||||
|
|
||||||
|
case *ast.StarExpr:
|
||||||
|
if pkginfo.IsType(n) {
|
||||||
|
return path, actionType
|
||||||
|
}
|
||||||
|
return path, actionExpr
|
||||||
|
|
||||||
|
case ast.Expr:
|
||||||
|
// All Expr but {BasicLit,Ident,StarExpr} are
|
||||||
|
// "true" expressions that evaluate to a value.
|
||||||
|
return path, actionExpr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ascend to parent.
|
||||||
|
path = path[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, actionUnknown // unreachable
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- VALUE ------------------------------------------------------------
|
||||||
|
|
||||||
|
// ssaValueForIdent returns the ssa.Value for the ast.Ident whose path
|
||||||
|
// to the root of the AST is path. It may return a nil Value without
|
||||||
|
// an error to indicate the pointer analysis is not appropriate.
|
||||||
|
//
|
||||||
|
func ssaValueForIdent(o *oracle, obj types.Object, path []ast.Node) (ssa.Value, error) {
|
||||||
|
if obj, ok := obj.(*types.Var); ok {
|
||||||
|
pkg := o.prog.Package(o.queryPkgInfo.Pkg)
|
||||||
|
pkg.Build()
|
||||||
|
if v := o.prog.VarValue(obj, pkg, path); v != nil {
|
||||||
|
// Don't run pointer analysis on a ref to a const expression.
|
||||||
|
if _, ok := v.(*ssa.Const); ok {
|
||||||
|
v = nil
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("can't locate SSA Value for var %s", obj.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't run pointer analysis on const/func objects.
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ssaValueForExpr returns the ssa.Value of the non-ast.Ident
|
||||||
|
// expression whose path to the root of the AST is path. It may
|
||||||
|
// return a nil Value without an error to indicate the pointer
|
||||||
|
// analysis is not appropriate.
|
||||||
|
//
|
||||||
|
func ssaValueForExpr(o *oracle, path []ast.Node) (ssa.Value, error) {
|
||||||
|
pkg := o.prog.Package(o.queryPkgInfo.Pkg)
|
||||||
|
pkg.SetDebugMode(true)
|
||||||
|
pkg.Build()
|
||||||
|
|
||||||
|
fn := ssa.EnclosingFunction(pkg, path)
|
||||||
|
if fn == nil {
|
||||||
|
return nil, fmt.Errorf("no SSA function built for this location (dead code?)")
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := fn.ValueForExpr(path[0].(ast.Expr)); v != nil {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("can't locate SSA Value for expression in %s", fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func describeValue(o *oracle, path []ast.Node) (*describeValueResult, error) {
|
||||||
|
var expr ast.Expr
|
||||||
|
switch n := path[0].(type) {
|
||||||
|
case *ast.ValueSpec:
|
||||||
|
// ambiguous ValueSpec containing multiple names
|
||||||
|
return nil, o.errorf(n, "multiple value specification")
|
||||||
|
case ast.Expr:
|
||||||
|
expr = n
|
||||||
|
default:
|
||||||
|
// Is this reachable?
|
||||||
|
return nil, o.errorf(n, "unexpected AST for expr: %T", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// From this point on, we cannot fail with an error.
|
||||||
|
// Failure to run the pointer analysis will be reported later.
|
||||||
|
|
||||||
|
var value ssa.Value
|
||||||
|
var ptaErr error
|
||||||
|
var obj types.Object
|
||||||
|
|
||||||
|
// Determine the ssa.Value for the expression.
|
||||||
|
if id, ok := expr.(*ast.Ident); ok {
|
||||||
|
// def/ref of func/var/const object
|
||||||
|
obj = o.queryPkgInfo.ObjectOf(id)
|
||||||
|
value, ptaErr = ssaValueForIdent(o, obj, path)
|
||||||
|
} else {
|
||||||
|
// any other expression
|
||||||
|
if o.queryPkgInfo.ValueOf(expr) == nil { // non-constant?
|
||||||
|
value, ptaErr = ssaValueForExpr(o, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't run pointer analysis on non-pointerlike types.
|
||||||
|
if value != nil && !pointer.CanPoint(value.Type()) {
|
||||||
|
value = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run pointer analysis of the selected SSA value.
|
||||||
|
var ptrs []pointer.Pointer
|
||||||
|
if value != nil {
|
||||||
|
buildSSA(o)
|
||||||
|
|
||||||
|
o.config.QueryValues = map[ssa.Value][]pointer.Pointer{value: nil}
|
||||||
|
ptrAnalysis(o)
|
||||||
|
ptrs = o.config.QueryValues[value]
|
||||||
|
}
|
||||||
|
|
||||||
|
return &describeValueResult{
|
||||||
|
expr: expr,
|
||||||
|
obj: obj,
|
||||||
|
value: value,
|
||||||
|
ptaErr: ptaErr,
|
||||||
|
ptrs: ptrs,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type describeValueResult struct {
|
||||||
|
expr ast.Expr // query node
|
||||||
|
obj types.Object // var/func/const object, if expr was Ident
|
||||||
|
value ssa.Value // ssa.Value for pointer analysis query
|
||||||
|
ptaErr error // explanation of why we couldn't run pointer analysis
|
||||||
|
ptrs []pointer.Pointer // result of pointer analysis query
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *describeValueResult) display(o *oracle) {
|
||||||
|
suffix := ""
|
||||||
|
if val := o.queryPkgInfo.ValueOf(r.expr); val != nil {
|
||||||
|
suffix = fmt.Sprintf(" of constant value %s", val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Describe the expression.
|
||||||
|
if r.obj != nil {
|
||||||
|
if r.obj.Pos() == r.expr.Pos() {
|
||||||
|
// defining ident
|
||||||
|
o.printf(r.expr, "definition of %s%s", r.obj, suffix)
|
||||||
|
} else {
|
||||||
|
// referring ident
|
||||||
|
o.printf(r.expr, "reference to %s%s", r.obj, suffix)
|
||||||
|
if def := r.obj.Pos(); def != token.NoPos {
|
||||||
|
o.printf(def, "defined here")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
desc := importer.NodeDescription(r.expr)
|
||||||
|
if suffix != "" {
|
||||||
|
// constant expression
|
||||||
|
o.printf(r.expr, "%s%s", desc, suffix)
|
||||||
|
} else {
|
||||||
|
// non-constant expression
|
||||||
|
o.printf(r.expr, "%s of type %s", desc, o.queryPkgInfo.TypeOf(r.expr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.value == nil {
|
||||||
|
// pointer analysis was not run
|
||||||
|
if r.ptaErr != nil {
|
||||||
|
o.printf(r.expr, "no pointer analysis: %s", r.ptaErr)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.ptrs == nil {
|
||||||
|
o.printf(r.expr, "pointer analysis did not analyze this expression (dead code?)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display the results of pointer analysis.
|
||||||
|
|
||||||
|
// Combine the PT sets from all contexts.
|
||||||
|
pts := pointer.PointsToCombined(r.ptrs)
|
||||||
|
|
||||||
|
// Report which make(chan) labels the query's channel can alias.
|
||||||
|
if _, ok := r.value.Type().Underlying().(*types.Interface); ok {
|
||||||
|
// Show concrete types for interface expression.
|
||||||
|
if concs := pts.ConcreteTypes(); concs.Len() > 0 {
|
||||||
|
o.printf(o, "interface may contain these concrete types:")
|
||||||
|
// TODO(adonovan): must sort to ensure deterministic test behaviour.
|
||||||
|
concs.Iterate(func(conc types.Type, ptrs interface{}) {
|
||||||
|
var obj types.Object
|
||||||
|
if nt, ok := deref(conc).(*types.Named); ok {
|
||||||
|
obj = nt.Obj()
|
||||||
|
}
|
||||||
|
|
||||||
|
pts := pointer.PointsToCombined(ptrs.([]pointer.Pointer))
|
||||||
|
if labels := pts.Labels(); len(labels) > 0 {
|
||||||
|
o.printf(obj, "\t%s, may point to:", conc)
|
||||||
|
printLabels(o, labels, "\t\t")
|
||||||
|
} else {
|
||||||
|
o.printf(obj, "\t%s", conc)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
o.printf(o, "interface cannot contain any concrete values.")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Show labels for other expressions.
|
||||||
|
if labels := pts.Labels(); len(labels) > 0 {
|
||||||
|
o.printf(o, "value may point to these labels:")
|
||||||
|
printLabels(o, labels, "\t")
|
||||||
|
} else {
|
||||||
|
o.printf(o, "value cannot point to anything.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type byPosAndString []*pointer.Label
|
||||||
|
|
||||||
|
func (a byPosAndString) Len() int { return len(a) }
|
||||||
|
func (a byPosAndString) Less(i, j int) bool {
|
||||||
|
cmp := a[i].Pos() - a[j].Pos()
|
||||||
|
return cmp < 0 || (cmp == 0 && a[i].String() < a[j].String())
|
||||||
|
}
|
||||||
|
func (a byPosAndString) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
|
|
||||||
|
func printLabels(o *oracle, labels []*pointer.Label, prefix string) {
|
||||||
|
// Sort, to ensure deterministic test behaviour.
|
||||||
|
sort.Sort(byPosAndString(labels))
|
||||||
|
// TODO(adonovan): due to context-sensitivity, many of these
|
||||||
|
// labels may differ only by context, which isn't apparent.
|
||||||
|
for _, label := range labels {
|
||||||
|
o.printf(label, "%s%s", prefix, label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- TYPE ------------------------------------------------------------
|
||||||
|
|
||||||
|
func describeType(o *oracle, path []ast.Node) (*describeTypeResult, error) {
|
||||||
|
var description string
|
||||||
|
var t types.Type
|
||||||
|
switch n := path[0].(type) {
|
||||||
|
case *ast.Ident:
|
||||||
|
t = o.queryPkgInfo.TypeOf(n)
|
||||||
|
switch t := t.(type) {
|
||||||
|
case *types.Basic:
|
||||||
|
description = "reference to built-in type " + t.String()
|
||||||
|
|
||||||
|
case *types.Named:
|
||||||
|
isDef := t.Obj().Pos() == n.Pos() // see caveats at isDef above
|
||||||
|
if isDef {
|
||||||
|
description = "definition of type " + t.String()
|
||||||
|
} else {
|
||||||
|
description = "reference to type " + t.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case ast.Expr:
|
||||||
|
t = o.queryPkgInfo.TypeOf(n)
|
||||||
|
description = "type " + t.String()
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Unreachable?
|
||||||
|
return nil, o.errorf(n, "unexpected AST for type: %T", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &describeTypeResult{path[0], description, t}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type describeTypeResult struct {
|
||||||
|
node ast.Node
|
||||||
|
description string
|
||||||
|
typ types.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *describeTypeResult) display(o *oracle) {
|
||||||
|
o.printf(r.node, "%s", r.description)
|
||||||
|
|
||||||
|
// Show the underlying type for a reference to a named type.
|
||||||
|
if nt, ok := r.typ.(*types.Named); ok && r.node.Pos() != nt.Obj().Pos() {
|
||||||
|
o.printf(nt.Obj(), "defined as %s", nt.Underlying())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print the method set, if the type kind is capable of bearing methods.
|
||||||
|
switch r.typ.(type) {
|
||||||
|
case *types.Interface, *types.Struct, *types.Named:
|
||||||
|
// TODO(adonovan): don't show unexported methods if
|
||||||
|
// r.typ belongs to a package other than the query
|
||||||
|
// package.
|
||||||
|
if m := ssa.IntuitiveMethodSet(r.typ); m != nil {
|
||||||
|
o.printf(r.node, "Method set:")
|
||||||
|
for _, meth := range m {
|
||||||
|
o.printf(meth.Obj(), "\t%s", meth)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
o.printf(r.node, "No methods.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- PACKAGE ------------------------------------------------------------
|
||||||
|
|
||||||
|
func describePackage(o *oracle, path []ast.Node) (*describePackageResult, error) {
|
||||||
|
var description string
|
||||||
|
var importPath string
|
||||||
|
switch n := path[0].(type) {
|
||||||
|
case *ast.ImportSpec:
|
||||||
|
// importPath = o.queryPkgInfo.ObjectOf(n.Name).(*types.Package).Path()
|
||||||
|
// description = "import of package " + importPath
|
||||||
|
// TODO(gri): o.queryPkgInfo.ObjectOf(n.Name) may be nil.
|
||||||
|
// e.g. "fmt" import in cmd/oracle/main.go. Why?
|
||||||
|
// Workaround:
|
||||||
|
description = "import of package " + n.Path.Value
|
||||||
|
importPath, _ = strconv.Unquote(n.Path.Value)
|
||||||
|
|
||||||
|
case *ast.Ident:
|
||||||
|
importPath = o.queryPkgInfo.ObjectOf(n).(*types.Package).Path()
|
||||||
|
if _, isDef := path[1].(*ast.File); isDef {
|
||||||
|
description = fmt.Sprintf("definition of package %q", importPath)
|
||||||
|
} else {
|
||||||
|
description = fmt.Sprintf("reference to package %q", importPath)
|
||||||
|
}
|
||||||
|
if importPath == "" {
|
||||||
|
// TODO(gri): fix.
|
||||||
|
return nil, o.errorf(n, "types.Package.Path() returned \"\"\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Unreachable?
|
||||||
|
return nil, o.errorf(n, "unexpected AST for package: %T", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
pkg := o.prog.PackagesByPath[importPath]
|
||||||
|
|
||||||
|
return &describePackageResult{path[0], description, pkg}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type describePackageResult struct {
|
||||||
|
node ast.Node
|
||||||
|
description string
|
||||||
|
pkg *ssa.Package
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *describePackageResult) display(o *oracle) {
|
||||||
|
o.printf(r.node, "%s", r.description)
|
||||||
|
// TODO(adonovan): factor this into a testable utility function.
|
||||||
|
if p := r.pkg; p != nil {
|
||||||
|
samePkg := p.Object == o.queryPkgInfo.Pkg
|
||||||
|
|
||||||
|
// Describe exported package members, in lexicographic order.
|
||||||
|
|
||||||
|
// Compute max width of name "column".
|
||||||
|
var names []string
|
||||||
|
maxname := 0
|
||||||
|
for name := range p.Members {
|
||||||
|
if samePkg || ast.IsExported(name) {
|
||||||
|
if l := len(name); l > maxname {
|
||||||
|
maxname = l
|
||||||
|
}
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(names)
|
||||||
|
|
||||||
|
// Print the members.
|
||||||
|
for _, name := range names {
|
||||||
|
mem := p.Members[name]
|
||||||
|
o.printf(mem, "%s", formatMember(mem, maxname))
|
||||||
|
// Print method set.
|
||||||
|
if mem, ok := mem.(*ssa.Type); ok {
|
||||||
|
for _, meth := range ssa.IntuitiveMethodSet(mem.Type()) {
|
||||||
|
if samePkg || ast.IsExported(meth.Obj().Name()) {
|
||||||
|
o.printf(meth.Obj(), "\t\t%s", meth)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatMember(mem ssa.Member, maxname int) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
fmt.Fprintf(&buf, "\t%-5s %-*s", mem.Token(), maxname, mem.Name())
|
||||||
|
switch mem := mem.(type) {
|
||||||
|
case *ssa.NamedConst:
|
||||||
|
fmt.Fprintf(&buf, " %s = %s", mem.Type(), mem.Value.Name())
|
||||||
|
|
||||||
|
case *ssa.Function:
|
||||||
|
fmt.Fprintf(&buf, " %s", mem.Type())
|
||||||
|
|
||||||
|
case *ssa.Type:
|
||||||
|
// Abbreviate long aggregate type names.
|
||||||
|
var abbrev string
|
||||||
|
switch t := mem.Type().Underlying().(type) {
|
||||||
|
case *types.Interface:
|
||||||
|
if t.NumMethods() > 1 {
|
||||||
|
abbrev = "interface{...}"
|
||||||
|
}
|
||||||
|
case *types.Struct:
|
||||||
|
if t.NumFields() > 1 {
|
||||||
|
abbrev = "struct{...}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if abbrev == "" {
|
||||||
|
fmt.Fprintf(&buf, " %s", mem.Type().Underlying())
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(&buf, " %s", abbrev)
|
||||||
|
}
|
||||||
|
|
||||||
|
case *ssa.Global:
|
||||||
|
fmt.Fprintf(&buf, " %s", deref(mem.Type()))
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- STATEMENT ------------------------------------------------------------
|
||||||
|
|
||||||
|
func describeStmt(o *oracle, path []ast.Node) (*describeStmtResult, error) {
|
||||||
|
var description string
|
||||||
|
switch n := path[0].(type) {
|
||||||
|
case *ast.Ident:
|
||||||
|
if o.queryPkgInfo.ObjectOf(n).Pos() == n.Pos() {
|
||||||
|
description = "labelled statement"
|
||||||
|
} else {
|
||||||
|
description = "reference to labelled statement"
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Nothing much to say about statements.
|
||||||
|
description = importer.NodeDescription(n)
|
||||||
|
}
|
||||||
|
return &describeStmtResult{path[0], description}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type describeStmtResult struct {
|
||||||
|
node ast.Node
|
||||||
|
description string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *describeStmtResult) display(o *oracle) {
|
||||||
|
o.printf(r.node, "%s", r.description)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------- Utilities -------------------
|
||||||
|
|
||||||
|
// pathToString returns a string containing the concrete types of the
|
||||||
|
// nodes in path.
|
||||||
|
func pathToString2(path []ast.Node) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
fmt.Fprint(&buf, "[")
|
||||||
|
for i, n := range path {
|
||||||
|
if i > 0 {
|
||||||
|
fmt.Fprint(&buf, " ")
|
||||||
|
}
|
||||||
|
fmt.Fprint(&buf, strings.TrimPrefix(fmt.Sprintf("%T", n), "*ast."))
|
||||||
|
}
|
||||||
|
fmt.Fprint(&buf, "]")
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,143 @@
|
||||||
|
package oracle
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/ast"
|
||||||
|
|
||||||
|
"code.google.com/p/go.tools/go/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// freevars displays the lexical (not package-level) free variables of
|
||||||
|
// the selection.
|
||||||
|
//
|
||||||
|
// It treats A.B.C as a separate variable from A to reveal the parts
|
||||||
|
// of an aggregate type that are actually needed.
|
||||||
|
// This aids refactoring.
|
||||||
|
//
|
||||||
|
// TODO(adonovan): optionally display the free references to
|
||||||
|
// file/package scope objects, and to objects from other packages.
|
||||||
|
// Depending on where the resulting function abstraction will go,
|
||||||
|
// these might be interesting. Perhaps group the results into three
|
||||||
|
// bands.
|
||||||
|
//
|
||||||
|
func freevars(o *oracle) (queryResult, error) {
|
||||||
|
file := o.queryPath[len(o.queryPath)-1] // the enclosing file
|
||||||
|
|
||||||
|
// The id and sel functions return non-nil if they denote an
|
||||||
|
// object o or selection o.x.y that is referenced by the
|
||||||
|
// selection but defined neither within the selection nor at
|
||||||
|
// file scope, i.e. it is in the lexical environment.
|
||||||
|
var id func(n *ast.Ident) types.Object
|
||||||
|
var sel func(n *ast.SelectorExpr) types.Object
|
||||||
|
|
||||||
|
sel = func(n *ast.SelectorExpr) types.Object {
|
||||||
|
switch x := unparen(n.X).(type) {
|
||||||
|
case *ast.SelectorExpr:
|
||||||
|
return sel(x)
|
||||||
|
case *ast.Ident:
|
||||||
|
return id(x)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
id = func(n *ast.Ident) types.Object {
|
||||||
|
obj := o.queryPkgInfo.ObjectOf(n)
|
||||||
|
if obj == nil {
|
||||||
|
return nil // TODO(adonovan): fix: this fails for *types.Label.
|
||||||
|
panic(o.errorf(n, "no types.Object for ast.Ident"))
|
||||||
|
}
|
||||||
|
if _, ok := obj.(*types.Package); ok {
|
||||||
|
return nil // imported package
|
||||||
|
}
|
||||||
|
if n.Pos() == obj.Pos() {
|
||||||
|
return nil // this ident is the definition, not a reference
|
||||||
|
}
|
||||||
|
if !(file.Pos() <= obj.Pos() && obj.Pos() <= file.End()) {
|
||||||
|
return nil // not defined in this file
|
||||||
|
}
|
||||||
|
if obj.Parent() == nil {
|
||||||
|
return nil // e.g. interface method TODO(adonovan): what else?
|
||||||
|
}
|
||||||
|
if obj.Parent() == o.queryPkgInfo.Scopes[file] {
|
||||||
|
return nil // defined at file scope
|
||||||
|
}
|
||||||
|
if o.startPos <= obj.Pos() && obj.Pos() <= o.endPos {
|
||||||
|
return nil // defined within selection => not free
|
||||||
|
}
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maps each reference that is free in the selection
|
||||||
|
// to the object it refers to.
|
||||||
|
freeRefs := make(map[string]freevarsRef)
|
||||||
|
|
||||||
|
// Visit all the identifiers in the selected ASTs.
|
||||||
|
ast.Inspect(o.queryPath[0], func(n ast.Node) bool {
|
||||||
|
if n == nil {
|
||||||
|
return true // popping DFS stack
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is this node contained within the selection?
|
||||||
|
// (freevars permits inexact selections,
|
||||||
|
// like two stmts in a block.)
|
||||||
|
if o.startPos <= n.Pos() && n.End() <= o.endPos {
|
||||||
|
switch n := n.(type) {
|
||||||
|
case *ast.Ident:
|
||||||
|
if obj := id(n); obj != nil {
|
||||||
|
freeRefs[o.printNode(n)] = freevarsRef{n, obj}
|
||||||
|
}
|
||||||
|
|
||||||
|
case *ast.SelectorExpr:
|
||||||
|
if obj := sel(n); obj != nil {
|
||||||
|
freeRefs[o.printNode(n)] = freevarsRef{n, obj}
|
||||||
|
return false // don't descend
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true // descend
|
||||||
|
})
|
||||||
|
|
||||||
|
return &freevarsResult{
|
||||||
|
refs: freeRefs,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type freevarsResult struct {
|
||||||
|
refs map[string]freevarsRef
|
||||||
|
}
|
||||||
|
|
||||||
|
type freevarsRef struct {
|
||||||
|
expr ast.Expr
|
||||||
|
obj types.Object
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *freevarsResult) display(o *oracle) {
|
||||||
|
if len(r.refs) == 0 {
|
||||||
|
o.printf(o, "No free identifers.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
o.printf(o, "Free identifers:")
|
||||||
|
for s, ref := range r.refs {
|
||||||
|
typ := ref.obj.Type()
|
||||||
|
if _, ok := ref.expr.(*ast.SelectorExpr); ok {
|
||||||
|
typ = o.queryPkgInfo.TypeOf(ref.expr)
|
||||||
|
}
|
||||||
|
var kind string
|
||||||
|
switch ref.obj.(type) {
|
||||||
|
case *types.Var:
|
||||||
|
kind = "var"
|
||||||
|
case *types.Func:
|
||||||
|
kind = "func"
|
||||||
|
case *types.TypeName:
|
||||||
|
kind = "type"
|
||||||
|
case *types.Const:
|
||||||
|
kind = "const"
|
||||||
|
case *types.Label:
|
||||||
|
kind = "label"
|
||||||
|
default:
|
||||||
|
panic(ref.obj)
|
||||||
|
}
|
||||||
|
o.printf(ref.obj, "%s %s %s", kind, s, typ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
package oracle
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.google.com/p/go.tools/go/types"
|
||||||
|
"code.google.com/p/go.tools/ssa"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Implements displays the 'implements" relation among all
|
||||||
|
// package-level named types in the package containing the query
|
||||||
|
// position.
|
||||||
|
//
|
||||||
|
// TODO(adonovan): more features:
|
||||||
|
// - should we include pairs of types belonging to
|
||||||
|
// different packages in the 'implements' relation?//
|
||||||
|
// - should we restrict the query to the type declaration identified
|
||||||
|
// by the query position, if any, and use all types in the package
|
||||||
|
// otherwise?
|
||||||
|
// - should we show types that are local to functions?
|
||||||
|
// They can only have methods via promotion.
|
||||||
|
// - abbreviate the set of concrete types implementing the empty
|
||||||
|
// interface.
|
||||||
|
// - should we scan the instruction stream for MakeInterface
|
||||||
|
// instructions and report which concrete->interface conversions
|
||||||
|
// actually occur, with examples? (NB: this is not a conservative
|
||||||
|
// answer due to ChangeInterface, i.e. subtyping among interfaces.)
|
||||||
|
//
|
||||||
|
func implements(o *oracle) (queryResult, error) {
|
||||||
|
pkg := o.prog.Package(o.queryPkgInfo.Pkg)
|
||||||
|
if pkg == nil {
|
||||||
|
return nil, o.errorf(o.queryPath[0], "no SSA package")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute set of named interface/concrete types at package level.
|
||||||
|
var interfaces, concretes []*types.Named
|
||||||
|
for _, mem := range pkg.Members {
|
||||||
|
if t, ok := mem.(*ssa.Type); ok {
|
||||||
|
nt := t.Type().(*types.Named)
|
||||||
|
if _, ok := nt.Underlying().(*types.Interface); ok {
|
||||||
|
interfaces = append(interfaces, nt)
|
||||||
|
} else {
|
||||||
|
concretes = append(concretes, nt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For each interface, show the concrete types that implement it.
|
||||||
|
var facts []implementsFact
|
||||||
|
for _, iface := range interfaces {
|
||||||
|
fact := implementsFact{iface: iface}
|
||||||
|
for _, conc := range concretes {
|
||||||
|
if types.IsAssignableTo(conc, iface) {
|
||||||
|
fact.conc = conc
|
||||||
|
} else if ptr := types.NewPointer(conc); types.IsAssignableTo(ptr, iface) {
|
||||||
|
fact.conc = ptr
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
facts = append(facts, fact)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &implementsResult{facts}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type implementsFact struct {
|
||||||
|
iface *types.Named
|
||||||
|
conc types.Type // Named or Pointer(Named)
|
||||||
|
}
|
||||||
|
|
||||||
|
type implementsResult struct {
|
||||||
|
facts []implementsFact // facts are grouped by interface
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *implementsResult) display(o *oracle) {
|
||||||
|
// TODO(adonovan): sort to ensure test nondeterminism.
|
||||||
|
var prevIface *types.Named
|
||||||
|
for _, fact := range r.facts {
|
||||||
|
if fact.iface != prevIface {
|
||||||
|
o.printf(fact.iface.Obj(), "\tInterface %s:", fact.iface)
|
||||||
|
prevIface = fact.iface
|
||||||
|
}
|
||||||
|
o.printf(deref(fact.conc).(*types.Named).Obj(), "\t\t%s", fact.conc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,381 @@
|
||||||
|
package oracle
|
||||||
|
|
||||||
|
// This file defines oracle.Main, the entry point for the oracle tool.
|
||||||
|
// The actual executable is defined in cmd/oracle.
|
||||||
|
|
||||||
|
// TODO(adonovan): new query: show all statements that may update the
|
||||||
|
// selected lvalue (local, global, field, etc).
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/build"
|
||||||
|
"go/printer"
|
||||||
|
"go/token"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"code.google.com/p/go.tools/go/types"
|
||||||
|
"code.google.com/p/go.tools/importer"
|
||||||
|
"code.google.com/p/go.tools/pointer"
|
||||||
|
"code.google.com/p/go.tools/ssa"
|
||||||
|
)
|
||||||
|
|
||||||
|
type oracle struct {
|
||||||
|
out io.Writer // standard output
|
||||||
|
prog *ssa.Program // the SSA program [need&SSA]
|
||||||
|
config pointer.Config // pointer analysis configuration
|
||||||
|
|
||||||
|
// need&(Pos|ExactPos):
|
||||||
|
startPos, endPos token.Pos // source extent of query
|
||||||
|
queryPkgInfo *importer.PackageInfo // type info for the queried package
|
||||||
|
queryPath []ast.Node // AST path from query node to root of ast.File
|
||||||
|
|
||||||
|
timers map[string]time.Duration // phase timing information
|
||||||
|
}
|
||||||
|
|
||||||
|
// A set of bits indicating the analytical requirements of each mode.
|
||||||
|
const (
|
||||||
|
Pos = 1 << iota // needs a position
|
||||||
|
ExactPos // needs an exact AST selection; implies Pos
|
||||||
|
SSA // needs SSA intermediate form
|
||||||
|
WholeSource // needs ASTs/SSA (not just types) for whole program
|
||||||
|
|
||||||
|
// TODO(adonovan): implement more efficiently than WholeSource|SSA.
|
||||||
|
TypedAST = WholeSource | SSA // needs typed AST for the queried package; implies Pos
|
||||||
|
)
|
||||||
|
|
||||||
|
type modeInfo struct {
|
||||||
|
needs int
|
||||||
|
impl func(*oracle) (queryResult, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
var modes = map[string]modeInfo{
|
||||||
|
"callees": modeInfo{WholeSource | SSA | ExactPos, callees},
|
||||||
|
"callers": modeInfo{WholeSource | SSA | Pos, callers},
|
||||||
|
"callgraph": modeInfo{WholeSource | SSA, callgraph},
|
||||||
|
"callstack": modeInfo{WholeSource | SSA | Pos, callstack},
|
||||||
|
"describe": modeInfo{WholeSource | SSA | ExactPos, describe},
|
||||||
|
"freevars": modeInfo{TypedAST | Pos, freevars},
|
||||||
|
"implements": modeInfo{TypedAST | Pos, implements},
|
||||||
|
"peers": modeInfo{WholeSource | SSA | Pos, peers},
|
||||||
|
}
|
||||||
|
|
||||||
|
type queryResult interface {
|
||||||
|
display(o *oracle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main runs the oracle.
|
||||||
|
// args specify the main package in importer.CreatePackageFromArgs syntax.
|
||||||
|
// mode is the query mode ("callers", etc).
|
||||||
|
// pos is the selection in parseQueryPos() syntax.
|
||||||
|
// ptalog is the (optional) pointer-analysis log file.
|
||||||
|
// out is the standard output stream.
|
||||||
|
// buildContext is the optional configuration for locating packages.
|
||||||
|
//
|
||||||
|
func Main(args []string, mode, pos string, ptalog, out io.Writer, buildContext *build.Context) error {
|
||||||
|
minfo, ok := modes[mode]
|
||||||
|
if !ok {
|
||||||
|
if mode == "" {
|
||||||
|
return errors.New("You must specify a -mode to perform.")
|
||||||
|
}
|
||||||
|
return fmt.Errorf("Invalid mode type '%s'.", mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
var loader importer.SourceLoader
|
||||||
|
if minfo.needs&WholeSource != 0 {
|
||||||
|
loader = importer.MakeGoBuildLoader(buildContext)
|
||||||
|
}
|
||||||
|
imp := importer.New(&importer.Config{Loader: loader})
|
||||||
|
o := &oracle{
|
||||||
|
out: out,
|
||||||
|
prog: ssa.NewProgram(imp.Fset, 0),
|
||||||
|
timers: make(map[string]time.Duration),
|
||||||
|
}
|
||||||
|
o.config.Log = ptalog
|
||||||
|
|
||||||
|
type warning struct {
|
||||||
|
pos token.Pos
|
||||||
|
format string
|
||||||
|
args []interface{}
|
||||||
|
}
|
||||||
|
var warnings []warning
|
||||||
|
o.config.Warn = func(pos token.Pos, format string, args ...interface{}) {
|
||||||
|
warnings = append(warnings, warning{pos, format, args})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase timing diagnostics.
|
||||||
|
if false {
|
||||||
|
defer func() {
|
||||||
|
fmt.Println()
|
||||||
|
for name, duration := range o.timers {
|
||||||
|
fmt.Printf("# %-30s %s\n", name, duration)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load/parse/type-check program from args.
|
||||||
|
start := time.Now()
|
||||||
|
initialPkgInfo, _, err := importer.CreatePackageFromArgs(imp, args)
|
||||||
|
if err != nil {
|
||||||
|
return err // I/O, parser or type error
|
||||||
|
}
|
||||||
|
o.timers["load/parse/type"] = time.Since(start)
|
||||||
|
|
||||||
|
// Parse the source query position.
|
||||||
|
if minfo.needs&(Pos|ExactPos) != 0 {
|
||||||
|
var err error
|
||||||
|
o.startPos, o.endPos, err = parseQueryPos(o.prog.Fset, pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var exact bool
|
||||||
|
o.queryPkgInfo, o.queryPath, exact = imp.PathEnclosingInterval(o.startPos, o.endPos)
|
||||||
|
if o.queryPath == nil {
|
||||||
|
return o.errorf(o, "no syntax here")
|
||||||
|
}
|
||||||
|
if minfo.needs&ExactPos != 0 && !exact {
|
||||||
|
return o.errorf(o.queryPath[0], "ambiguous selection within %s",
|
||||||
|
importer.NodeDescription(o.queryPath[0]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create SSA package for the initial package and its dependencies.
|
||||||
|
if minfo.needs&SSA != 0 {
|
||||||
|
start = time.Now()
|
||||||
|
|
||||||
|
// All packages.
|
||||||
|
for _, info := range imp.Packages {
|
||||||
|
o.prog.CreatePackage(info) // create ssa.Package
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial package (specified on command line)
|
||||||
|
initialPkg := o.prog.Package(initialPkgInfo.Pkg)
|
||||||
|
|
||||||
|
// Add package to the pointer analysis scope.
|
||||||
|
if initialPkg.Func("main") == nil {
|
||||||
|
if initialPkg.CreateTestMainFunction() == nil {
|
||||||
|
return o.errorf(o, "analysis scope has no main() entry points")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
o.config.Mains = append(o.config.Mains, initialPkg)
|
||||||
|
|
||||||
|
// Query package.
|
||||||
|
if o.queryPkgInfo != nil {
|
||||||
|
pkg := o.prog.Package(o.queryPkgInfo.Pkg)
|
||||||
|
pkg.SetDebugMode(true)
|
||||||
|
pkg.Build()
|
||||||
|
}
|
||||||
|
|
||||||
|
o.timers["SSA-create"] = time.Since(start)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSA is built and we have query{Path,PkgInfo}.
|
||||||
|
// Release the other ASTs and type info to the GC.
|
||||||
|
imp = nil
|
||||||
|
|
||||||
|
result, err := minfo.impl(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// TODO(adonovan): use this as a seam for testing.
|
||||||
|
result.display(o)
|
||||||
|
|
||||||
|
// Print warnings after the main output.
|
||||||
|
if warnings != nil {
|
||||||
|
fmt.Fprintln(o.out, "\nPointer analysis warnings:")
|
||||||
|
for _, w := range warnings {
|
||||||
|
o.fprintf(o.out, w.pos, "warning: "+w.format, w.args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- Utilities ----------
|
||||||
|
|
||||||
|
// buildSSA constructs the SSA representation of Go-source function bodies.
|
||||||
|
// Not needed in simpler modes, e.g. freevars.
|
||||||
|
//
|
||||||
|
func buildSSA(o *oracle) {
|
||||||
|
start := time.Now()
|
||||||
|
o.prog.BuildAll()
|
||||||
|
o.timers["SSA-build"] = time.Since(start)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ptrAnalysis runs the pointer analysis and returns the synthetic
|
||||||
|
// root of the callgraph.
|
||||||
|
//
|
||||||
|
func ptrAnalysis(o *oracle) pointer.CallGraphNode {
|
||||||
|
start := time.Now()
|
||||||
|
root := pointer.Analyze(&o.config)
|
||||||
|
o.timers["pointer analysis"] = time.Since(start)
|
||||||
|
return root
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseQueryPos parses a string of the form "file pos" or file
|
||||||
|
// start-end" where pos, start, end are decimal integers, and returns
|
||||||
|
// the extent to which it refers.
|
||||||
|
//
|
||||||
|
func parseQueryPos(fset *token.FileSet, queryPos string) (start, end token.Pos, err error) {
|
||||||
|
if queryPos == "" {
|
||||||
|
err = fmt.Errorf("no source position specified (-pos flag)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var filename string
|
||||||
|
var startOffset, endOffset int
|
||||||
|
n, err := fmt.Sscanf(queryPos, "%s %d-%d", &filename, &startOffset, &endOffset)
|
||||||
|
if n != 3 {
|
||||||
|
n, err = fmt.Sscanf(queryPos, "%s %d", &filename, &startOffset)
|
||||||
|
if n != 2 {
|
||||||
|
err = fmt.Errorf("invalid source position -pos=%q", queryPos)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
endOffset = startOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
var file *token.File
|
||||||
|
fset.Iterate(func(f *token.File) bool {
|
||||||
|
if sameFile(filename, f.Name()) {
|
||||||
|
// (f.Name() is absolute)
|
||||||
|
file = f
|
||||||
|
return false // done
|
||||||
|
}
|
||||||
|
return true // continue
|
||||||
|
})
|
||||||
|
if file == nil {
|
||||||
|
err = fmt.Errorf("couldn't find file containing position -pos=%q", queryPos)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Range check [start..end], inclusive of both end-points.
|
||||||
|
|
||||||
|
if 0 <= startOffset && startOffset <= file.Size() {
|
||||||
|
start = file.Pos(int(startOffset))
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("start position is beyond end of file -pos=%q", queryPos)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if 0 <= endOffset && endOffset <= file.Size() {
|
||||||
|
end = file.Pos(int(endOffset))
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("end position is beyond end of file -pos=%q", queryPos)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// sameFile returns true if x and y have the same basename and denote
|
||||||
|
// the same file.
|
||||||
|
//
|
||||||
|
func sameFile(x, y string) bool {
|
||||||
|
if filepath.Base(x) == filepath.Base(y) { // (optimisation)
|
||||||
|
if xi, err := os.Stat(x); err == nil {
|
||||||
|
if yi, err := os.Stat(y); err == nil {
|
||||||
|
return os.SameFile(xi, yi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// unparen returns e with any enclosing parentheses stripped.
|
||||||
|
func unparen(e ast.Expr) ast.Expr {
|
||||||
|
for {
|
||||||
|
p, ok := e.(*ast.ParenExpr)
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
e = p.X
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// deref returns a pointer's element type; otherwise it returns typ.
|
||||||
|
func deref(typ types.Type) types.Type {
|
||||||
|
if p, ok := typ.Underlying().(*types.Pointer); ok {
|
||||||
|
return p.Elem()
|
||||||
|
}
|
||||||
|
return typ
|
||||||
|
}
|
||||||
|
|
||||||
|
// fprintf prints to w a message of the form "location: message\n"
|
||||||
|
// where location is derived from pos.
|
||||||
|
//
|
||||||
|
// pos must be one of:
|
||||||
|
// - a token.Pos, denoting a position
|
||||||
|
// - an ast.Node, denoting an interval
|
||||||
|
// - anything with a Pos() method:
|
||||||
|
// ssa.Member, ssa.Value, ssa.Instruction, types.Object, pointer.Label, etc.
|
||||||
|
// - o *oracle, meaning the extent [o.startPos, o.endPos) of the user's query.
|
||||||
|
// - nil, meaning no position at all.
|
||||||
|
//
|
||||||
|
// The output format is is compatible with the 'gnu'
|
||||||
|
// compilation-error-regexp in Emacs' compilation mode.
|
||||||
|
// TODO(adonovan): support other editors.
|
||||||
|
//
|
||||||
|
func (o *oracle) fprintf(w io.Writer, pos interface{}, format string, args ...interface{}) {
|
||||||
|
var start, end token.Pos
|
||||||
|
switch pos := pos.(type) {
|
||||||
|
case ast.Node:
|
||||||
|
start = pos.Pos()
|
||||||
|
end = pos.End()
|
||||||
|
case token.Pos:
|
||||||
|
start = pos
|
||||||
|
end = start
|
||||||
|
case interface {
|
||||||
|
Pos() token.Pos
|
||||||
|
}:
|
||||||
|
start = pos.Pos()
|
||||||
|
end = start
|
||||||
|
case *oracle:
|
||||||
|
start = o.startPos
|
||||||
|
end = o.endPos
|
||||||
|
case nil:
|
||||||
|
// no-op
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("invalid pos: %T", pos))
|
||||||
|
}
|
||||||
|
|
||||||
|
if sp := o.prog.Fset.Position(start); start == end {
|
||||||
|
// (prints "-: " for token.NoPos)
|
||||||
|
fmt.Fprintf(w, "%s: ", sp)
|
||||||
|
} else {
|
||||||
|
ep := o.prog.Fset.Position(end)
|
||||||
|
// The -1 below is a concession to Emacs's broken use of
|
||||||
|
// inclusive (not half-open) intervals.
|
||||||
|
// Other editors may not want it.
|
||||||
|
// TODO(adonovan): add an -editor=vim|emacs|acme|auto
|
||||||
|
// flag; auto uses EMACS=t / VIM=... / etc env vars.
|
||||||
|
fmt.Fprintf(w, "%s:%d.%d-%d.%d: ",
|
||||||
|
sp.Filename, sp.Line, sp.Column, ep.Line, ep.Column-1)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, format, args...)
|
||||||
|
io.WriteString(w, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// printf is like fprintf, but writes to to o.out.
|
||||||
|
func (o *oracle) printf(pos interface{}, format string, args ...interface{}) {
|
||||||
|
o.fprintf(o.out, pos, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// errorf is like fprintf, but returns a formatted error string.
|
||||||
|
func (o *oracle) errorf(pos interface{}, format string, args ...interface{}) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
o.fprintf(&buf, pos, format, args...)
|
||||||
|
return errors.New(buf.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// printNode returns the pretty-printed syntax of n.
|
||||||
|
func (o *oracle) printNode(n ast.Node) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
printer.Fprint(&buf, o.prog.Fset, n)
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,218 @@
|
||||||
|
package oracle_test
|
||||||
|
|
||||||
|
// This file defines a test framework for oracle queries.
|
||||||
|
//
|
||||||
|
// The files beneath testdata/src/main contain Go programs containing
|
||||||
|
// query annotations of the form:
|
||||||
|
//
|
||||||
|
// @verb id "select"
|
||||||
|
//
|
||||||
|
// where verb is the query mode (e.g. "callers"), id is a unique name
|
||||||
|
// for this query, and "select" is a regular expression matching the
|
||||||
|
// substring of the current line that is the query's input selection.
|
||||||
|
//
|
||||||
|
// The expected output for each query is provided in the accompanying
|
||||||
|
// .golden file.
|
||||||
|
//
|
||||||
|
// (Location information is not included because it's too fragile to
|
||||||
|
// display as text. TODO(adonovan): think about how we can test its
|
||||||
|
// correctness, since it is critical information.)
|
||||||
|
//
|
||||||
|
// Run this test with:
|
||||||
|
// % go test code.google.com/p/go.tools/oracle -update
|
||||||
|
// to update the golden files.
|
||||||
|
|
||||||
|
// TODO(adonovan): improve coverage:
|
||||||
|
// - output of @callgraph is nondeterministic.
|
||||||
|
// - as are lists of labels.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"go/build"
|
||||||
|
"go/parser"
|
||||||
|
"go/token"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.google.com/p/go.tools/oracle"
|
||||||
|
)
|
||||||
|
|
||||||
|
var updateFlag = flag.Bool("update", false, "Update the golden files.")
|
||||||
|
|
||||||
|
type query struct {
|
||||||
|
id string // unique id
|
||||||
|
verb string // query mode, e.g. "callees"
|
||||||
|
posn token.Position // position of of query
|
||||||
|
filename string
|
||||||
|
start, end int // selection of file to pass to oracle
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseRegexp(text string) (*regexp.Regexp, error) {
|
||||||
|
pattern, err := strconv.Unquote(text)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't unquote %s", text)
|
||||||
|
}
|
||||||
|
return regexp.Compile(pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseQueries parses and returns the queries in the named file.
|
||||||
|
func parseQueries(t *testing.T, filename string) []*query {
|
||||||
|
filedata, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the file once to discover the test queries.
|
||||||
|
var fset token.FileSet
|
||||||
|
f, err := parser.ParseFile(&fset, filename, filedata,
|
||||||
|
parser.DeclarationErrors|parser.ParseComments)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := bytes.Split(filedata, []byte("\n"))
|
||||||
|
|
||||||
|
var queries []*query
|
||||||
|
queriesById := make(map[string]*query)
|
||||||
|
|
||||||
|
// Find all annotations of these forms:
|
||||||
|
expectRe := regexp.MustCompile(`@([a-z]+)\s+(\S+)\s+(\".*)$`) // @verb id "regexp"
|
||||||
|
for _, c := range f.Comments {
|
||||||
|
text := strings.TrimSpace(c.Text())
|
||||||
|
if text == "" || text[0] != '@' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
posn := fset.Position(c.Pos())
|
||||||
|
|
||||||
|
// @verb id "regexp"
|
||||||
|
match := expectRe.FindStringSubmatch(text)
|
||||||
|
if match == nil {
|
||||||
|
t.Errorf("%s: ill-formed query: %s", posn, text)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
id := match[2]
|
||||||
|
if prev, ok := queriesById[id]; ok {
|
||||||
|
t.Errorf("%s: duplicate id %s", posn, id)
|
||||||
|
t.Errorf("%s: previously used here", prev.posn)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
selectRe, err := parseRegexp(match[3])
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: %s", posn, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find text of the current line, sans query.
|
||||||
|
// (Queries must be // not /**/ comments.)
|
||||||
|
line := lines[posn.Line-1][:posn.Column-1]
|
||||||
|
|
||||||
|
// Apply regexp to current line to find input selection.
|
||||||
|
loc := selectRe.FindIndex(line)
|
||||||
|
if loc == nil {
|
||||||
|
t.Errorf("%s: selection pattern %s doesn't match line %q",
|
||||||
|
posn, match[3], string(line))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assumes ASCII. TODO(adonovan): test on UTF-8.
|
||||||
|
linestart := posn.Offset - (posn.Column - 1)
|
||||||
|
|
||||||
|
// Compute the file offsets
|
||||||
|
q := &query{
|
||||||
|
id: id,
|
||||||
|
verb: match[1],
|
||||||
|
posn: posn,
|
||||||
|
filename: filename,
|
||||||
|
start: linestart + loc[0],
|
||||||
|
end: linestart + loc[1],
|
||||||
|
}
|
||||||
|
queries = append(queries, q)
|
||||||
|
queriesById[id] = q
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the slice, not map, for deterministic iteration.
|
||||||
|
return queries
|
||||||
|
}
|
||||||
|
|
||||||
|
// stripLocation removes a "file:line: " prefix.
|
||||||
|
func stripLocation(line string) string {
|
||||||
|
if i := strings.Index(line, ": "); i >= 0 {
|
||||||
|
line = line[i+2:]
|
||||||
|
}
|
||||||
|
return line
|
||||||
|
}
|
||||||
|
|
||||||
|
// doQuery poses query q to the oracle and writes its response and
|
||||||
|
// error (if any) to out.
|
||||||
|
func doQuery(out io.Writer, q *query) {
|
||||||
|
fmt.Fprintf(out, "-------- @%s %s --------\n", q.verb, q.id)
|
||||||
|
|
||||||
|
capture := new(bytes.Buffer) // capture standard output
|
||||||
|
var buildContext = build.Default
|
||||||
|
buildContext.GOPATH = "testdata"
|
||||||
|
err := oracle.Main([]string{q.filename},
|
||||||
|
q.verb,
|
||||||
|
fmt.Sprintf("%s %d-%d", q.filename, q.start, q.end),
|
||||||
|
/*PTA-log=*/ nil, capture, &buildContext)
|
||||||
|
|
||||||
|
for _, line := range strings.Split(capture.String(), "\n") {
|
||||||
|
fmt.Fprintf(out, "%s\n", stripLocation(line))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(out, "Error: %s\n", stripLocation(err.Error()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOracle(t *testing.T) {
|
||||||
|
for _, filename := range []string{
|
||||||
|
"testdata/src/main/calls.go",
|
||||||
|
"testdata/src/main/describe.go",
|
||||||
|
"testdata/src/main/freevars.go",
|
||||||
|
"testdata/src/main/implements.go",
|
||||||
|
"testdata/src/main/imports.go",
|
||||||
|
"testdata/src/main/peers.go",
|
||||||
|
} {
|
||||||
|
queries := parseQueries(t, filename)
|
||||||
|
golden := filename + "lden"
|
||||||
|
got := filename + "t"
|
||||||
|
gotfh, err := os.Create(got)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Create(%s) failed: %s", got, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
defer gotfh.Close()
|
||||||
|
|
||||||
|
// Run the oracle on each query, redirecting its output
|
||||||
|
// and error (if any) to the foo.got file.
|
||||||
|
for _, q := range queries {
|
||||||
|
doQuery(gotfh, q)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare foo.got with foo.golden.
|
||||||
|
cmd := exec.Command("/usr/bin/diff", "-u3", golden, got) // assumes POSIX
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
cmd.Stdout = buf
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
t.Errorf("Oracle tests for %s failed: %s.\n%s\n",
|
||||||
|
filename, err, buf)
|
||||||
|
|
||||||
|
if *updateFlag {
|
||||||
|
t.Logf("Updating %s...", golden)
|
||||||
|
if err := exec.Command("/bin/cp", got, golden).Run(); err != nil {
|
||||||
|
t.Errorf("Update failed: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,147 @@
|
||||||
|
package oracle
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/ast"
|
||||||
|
"go/token"
|
||||||
|
|
||||||
|
"code.google.com/p/go.tools/go/types"
|
||||||
|
"code.google.com/p/go.tools/pointer"
|
||||||
|
"code.google.com/p/go.tools/ssa"
|
||||||
|
)
|
||||||
|
|
||||||
|
// peers enumerates, for a given channel send (or receive) operation,
|
||||||
|
// the set of possible receives (or sends) that correspond to it.
|
||||||
|
//
|
||||||
|
// TODO(adonovan): support reflect.{Select,Recv,Send}.
|
||||||
|
// TODO(adonovan): permit the user to query based on a MakeChan (not send/recv),
|
||||||
|
// or the implicit receive in "for v := range ch".
|
||||||
|
//
|
||||||
|
func peers(o *oracle) (queryResult, error) {
|
||||||
|
// Determine the enclosing send/receive op for the specified position.
|
||||||
|
var arrowPos token.Pos
|
||||||
|
for _, n := range o.queryPath {
|
||||||
|
switch n := n.(type) {
|
||||||
|
case *ast.UnaryExpr:
|
||||||
|
if n.Op == token.ARROW {
|
||||||
|
arrowPos = n.OpPos
|
||||||
|
goto found
|
||||||
|
}
|
||||||
|
case *ast.SendStmt:
|
||||||
|
arrowPos = n.Arrow
|
||||||
|
goto found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, o.errorf(o.queryPath[0], "there is no send/receive here")
|
||||||
|
found:
|
||||||
|
|
||||||
|
buildSSA(o)
|
||||||
|
|
||||||
|
var queryOp chanOp // the originating send or receive operation
|
||||||
|
var ops []chanOp // all sends/receives of opposite direction
|
||||||
|
|
||||||
|
// Look at all send/receive instructions in the whole ssa.Program.
|
||||||
|
// Build a list of those of same type to query.
|
||||||
|
allFuncs := ssa.AllFunctions(o.prog)
|
||||||
|
for fn := range allFuncs {
|
||||||
|
for _, b := range fn.Blocks {
|
||||||
|
for _, instr := range b.Instrs {
|
||||||
|
for _, op := range chanOps(instr) {
|
||||||
|
ops = append(ops, op)
|
||||||
|
if op.pos == arrowPos {
|
||||||
|
queryOp = op // we found the query op
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if queryOp.ch == nil {
|
||||||
|
return nil, o.errorf(arrowPos, "ssa.Instruction for send/receive not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discard operations of wrong channel element type.
|
||||||
|
// Build set of channel ssa.Values as query to pointer analysis.
|
||||||
|
queryElemType := queryOp.ch.Type().Underlying().(*types.Chan).Elem()
|
||||||
|
channels := map[ssa.Value][]pointer.Pointer{queryOp.ch: nil}
|
||||||
|
i := 0
|
||||||
|
for _, op := range ops {
|
||||||
|
if types.IsIdentical(op.ch.Type().Underlying().(*types.Chan).Elem(), queryElemType) {
|
||||||
|
channels[op.ch] = nil
|
||||||
|
ops[i] = op
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ops = ops[:i]
|
||||||
|
|
||||||
|
// Run the pointer analysis.
|
||||||
|
o.config.QueryValues = channels
|
||||||
|
ptrAnalysis(o)
|
||||||
|
|
||||||
|
// Combine the PT sets from all contexts.
|
||||||
|
queryChanPts := pointer.PointsToCombined(channels[queryOp.ch])
|
||||||
|
|
||||||
|
return &peersResult{
|
||||||
|
queryOp: queryOp,
|
||||||
|
ops: ops,
|
||||||
|
queryChanPts: queryChanPts,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// chanOp abstracts an ssa.Send, ssa.Unop(ARROW), or a SelectState.
|
||||||
|
type chanOp struct {
|
||||||
|
ch ssa.Value
|
||||||
|
dir ast.ChanDir
|
||||||
|
pos token.Pos
|
||||||
|
}
|
||||||
|
|
||||||
|
// chanOps returns a slice of all the channel operations in the instruction.
|
||||||
|
func chanOps(instr ssa.Instruction) []chanOp {
|
||||||
|
// TODO(adonovan): handle calls to reflect.{Select,Recv,Send} too.
|
||||||
|
var ops []chanOp
|
||||||
|
switch instr := instr.(type) {
|
||||||
|
case *ssa.UnOp:
|
||||||
|
if instr.Op == token.ARROW {
|
||||||
|
ops = append(ops, chanOp{instr.X, ast.RECV, instr.Pos()})
|
||||||
|
}
|
||||||
|
case *ssa.Send:
|
||||||
|
ops = append(ops, chanOp{instr.Chan, ast.SEND, instr.Pos()})
|
||||||
|
case *ssa.Select:
|
||||||
|
for _, st := range instr.States {
|
||||||
|
ops = append(ops, chanOp{st.Chan, st.Dir, st.Pos})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ops
|
||||||
|
}
|
||||||
|
|
||||||
|
type peersResult struct {
|
||||||
|
queryOp chanOp
|
||||||
|
ops []chanOp
|
||||||
|
queryChanPts pointer.PointsToSet
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *peersResult) display(o *oracle) {
|
||||||
|
// Report which make(chan) labels the query's channel can alias.
|
||||||
|
labels := r.queryChanPts.Labels()
|
||||||
|
if len(labels) == 0 {
|
||||||
|
o.printf(r.queryOp.pos, "This channel can't point to anything.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
o.printf(r.queryOp.pos, "This channel of type %s may be:", r.queryOp.ch.Type())
|
||||||
|
// TODO(adonovan): sort, to ensure test determinism.
|
||||||
|
for _, label := range labels {
|
||||||
|
o.printf(label, "\tallocated here")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report which send/receive operations can alias the same make(chan) labels.
|
||||||
|
for _, op := range r.ops {
|
||||||
|
// TODO(adonovan): sort, to ensure test determinism.
|
||||||
|
for _, ptr := range o.config.QueryValues[op.ch] {
|
||||||
|
if ptr != nil && ptr.PointsTo().Intersects(r.queryChanPts) {
|
||||||
|
verb := "received from"
|
||||||
|
if op.dir == ast.SEND {
|
||||||
|
verb = "sent to"
|
||||||
|
}
|
||||||
|
o.printf(op.pos, "\t%s, here", verb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package lib
|
||||||
|
|
||||||
|
type Type int
|
||||||
|
|
||||||
|
func (Type) Method(x *int) *int {
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
func Func() {
|
||||||
|
}
|
||||||
|
|
||||||
|
const Const = 3
|
||||||
|
|
||||||
|
var Var = 0
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
// Tests of call-graph queries.
|
||||||
|
// See go.tools/oracle/oracle_test.go for explanation.
|
||||||
|
// See calls.golden for expected query results.
|
||||||
|
|
||||||
|
func A(x *int) { // @describe describe-A-x "x"
|
||||||
|
// @callers callers-A "^"
|
||||||
|
// @callstack callstack-A "^"
|
||||||
|
}
|
||||||
|
|
||||||
|
func B(x *int) { // @describe describe-B-x "x"
|
||||||
|
// @callers callers-B "^"
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply is not (yet) treated context-sensitively.
|
||||||
|
func apply(f func(x *int), x *int) {
|
||||||
|
f(x) // @callees callees-apply "f"
|
||||||
|
// @callers callers-apply "^"
|
||||||
|
}
|
||||||
|
|
||||||
|
// store *is* treated context-sensitively,
|
||||||
|
// so the points-to sets for pc, pd are precise.
|
||||||
|
func store(ptr **int, value *int) {
|
||||||
|
*ptr = value
|
||||||
|
// @callers callers-store "^"
|
||||||
|
}
|
||||||
|
|
||||||
|
func call(f func() *int) {
|
||||||
|
// Result points to anon function.
|
||||||
|
f() // @describe describe-result-f "f"
|
||||||
|
|
||||||
|
// Target of call is anon function.
|
||||||
|
f() // @callees callees-main.call-f "f"
|
||||||
|
|
||||||
|
// @callers callers-main.call "^"
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var a, b int
|
||||||
|
apply(A, &a) // @callees callees-main-apply1 "app"
|
||||||
|
apply(B, &b)
|
||||||
|
|
||||||
|
var c, d int
|
||||||
|
var pc, pd *int // @describe describe-pc "pc"
|
||||||
|
store(&pc, &c)
|
||||||
|
store(&pd, &d)
|
||||||
|
_ = pd // @describe describe-pd "pd"
|
||||||
|
|
||||||
|
call(func() *int {
|
||||||
|
// We are called twice from main.call
|
||||||
|
// @callers callers-main.anon "^"
|
||||||
|
return &a
|
||||||
|
})
|
||||||
|
|
||||||
|
// Errors
|
||||||
|
_ = "no function call here" // @callees callees-err-no-call "no"
|
||||||
|
print("builtin") // @callees callees-err-builtin "builtin"
|
||||||
|
_ = string("type conversion") // @callees callees-err-conversion "str"
|
||||||
|
call(nil) // @callees callees-err-bad-selection "call\\(nil"
|
||||||
|
if false {
|
||||||
|
main() // @callees callees-err-deadcode1 "main"
|
||||||
|
}
|
||||||
|
var nilFunc func()
|
||||||
|
nilFunc() // @callees callees-err-nil-func "nilFunc"
|
||||||
|
var i interface {
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
i.f() // @callees callees-err-nil-interface "i.f"
|
||||||
|
}
|
||||||
|
|
||||||
|
func deadcode() {
|
||||||
|
main() // @callees callees-err-deadcode2 "main"
|
||||||
|
// @callers callers-err-deadcode "^"
|
||||||
|
// @callstack callstack-err-deadcode "^"
|
||||||
|
}
|
||||||
|
|
||||||
|
// This code belongs to init.
|
||||||
|
var global = 123 // @callers callers-global "global"
|
||||||
|
|
||||||
|
// init may be called by other packages' inits, or in this case, the
|
||||||
|
// root of the callgraph.
|
||||||
|
func init() {
|
||||||
|
// @callers callers-init "^"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
-------- @describe describe-A-x --------
|
||||||
|
definition of var x *int
|
||||||
|
value may point to these labels:
|
||||||
|
a
|
||||||
|
b
|
||||||
|
|
||||||
|
-------- @callstack callstack-A --------
|
||||||
|
Found a call path from root to main.A
|
||||||
|
main.A
|
||||||
|
dynamic function call from main.apply
|
||||||
|
static function call from main.main
|
||||||
|
|
||||||
|
-------- @describe describe-B-x --------
|
||||||
|
definition of var x *int
|
||||||
|
value may point to these labels:
|
||||||
|
a
|
||||||
|
b
|
||||||
|
|
||||||
|
-------- @callers callers-B --------
|
||||||
|
main.B is called from these 1 sites:
|
||||||
|
dynamic function call from main.apply
|
||||||
|
|
||||||
|
-------- @callees callees-apply --------
|
||||||
|
this dynamic function call dispatches to:
|
||||||
|
main.A
|
||||||
|
main.B
|
||||||
|
|
||||||
|
-------- @callers callers-apply --------
|
||||||
|
main.apply is called from these 2 sites:
|
||||||
|
static function call from main.main
|
||||||
|
static function call from main.main
|
||||||
|
|
||||||
|
-------- @callers callers-store --------
|
||||||
|
main.store is called from these 2 sites:
|
||||||
|
static function call from main.main
|
||||||
|
static function call from main.main
|
||||||
|
|
||||||
|
-------- @describe describe-result-f --------
|
||||||
|
reference to var f func() *int
|
||||||
|
defined here
|
||||||
|
value may point to these labels:
|
||||||
|
func@50.7
|
||||||
|
|
||||||
|
-------- @callees callees-main.call-f --------
|
||||||
|
this dynamic function call dispatches to:
|
||||||
|
func@50.7
|
||||||
|
|
||||||
|
-------- @callers callers-main.call --------
|
||||||
|
main.call is called from these 2 sites:
|
||||||
|
static function call from main.main
|
||||||
|
static function call from main.main
|
||||||
|
|
||||||
|
-------- @callees callees-main-apply1 --------
|
||||||
|
this static function call dispatches to:
|
||||||
|
main.apply
|
||||||
|
|
||||||
|
-------- @describe describe-pc --------
|
||||||
|
definition of var pc *int
|
||||||
|
value may point to these labels:
|
||||||
|
c
|
||||||
|
|
||||||
|
-------- @describe describe-pd --------
|
||||||
|
reference to var pd *int
|
||||||
|
defined here
|
||||||
|
value may point to these labels:
|
||||||
|
d
|
||||||
|
|
||||||
|
-------- @callees callees-err-no-call --------
|
||||||
|
|
||||||
|
Error: there is no function call here
|
||||||
|
|
||||||
|
-------- @callees callees-err-builtin --------
|
||||||
|
|
||||||
|
Error: this is a call to the built-in 'print' operator
|
||||||
|
|
||||||
|
-------- @callees callees-err-conversion --------
|
||||||
|
|
||||||
|
Error: this is a type conversion, not a function call
|
||||||
|
|
||||||
|
-------- @callees callees-err-bad-selection --------
|
||||||
|
|
||||||
|
Error: ambiguous selection within function call (or conversion)
|
||||||
|
|
||||||
|
-------- @callees callees-err-deadcode1 --------
|
||||||
|
this call site is unreachable in this analysis
|
||||||
|
|
||||||
|
-------- @callees callees-err-nil-func --------
|
||||||
|
dynamic function call on nil value
|
||||||
|
|
||||||
|
-------- @callees callees-err-nil-interface --------
|
||||||
|
dynamic method call on nil value
|
||||||
|
|
||||||
|
-------- @callees callees-err-deadcode2 --------
|
||||||
|
this call site is unreachable in this analysis
|
||||||
|
|
||||||
|
-------- @callstack callstack-err-deadcode --------
|
||||||
|
main.deadcode is unreachable in this analysis scope
|
||||||
|
|
||||||
|
-------- @callers callers-global --------
|
||||||
|
main.init is called from these 1 sites:
|
||||||
|
the root of the call graph
|
||||||
|
|
||||||
|
-------- @callers callers-init --------
|
||||||
|
main.init is called from these 1 sites:
|
||||||
|
the root of the call graph
|
||||||
|
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
package describe // @describe pkgdecl "describe"
|
||||||
|
|
||||||
|
// Tests of 'describe' query.
|
||||||
|
// See go.tools/oracle/oracle_test.go for explanation.
|
||||||
|
// See describe.golden for expected query results.
|
||||||
|
|
||||||
|
// TODO(adonovan): more coverage of the (extensive) logic.
|
||||||
|
|
||||||
|
type cake float64 // @describe type-ref-builtin "float64"
|
||||||
|
|
||||||
|
const pi = 3.141 // @describe const-def-pi "pi"
|
||||||
|
const pie = cake(pi) // @describe const-def-pie "pie"
|
||||||
|
const _ = pi // @describe const-ref-pi "pi"
|
||||||
|
|
||||||
|
func main() { // @describe func-def-main "main"
|
||||||
|
// func objects
|
||||||
|
_ = main // @describe func-ref-main "main"
|
||||||
|
_ = (*C).f // @describe func-ref-*C.f "..C..f"
|
||||||
|
_ = D.f // @describe func-ref-D.f "D.f"
|
||||||
|
_ = I.f // @describe func-ref-I.f "I.f"
|
||||||
|
var d D // @describe type-D "D"
|
||||||
|
var i I // @describe type-I "I"
|
||||||
|
_ = d.f // @describe func-ref-d.f "d.f"
|
||||||
|
_ = i.f // @describe func-ref-i.f "i.f"
|
||||||
|
|
||||||
|
// var objects
|
||||||
|
anon := func() {
|
||||||
|
_ = d // @describe ref-lexical-d "d"
|
||||||
|
}
|
||||||
|
_ = anon // @describe ref-anon "anon"
|
||||||
|
|
||||||
|
// SSA affords some local flow sensitivity.
|
||||||
|
var a, b int
|
||||||
|
var x = &a // @describe var-def-x-1 "x"
|
||||||
|
_ = x // @describe var-ref-x-1 "x"
|
||||||
|
x = &b // @describe var-def-x-2 "x"
|
||||||
|
_ = x // @describe var-ref-x-2 "x"
|
||||||
|
|
||||||
|
// const objects
|
||||||
|
const localpi = 3.141 // @describe const-local-pi "localpi"
|
||||||
|
const localpie = cake(pi) // @describe const-local-pie "localpie"
|
||||||
|
const _ = localpi // @describe const-ref-localpi "localpi"
|
||||||
|
|
||||||
|
// type objects
|
||||||
|
type T int // @describe type-def-T "T"
|
||||||
|
var three T = 3 // @describe type-ref-T "T"
|
||||||
|
|
||||||
|
print(1 + 2*3) // @describe const-expr " 2.3"
|
||||||
|
print(real(1+2i) - 3) // @describe const-expr2 "real.*3"
|
||||||
|
|
||||||
|
m := map[string]*int{"a": &a}
|
||||||
|
// TODO(adonovan): fix spurious error in map-lookup,ok result.
|
||||||
|
mapval, _ := m["a"] // @describe map-lookup,ok "m..a.."
|
||||||
|
_ = mapval // @describe mapval "mapval"
|
||||||
|
_ = m // @describe m "m"
|
||||||
|
|
||||||
|
defer main() // @describe defer-stmt "defer"
|
||||||
|
go main() // @describe go-stmt "go"
|
||||||
|
}
|
||||||
|
|
||||||
|
func deadcode() {
|
||||||
|
var a int // @describe var-decl-stmt "var a int"
|
||||||
|
// Pointer analysis can't run on dead code.
|
||||||
|
var b = &a // @describe b "b"
|
||||||
|
}
|
||||||
|
|
||||||
|
type I interface { // @describe def-iface-I "I"
|
||||||
|
f() // @describe def-imethod-I.f "f"
|
||||||
|
}
|
||||||
|
|
||||||
|
type C int
|
||||||
|
type D struct{}
|
||||||
|
|
||||||
|
func (c *C) f() {}
|
||||||
|
func (d D) f() {}
|
||||||
|
|
@ -0,0 +1,165 @@
|
||||||
|
-------- @describe pkgdecl --------
|
||||||
|
definition of package "main"
|
||||||
|
type C int
|
||||||
|
method (*describe.C) f()
|
||||||
|
type D struct{}
|
||||||
|
method (describe.D) f()
|
||||||
|
type I interface{f()}
|
||||||
|
method (describe.I) f()
|
||||||
|
type cake float64
|
||||||
|
func deadcode func()
|
||||||
|
func init func()
|
||||||
|
var init$guard bool
|
||||||
|
func main func()
|
||||||
|
const pi untyped float = 3141/1000:untyped float
|
||||||
|
const pie describe.cake = 1768225803696341/562949953421312:describe.cake
|
||||||
|
|
||||||
|
-------- @describe type-ref-builtin --------
|
||||||
|
reference to built-in type float64
|
||||||
|
|
||||||
|
-------- @describe const-def-pi --------
|
||||||
|
definition of const pi untyped float
|
||||||
|
|
||||||
|
-------- @describe const-def-pie --------
|
||||||
|
definition of const pie describe.cake
|
||||||
|
|
||||||
|
-------- @describe const-ref-pi --------
|
||||||
|
reference to const pi untyped float of constant value 3141/1000
|
||||||
|
defined here
|
||||||
|
|
||||||
|
-------- @describe func-def-main --------
|
||||||
|
definition of func describe.main()
|
||||||
|
|
||||||
|
-------- @describe func-ref-main --------
|
||||||
|
reference to func describe.main()
|
||||||
|
defined here
|
||||||
|
|
||||||
|
-------- @describe func-ref-*C.f --------
|
||||||
|
reference to func (*describe.C).f()
|
||||||
|
defined here
|
||||||
|
|
||||||
|
-------- @describe func-ref-D.f --------
|
||||||
|
reference to func (describe.D).f()
|
||||||
|
defined here
|
||||||
|
|
||||||
|
-------- @describe func-ref-I.f --------
|
||||||
|
reference to func (describe.I).f()
|
||||||
|
defined here
|
||||||
|
|
||||||
|
-------- @describe type-D --------
|
||||||
|
reference to type describe.D
|
||||||
|
defined as struct{}
|
||||||
|
Method set:
|
||||||
|
method (describe.D) f()
|
||||||
|
|
||||||
|
-------- @describe type-I --------
|
||||||
|
reference to type describe.I
|
||||||
|
defined as interface{f()}
|
||||||
|
Method set:
|
||||||
|
method (describe.I) f()
|
||||||
|
|
||||||
|
-------- @describe func-ref-d.f --------
|
||||||
|
reference to func (describe.D).f()
|
||||||
|
defined here
|
||||||
|
|
||||||
|
-------- @describe func-ref-i.f --------
|
||||||
|
reference to func (describe.I).f()
|
||||||
|
defined here
|
||||||
|
|
||||||
|
-------- @describe ref-lexical-d --------
|
||||||
|
reference to var d describe.D
|
||||||
|
defined here
|
||||||
|
|
||||||
|
-------- @describe ref-anon --------
|
||||||
|
reference to var anon func()
|
||||||
|
defined here
|
||||||
|
value may point to these labels:
|
||||||
|
func@27.10
|
||||||
|
|
||||||
|
-------- @describe var-def-x-1 --------
|
||||||
|
definition of var x *int
|
||||||
|
value may point to these labels:
|
||||||
|
a
|
||||||
|
|
||||||
|
-------- @describe var-ref-x-1 --------
|
||||||
|
reference to var x *int
|
||||||
|
defined here
|
||||||
|
value may point to these labels:
|
||||||
|
a
|
||||||
|
|
||||||
|
-------- @describe var-def-x-2 --------
|
||||||
|
reference to var x *int
|
||||||
|
defined here
|
||||||
|
value may point to these labels:
|
||||||
|
b
|
||||||
|
|
||||||
|
-------- @describe var-ref-x-2 --------
|
||||||
|
reference to var x *int
|
||||||
|
defined here
|
||||||
|
value may point to these labels:
|
||||||
|
b
|
||||||
|
|
||||||
|
-------- @describe const-local-pi --------
|
||||||
|
definition of const localpi untyped float
|
||||||
|
|
||||||
|
-------- @describe const-local-pie --------
|
||||||
|
definition of const localpie describe.cake
|
||||||
|
|
||||||
|
-------- @describe const-ref-localpi --------
|
||||||
|
reference to const localpi untyped float of constant value 3141/1000
|
||||||
|
defined here
|
||||||
|
|
||||||
|
-------- @describe type-def-T --------
|
||||||
|
definition of type describe.T
|
||||||
|
No methods.
|
||||||
|
|
||||||
|
-------- @describe type-ref-T --------
|
||||||
|
reference to type describe.T
|
||||||
|
defined as int
|
||||||
|
No methods.
|
||||||
|
|
||||||
|
-------- @describe const-expr --------
|
||||||
|
binary * operation of constant value 6
|
||||||
|
|
||||||
|
-------- @describe const-expr2 --------
|
||||||
|
binary - operation of constant value -2
|
||||||
|
|
||||||
|
-------- @describe map-lookup,ok --------
|
||||||
|
index expression of type (*int, bool)
|
||||||
|
no pointer analysis: can't locate SSA Value for expression in main.main
|
||||||
|
|
||||||
|
-------- @describe mapval --------
|
||||||
|
reference to var mapval *int
|
||||||
|
defined here
|
||||||
|
value may point to these labels:
|
||||||
|
a
|
||||||
|
|
||||||
|
-------- @describe m --------
|
||||||
|
reference to var m map[string]*int
|
||||||
|
defined here
|
||||||
|
value may point to these labels:
|
||||||
|
makemap
|
||||||
|
|
||||||
|
-------- @describe defer-stmt --------
|
||||||
|
defer statement
|
||||||
|
|
||||||
|
-------- @describe go-stmt --------
|
||||||
|
go statement
|
||||||
|
|
||||||
|
-------- @describe var-decl-stmt --------
|
||||||
|
definition of var a int
|
||||||
|
|
||||||
|
-------- @describe b --------
|
||||||
|
definition of var b *int
|
||||||
|
pointer analysis did not analyze this expression (dead code?)
|
||||||
|
|
||||||
|
-------- @describe def-iface-I --------
|
||||||
|
definition of type describe.I
|
||||||
|
Method set:
|
||||||
|
method (describe.I) f()
|
||||||
|
|
||||||
|
-------- @describe def-imethod-I.f --------
|
||||||
|
type interface{f()}
|
||||||
|
Method set:
|
||||||
|
method (interface{f()}) f()
|
||||||
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
// Tests of 'freevars' query.
|
||||||
|
// See go.tools/oracle/oracle_test.go for explanation.
|
||||||
|
// See freevars.golden for expected query results.
|
||||||
|
|
||||||
|
// TODO(adonovan): it's hard to test this query in a single line of gofmt'd code.
|
||||||
|
|
||||||
|
type T struct {
|
||||||
|
a, b int
|
||||||
|
}
|
||||||
|
|
||||||
|
type S struct {
|
||||||
|
x int
|
||||||
|
t T
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
type C int
|
||||||
|
x := 1
|
||||||
|
const exp = 6
|
||||||
|
if y := 2; x+y+int(C(3)) != exp { // @freevars fv1 "if.*{"
|
||||||
|
panic("expected 6")
|
||||||
|
}
|
||||||
|
|
||||||
|
var s S
|
||||||
|
|
||||||
|
for x, y := range "foo" {
|
||||||
|
println(s.x + s.t.a + s.t.b + x + int(y)) // @freevars fv2 "print.*y."
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(adonovan): enable when go/types supports labels.
|
||||||
|
loop: // #@freevars fv-def-label "loop:"
|
||||||
|
for {
|
||||||
|
break loop // #@freevars fv-ref-label "break loop"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
-------- @freevars fv1 --------
|
||||||
|
Free identifers:
|
||||||
|
var x int
|
||||||
|
type C main.C
|
||||||
|
const exp untyped integer
|
||||||
|
|
||||||
|
-------- @freevars fv2 --------
|
||||||
|
Free identifers:
|
||||||
|
var s.x int
|
||||||
|
var s.t.a int
|
||||||
|
var s.t.b int
|
||||||
|
var x int
|
||||||
|
var y int32
|
||||||
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
// Tests of 'implements' query.
|
||||||
|
// See go.tools/oracle/oracle_test.go for explanation.
|
||||||
|
// See implements.golden for expected query results.
|
||||||
|
|
||||||
|
// @implements impl ""
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
}
|
||||||
|
|
||||||
|
type E interface{}
|
||||||
|
|
||||||
|
type F interface {
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
|
||||||
|
type FG interface {
|
||||||
|
f()
|
||||||
|
g() int
|
||||||
|
}
|
||||||
|
|
||||||
|
type C int
|
||||||
|
type D struct{}
|
||||||
|
|
||||||
|
func (c *C) f() {}
|
||||||
|
func (d D) f() {}
|
||||||
|
|
||||||
|
func (d *D) g() int { return 0 }
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
-------- @implements impl --------
|
||||||
|
Interface main.E:
|
||||||
|
main.C
|
||||||
|
main.D
|
||||||
|
Interface main.F:
|
||||||
|
*main.C
|
||||||
|
main.D
|
||||||
|
Interface main.FG:
|
||||||
|
*main.D
|
||||||
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package imports
|
||||||
|
|
||||||
|
import (
|
||||||
|
"lib" // @describe ref-pkg-import "lib"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Tests that import another package. (To make the tests run quickly,
|
||||||
|
// we avoid using imports in all the other tests. Remember, each
|
||||||
|
// query causes parsing and typechecking of the whole program.)
|
||||||
|
//
|
||||||
|
// See go.tools/oracle/oracle_test.go for explanation.
|
||||||
|
// See imports.golden for expected query results.
|
||||||
|
|
||||||
|
var a int
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
const c = lib.Const // @describe ref-const "Const"
|
||||||
|
lib.Func() // @describe ref-func "Func"
|
||||||
|
lib.Var++ // @describe ref-var "Var"
|
||||||
|
var t lib.Type // @describe ref-type "Type"
|
||||||
|
p := t.Method(&a) // @describe ref-method "Method"
|
||||||
|
|
||||||
|
print(*p + 1) // @describe p "p "
|
||||||
|
|
||||||
|
var _ lib.Type // @describe ref-pkg "lib"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
-------- @describe ref-pkg-import --------
|
||||||
|
import of package "lib"
|
||||||
|
const Const untyped integer = 3:untyped integer
|
||||||
|
func Func func()
|
||||||
|
type Type int
|
||||||
|
method (lib.Type) Method(x *int) *int
|
||||||
|
var Var int
|
||||||
|
|
||||||
|
-------- @describe ref-const --------
|
||||||
|
reference to const Const untyped integer
|
||||||
|
defined here
|
||||||
|
|
||||||
|
-------- @describe ref-func --------
|
||||||
|
reference to func lib.Func()
|
||||||
|
defined here
|
||||||
|
|
||||||
|
-------- @describe ref-var --------
|
||||||
|
reference to var Var int
|
||||||
|
defined here
|
||||||
|
value may point to these labels:
|
||||||
|
lib.Var
|
||||||
|
|
||||||
|
-------- @describe ref-type --------
|
||||||
|
reference to type lib.Type
|
||||||
|
defined as int
|
||||||
|
Method set:
|
||||||
|
method (lib.Type) Method(x *int) *int
|
||||||
|
|
||||||
|
-------- @describe ref-method --------
|
||||||
|
reference to func (lib.Type).Method(x *int) *int
|
||||||
|
defined here
|
||||||
|
|
||||||
|
-------- @describe p --------
|
||||||
|
reference to var p *int
|
||||||
|
defined here
|
||||||
|
value may point to these labels:
|
||||||
|
main.a
|
||||||
|
|
||||||
|
-------- @describe ref-pkg --------
|
||||||
|
reference to package "lib"
|
||||||
|
const Const untyped integer = 3:untyped integer
|
||||||
|
func Func func()
|
||||||
|
type Type int
|
||||||
|
method (lib.Type) Method(x *int) *int
|
||||||
|
var Var int
|
||||||
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
package peers
|
||||||
|
|
||||||
|
// Tests of channel 'peers' query.
|
||||||
|
// See go.tools/oracle/oracle_test.go for explanation.
|
||||||
|
// See peers.golden for expected query results.
|
||||||
|
|
||||||
|
var a2 int
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
chA := make(chan *int)
|
||||||
|
a1 := 1
|
||||||
|
chA <- &a1
|
||||||
|
|
||||||
|
chA2 := make(chan *int, 2)
|
||||||
|
if a2 == 0 {
|
||||||
|
chA = chA2
|
||||||
|
}
|
||||||
|
|
||||||
|
chB := make(chan *int)
|
||||||
|
b := 3
|
||||||
|
chB <- &b
|
||||||
|
|
||||||
|
<-chA // @describe describe-chA "chA"
|
||||||
|
<-chA2 // @describe describe-chA2 "chA2"
|
||||||
|
<-chB // @describe describe-chB "chB"
|
||||||
|
|
||||||
|
select {
|
||||||
|
case rA := <-chA: // @peers peer-recv-chA "<-"
|
||||||
|
_ = rA // @describe describe-rA "rA"
|
||||||
|
case rB := <-chB: // @peers peer-recv-chB "<-"
|
||||||
|
_ = rB // @describe describe-rB "rB"
|
||||||
|
|
||||||
|
case <-chA: // @peers peer-recv-chA' "<-"
|
||||||
|
|
||||||
|
case chA2 <- &a2: // @peers peer-send-chA' "<-"
|
||||||
|
}
|
||||||
|
|
||||||
|
for _ = range chA {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
-------- @describe describe-chA --------
|
||||||
|
reference to var chA chan *int
|
||||||
|
defined here
|
||||||
|
value may point to these labels:
|
||||||
|
makechan
|
||||||
|
makechan
|
||||||
|
|
||||||
|
-------- @describe describe-chA2 --------
|
||||||
|
reference to var chA2 chan *int
|
||||||
|
defined here
|
||||||
|
value may point to these labels:
|
||||||
|
makechan
|
||||||
|
|
||||||
|
-------- @describe describe-chB --------
|
||||||
|
reference to var chB chan *int
|
||||||
|
defined here
|
||||||
|
value may point to these labels:
|
||||||
|
makechan
|
||||||
|
|
||||||
|
-------- @peers peer-recv-chA --------
|
||||||
|
This channel of type chan *int may be:
|
||||||
|
allocated here
|
||||||
|
allocated here
|
||||||
|
sent to, here
|
||||||
|
received from, here
|
||||||
|
received from, here
|
||||||
|
received from, here
|
||||||
|
received from, here
|
||||||
|
sent to, here
|
||||||
|
received from, here
|
||||||
|
|
||||||
|
-------- @describe describe-rA --------
|
||||||
|
reference to var rA *int
|
||||||
|
defined here
|
||||||
|
value may point to these labels:
|
||||||
|
main.a2
|
||||||
|
a1
|
||||||
|
|
||||||
|
-------- @peers peer-recv-chB --------
|
||||||
|
This channel of type chan *int may be:
|
||||||
|
allocated here
|
||||||
|
sent to, here
|
||||||
|
received from, here
|
||||||
|
received from, here
|
||||||
|
|
||||||
|
-------- @describe describe-rB --------
|
||||||
|
reference to var rB *int
|
||||||
|
defined here
|
||||||
|
value may point to these labels:
|
||||||
|
b
|
||||||
|
|
||||||
|
-------- @peers peer-recv-chA' --------
|
||||||
|
This channel of type chan *int may be:
|
||||||
|
allocated here
|
||||||
|
allocated here
|
||||||
|
sent to, here
|
||||||
|
received from, here
|
||||||
|
received from, here
|
||||||
|
received from, here
|
||||||
|
received from, here
|
||||||
|
sent to, here
|
||||||
|
received from, here
|
||||||
|
|
||||||
|
-------- @peers peer-send-chA' --------
|
||||||
|
This channel of type chan *int may be:
|
||||||
|
allocated here
|
||||||
|
received from, here
|
||||||
|
received from, here
|
||||||
|
received from, here
|
||||||
|
received from, here
|
||||||
|
sent to, here
|
||||||
|
received from, here
|
||||||
|
|
||||||
|
|
@ -14,7 +14,7 @@ func CanPoint(T types.Type) bool {
|
||||||
case *types.Named:
|
case *types.Named:
|
||||||
return CanPoint(T.Underlying())
|
return CanPoint(T.Underlying())
|
||||||
|
|
||||||
case *types.Pointer, *types.Interface, *types.Map, *types.Chan, *types.Signature:
|
case *types.Pointer, *types.Interface, *types.Map, *types.Chan, *types.Signature, *types.Slice:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue