cmd/oracle: oracle is dead, long live guru
Change-Id: I0f729b1477d8b14f255538414087d25f99b20c1e Reviewed-on: https://go-review.googlesource.com/30982 Reviewed-by: Robert Griesemer <gri@golang.org>
This commit is contained in:
parent
b44548ae6a
commit
d9f9484612
|
|
@ -1,50 +0,0 @@
|
|||
#!/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 golang.org/x/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 "Run.*help" $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 "golang.org/x/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"
|
||||
|
|
@ -1,208 +0,0 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// oracle: a tool for answering questions about Go source code.
|
||||
// http://golang.org/s/oracle-design
|
||||
// http://golang.org/s/oracle-user-manual
|
||||
//
|
||||
// DEPRECATED: oracle has been superseded by guru;
|
||||
// see https://golang.org/s/using-guru for details.
|
||||
// This package will be deleted on October 1, 2016.
|
||||
//
|
||||
// Run with -help flag or help subcommand for usage information.
|
||||
//
|
||||
package main // import "golang.org/x/tools/cmd/oracle"
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
|
||||
"golang.org/x/tools/go/buildutil"
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/oracle"
|
||||
)
|
||||
|
||||
var posFlag = flag.String("pos", "",
|
||||
"Filename and byte offset or extent of a syntax element about which to query, "+
|
||||
"e.g. foo.go:#123,#456, bar.go:#123.")
|
||||
|
||||
var ptalogFlag = flag.String("ptalog", "",
|
||||
"Location of the points-to analysis log file, or empty to disable logging.")
|
||||
|
||||
var formatFlag = flag.String("format", "plain", "Output format. One of {plain,json,xml}.")
|
||||
|
||||
var reflectFlag = flag.Bool("reflect", false, "Analyze reflection soundly (slow).")
|
||||
|
||||
func init() {
|
||||
flag.Var((*buildutil.TagsFlag)(&build.Default.BuildTags), "tags", buildutil.TagsFlagDoc)
|
||||
}
|
||||
|
||||
const useHelp = "Run 'oracle -help' for more information.\n"
|
||||
|
||||
const helpMessage = `Go source code oracle.
|
||||
Usage: oracle [<flag> ...] <mode> <args> ...
|
||||
|
||||
The -format flag controls the output format:
|
||||
plain an editor-friendly format in which every line of output
|
||||
is of the form "pos: text", where pos is "-" if unknown.
|
||||
json structured data in JSON syntax.
|
||||
xml structured data in XML syntax.
|
||||
|
||||
The -pos flag is required in all modes.
|
||||
|
||||
The mode argument determines the query to perform:
|
||||
|
||||
callees show possible targets of selected function call
|
||||
callers show possible callers of selected function
|
||||
callstack show path from callgraph root to selected function
|
||||
definition show declaration of selected identifier
|
||||
describe describe selected syntax: definition, methods, etc
|
||||
freevars show free variables of selection
|
||||
implements show 'implements' relation for selected type or method
|
||||
peers show send/receive corresponding to selected channel op
|
||||
pointsto show variables to which the selected pointer may point
|
||||
referrers show all refs to entity denoted by selected identifier
|
||||
what show basic information about the selected syntax node
|
||||
whicherrs show possible values of the selected error variable
|
||||
|
||||
The user manual is available here: http://golang.org/s/oracle-user-manual
|
||||
|
||||
Examples:
|
||||
|
||||
Describe the syntax at offset 530 in this file (an import spec):
|
||||
% oracle -pos=src/golang.org/x/tools/cmd/oracle/main.go:#530 describe \
|
||||
golang.org/x/tools/cmd/oracle
|
||||
|
||||
` + loader.FromArgsUsage
|
||||
|
||||
var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
|
||||
|
||||
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 printHelp() {
|
||||
fmt.Fprintln(os.Stderr, helpMessage)
|
||||
fmt.Fprintln(os.Stderr, "Flags:")
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Don't print full help unless -help was requested.
|
||||
// Just gently remind users that it's there.
|
||||
flag.Usage = func() { fmt.Fprint(os.Stderr, useHelp) }
|
||||
flag.CommandLine.Init(os.Args[0], flag.ContinueOnError) // hack
|
||||
if err := flag.CommandLine.Parse(os.Args[1:]); err != nil {
|
||||
// (err has already been printed)
|
||||
if err == flag.ErrHelp {
|
||||
printHelp()
|
||||
}
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
args := flag.Args()
|
||||
if len(args) == 0 || args[0] == "" {
|
||||
fmt.Fprint(os.Stderr, "oracle: a mode argument is required.\n"+useHelp)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
mode := args[0]
|
||||
args = args[1:]
|
||||
if mode == "help" {
|
||||
printHelp()
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
// Set up points-to analysis log file.
|
||||
var ptalog io.Writer
|
||||
if *ptalogFlag != "" {
|
||||
if f, err := os.Create(*ptalogFlag); err != nil {
|
||||
log.Fatalf("Failed to create PTA log file: %s", err)
|
||||
} else {
|
||||
buf := bufio.NewWriter(f)
|
||||
ptalog = buf
|
||||
defer func() {
|
||||
if err := buf.Flush(); err != nil {
|
||||
log.Printf("flush: %s", err)
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
log.Printf("close: %s", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// Profiling support.
|
||||
if *cpuprofile != "" {
|
||||
f, err := os.Create(*cpuprofile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
pprof.StartCPUProfile(f)
|
||||
defer pprof.StopCPUProfile()
|
||||
}
|
||||
|
||||
// -format flag
|
||||
switch *formatFlag {
|
||||
case "json", "plain", "xml":
|
||||
// ok
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "oracle: illegal -format value: %q.\n"+useHelp, *formatFlag)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
// Ask the oracle.
|
||||
query := oracle.Query{
|
||||
Mode: mode,
|
||||
Pos: *posFlag,
|
||||
Build: &build.Default,
|
||||
Scope: args,
|
||||
PTALog: ptalog,
|
||||
Reflection: *reflectFlag,
|
||||
}
|
||||
|
||||
if err := oracle.Run(&query); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "oracle: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Print the result.
|
||||
switch *formatFlag {
|
||||
case "json":
|
||||
b, err := json.MarshalIndent(query.Serial(), "", "\t")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "oracle: JSON error: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Stdout.Write(b)
|
||||
|
||||
case "xml":
|
||||
b, err := xml.MarshalIndent(query.Serial(), "", "\t")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "oracle: XML error: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Stdout.Write(b)
|
||||
|
||||
case "plain":
|
||||
query.WriteTo(os.Stdout)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,230 +0,0 @@
|
|||
;;;
|
||||
;;; Integration of the Go 'oracle' analysis tool into Emacs.
|
||||
;;;
|
||||
;;; To install the Go oracle, run:
|
||||
;;; % export GOROOT=... GOPATH=...
|
||||
;;; % go get golang.org/x/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, enable
|
||||
;;; go-oracle-mode, select an expression of interest, and press `C-c C-o d'
|
||||
;;; (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 "oracle"
|
||||
"The Go oracle command."
|
||||
: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'.")
|
||||
|
||||
;; Extend go-mode-map.
|
||||
(let ((m go-mode-map))
|
||||
(define-key m (kbd "C-c C-o t") #'go-oracle-describe) ; t for type
|
||||
(define-key m (kbd "C-c C-o f") #'go-oracle-freevars)
|
||||
(define-key m (kbd "C-c C-o g") #'go-oracle-callgraph)
|
||||
(define-key m (kbd "C-c C-o i") #'go-oracle-implements)
|
||||
(define-key m (kbd "C-c C-o c") #'go-oracle-peers) ; c for channel
|
||||
(define-key m (kbd "C-c C-o r") #'go-oracle-referrers)
|
||||
(define-key m (kbd "C-c C-o d") #'go-oracle-definition)
|
||||
(define-key m (kbd "C-c C-o p") #'go-oracle-pointsto)
|
||||
(define-key m (kbd "C-c C-o s") #'go-oracle-callstack)
|
||||
(define-key m (kbd "C-c C-o <") #'go-oracle-callers)
|
||||
(define-key m (kbd "C-c C-o >") #'go-oracle-callees)
|
||||
(define-key m (kbd "<f5>") #'go-oracle-describe)
|
||||
(define-key m (kbd "<f6>") #'go-oracle-referrers))
|
||||
|
||||
;; TODO(dominikh): Rethink set-scope some. Setting it to a file is
|
||||
;; painful because it doesn't use find-file, and variables/~ aren't
|
||||
;; expanded. Setting it to an import path is somewhat painful because
|
||||
;; it doesn't make use of go-mode's import path completion. One option
|
||||
;; would be having two different functions, but then we can't
|
||||
;; automatically call it when no scope has been set. Also it wouldn't
|
||||
;; easily allow specifying more than one file/package.
|
||||
(defun go-oracle-set-scope ()
|
||||
"Set 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 &optional need-scope)
|
||||
"Run the Go oracle in the specified MODE, passing it the
|
||||
selected region of the current buffer. If NEED-SCOPE, prompt for
|
||||
a scope if not already set. 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"))
|
||||
(and need-scope
|
||||
(string-equal "" go-oracle-scope)
|
||||
(go-oracle-set-scope))
|
||||
(let* ((filename (file-truename buffer-file-name))
|
||||
(posflag (if (use-region-p)
|
||||
(format "-pos=%s:#%d,#%d"
|
||||
filename
|
||||
(1- (go--position-bytes (region-beginning)))
|
||||
(1- (go--position-bytes (region-end))))
|
||||
(format "-pos=%s:#%d"
|
||||
filename
|
||||
(1- (position-bytes (point))))))
|
||||
(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 posflag 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 process-environment)))
|
||||
(apply #'call-process args)))
|
||||
(insert "\n")
|
||||
(compilation-mode)
|
||||
(setq compilation-error-screen-columns nil)
|
||||
|
||||
;; 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 JSON
|
||||
;; 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)))
|
||||
(if np
|
||||
(when (equal (line-number-at-pos p) (line-number-at-pos np))
|
||||
;; Using a fixed width greatly improves readability, so
|
||||
;; if the filename is longer than 20, show ".../last/17chars.go".
|
||||
;; This usually includes the last segment of the package name.
|
||||
;; Don't show the line or column number.
|
||||
(let* ((loc (buffer-substring p np)) ; "/home/foo/go/pkg/file.go:1:2-3:4"
|
||||
(i (search ":" loc)))
|
||||
(setq loc (cond
|
||||
((null i) "...")
|
||||
((>= i 17) (concat "..." (substring loc (- i 17) i)))
|
||||
(t (substring loc 0 i))))
|
||||
;; np is (typically) the space following ":"; consume it too.
|
||||
(put-text-property p np 'display (concat loc ":")))
|
||||
(goto-char np)
|
||||
(insert " ")
|
||||
(incf np))) ; so we don't get stuck (e.g. on a panic stack dump)
|
||||
(setq p np)))
|
||||
(message nil))
|
||||
|
||||
(let ((w (display-buffer (current-buffer))))
|
||||
(balance-windows)
|
||||
(shrink-window-if-larger-than-buffer w)
|
||||
(set-window-point w (point-min))))))
|
||||
|
||||
(defun go-oracle-callees ()
|
||||
"Show possible callees of the function call at the current point."
|
||||
(interactive)
|
||||
(go-oracle--run "callees" t))
|
||||
|
||||
(defun go-oracle-callers ()
|
||||
"Show the set of callers of the function containing the current point."
|
||||
(interactive)
|
||||
(go-oracle--run "callers" t))
|
||||
|
||||
(defun go-oracle-callgraph ()
|
||||
"Show the callgraph of the current program."
|
||||
(interactive)
|
||||
(go-oracle--run "callgraph" t))
|
||||
|
||||
(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" t))
|
||||
|
||||
(defun go-oracle-definition ()
|
||||
"Show the definition of the selected identifier."
|
||||
(interactive)
|
||||
(go-oracle--run "definition"))
|
||||
|
||||
(defun go-oracle-describe ()
|
||||
"Describe the selected syntax, its kind, type and methods."
|
||||
(interactive)
|
||||
(go-oracle--run "describe"))
|
||||
|
||||
(defun go-oracle-pointsto ()
|
||||
"Show what the selected expression points to."
|
||||
(interactive)
|
||||
(go-oracle--run "pointsto" t))
|
||||
|
||||
(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-peers ()
|
||||
"Enumerate the set of possible corresponding sends/receives for
|
||||
this channel receive/send operation."
|
||||
(interactive)
|
||||
(go-oracle--run "peers" t))
|
||||
|
||||
(defun go-oracle-referrers ()
|
||||
"Enumerate all references to the object denoted by the selected
|
||||
identifier."
|
||||
(interactive)
|
||||
(go-oracle--run "referrers"))
|
||||
|
||||
(defun go-oracle-whicherrs ()
|
||||
"Show globals, constants and types to which the selected
|
||||
expression (of type 'error') may refer."
|
||||
(interactive)
|
||||
(go-oracle--run "whicherrs" t))
|
||||
|
||||
(provide 'go-oracle)
|
||||
|
|
@ -1,107 +0,0 @@
|
|||
" -*- 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):
|
||||
" - reject buffers with no filename.
|
||||
" - hide all filenames in quickfix buffer.
|
||||
|
||||
" Get the path to the Go oracle executable.
|
||||
func! s:go_oracle_bin()
|
||||
let [ext, sep] = (has('win32') || has('win64') ? ['.exe', ';'] : ['', ':'])
|
||||
let go_oracle = globpath(join(split($GOPATH, sep), ','), '/bin/oracle' . ext)
|
||||
if go_oracle == ''
|
||||
let go_oracle = globpath($GOROOT, '/bin/oracle' . ext)
|
||||
endif
|
||||
return go_oracle
|
||||
endfunction
|
||||
|
||||
let s:go_oracle = s:go_oracle_bin()
|
||||
|
||||
func! s:qflist(output)
|
||||
let qflist = []
|
||||
" Parse GNU-style 'file:line.col-line.col: message' format.
|
||||
let mx = '^\(\a:[\\/][^:]\+\|[^:]\+\):\(\d\+\):\(\d\+\):\(.*\)$'
|
||||
for line in split(a:output, "\n")
|
||||
let ml = matchlist(line, mx)
|
||||
" Ignore non-match lines or warnings
|
||||
if ml == [] || ml[4] =~ '^ warning:'
|
||||
continue
|
||||
endif
|
||||
let item = {
|
||||
\ 'filename': ml[1],
|
||||
\ 'text': ml[4],
|
||||
\ 'lnum': ml[2],
|
||||
\ 'col': ml[3],
|
||||
\}
|
||||
let bnr = bufnr(fnameescape(ml[1]))
|
||||
if bnr != -1
|
||||
let item['bufnr'] = bnr
|
||||
endif
|
||||
call add(qflist, item)
|
||||
endfor
|
||||
call setqflist(qflist)
|
||||
cwindow
|
||||
endfun
|
||||
|
||||
func! s:getpos(l, c)
|
||||
if &encoding != 'utf-8'
|
||||
let buf = a:l == 1 ? '' : (join(getline(1, a:l-1), "\n") . "\n")
|
||||
let buf .= a:c == 1 ? '' : getline('.')[:a:c-2]
|
||||
return len(iconv(buf, &encoding, 'utf-8'))
|
||||
endif
|
||||
return line2byte(a:l) + (a:c-2)
|
||||
endfun
|
||||
|
||||
func! s:RunOracle(mode, selected) range abort
|
||||
let fname = expand('%:p')
|
||||
let sname = get(g:, 'go_oracle_scope_file', fname)
|
||||
if a:selected != -1
|
||||
let pos1 = s:getpos(line("'<"), col("'<"))
|
||||
let pos2 = s:getpos(line("'>"), col("'>"))
|
||||
let cmd = printf('%s -pos=%s:#%d,#%d %s %s',
|
||||
\ s:go_oracle,
|
||||
\ shellescape(fname), pos1, pos2, a:mode, shellescape(sname))
|
||||
else
|
||||
let pos = s:getpos(line('.'), col('.'))
|
||||
let cmd = printf('%s -pos=%s:#%d %s %s',
|
||||
\ s:go_oracle,
|
||||
\ shellescape(fname), pos, a:mode, shellescape(sname))
|
||||
endif
|
||||
call s:qflist(system(cmd))
|
||||
endfun
|
||||
|
||||
" Describe the expression at the current point.
|
||||
command! -range=% GoOracleDescribe
|
||||
\ call s:RunOracle('describe', <count>)
|
||||
|
||||
" Show possible callees of the function call at the current point.
|
||||
command! -range=% GoOracleCallees
|
||||
\ call s:RunOracle('callees', <count>)
|
||||
|
||||
" Show the set of callers of the function containing the current point.
|
||||
command! -range=% GoOracleCallers
|
||||
\ call s:RunOracle('callers', <count>)
|
||||
|
||||
" Show the callgraph of the current program.
|
||||
command! -range=% GoOracleCallgraph
|
||||
\ call s:RunOracle('callgraph', <count>)
|
||||
|
||||
" Describe the 'implements' relation for types in the
|
||||
" package containing the current point.
|
||||
command! -range=% GoOracleImplements
|
||||
\ call s:RunOracle('implements', <count>)
|
||||
|
||||
" Enumerate the set of possible corresponding sends/receives for
|
||||
" this channel receive/send operation.
|
||||
command! -range=% GoOracleChannelPeers
|
||||
\ call s:RunOracle('peers', <count>)
|
||||
83
oracle/TODO
83
oracle/TODO
|
|
@ -1,83 +0,0 @@
|
|||
|
||||
|
||||
ORACLE TODO
|
||||
===========
|
||||
|
||||
General
|
||||
=======
|
||||
|
||||
Save unsaved editor buffers into an archive and provide that to the
|
||||
tools, which should act as if they were saved.
|
||||
|
||||
Include complete pos/end information Serial output.
|
||||
But beware that sometimes a single token (e.g. +) is more helpful
|
||||
than the pos/end of the containing expression (e.g. x \n + \n y).
|
||||
|
||||
Specific queries
|
||||
================
|
||||
|
||||
callers, callees
|
||||
|
||||
Use a type-based (e.g. RTA) callgraph when a callers/callees query is
|
||||
outside the analysis scope.
|
||||
|
||||
implements
|
||||
|
||||
Make it require that the selection is a type, and show only the
|
||||
implements relation as it applies to that type.
|
||||
|
||||
definition, referrers
|
||||
|
||||
definition: Make it work with qualified identifiers (SelectorExpr) too.
|
||||
|
||||
references: Make it work on things that are implicit idents, like
|
||||
import specs, perhaps?
|
||||
|
||||
what
|
||||
|
||||
Report def/ref info if available.
|
||||
Editors could use it to highlight all idents of the same local var.
|
||||
|
||||
More tests.
|
||||
|
||||
pointsto
|
||||
|
||||
When invoked on a function Ident, we get an error.
|
||||
|
||||
When invoked on a named return parameter, we get an error.
|
||||
|
||||
describe
|
||||
|
||||
When invoked on a var, we want to see the type and its methods.
|
||||
|
||||
Split "show type" and "describe syntax" into separate commands?
|
||||
|
||||
peers
|
||||
|
||||
Permit querying from a makechan, for...range, or reflective op.
|
||||
|
||||
Report aliasing reflect.{Send,Recv,Close} and close() operations.
|
||||
|
||||
New queries
|
||||
|
||||
"updaters": show all statements that may update the selected lvalue
|
||||
(local, global, field, etc).
|
||||
|
||||
"creators": show all places where an object of type T is created
|
||||
(&T{}, var t T, new(T), new(struct{array [3]T}), etc.
|
||||
(Useful for datatypes whose zero value is not safe)
|
||||
|
||||
|
||||
Editor-specific
|
||||
===============
|
||||
|
||||
Add support for "what" to .el; clean up.
|
||||
|
||||
Emacs: use JSON to get the raw information from the oracle. Don't
|
||||
open an editor buffer for simpler queries, just jump to the result
|
||||
and/or display it in the modeline.
|
||||
|
||||
Emacs: go-root-and-paths depends on the current buffer, so be sure to
|
||||
call it from within the source file, not the *go-oracle* buffer:
|
||||
the user may have switched workspaces and the oracle should run in
|
||||
the new one.
|
||||
|
|
@ -1,260 +0,0 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package oracle
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"sort"
|
||||
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/pointer"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/ssa/ssautil"
|
||||
"golang.org/x/tools/oracle/serial"
|
||||
)
|
||||
|
||||
// Callees reports the possible callees of the function call site
|
||||
// identified by the specified source location.
|
||||
func callees(q *Query) error {
|
||||
lconf := loader.Config{Build: q.Build}
|
||||
|
||||
if err := setPTAScope(&lconf, q.Scope); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load/parse/type-check the program.
|
||||
lprog, err := lconf.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.Fset = lprog.Fset
|
||||
|
||||
qpos, err := parseQueryPos(lprog, q.Pos, true) // needs exact pos
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Determine the enclosing call for the specified position.
|
||||
var e *ast.CallExpr
|
||||
for _, n := range qpos.path {
|
||||
if e, _ = n.(*ast.CallExpr); e != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if e == nil {
|
||||
return fmt.Errorf("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 qpos.info.Types[e.Fun].IsType() {
|
||||
return fmt.Errorf("this is a type conversion, not a function call")
|
||||
}
|
||||
|
||||
// Deal with obviously static calls before constructing SSA form.
|
||||
// Some static calls may yet require SSA construction,
|
||||
// e.g. f := func(){}; f().
|
||||
switch funexpr := unparen(e.Fun).(type) {
|
||||
case *ast.Ident:
|
||||
switch obj := qpos.info.Uses[funexpr].(type) {
|
||||
case *types.Builtin:
|
||||
// Reject calls to built-ins.
|
||||
return fmt.Errorf("this is a call to the built-in '%s' operator", obj.Name())
|
||||
case *types.Func:
|
||||
// This is a static function call
|
||||
q.result = &calleesTypesResult{
|
||||
site: e,
|
||||
callee: obj,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
case *ast.SelectorExpr:
|
||||
sel := qpos.info.Selections[funexpr]
|
||||
if sel == nil {
|
||||
// qualified identifier.
|
||||
// May refer to top level function variable
|
||||
// or to top level function.
|
||||
callee := qpos.info.Uses[funexpr.Sel]
|
||||
if obj, ok := callee.(*types.Func); ok {
|
||||
q.result = &calleesTypesResult{
|
||||
site: e,
|
||||
callee: obj,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
} else if sel.Kind() == types.MethodVal {
|
||||
// Inspect the receiver type of the selected method.
|
||||
// If it is concrete, the call is statically dispatched.
|
||||
// (Due to implicit field selections, it is not enough to look
|
||||
// at sel.Recv(), the type of the actual receiver expression.)
|
||||
method := sel.Obj().(*types.Func)
|
||||
recvtype := method.Type().(*types.Signature).Recv().Type()
|
||||
if !types.IsInterface(recvtype) {
|
||||
// static method call
|
||||
q.result = &calleesTypesResult{
|
||||
site: e,
|
||||
callee: method,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug)
|
||||
|
||||
ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pkg := prog.Package(qpos.info.Pkg)
|
||||
if pkg == nil {
|
||||
return fmt.Errorf("no SSA package")
|
||||
}
|
||||
|
||||
// Defer SSA construction till after errors are reported.
|
||||
prog.Build()
|
||||
|
||||
// Ascertain calling function and call site.
|
||||
callerFn := ssa.EnclosingFunction(pkg, qpos.path)
|
||||
if callerFn == nil {
|
||||
return fmt.Errorf("no SSA function built for this location (dead code?)")
|
||||
}
|
||||
|
||||
// Find the call site.
|
||||
site, err := findCallSite(callerFn, e)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
funcs, err := findCallees(ptaConfig, site)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
q.result = &calleesSSAResult{
|
||||
site: site,
|
||||
funcs: funcs,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func findCallSite(fn *ssa.Function, call *ast.CallExpr) (ssa.CallInstruction, error) {
|
||||
instr, _ := fn.ValueForExpr(call)
|
||||
callInstr, _ := instr.(ssa.CallInstruction)
|
||||
if instr == nil {
|
||||
return nil, fmt.Errorf("this call site is unreachable in this analysis")
|
||||
}
|
||||
return callInstr, nil
|
||||
}
|
||||
|
||||
func findCallees(conf *pointer.Config, site ssa.CallInstruction) ([]*ssa.Function, error) {
|
||||
// Avoid running the pointer analysis for static calls.
|
||||
if callee := site.Common().StaticCallee(); callee != nil {
|
||||
switch callee.String() {
|
||||
case "runtime.SetFinalizer", "(reflect.Value).Call":
|
||||
// The PTA treats calls to these intrinsics as dynamic.
|
||||
// TODO(adonovan): avoid reliance on PTA internals.
|
||||
|
||||
default:
|
||||
return []*ssa.Function{callee}, nil // singleton
|
||||
}
|
||||
}
|
||||
|
||||
// Dynamic call: use pointer analysis.
|
||||
conf.BuildCallGraph = true
|
||||
cg := ptrAnalysis(conf).CallGraph
|
||||
cg.DeleteSyntheticNodes()
|
||||
|
||||
// Find all call edges from the site.
|
||||
n := cg.Nodes[site.Parent()]
|
||||
if n == nil {
|
||||
return nil, fmt.Errorf("this call site is unreachable in this analysis")
|
||||
}
|
||||
calleesMap := make(map[*ssa.Function]bool)
|
||||
for _, edge := range n.Out {
|
||||
if edge.Site == site {
|
||||
calleesMap[edge.Callee.Func] = true
|
||||
}
|
||||
}
|
||||
|
||||
// De-duplicate and sort.
|
||||
funcs := make([]*ssa.Function, 0, len(calleesMap))
|
||||
for f := range calleesMap {
|
||||
funcs = append(funcs, f)
|
||||
}
|
||||
sort.Sort(byFuncPos(funcs))
|
||||
return funcs, nil
|
||||
}
|
||||
|
||||
type calleesSSAResult struct {
|
||||
site ssa.CallInstruction
|
||||
funcs []*ssa.Function
|
||||
}
|
||||
|
||||
type calleesTypesResult struct {
|
||||
site *ast.CallExpr
|
||||
callee *types.Func
|
||||
}
|
||||
|
||||
func (r *calleesSSAResult) display(printf printfFunc) {
|
||||
if len(r.funcs) == 0 {
|
||||
// dynamic call on a provably nil func/interface
|
||||
printf(r.site, "%s on nil value", r.site.Common().Description())
|
||||
} else {
|
||||
printf(r.site, "this %s dispatches to:", r.site.Common().Description())
|
||||
for _, callee := range r.funcs {
|
||||
printf(callee, "\t%s", callee)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *calleesSSAResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
||||
j := &serial.Callees{
|
||||
Pos: fset.Position(r.site.Pos()).String(),
|
||||
Desc: r.site.Common().Description(),
|
||||
}
|
||||
for _, callee := range r.funcs {
|
||||
j.Callees = append(j.Callees, &serial.CalleesItem{
|
||||
Name: callee.String(),
|
||||
Pos: fset.Position(callee.Pos()).String(),
|
||||
})
|
||||
}
|
||||
res.Callees = j
|
||||
}
|
||||
|
||||
func (r *calleesTypesResult) display(printf printfFunc) {
|
||||
printf(r.site, "this static function call dispatches to:")
|
||||
printf(r.callee, "\t%s", r.callee.FullName())
|
||||
}
|
||||
|
||||
func (r *calleesTypesResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
||||
j := &serial.Callees{
|
||||
Pos: fset.Position(r.site.Pos()).String(),
|
||||
Desc: "static function call",
|
||||
}
|
||||
j.Callees = []*serial.CalleesItem{
|
||||
&serial.CalleesItem{
|
||||
Name: r.callee.FullName(),
|
||||
Pos: fset.Position(r.callee.Pos()).String(),
|
||||
},
|
||||
}
|
||||
res.Callees = j
|
||||
}
|
||||
|
||||
// NB: byFuncPos is not deterministic across packages since it depends on load order.
|
||||
// Use lessPos if the tests need it.
|
||||
type byFuncPos []*ssa.Function
|
||||
|
||||
func (a byFuncPos) Len() int { return len(a) }
|
||||
func (a byFuncPos) Less(i, j int) bool { return a[i].Pos() < a[j].Pos() }
|
||||
func (a byFuncPos) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
|
@ -1,115 +0,0 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package oracle
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
|
||||
"golang.org/x/tools/go/callgraph"
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/ssa/ssautil"
|
||||
"golang.org/x/tools/oracle/serial"
|
||||
)
|
||||
|
||||
// Callers reports the possible callers of the function
|
||||
// immediately enclosing the specified source location.
|
||||
//
|
||||
func callers(q *Query) error {
|
||||
lconf := loader.Config{Build: q.Build}
|
||||
|
||||
if err := setPTAScope(&lconf, q.Scope); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load/parse/type-check the program.
|
||||
lprog, err := lconf.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.Fset = lprog.Fset
|
||||
|
||||
qpos, err := parseQueryPos(lprog, q.Pos, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
prog := ssautil.CreateProgram(lprog, 0)
|
||||
|
||||
ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pkg := prog.Package(qpos.info.Pkg)
|
||||
if pkg == nil {
|
||||
return fmt.Errorf("no SSA package")
|
||||
}
|
||||
if !ssa.HasEnclosingFunction(pkg, qpos.path) {
|
||||
return fmt.Errorf("this position is not inside a function")
|
||||
}
|
||||
|
||||
// Defer SSA construction till after errors are reported.
|
||||
prog.Build()
|
||||
|
||||
target := ssa.EnclosingFunction(pkg, qpos.path)
|
||||
if target == nil {
|
||||
return fmt.Errorf("no SSA function built for this location (dead code?)")
|
||||
}
|
||||
|
||||
// TODO(adonovan): opt: if function is never address-taken, skip
|
||||
// the pointer analysis. Just look for direct calls. This can
|
||||
// be done in a single pass over the SSA.
|
||||
|
||||
// Run the pointer analysis, recording each
|
||||
// call found to originate from target.
|
||||
ptaConfig.BuildCallGraph = true
|
||||
cg := ptrAnalysis(ptaConfig).CallGraph
|
||||
cg.DeleteSyntheticNodes()
|
||||
edges := cg.CreateNode(target).In
|
||||
// TODO(adonovan): sort + dedup calls to ensure test determinism.
|
||||
|
||||
q.result = &callersResult{
|
||||
target: target,
|
||||
callgraph: cg,
|
||||
edges: edges,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type callersResult struct {
|
||||
target *ssa.Function
|
||||
callgraph *callgraph.Graph
|
||||
edges []*callgraph.Edge
|
||||
}
|
||||
|
||||
func (r *callersResult) display(printf printfFunc) {
|
||||
root := r.callgraph.Root
|
||||
if r.edges == nil {
|
||||
printf(r.target, "%s is not reachable in this program.", r.target)
|
||||
} else {
|
||||
printf(r.target, "%s is called from these %d sites:", r.target, len(r.edges))
|
||||
for _, edge := range r.edges {
|
||||
if edge.Caller == root {
|
||||
printf(r.target, "the root of the call graph")
|
||||
} else {
|
||||
printf(edge, "\t%s from %s", edge.Description(), edge.Caller.Func)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *callersResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
||||
var callers []serial.Caller
|
||||
for _, edge := range r.edges {
|
||||
callers = append(callers, serial.Caller{
|
||||
Caller: edge.Caller.Func.String(),
|
||||
Pos: fset.Position(edge.Pos()).String(),
|
||||
Desc: edge.Description(),
|
||||
})
|
||||
}
|
||||
res.Callers = callers
|
||||
}
|
||||
|
|
@ -1,126 +0,0 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package oracle
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
|
||||
"golang.org/x/tools/go/callgraph"
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/ssa/ssautil"
|
||||
"golang.org/x/tools/oracle/serial"
|
||||
)
|
||||
|
||||
// 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(q *Query) error {
|
||||
fset := token.NewFileSet()
|
||||
lconf := loader.Config{Fset: fset, Build: q.Build}
|
||||
|
||||
if err := setPTAScope(&lconf, q.Scope); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load/parse/type-check the program.
|
||||
lprog, err := lconf.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
qpos, err := parseQueryPos(lprog, q.Pos, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
prog := ssautil.CreateProgram(lprog, 0)
|
||||
|
||||
ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pkg := prog.Package(qpos.info.Pkg)
|
||||
if pkg == nil {
|
||||
return fmt.Errorf("no SSA package")
|
||||
}
|
||||
|
||||
if !ssa.HasEnclosingFunction(pkg, qpos.path) {
|
||||
return fmt.Errorf("this position is not inside a function")
|
||||
}
|
||||
|
||||
// Defer SSA construction till after errors are reported.
|
||||
prog.Build()
|
||||
|
||||
target := ssa.EnclosingFunction(pkg, qpos.path)
|
||||
if target == nil {
|
||||
return fmt.Errorf("no SSA function built for this location (dead code?)")
|
||||
}
|
||||
|
||||
// Run the pointer analysis and build the complete call graph.
|
||||
ptaConfig.BuildCallGraph = true
|
||||
cg := ptrAnalysis(ptaConfig).CallGraph
|
||||
cg.DeleteSyntheticNodes()
|
||||
|
||||
// Search for an arbitrary path from a root to the target function.
|
||||
isEnd := func(n *callgraph.Node) bool { return n.Func == target }
|
||||
callpath := callgraph.PathSearch(cg.Root, isEnd)
|
||||
if callpath != nil {
|
||||
callpath = callpath[1:] // remove synthetic edge from <root>
|
||||
}
|
||||
|
||||
q.Fset = fset
|
||||
q.result = &callstackResult{
|
||||
qpos: qpos,
|
||||
target: target,
|
||||
callpath: callpath,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type callstackResult struct {
|
||||
qpos *queryPos
|
||||
target *ssa.Function
|
||||
callpath []*callgraph.Edge
|
||||
}
|
||||
|
||||
func (r *callstackResult) display(printf printfFunc) {
|
||||
if r.callpath != nil {
|
||||
printf(r.qpos, "Found a call path from root to %s", r.target)
|
||||
printf(r.target, "%s", r.target)
|
||||
for i := len(r.callpath) - 1; i >= 0; i-- {
|
||||
edge := r.callpath[i]
|
||||
printf(edge, "%s from %s", edge.Description(), edge.Caller.Func)
|
||||
}
|
||||
} else {
|
||||
printf(r.target, "%s is unreachable in this analysis scope", r.target)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *callstackResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
||||
var callers []serial.Caller
|
||||
for i := len(r.callpath) - 1; i >= 0; i-- { // (innermost first)
|
||||
edge := r.callpath[i]
|
||||
callers = append(callers, serial.Caller{
|
||||
Pos: fset.Position(edge.Pos()).String(),
|
||||
Caller: edge.Caller.Func.String(),
|
||||
Desc: edge.Description(),
|
||||
})
|
||||
}
|
||||
res.Callstack = &serial.CallStack{
|
||||
Pos: fset.Position(r.target.Pos()).String(),
|
||||
Target: r.target.String(),
|
||||
Callers: callers,
|
||||
}
|
||||
}
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package oracle
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/oracle/serial"
|
||||
)
|
||||
|
||||
// definition reports the location of the definition of an identifier.
|
||||
//
|
||||
// TODO(adonovan): opt: for intra-file references, the parser's
|
||||
// resolution might be enough; we should start with that.
|
||||
//
|
||||
func definition(q *Query) error {
|
||||
lconf := loader.Config{Build: q.Build}
|
||||
allowErrors(&lconf)
|
||||
|
||||
if _, err := importQueryPackage(q.Pos, &lconf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load/parse/type-check the program.
|
||||
lprog, err := lconf.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.Fset = lprog.Fset
|
||||
|
||||
qpos, err := parseQueryPos(lprog, q.Pos, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
id, _ := qpos.path[0].(*ast.Ident)
|
||||
if id == nil {
|
||||
return fmt.Errorf("no identifier here")
|
||||
}
|
||||
|
||||
obj := qpos.info.ObjectOf(id)
|
||||
if obj == nil {
|
||||
// Happens for y in "switch y := x.(type)",
|
||||
// and the package declaration,
|
||||
// but I think that's all.
|
||||
return fmt.Errorf("no object for identifier")
|
||||
}
|
||||
|
||||
q.result = &definitionResult{qpos, obj}
|
||||
return nil
|
||||
}
|
||||
|
||||
type definitionResult struct {
|
||||
qpos *queryPos
|
||||
obj types.Object // object it denotes
|
||||
}
|
||||
|
||||
func (r *definitionResult) display(printf printfFunc) {
|
||||
printf(r.obj, "defined here as %s", r.qpos.objectString(r.obj))
|
||||
}
|
||||
|
||||
func (r *definitionResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
||||
definition := &serial.Definition{
|
||||
Desc: r.obj.String(),
|
||||
}
|
||||
if pos := r.obj.Pos(); pos != token.NoPos { // Package objects have no Pos()
|
||||
definition.ObjPos = fset.Position(pos).String()
|
||||
}
|
||||
res.Definition = definition
|
||||
}
|
||||
|
|
@ -1,775 +0,0 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.6
|
||||
|
||||
package oracle
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
exact "go/constant"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/types/typeutil"
|
||||
"golang.org/x/tools/oracle/serial"
|
||||
)
|
||||
|
||||
// describe describes the syntax node denoted by the query position,
|
||||
// including:
|
||||
// - its syntactic category
|
||||
// - the definition of its referent (for identifiers) [now redundant]
|
||||
// - its type and method set (for an expression or type expression)
|
||||
//
|
||||
func describe(q *Query) error {
|
||||
lconf := loader.Config{Build: q.Build}
|
||||
allowErrors(&lconf)
|
||||
|
||||
if _, err := importQueryPackage(q.Pos, &lconf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load/parse/type-check the program.
|
||||
lprog, err := lconf.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.Fset = lprog.Fset
|
||||
|
||||
qpos, err := parseQueryPos(lprog, q.Pos, true) // (need exact pos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if false { // debugging
|
||||
fprintf(os.Stderr, lprog.Fset, qpos.path[0], "you selected: %s %s",
|
||||
astutil.NodeDescription(qpos.path[0]), pathToString(qpos.path))
|
||||
}
|
||||
|
||||
path, action := findInterestingNode(qpos.info, qpos.path)
|
||||
switch action {
|
||||
case actionExpr:
|
||||
q.result, err = describeValue(qpos, path)
|
||||
|
||||
case actionType:
|
||||
q.result, err = describeType(qpos, path)
|
||||
|
||||
case actionPackage:
|
||||
q.result, err = describePackage(qpos, path)
|
||||
|
||||
case actionStmt:
|
||||
q.result, err = describeStmt(qpos, path)
|
||||
|
||||
case actionUnknown:
|
||||
q.result = &describeUnknownResult{path[0]}
|
||||
|
||||
default:
|
||||
panic(action) // unreachable
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
type describeUnknownResult struct {
|
||||
node ast.Node
|
||||
}
|
||||
|
||||
func (r *describeUnknownResult) display(printf printfFunc) {
|
||||
// Nothing much to say about misc syntax.
|
||||
printf(r.node, "%s", astutil.NodeDescription(r.node))
|
||||
}
|
||||
|
||||
func (r *describeUnknownResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
||||
res.Describe = &serial.Describe{
|
||||
Desc: astutil.NodeDescription(r.node),
|
||||
Pos: fset.Position(r.node.Pos()).String(),
|
||||
}
|
||||
}
|
||||
|
||||
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 *loader.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".
|
||||
// We should ascend to the enclosing type decl, if any.
|
||||
|
||||
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:
|
||||
// TODO(adonovan): use Selections info directly.
|
||||
if pkginfo.Uses[n.Sel] == nil {
|
||||
// TODO(adonovan): is this reachable?
|
||||
return path, actionUnknown
|
||||
}
|
||||
// Descend to .Sel child.
|
||||
path = append([]ast.Node{n.Sel}, path...)
|
||||
continue
|
||||
|
||||
case *ast.Ident:
|
||||
switch pkginfo.ObjectOf(n).(type) {
|
||||
case *types.PkgName:
|
||||
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:
|
||||
return path, actionExpr
|
||||
|
||||
case *types.Builtin:
|
||||
// For reference to built-in function, return enclosing call.
|
||||
path = path[1:] // ascend to enclosing function call
|
||||
continue
|
||||
|
||||
case *types.Nil:
|
||||
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.File:
|
||||
// 'package foo'
|
||||
return path, actionPackage
|
||||
|
||||
case *ast.ImportSpec:
|
||||
// TODO(adonovan): fix: why no package object? go/types bug?
|
||||
return path[1:], actionPackage
|
||||
|
||||
default:
|
||||
// e.g. blank identifier
|
||||
// or y in "switch y := x.(type)"
|
||||
// or code in a _test.go file that's not part of the package.
|
||||
log.Printf("unknown reference %s in %T\n", n, path[1])
|
||||
return path, actionUnknown
|
||||
}
|
||||
|
||||
case *ast.StarExpr:
|
||||
if pkginfo.Types[n].IsType() {
|
||||
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
|
||||
}
|
||||
|
||||
func describeValue(qpos *queryPos, path []ast.Node) (*describeValueResult, error) {
|
||||
var expr ast.Expr
|
||||
var obj types.Object
|
||||
switch n := path[0].(type) {
|
||||
case *ast.ValueSpec:
|
||||
// ambiguous ValueSpec containing multiple names
|
||||
return nil, fmt.Errorf("multiple value specification")
|
||||
case *ast.Ident:
|
||||
obj = qpos.info.ObjectOf(n)
|
||||
expr = n
|
||||
case ast.Expr:
|
||||
expr = n
|
||||
default:
|
||||
// TODO(adonovan): is this reachable?
|
||||
return nil, fmt.Errorf("unexpected AST for expr: %T", n)
|
||||
}
|
||||
|
||||
typ := qpos.info.TypeOf(expr)
|
||||
constVal := qpos.info.Types[expr].Value
|
||||
|
||||
return &describeValueResult{
|
||||
qpos: qpos,
|
||||
expr: expr,
|
||||
typ: typ,
|
||||
constVal: constVal,
|
||||
obj: obj,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type describeValueResult struct {
|
||||
qpos *queryPos
|
||||
expr ast.Expr // query node
|
||||
typ types.Type // type of expression
|
||||
constVal exact.Value // value of expression, if constant
|
||||
obj types.Object // var/func/const object, if expr was Ident
|
||||
}
|
||||
|
||||
func (r *describeValueResult) display(printf printfFunc) {
|
||||
var prefix, suffix string
|
||||
if r.constVal != nil {
|
||||
suffix = fmt.Sprintf(" of constant value %s", r.constVal)
|
||||
}
|
||||
switch obj := r.obj.(type) {
|
||||
case *types.Func:
|
||||
if recv := obj.Type().(*types.Signature).Recv(); recv != nil {
|
||||
if _, ok := recv.Type().Underlying().(*types.Interface); ok {
|
||||
prefix = "interface method "
|
||||
} else {
|
||||
prefix = "method "
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Describe the expression.
|
||||
if r.obj != nil {
|
||||
if r.obj.Pos() == r.expr.Pos() {
|
||||
// defining ident
|
||||
printf(r.expr, "definition of %s%s%s", prefix, r.qpos.objectString(r.obj), suffix)
|
||||
} else {
|
||||
// referring ident
|
||||
printf(r.expr, "reference to %s%s%s", prefix, r.qpos.objectString(r.obj), suffix)
|
||||
if def := r.obj.Pos(); def != token.NoPos {
|
||||
printf(def, "defined here")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
desc := astutil.NodeDescription(r.expr)
|
||||
if suffix != "" {
|
||||
// constant expression
|
||||
printf(r.expr, "%s%s", desc, suffix)
|
||||
} else {
|
||||
// non-constant expression
|
||||
printf(r.expr, "%s of type %s", desc, r.qpos.typeString(r.typ))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *describeValueResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
||||
var value, objpos string
|
||||
if r.constVal != nil {
|
||||
value = r.constVal.String()
|
||||
}
|
||||
if r.obj != nil {
|
||||
objpos = fset.Position(r.obj.Pos()).String()
|
||||
}
|
||||
|
||||
res.Describe = &serial.Describe{
|
||||
Desc: astutil.NodeDescription(r.expr),
|
||||
Pos: fset.Position(r.expr.Pos()).String(),
|
||||
Detail: "value",
|
||||
Value: &serial.DescribeValue{
|
||||
Type: r.qpos.typeString(r.typ),
|
||||
Value: value,
|
||||
ObjPos: objpos,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ---- TYPE ------------------------------------------------------------
|
||||
|
||||
func describeType(qpos *queryPos, path []ast.Node) (*describeTypeResult, error) {
|
||||
var description string
|
||||
var t types.Type
|
||||
switch n := path[0].(type) {
|
||||
case *ast.Ident:
|
||||
t = qpos.info.TypeOf(n)
|
||||
switch t := t.(type) {
|
||||
case *types.Basic:
|
||||
description = "reference to built-in "
|
||||
|
||||
case *types.Named:
|
||||
isDef := t.Obj().Pos() == n.Pos() // see caveats at isDef above
|
||||
if isDef {
|
||||
description = "definition of "
|
||||
} else {
|
||||
description = "reference to "
|
||||
}
|
||||
}
|
||||
|
||||
case ast.Expr:
|
||||
t = qpos.info.TypeOf(n)
|
||||
|
||||
default:
|
||||
// Unreachable?
|
||||
return nil, fmt.Errorf("unexpected AST for type: %T", n)
|
||||
}
|
||||
|
||||
description = description + "type " + qpos.typeString(t)
|
||||
|
||||
// Show sizes for structs and named types (it's fairly obvious for others).
|
||||
switch t.(type) {
|
||||
case *types.Named, *types.Struct:
|
||||
szs := types.StdSizes{WordSize: 8, MaxAlign: 8} // assume amd64
|
||||
description = fmt.Sprintf("%s (size %d, align %d)", description,
|
||||
szs.Sizeof(t), szs.Alignof(t))
|
||||
}
|
||||
|
||||
return &describeTypeResult{
|
||||
qpos: qpos,
|
||||
node: path[0],
|
||||
description: description,
|
||||
typ: t,
|
||||
methods: accessibleMethods(t, qpos.info.Pkg),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type describeTypeResult struct {
|
||||
qpos *queryPos
|
||||
node ast.Node
|
||||
description string
|
||||
typ types.Type
|
||||
methods []*types.Selection
|
||||
}
|
||||
|
||||
func (r *describeTypeResult) display(printf printfFunc) {
|
||||
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() {
|
||||
printf(nt.Obj(), "defined as %s", r.qpos.typeString(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:
|
||||
if len(r.methods) > 0 {
|
||||
printf(r.node, "Method set:")
|
||||
for _, meth := range r.methods {
|
||||
// TODO(adonovan): print these relative
|
||||
// to the owning package, not the
|
||||
// query package.
|
||||
printf(meth.Obj(), "\t%s", r.qpos.selectionString(meth))
|
||||
}
|
||||
} else {
|
||||
printf(r.node, "No methods.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *describeTypeResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
||||
var namePos, nameDef string
|
||||
if nt, ok := r.typ.(*types.Named); ok {
|
||||
namePos = fset.Position(nt.Obj().Pos()).String()
|
||||
nameDef = nt.Underlying().String()
|
||||
}
|
||||
res.Describe = &serial.Describe{
|
||||
Desc: r.description,
|
||||
Pos: fset.Position(r.node.Pos()).String(),
|
||||
Detail: "type",
|
||||
Type: &serial.DescribeType{
|
||||
Type: r.qpos.typeString(r.typ),
|
||||
NamePos: namePos,
|
||||
NameDef: nameDef,
|
||||
Methods: methodsToSerial(r.qpos.info.Pkg, r.methods, fset),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ---- PACKAGE ------------------------------------------------------------
|
||||
|
||||
func describePackage(qpos *queryPos, path []ast.Node) (*describePackageResult, error) {
|
||||
var description string
|
||||
var pkg *types.Package
|
||||
switch n := path[0].(type) {
|
||||
case *ast.ImportSpec:
|
||||
var obj types.Object
|
||||
if n.Name != nil {
|
||||
obj = qpos.info.Defs[n.Name]
|
||||
} else {
|
||||
obj = qpos.info.Implicits[n]
|
||||
}
|
||||
pkgname, _ := obj.(*types.PkgName)
|
||||
if pkgname == nil {
|
||||
return nil, fmt.Errorf("can't import package %s", n.Path.Value)
|
||||
}
|
||||
pkg = pkgname.Imported()
|
||||
description = fmt.Sprintf("import of package %q", pkg.Path())
|
||||
|
||||
case *ast.Ident:
|
||||
if _, isDef := path[1].(*ast.File); isDef {
|
||||
// e.g. package id
|
||||
pkg = qpos.info.Pkg
|
||||
description = fmt.Sprintf("definition of package %q", pkg.Path())
|
||||
} else {
|
||||
// e.g. import id "..."
|
||||
// or id.F()
|
||||
pkg = qpos.info.ObjectOf(n).(*types.PkgName).Imported()
|
||||
description = fmt.Sprintf("reference to package %q", pkg.Path())
|
||||
}
|
||||
|
||||
default:
|
||||
// Unreachable?
|
||||
return nil, fmt.Errorf("unexpected AST for package: %T", n)
|
||||
}
|
||||
|
||||
var members []*describeMember
|
||||
// NB: "unsafe" has no types.Package
|
||||
if pkg != nil {
|
||||
// Enumerate the accessible package members
|
||||
// in lexicographic order.
|
||||
for _, name := range pkg.Scope().Names() {
|
||||
if pkg == qpos.info.Pkg || ast.IsExported(name) {
|
||||
mem := pkg.Scope().Lookup(name)
|
||||
var methods []*types.Selection
|
||||
if mem, ok := mem.(*types.TypeName); ok {
|
||||
methods = accessibleMethods(mem.Type(), qpos.info.Pkg)
|
||||
}
|
||||
members = append(members, &describeMember{
|
||||
mem,
|
||||
methods,
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &describePackageResult{qpos.fset, path[0], description, pkg, members}, nil
|
||||
}
|
||||
|
||||
type describePackageResult struct {
|
||||
fset *token.FileSet
|
||||
node ast.Node
|
||||
description string
|
||||
pkg *types.Package
|
||||
members []*describeMember // in lexicographic name order
|
||||
}
|
||||
|
||||
type describeMember struct {
|
||||
obj types.Object
|
||||
methods []*types.Selection // in types.MethodSet order
|
||||
}
|
||||
|
||||
func (r *describePackageResult) display(printf printfFunc) {
|
||||
printf(r.node, "%s", r.description)
|
||||
|
||||
// Compute max width of name "column".
|
||||
maxname := 0
|
||||
for _, mem := range r.members {
|
||||
if l := len(mem.obj.Name()); l > maxname {
|
||||
maxname = l
|
||||
}
|
||||
}
|
||||
|
||||
for _, mem := range r.members {
|
||||
printf(mem.obj, "\t%s", formatMember(mem.obj, maxname))
|
||||
for _, meth := range mem.methods {
|
||||
printf(meth.Obj(), "\t\t%s", types.SelectionString(meth, types.RelativeTo(r.pkg)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func formatMember(obj types.Object, maxname int) string {
|
||||
qualifier := types.RelativeTo(obj.Pkg())
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintf(&buf, "%-5s %-*s", tokenOf(obj), maxname, obj.Name())
|
||||
switch obj := obj.(type) {
|
||||
case *types.Const:
|
||||
fmt.Fprintf(&buf, " %s = %s", types.TypeString(obj.Type(), qualifier), obj.Val().String())
|
||||
|
||||
case *types.Func:
|
||||
fmt.Fprintf(&buf, " %s", types.TypeString(obj.Type(), qualifier))
|
||||
|
||||
case *types.TypeName:
|
||||
// Abbreviate long aggregate type names.
|
||||
var abbrev string
|
||||
switch t := obj.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", types.TypeString(obj.Type().Underlying(), qualifier))
|
||||
} else {
|
||||
fmt.Fprintf(&buf, " %s", abbrev)
|
||||
}
|
||||
|
||||
case *types.Var:
|
||||
fmt.Fprintf(&buf, " %s", types.TypeString(obj.Type(), qualifier))
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (r *describePackageResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
||||
var members []*serial.DescribeMember
|
||||
for _, mem := range r.members {
|
||||
typ := mem.obj.Type()
|
||||
var val string
|
||||
switch mem := mem.obj.(type) {
|
||||
case *types.Const:
|
||||
val = mem.Val().String()
|
||||
case *types.TypeName:
|
||||
typ = typ.Underlying()
|
||||
}
|
||||
members = append(members, &serial.DescribeMember{
|
||||
Name: mem.obj.Name(),
|
||||
Type: typ.String(),
|
||||
Value: val,
|
||||
Pos: fset.Position(mem.obj.Pos()).String(),
|
||||
Kind: tokenOf(mem.obj),
|
||||
Methods: methodsToSerial(r.pkg, mem.methods, fset),
|
||||
})
|
||||
}
|
||||
res.Describe = &serial.Describe{
|
||||
Desc: r.description,
|
||||
Pos: fset.Position(r.node.Pos()).String(),
|
||||
Detail: "package",
|
||||
Package: &serial.DescribePackage{
|
||||
Path: r.pkg.Path(),
|
||||
Members: members,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func tokenOf(o types.Object) string {
|
||||
switch o.(type) {
|
||||
case *types.Func:
|
||||
return "func"
|
||||
case *types.Var:
|
||||
return "var"
|
||||
case *types.TypeName:
|
||||
return "type"
|
||||
case *types.Const:
|
||||
return "const"
|
||||
case *types.PkgName:
|
||||
return "package"
|
||||
case *types.Builtin:
|
||||
return "builtin" // e.g. when describing package "unsafe"
|
||||
case *types.Nil:
|
||||
return "nil"
|
||||
case *types.Label:
|
||||
return "label"
|
||||
}
|
||||
panic(o)
|
||||
}
|
||||
|
||||
// ---- STATEMENT ------------------------------------------------------------
|
||||
|
||||
func describeStmt(qpos *queryPos, path []ast.Node) (*describeStmtResult, error) {
|
||||
var description string
|
||||
switch n := path[0].(type) {
|
||||
case *ast.Ident:
|
||||
if qpos.info.Defs[n] != nil {
|
||||
description = "labelled statement"
|
||||
} else {
|
||||
description = "reference to labelled statement"
|
||||
}
|
||||
|
||||
default:
|
||||
// Nothing much to say about statements.
|
||||
description = astutil.NodeDescription(n)
|
||||
}
|
||||
return &describeStmtResult{qpos.fset, path[0], description}, nil
|
||||
}
|
||||
|
||||
type describeStmtResult struct {
|
||||
fset *token.FileSet
|
||||
node ast.Node
|
||||
description string
|
||||
}
|
||||
|
||||
func (r *describeStmtResult) display(printf printfFunc) {
|
||||
printf(r.node, "%s", r.description)
|
||||
}
|
||||
|
||||
func (r *describeStmtResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
||||
res.Describe = &serial.Describe{
|
||||
Desc: r.description,
|
||||
Pos: fset.Position(r.node.Pos()).String(),
|
||||
Detail: "unknown",
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------- Utilities -------------------
|
||||
|
||||
// pathToString returns a string containing the concrete types of the
|
||||
// nodes in path.
|
||||
func pathToString(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()
|
||||
}
|
||||
|
||||
func accessibleMethods(t types.Type, from *types.Package) []*types.Selection {
|
||||
var methods []*types.Selection
|
||||
for _, meth := range typeutil.IntuitiveMethodSet(t, nil) {
|
||||
if isAccessibleFrom(meth.Obj(), from) {
|
||||
methods = append(methods, meth)
|
||||
}
|
||||
}
|
||||
return methods
|
||||
}
|
||||
|
||||
func isAccessibleFrom(obj types.Object, pkg *types.Package) bool {
|
||||
return ast.IsExported(obj.Name()) || obj.Pkg() == pkg
|
||||
}
|
||||
|
||||
func methodsToSerial(this *types.Package, methods []*types.Selection, fset *token.FileSet) []serial.DescribeMethod {
|
||||
qualifier := types.RelativeTo(this)
|
||||
var jmethods []serial.DescribeMethod
|
||||
for _, meth := range methods {
|
||||
var ser serial.DescribeMethod
|
||||
if meth != nil { // may contain nils when called by implements (on a method)
|
||||
ser = serial.DescribeMethod{
|
||||
Name: types.SelectionString(meth, qualifier),
|
||||
Pos: fset.Position(meth.Obj().Pos()).String(),
|
||||
}
|
||||
}
|
||||
jmethods = append(jmethods, ser)
|
||||
}
|
||||
return jmethods
|
||||
}
|
||||
|
|
@ -1,786 +0,0 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5,!go1.6
|
||||
|
||||
package oracle
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
exact "go/constant"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/types/typeutil"
|
||||
"golang.org/x/tools/oracle/serial"
|
||||
)
|
||||
|
||||
// describe describes the syntax node denoted by the query position,
|
||||
// including:
|
||||
// - its syntactic category
|
||||
// - the definition of its referent (for identifiers) [now redundant]
|
||||
// - its type and method set (for an expression or type expression)
|
||||
//
|
||||
func describe(q *Query) error {
|
||||
lconf := loader.Config{Build: q.Build}
|
||||
allowErrors(&lconf)
|
||||
|
||||
if _, err := importQueryPackage(q.Pos, &lconf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load/parse/type-check the program.
|
||||
lprog, err := lconf.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.Fset = lprog.Fset
|
||||
|
||||
qpos, err := parseQueryPos(lprog, q.Pos, true) // (need exact pos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if false { // debugging
|
||||
fprintf(os.Stderr, lprog.Fset, qpos.path[0], "you selected: %s %s",
|
||||
astutil.NodeDescription(qpos.path[0]), pathToString(qpos.path))
|
||||
}
|
||||
|
||||
path, action := findInterestingNode(qpos.info, qpos.path)
|
||||
switch action {
|
||||
case actionExpr:
|
||||
q.result, err = describeValue(qpos, path)
|
||||
|
||||
case actionType:
|
||||
q.result, err = describeType(qpos, path)
|
||||
|
||||
case actionPackage:
|
||||
q.result, err = describePackage(qpos, path)
|
||||
|
||||
case actionStmt:
|
||||
q.result, err = describeStmt(qpos, path)
|
||||
|
||||
case actionUnknown:
|
||||
q.result = &describeUnknownResult{path[0]}
|
||||
|
||||
default:
|
||||
panic(action) // unreachable
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
type describeUnknownResult struct {
|
||||
node ast.Node
|
||||
}
|
||||
|
||||
func (r *describeUnknownResult) display(printf printfFunc) {
|
||||
// Nothing much to say about misc syntax.
|
||||
printf(r.node, "%s", astutil.NodeDescription(r.node))
|
||||
}
|
||||
|
||||
func (r *describeUnknownResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
||||
res.Describe = &serial.Describe{
|
||||
Desc: astutil.NodeDescription(r.node),
|
||||
Pos: fset.Position(r.node.Pos()).String(),
|
||||
}
|
||||
}
|
||||
|
||||
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 *loader.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".
|
||||
// We should ascend to the enclosing type decl, if any.
|
||||
|
||||
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:
|
||||
// TODO(adonovan): use Selections info directly.
|
||||
if pkginfo.Uses[n.Sel] == nil {
|
||||
// TODO(adonovan): is this reachable?
|
||||
return path, actionUnknown
|
||||
}
|
||||
// Descend to .Sel child.
|
||||
path = append([]ast.Node{n.Sel}, path...)
|
||||
continue
|
||||
|
||||
case *ast.Ident:
|
||||
switch pkginfo.ObjectOf(n).(type) {
|
||||
case *types.PkgName:
|
||||
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:
|
||||
return path, actionExpr
|
||||
|
||||
case *types.Builtin:
|
||||
// For reference to built-in function, return enclosing call.
|
||||
path = path[1:] // ascend to enclosing function call
|
||||
continue
|
||||
|
||||
case *types.Nil:
|
||||
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.File:
|
||||
// 'package foo'
|
||||
return path, actionPackage
|
||||
|
||||
case *ast.ImportSpec:
|
||||
// TODO(adonovan): fix: why no package object? go/types bug?
|
||||
return path[1:], actionPackage
|
||||
|
||||
default:
|
||||
// e.g. blank identifier
|
||||
// or y in "switch y := x.(type)"
|
||||
// or code in a _test.go file that's not part of the package.
|
||||
log.Printf("unknown reference %s in %T\n", n, path[1])
|
||||
return path, actionUnknown
|
||||
}
|
||||
|
||||
case *ast.StarExpr:
|
||||
if pkginfo.Types[n].IsType() {
|
||||
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
|
||||
}
|
||||
|
||||
func describeValue(qpos *queryPos, path []ast.Node) (*describeValueResult, error) {
|
||||
var expr ast.Expr
|
||||
var obj types.Object
|
||||
switch n := path[0].(type) {
|
||||
case *ast.ValueSpec:
|
||||
// ambiguous ValueSpec containing multiple names
|
||||
return nil, fmt.Errorf("multiple value specification")
|
||||
case *ast.Ident:
|
||||
obj = qpos.info.ObjectOf(n)
|
||||
expr = n
|
||||
case ast.Expr:
|
||||
expr = n
|
||||
default:
|
||||
// TODO(adonovan): is this reachable?
|
||||
return nil, fmt.Errorf("unexpected AST for expr: %T", n)
|
||||
}
|
||||
|
||||
typ := qpos.info.TypeOf(expr)
|
||||
constVal := qpos.info.Types[expr].Value
|
||||
|
||||
return &describeValueResult{
|
||||
qpos: qpos,
|
||||
expr: expr,
|
||||
typ: typ,
|
||||
constVal: constVal,
|
||||
obj: obj,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type describeValueResult struct {
|
||||
qpos *queryPos
|
||||
expr ast.Expr // query node
|
||||
typ types.Type // type of expression
|
||||
constVal exact.Value // value of expression, if constant
|
||||
obj types.Object // var/func/const object, if expr was Ident
|
||||
}
|
||||
|
||||
func (r *describeValueResult) display(printf printfFunc) {
|
||||
var prefix, suffix string
|
||||
if r.constVal != nil {
|
||||
suffix = fmt.Sprintf(" of constant value %s", constValString(r.constVal))
|
||||
}
|
||||
switch obj := r.obj.(type) {
|
||||
case *types.Func:
|
||||
if recv := obj.Type().(*types.Signature).Recv(); recv != nil {
|
||||
if _, ok := recv.Type().Underlying().(*types.Interface); ok {
|
||||
prefix = "interface method "
|
||||
} else {
|
||||
prefix = "method "
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Describe the expression.
|
||||
if r.obj != nil {
|
||||
if r.obj.Pos() == r.expr.Pos() {
|
||||
// defining ident
|
||||
printf(r.expr, "definition of %s%s%s", prefix, r.qpos.objectString(r.obj), suffix)
|
||||
} else {
|
||||
// referring ident
|
||||
printf(r.expr, "reference to %s%s%s", prefix, r.qpos.objectString(r.obj), suffix)
|
||||
if def := r.obj.Pos(); def != token.NoPos {
|
||||
printf(def, "defined here")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
desc := astutil.NodeDescription(r.expr)
|
||||
if suffix != "" {
|
||||
// constant expression
|
||||
printf(r.expr, "%s%s", desc, suffix)
|
||||
} else {
|
||||
// non-constant expression
|
||||
printf(r.expr, "%s of type %s", desc, r.qpos.typeString(r.typ))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *describeValueResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
||||
var value, objpos string
|
||||
if r.constVal != nil {
|
||||
value = r.constVal.String()
|
||||
}
|
||||
if r.obj != nil {
|
||||
objpos = fset.Position(r.obj.Pos()).String()
|
||||
}
|
||||
|
||||
res.Describe = &serial.Describe{
|
||||
Desc: astutil.NodeDescription(r.expr),
|
||||
Pos: fset.Position(r.expr.Pos()).String(),
|
||||
Detail: "value",
|
||||
Value: &serial.DescribeValue{
|
||||
Type: r.qpos.typeString(r.typ),
|
||||
Value: value,
|
||||
ObjPos: objpos,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ---- TYPE ------------------------------------------------------------
|
||||
|
||||
func describeType(qpos *queryPos, path []ast.Node) (*describeTypeResult, error) {
|
||||
var description string
|
||||
var t types.Type
|
||||
switch n := path[0].(type) {
|
||||
case *ast.Ident:
|
||||
t = qpos.info.TypeOf(n)
|
||||
switch t := t.(type) {
|
||||
case *types.Basic:
|
||||
description = "reference to built-in "
|
||||
|
||||
case *types.Named:
|
||||
isDef := t.Obj().Pos() == n.Pos() // see caveats at isDef above
|
||||
if isDef {
|
||||
description = "definition of "
|
||||
} else {
|
||||
description = "reference to "
|
||||
}
|
||||
}
|
||||
|
||||
case ast.Expr:
|
||||
t = qpos.info.TypeOf(n)
|
||||
|
||||
default:
|
||||
// Unreachable?
|
||||
return nil, fmt.Errorf("unexpected AST for type: %T", n)
|
||||
}
|
||||
|
||||
description = description + "type " + qpos.typeString(t)
|
||||
|
||||
// Show sizes for structs and named types (it's fairly obvious for others).
|
||||
switch t.(type) {
|
||||
case *types.Named, *types.Struct:
|
||||
szs := types.StdSizes{8, 8} // assume amd64
|
||||
description = fmt.Sprintf("%s (size %d, align %d)", description,
|
||||
szs.Sizeof(t), szs.Alignof(t))
|
||||
}
|
||||
|
||||
return &describeTypeResult{
|
||||
qpos: qpos,
|
||||
node: path[0],
|
||||
description: description,
|
||||
typ: t,
|
||||
methods: accessibleMethods(t, qpos.info.Pkg),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type describeTypeResult struct {
|
||||
qpos *queryPos
|
||||
node ast.Node
|
||||
description string
|
||||
typ types.Type
|
||||
methods []*types.Selection
|
||||
}
|
||||
|
||||
func (r *describeTypeResult) display(printf printfFunc) {
|
||||
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() {
|
||||
printf(nt.Obj(), "defined as %s", r.qpos.typeString(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:
|
||||
if len(r.methods) > 0 {
|
||||
printf(r.node, "Method set:")
|
||||
for _, meth := range r.methods {
|
||||
// TODO(adonovan): print these relative
|
||||
// to the owning package, not the
|
||||
// query package.
|
||||
printf(meth.Obj(), "\t%s", r.qpos.selectionString(meth))
|
||||
}
|
||||
} else {
|
||||
printf(r.node, "No methods.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *describeTypeResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
||||
var namePos, nameDef string
|
||||
if nt, ok := r.typ.(*types.Named); ok {
|
||||
namePos = fset.Position(nt.Obj().Pos()).String()
|
||||
nameDef = nt.Underlying().String()
|
||||
}
|
||||
res.Describe = &serial.Describe{
|
||||
Desc: r.description,
|
||||
Pos: fset.Position(r.node.Pos()).String(),
|
||||
Detail: "type",
|
||||
Type: &serial.DescribeType{
|
||||
Type: r.qpos.typeString(r.typ),
|
||||
NamePos: namePos,
|
||||
NameDef: nameDef,
|
||||
Methods: methodsToSerial(r.qpos.info.Pkg, r.methods, fset),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ---- PACKAGE ------------------------------------------------------------
|
||||
|
||||
func describePackage(qpos *queryPos, path []ast.Node) (*describePackageResult, error) {
|
||||
var description string
|
||||
var pkg *types.Package
|
||||
switch n := path[0].(type) {
|
||||
case *ast.ImportSpec:
|
||||
var obj types.Object
|
||||
if n.Name != nil {
|
||||
obj = qpos.info.Defs[n.Name]
|
||||
} else {
|
||||
obj = qpos.info.Implicits[n]
|
||||
}
|
||||
pkgname, _ := obj.(*types.PkgName)
|
||||
if pkgname == nil {
|
||||
return nil, fmt.Errorf("can't import package %s", n.Path.Value)
|
||||
}
|
||||
pkg = pkgname.Imported()
|
||||
description = fmt.Sprintf("import of package %q", pkg.Path())
|
||||
|
||||
case *ast.Ident:
|
||||
if _, isDef := path[1].(*ast.File); isDef {
|
||||
// e.g. package id
|
||||
pkg = qpos.info.Pkg
|
||||
description = fmt.Sprintf("definition of package %q", pkg.Path())
|
||||
} else {
|
||||
// e.g. import id "..."
|
||||
// or id.F()
|
||||
pkg = qpos.info.ObjectOf(n).(*types.PkgName).Imported()
|
||||
description = fmt.Sprintf("reference to package %q", pkg.Path())
|
||||
}
|
||||
|
||||
default:
|
||||
// Unreachable?
|
||||
return nil, fmt.Errorf("unexpected AST for package: %T", n)
|
||||
}
|
||||
|
||||
var members []*describeMember
|
||||
// NB: "unsafe" has no types.Package
|
||||
if pkg != nil {
|
||||
// Enumerate the accessible package members
|
||||
// in lexicographic order.
|
||||
for _, name := range pkg.Scope().Names() {
|
||||
if pkg == qpos.info.Pkg || ast.IsExported(name) {
|
||||
mem := pkg.Scope().Lookup(name)
|
||||
var methods []*types.Selection
|
||||
if mem, ok := mem.(*types.TypeName); ok {
|
||||
methods = accessibleMethods(mem.Type(), qpos.info.Pkg)
|
||||
}
|
||||
members = append(members, &describeMember{
|
||||
mem,
|
||||
methods,
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &describePackageResult{qpos.fset, path[0], description, pkg, members}, nil
|
||||
}
|
||||
|
||||
type describePackageResult struct {
|
||||
fset *token.FileSet
|
||||
node ast.Node
|
||||
description string
|
||||
pkg *types.Package
|
||||
members []*describeMember // in lexicographic name order
|
||||
}
|
||||
|
||||
type describeMember struct {
|
||||
obj types.Object
|
||||
methods []*types.Selection // in types.MethodSet order
|
||||
}
|
||||
|
||||
func (r *describePackageResult) display(printf printfFunc) {
|
||||
printf(r.node, "%s", r.description)
|
||||
|
||||
// Compute max width of name "column".
|
||||
maxname := 0
|
||||
for _, mem := range r.members {
|
||||
if l := len(mem.obj.Name()); l > maxname {
|
||||
maxname = l
|
||||
}
|
||||
}
|
||||
|
||||
for _, mem := range r.members {
|
||||
printf(mem.obj, "\t%s", formatMember(mem.obj, maxname))
|
||||
for _, meth := range mem.methods {
|
||||
printf(meth.Obj(), "\t\t%s", types.SelectionString(meth, types.RelativeTo(r.pkg)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func formatMember(obj types.Object, maxname int) string {
|
||||
qualifier := types.RelativeTo(obj.Pkg())
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintf(&buf, "%-5s %-*s", tokenOf(obj), maxname, obj.Name())
|
||||
switch obj := obj.(type) {
|
||||
case *types.Const:
|
||||
fmt.Fprintf(&buf, " %s = %s", types.TypeString(obj.Type(), qualifier), constValString(obj.Val()))
|
||||
|
||||
case *types.Func:
|
||||
fmt.Fprintf(&buf, " %s", types.TypeString(obj.Type(), qualifier))
|
||||
|
||||
case *types.TypeName:
|
||||
// Abbreviate long aggregate type names.
|
||||
var abbrev string
|
||||
switch t := obj.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", types.TypeString(obj.Type().Underlying(), qualifier))
|
||||
} else {
|
||||
fmt.Fprintf(&buf, " %s", abbrev)
|
||||
}
|
||||
|
||||
case *types.Var:
|
||||
fmt.Fprintf(&buf, " %s", types.TypeString(obj.Type(), qualifier))
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (r *describePackageResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
||||
var members []*serial.DescribeMember
|
||||
for _, mem := range r.members {
|
||||
typ := mem.obj.Type()
|
||||
var val string
|
||||
switch mem := mem.obj.(type) {
|
||||
case *types.Const:
|
||||
val = constValString(mem.Val())
|
||||
case *types.TypeName:
|
||||
typ = typ.Underlying()
|
||||
}
|
||||
members = append(members, &serial.DescribeMember{
|
||||
Name: mem.obj.Name(),
|
||||
Type: typ.String(),
|
||||
Value: val,
|
||||
Pos: fset.Position(mem.obj.Pos()).String(),
|
||||
Kind: tokenOf(mem.obj),
|
||||
Methods: methodsToSerial(r.pkg, mem.methods, fset),
|
||||
})
|
||||
}
|
||||
res.Describe = &serial.Describe{
|
||||
Desc: r.description,
|
||||
Pos: fset.Position(r.node.Pos()).String(),
|
||||
Detail: "package",
|
||||
Package: &serial.DescribePackage{
|
||||
Path: r.pkg.Path(),
|
||||
Members: members,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func tokenOf(o types.Object) string {
|
||||
switch o.(type) {
|
||||
case *types.Func:
|
||||
return "func"
|
||||
case *types.Var:
|
||||
return "var"
|
||||
case *types.TypeName:
|
||||
return "type"
|
||||
case *types.Const:
|
||||
return "const"
|
||||
case *types.PkgName:
|
||||
return "package"
|
||||
case *types.Builtin:
|
||||
return "builtin" // e.g. when describing package "unsafe"
|
||||
case *types.Nil:
|
||||
return "nil"
|
||||
case *types.Label:
|
||||
return "label"
|
||||
}
|
||||
panic(o)
|
||||
}
|
||||
|
||||
// ---- STATEMENT ------------------------------------------------------------
|
||||
|
||||
func describeStmt(qpos *queryPos, path []ast.Node) (*describeStmtResult, error) {
|
||||
var description string
|
||||
switch n := path[0].(type) {
|
||||
case *ast.Ident:
|
||||
if qpos.info.Defs[n] != nil {
|
||||
description = "labelled statement"
|
||||
} else {
|
||||
description = "reference to labelled statement"
|
||||
}
|
||||
|
||||
default:
|
||||
// Nothing much to say about statements.
|
||||
description = astutil.NodeDescription(n)
|
||||
}
|
||||
return &describeStmtResult{qpos.fset, path[0], description}, nil
|
||||
}
|
||||
|
||||
type describeStmtResult struct {
|
||||
fset *token.FileSet
|
||||
node ast.Node
|
||||
description string
|
||||
}
|
||||
|
||||
func (r *describeStmtResult) display(printf printfFunc) {
|
||||
printf(r.node, "%s", r.description)
|
||||
}
|
||||
|
||||
func (r *describeStmtResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
||||
res.Describe = &serial.Describe{
|
||||
Desc: r.description,
|
||||
Pos: fset.Position(r.node.Pos()).String(),
|
||||
Detail: "unknown",
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------- Utilities -------------------
|
||||
|
||||
// pathToString returns a string containing the concrete types of the
|
||||
// nodes in path.
|
||||
func pathToString(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()
|
||||
}
|
||||
|
||||
func accessibleMethods(t types.Type, from *types.Package) []*types.Selection {
|
||||
var methods []*types.Selection
|
||||
for _, meth := range typeutil.IntuitiveMethodSet(t, nil) {
|
||||
if isAccessibleFrom(meth.Obj(), from) {
|
||||
methods = append(methods, meth)
|
||||
}
|
||||
}
|
||||
return methods
|
||||
}
|
||||
|
||||
func isAccessibleFrom(obj types.Object, pkg *types.Package) bool {
|
||||
return ast.IsExported(obj.Name()) || obj.Pkg() == pkg
|
||||
}
|
||||
|
||||
func methodsToSerial(this *types.Package, methods []*types.Selection, fset *token.FileSet) []serial.DescribeMethod {
|
||||
qualifier := types.RelativeTo(this)
|
||||
var jmethods []serial.DescribeMethod
|
||||
for _, meth := range methods {
|
||||
var ser serial.DescribeMethod
|
||||
if meth != nil { // may contain nils when called by implements (on a method)
|
||||
ser = serial.DescribeMethod{
|
||||
Name: types.SelectionString(meth, qualifier),
|
||||
Pos: fset.Position(meth.Obj().Pos()).String(),
|
||||
}
|
||||
}
|
||||
jmethods = append(jmethods, ser)
|
||||
}
|
||||
return jmethods
|
||||
}
|
||||
|
||||
// constValString emulates Go 1.6's go/constant.ExactString well enough
|
||||
// to make the tests pass. This is just a stopgap until we throw away
|
||||
// all the *15.go files.
|
||||
func constValString(v exact.Value) string {
|
||||
if v.Kind() == exact.Float {
|
||||
f, _ := exact.Float64Val(v)
|
||||
return fmt.Sprintf("%g", f)
|
||||
}
|
||||
return v.String()
|
||||
}
|
||||
|
|
@ -1,224 +0,0 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package oracle
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"go/ast"
|
||||
"go/printer"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"sort"
|
||||
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/oracle/serial"
|
||||
)
|
||||
|
||||
// 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(q *Query) error {
|
||||
lconf := loader.Config{Build: q.Build}
|
||||
allowErrors(&lconf)
|
||||
|
||||
if _, err := importQueryPackage(q.Pos, &lconf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load/parse/type-check the program.
|
||||
lprog, err := lconf.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.Fset = lprog.Fset
|
||||
|
||||
qpos, err := parseQueryPos(lprog, q.Pos, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
file := qpos.path[len(qpos.path)-1] // the enclosing file
|
||||
fileScope := qpos.info.Scopes[file]
|
||||
pkgScope := fileScope.Parent()
|
||||
|
||||
// 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 := qpos.info.Uses[n]
|
||||
if obj == nil {
|
||||
return nil // not a reference
|
||||
}
|
||||
if _, ok := obj.(*types.PkgName); ok {
|
||||
return nil // imported package
|
||||
}
|
||||
if !(file.Pos() <= obj.Pos() && obj.Pos() <= file.End()) {
|
||||
return nil // not defined in this file
|
||||
}
|
||||
scope := obj.Parent()
|
||||
if scope == nil {
|
||||
return nil // e.g. interface method, struct field
|
||||
}
|
||||
if scope == fileScope || scope == pkgScope {
|
||||
return nil // defined at file or package scope
|
||||
}
|
||||
if qpos.start <= obj.Pos() && obj.Pos() <= qpos.end {
|
||||
return nil // defined within selection => not free
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
// Maps each reference that is free in the selection
|
||||
// to the object it refers to.
|
||||
// The map de-duplicates repeated references.
|
||||
refsMap := make(map[string]freevarsRef)
|
||||
|
||||
// Visit all the identifiers in the selected ASTs.
|
||||
ast.Inspect(qpos.path[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 qpos.start <= n.Pos() && n.End() <= qpos.end {
|
||||
var obj types.Object
|
||||
var prune bool
|
||||
switch n := n.(type) {
|
||||
case *ast.Ident:
|
||||
obj = id(n)
|
||||
|
||||
case *ast.SelectorExpr:
|
||||
obj = sel(n)
|
||||
prune = true
|
||||
}
|
||||
|
||||
if obj != nil {
|
||||
var kind string
|
||||
switch 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(obj)
|
||||
}
|
||||
|
||||
typ := qpos.info.TypeOf(n.(ast.Expr))
|
||||
ref := freevarsRef{kind, printNode(lprog.Fset, n), typ, obj}
|
||||
refsMap[ref.ref] = ref
|
||||
|
||||
if prune {
|
||||
return false // don't descend
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true // descend
|
||||
})
|
||||
|
||||
refs := make([]freevarsRef, 0, len(refsMap))
|
||||
for _, ref := range refsMap {
|
||||
refs = append(refs, ref)
|
||||
}
|
||||
sort.Sort(byRef(refs))
|
||||
|
||||
q.result = &freevarsResult{
|
||||
qpos: qpos,
|
||||
refs: refs,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type freevarsResult struct {
|
||||
qpos *queryPos
|
||||
refs []freevarsRef
|
||||
}
|
||||
|
||||
type freevarsRef struct {
|
||||
kind string
|
||||
ref string
|
||||
typ types.Type
|
||||
obj types.Object
|
||||
}
|
||||
|
||||
func (r *freevarsResult) display(printf printfFunc) {
|
||||
if len(r.refs) == 0 {
|
||||
printf(r.qpos, "No free identifiers.")
|
||||
} else {
|
||||
printf(r.qpos, "Free identifiers:")
|
||||
qualifier := types.RelativeTo(r.qpos.info.Pkg)
|
||||
for _, ref := range r.refs {
|
||||
// Avoid printing "type T T".
|
||||
var typstr string
|
||||
if ref.kind != "type" {
|
||||
typstr = " " + types.TypeString(ref.typ, qualifier)
|
||||
}
|
||||
printf(ref.obj, "%s %s%s", ref.kind, ref.ref, typstr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *freevarsResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
||||
var refs []*serial.FreeVar
|
||||
for _, ref := range r.refs {
|
||||
refs = append(refs,
|
||||
&serial.FreeVar{
|
||||
Pos: fset.Position(ref.obj.Pos()).String(),
|
||||
Kind: ref.kind,
|
||||
Ref: ref.ref,
|
||||
Type: ref.typ.String(),
|
||||
})
|
||||
}
|
||||
res.Freevars = refs
|
||||
}
|
||||
|
||||
// -------- utils --------
|
||||
|
||||
type byRef []freevarsRef
|
||||
|
||||
func (p byRef) Len() int { return len(p) }
|
||||
func (p byRef) Less(i, j int) bool { return p[i].ref < p[j].ref }
|
||||
func (p byRef) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||
|
||||
// printNode returns the pretty-printed syntax of n.
|
||||
func printNode(fset *token.FileSet, n ast.Node) string {
|
||||
var buf bytes.Buffer
|
||||
printer.Fprint(&buf, fset, n)
|
||||
return buf.String()
|
||||
}
|
||||
|
|
@ -1,354 +0,0 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package oracle
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/types/typeutil"
|
||||
"golang.org/x/tools/oracle/serial"
|
||||
"golang.org/x/tools/refactor/importgraph"
|
||||
)
|
||||
|
||||
// Implements displays the "implements" relation as it pertains to the
|
||||
// selected type.
|
||||
// If the selection is a method, 'implements' displays
|
||||
// the corresponding methods of the types that would have been reported
|
||||
// by an implements query on the receiver type.
|
||||
//
|
||||
func implements(q *Query) error {
|
||||
lconf := loader.Config{Build: q.Build}
|
||||
allowErrors(&lconf)
|
||||
|
||||
qpkg, err := importQueryPackage(q.Pos, &lconf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set the packages to search.
|
||||
if len(q.Scope) > 0 {
|
||||
// Inspect all packages in the analysis scope, if specified.
|
||||
if err := setPTAScope(&lconf, q.Scope); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// Otherwise inspect the forward and reverse
|
||||
// transitive closure of the selected package.
|
||||
// (In theory even this is incomplete.)
|
||||
_, rev, _ := importgraph.Build(q.Build)
|
||||
for path := range rev.Search(qpkg) {
|
||||
lconf.ImportWithTests(path)
|
||||
}
|
||||
|
||||
// TODO(adonovan): for completeness, we should also
|
||||
// type-check and inspect function bodies in all
|
||||
// imported packages. This would be expensive, but we
|
||||
// could optimize by skipping functions that do not
|
||||
// contain type declarations. This would require
|
||||
// changing the loader's TypeCheckFuncBodies hook to
|
||||
// provide the []*ast.File.
|
||||
}
|
||||
|
||||
// Load/parse/type-check the program.
|
||||
lprog, err := lconf.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.Fset = lprog.Fset
|
||||
|
||||
qpos, err := parseQueryPos(lprog, q.Pos, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Find the selected type.
|
||||
path, action := findInterestingNode(qpos.info, qpos.path)
|
||||
|
||||
var method *types.Func
|
||||
var T types.Type // selected type (receiver if method != nil)
|
||||
|
||||
switch action {
|
||||
case actionExpr:
|
||||
// method?
|
||||
if id, ok := path[0].(*ast.Ident); ok {
|
||||
if obj, ok := qpos.info.ObjectOf(id).(*types.Func); ok {
|
||||
recv := obj.Type().(*types.Signature).Recv()
|
||||
if recv == nil {
|
||||
return fmt.Errorf("this function is not a method")
|
||||
}
|
||||
method = obj
|
||||
T = recv.Type()
|
||||
}
|
||||
}
|
||||
case actionType:
|
||||
T = qpos.info.TypeOf(path[0].(ast.Expr))
|
||||
}
|
||||
if T == nil {
|
||||
return fmt.Errorf("no type or method here")
|
||||
}
|
||||
|
||||
// Find all named types, even local types (which can have
|
||||
// methods via promotion) and the built-in "error".
|
||||
var allNamed []types.Type
|
||||
for _, info := range lprog.AllPackages {
|
||||
for _, obj := range info.Defs {
|
||||
if obj, ok := obj.(*types.TypeName); ok {
|
||||
allNamed = append(allNamed, obj.Type())
|
||||
}
|
||||
}
|
||||
}
|
||||
allNamed = append(allNamed, types.Universe.Lookup("error").Type())
|
||||
|
||||
var msets typeutil.MethodSetCache
|
||||
|
||||
// Test each named type.
|
||||
var to, from, fromPtr []types.Type
|
||||
for _, U := range allNamed {
|
||||
if isInterface(T) {
|
||||
if msets.MethodSet(T).Len() == 0 {
|
||||
continue // empty interface
|
||||
}
|
||||
if isInterface(U) {
|
||||
if msets.MethodSet(U).Len() == 0 {
|
||||
continue // empty interface
|
||||
}
|
||||
|
||||
// T interface, U interface
|
||||
if !types.Identical(T, U) {
|
||||
if types.AssignableTo(U, T) {
|
||||
to = append(to, U)
|
||||
}
|
||||
if types.AssignableTo(T, U) {
|
||||
from = append(from, U)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// T interface, U concrete
|
||||
if types.AssignableTo(U, T) {
|
||||
to = append(to, U)
|
||||
} else if pU := types.NewPointer(U); types.AssignableTo(pU, T) {
|
||||
to = append(to, pU)
|
||||
}
|
||||
}
|
||||
} else if isInterface(U) {
|
||||
if msets.MethodSet(U).Len() == 0 {
|
||||
continue // empty interface
|
||||
}
|
||||
|
||||
// T concrete, U interface
|
||||
if types.AssignableTo(T, U) {
|
||||
from = append(from, U)
|
||||
} else if pT := types.NewPointer(T); types.AssignableTo(pT, U) {
|
||||
fromPtr = append(fromPtr, U)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var pos interface{} = qpos
|
||||
if nt, ok := deref(T).(*types.Named); ok {
|
||||
pos = nt.Obj()
|
||||
}
|
||||
|
||||
// Sort types (arbitrarily) to ensure test determinism.
|
||||
sort.Sort(typesByString(to))
|
||||
sort.Sort(typesByString(from))
|
||||
sort.Sort(typesByString(fromPtr))
|
||||
|
||||
var toMethod, fromMethod, fromPtrMethod []*types.Selection // contain nils
|
||||
if method != nil {
|
||||
for _, t := range to {
|
||||
toMethod = append(toMethod,
|
||||
types.NewMethodSet(t).Lookup(method.Pkg(), method.Name()))
|
||||
}
|
||||
for _, t := range from {
|
||||
fromMethod = append(fromMethod,
|
||||
types.NewMethodSet(t).Lookup(method.Pkg(), method.Name()))
|
||||
}
|
||||
for _, t := range fromPtr {
|
||||
fromPtrMethod = append(fromPtrMethod,
|
||||
types.NewMethodSet(t).Lookup(method.Pkg(), method.Name()))
|
||||
}
|
||||
}
|
||||
|
||||
q.result = &implementsResult{
|
||||
qpos, T, pos, to, from, fromPtr, method, toMethod, fromMethod, fromPtrMethod,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type implementsResult struct {
|
||||
qpos *queryPos
|
||||
|
||||
t types.Type // queried type (not necessarily named)
|
||||
pos interface{} // pos of t (*types.Name or *QueryPos)
|
||||
to []types.Type // named or ptr-to-named types assignable to interface T
|
||||
from []types.Type // named interfaces assignable from T
|
||||
fromPtr []types.Type // named interfaces assignable only from *T
|
||||
|
||||
// if a method was queried:
|
||||
method *types.Func // queried method
|
||||
toMethod []*types.Selection // method of type to[i], if any
|
||||
fromMethod []*types.Selection // method of type from[i], if any
|
||||
fromPtrMethod []*types.Selection // method of type fromPtrMethod[i], if any
|
||||
}
|
||||
|
||||
func (r *implementsResult) display(printf printfFunc) {
|
||||
relation := "is implemented by"
|
||||
|
||||
meth := func(sel *types.Selection) {
|
||||
if sel != nil {
|
||||
printf(sel.Obj(), "\t%s method (%s).%s",
|
||||
relation, r.qpos.typeString(sel.Recv()), sel.Obj().Name())
|
||||
}
|
||||
}
|
||||
|
||||
if isInterface(r.t) {
|
||||
if types.NewMethodSet(r.t).Len() == 0 { // TODO(adonovan): cache mset
|
||||
printf(r.pos, "empty interface type %s", r.qpos.typeString(r.t))
|
||||
return
|
||||
}
|
||||
|
||||
if r.method == nil {
|
||||
printf(r.pos, "interface type %s", r.qpos.typeString(r.t))
|
||||
} else {
|
||||
printf(r.method, "abstract method %s", r.qpos.objectString(r.method))
|
||||
}
|
||||
|
||||
// Show concrete types (or methods) first; use two passes.
|
||||
for i, sub := range r.to {
|
||||
if !isInterface(sub) {
|
||||
if r.method == nil {
|
||||
printf(deref(sub).(*types.Named).Obj(), "\t%s %s type %s",
|
||||
relation, typeKind(sub), r.qpos.typeString(sub))
|
||||
} else {
|
||||
meth(r.toMethod[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
for i, sub := range r.to {
|
||||
if isInterface(sub) {
|
||||
if r.method == nil {
|
||||
printf(sub.(*types.Named).Obj(), "\t%s %s type %s",
|
||||
relation, typeKind(sub), r.qpos.typeString(sub))
|
||||
} else {
|
||||
meth(r.toMethod[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
relation = "implements"
|
||||
for i, super := range r.from {
|
||||
if r.method == nil {
|
||||
printf(super.(*types.Named).Obj(), "\t%s %s",
|
||||
relation, r.qpos.typeString(super))
|
||||
} else {
|
||||
meth(r.fromMethod[i])
|
||||
}
|
||||
}
|
||||
} else {
|
||||
relation = "implements"
|
||||
|
||||
if r.from != nil {
|
||||
if r.method == nil {
|
||||
printf(r.pos, "%s type %s",
|
||||
typeKind(r.t), r.qpos.typeString(r.t))
|
||||
} else {
|
||||
printf(r.method, "concrete method %s",
|
||||
r.qpos.objectString(r.method))
|
||||
}
|
||||
for i, super := range r.from {
|
||||
if r.method == nil {
|
||||
printf(super.(*types.Named).Obj(), "\t%s %s",
|
||||
relation, r.qpos.typeString(super))
|
||||
} else {
|
||||
meth(r.fromMethod[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
if r.fromPtr != nil {
|
||||
if r.method == nil {
|
||||
printf(r.pos, "pointer type *%s", r.qpos.typeString(r.t))
|
||||
} else {
|
||||
// TODO(adonovan): de-dup (C).f and (*C).f implementing (I).f.
|
||||
printf(r.method, "concrete method %s",
|
||||
r.qpos.objectString(r.method))
|
||||
}
|
||||
|
||||
for i, psuper := range r.fromPtr {
|
||||
if r.method == nil {
|
||||
printf(psuper.(*types.Named).Obj(), "\t%s %s",
|
||||
relation, r.qpos.typeString(psuper))
|
||||
} else {
|
||||
meth(r.fromPtrMethod[i])
|
||||
}
|
||||
}
|
||||
} else if r.from == nil {
|
||||
printf(r.pos, "%s type %s implements only interface{}",
|
||||
typeKind(r.t), r.qpos.typeString(r.t))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *implementsResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
||||
res.Implements = &serial.Implements{
|
||||
T: makeImplementsType(r.t, fset),
|
||||
AssignableTo: makeImplementsTypes(r.to, fset),
|
||||
AssignableFrom: makeImplementsTypes(r.from, fset),
|
||||
AssignableFromPtr: makeImplementsTypes(r.fromPtr, fset),
|
||||
AssignableToMethod: methodsToSerial(r.qpos.info.Pkg, r.toMethod, fset),
|
||||
AssignableFromMethod: methodsToSerial(r.qpos.info.Pkg, r.fromMethod, fset),
|
||||
AssignableFromPtrMethod: methodsToSerial(r.qpos.info.Pkg, r.fromPtrMethod, fset),
|
||||
}
|
||||
if r.method != nil {
|
||||
res.Implements.Method = &serial.DescribeMethod{
|
||||
Name: r.qpos.objectString(r.method),
|
||||
Pos: fset.Position(r.method.Pos()).String(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func makeImplementsTypes(tt []types.Type, fset *token.FileSet) []serial.ImplementsType {
|
||||
var r []serial.ImplementsType
|
||||
for _, t := range tt {
|
||||
r = append(r, makeImplementsType(t, fset))
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func makeImplementsType(T types.Type, fset *token.FileSet) serial.ImplementsType {
|
||||
var pos token.Pos
|
||||
if nt, ok := deref(T).(*types.Named); ok { // implementsResult.t may be non-named
|
||||
pos = nt.Obj().Pos()
|
||||
}
|
||||
return serial.ImplementsType{
|
||||
Name: T.String(),
|
||||
Pos: fset.Position(pos).String(),
|
||||
Kind: typeKind(T),
|
||||
}
|
||||
}
|
||||
|
||||
// typeKind returns a string describing the underlying kind of type,
|
||||
// e.g. "slice", "array", "struct".
|
||||
func typeKind(T types.Type) string {
|
||||
s := reflect.TypeOf(T.Underlying()).String()
|
||||
return strings.ToLower(strings.TrimPrefix(s, "*types."))
|
||||
}
|
||||
|
||||
func isInterface(T types.Type) bool { return types.IsInterface(T) }
|
||||
|
||||
type typesByString []types.Type
|
||||
|
||||
func (p typesByString) Len() int { return len(p) }
|
||||
func (p typesByString) Less(i, j int) bool { return p[i].String() < p[j].String() }
|
||||
func (p typesByString) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||
368
oracle/oracle.go
368
oracle/oracle.go
|
|
@ -1,368 +0,0 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
// Package oracle contains the implementation of the oracle tool whose
|
||||
// command-line is provided by golang.org/x/tools/cmd/oracle.
|
||||
//
|
||||
// DEPRECATED: oracle has been superseded by guru;
|
||||
// see https://golang.org/s/using-guru for details.
|
||||
// This package will be deleted on October 1, 2016.
|
||||
//
|
||||
package oracle // import "golang.org/x/tools/oracle"
|
||||
|
||||
// This file defines oracle.Query, the entry point for the oracle tool.
|
||||
// The actual executable is defined in cmd/oracle.
|
||||
|
||||
// TODO(adonovan): new queries
|
||||
// - show all statements that may update the selected lvalue
|
||||
// (local, global, field, etc).
|
||||
// - show all places where an object of type T is created
|
||||
// (&T{}, var t T, new(T), new(struct{array [3]T}), etc.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"io"
|
||||
"path/filepath"
|
||||
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/pointer"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/oracle/serial"
|
||||
)
|
||||
|
||||
type printfFunc func(pos interface{}, format string, args ...interface{})
|
||||
|
||||
// queryResult is the interface of each query-specific result type.
|
||||
type queryResult interface {
|
||||
toSerial(res *serial.Result, fset *token.FileSet)
|
||||
display(printf printfFunc)
|
||||
}
|
||||
|
||||
// A QueryPos represents the position provided as input to a query:
|
||||
// a textual extent in the program's source code, the AST node it
|
||||
// corresponds to, and the package to which it belongs.
|
||||
// Instances are created by parseQueryPos.
|
||||
type queryPos struct {
|
||||
fset *token.FileSet
|
||||
start, end token.Pos // source extent of query
|
||||
path []ast.Node // AST path from query node to root of ast.File
|
||||
exact bool // 2nd result of PathEnclosingInterval
|
||||
info *loader.PackageInfo // type info for the queried package (nil for fastQueryPos)
|
||||
}
|
||||
|
||||
// TypeString prints type T relative to the query position.
|
||||
func (qpos *queryPos) typeString(T types.Type) string {
|
||||
return types.TypeString(T, types.RelativeTo(qpos.info.Pkg))
|
||||
}
|
||||
|
||||
// ObjectString prints object obj relative to the query position.
|
||||
func (qpos *queryPos) objectString(obj types.Object) string {
|
||||
return types.ObjectString(obj, types.RelativeTo(qpos.info.Pkg))
|
||||
}
|
||||
|
||||
// SelectionString prints selection sel relative to the query position.
|
||||
func (qpos *queryPos) selectionString(sel *types.Selection) string {
|
||||
return types.SelectionString(sel, types.RelativeTo(qpos.info.Pkg))
|
||||
}
|
||||
|
||||
// A Query specifies a single oracle query.
|
||||
type Query struct {
|
||||
Mode string // query mode ("callers", etc)
|
||||
Pos string // query position
|
||||
Build *build.Context // package loading configuration
|
||||
|
||||
// pointer analysis options
|
||||
Scope []string // main packages in (*loader.Config).FromArgs syntax
|
||||
PTALog io.Writer // (optional) pointer-analysis log file
|
||||
Reflection bool // model reflection soundly (currently slow).
|
||||
|
||||
// Populated during Run()
|
||||
Fset *token.FileSet
|
||||
result queryResult
|
||||
}
|
||||
|
||||
// Serial returns an instance of serial.Result, which implements the
|
||||
// {xml,json}.Marshaler interfaces so that query results can be
|
||||
// serialized as JSON or XML.
|
||||
//
|
||||
func (q *Query) Serial() *serial.Result {
|
||||
resj := &serial.Result{Mode: q.Mode}
|
||||
q.result.toSerial(resj, q.Fset)
|
||||
return resj
|
||||
}
|
||||
|
||||
// WriteTo writes the oracle query result res to out in a compiler diagnostic format.
|
||||
func (q *Query) WriteTo(out io.Writer) {
|
||||
printf := func(pos interface{}, format string, args ...interface{}) {
|
||||
fprintf(out, q.Fset, pos, format, args...)
|
||||
}
|
||||
q.result.display(printf)
|
||||
}
|
||||
|
||||
// Run runs an oracle query and populates its Fset and Result.
|
||||
func Run(q *Query) error {
|
||||
switch q.Mode {
|
||||
case "callees":
|
||||
return callees(q)
|
||||
case "callers":
|
||||
return callers(q)
|
||||
case "callstack":
|
||||
return callstack(q)
|
||||
case "peers":
|
||||
return peers(q)
|
||||
case "pointsto":
|
||||
return pointsto(q)
|
||||
case "whicherrs":
|
||||
return whicherrs(q)
|
||||
case "definition":
|
||||
return definition(q)
|
||||
case "describe":
|
||||
return describe(q)
|
||||
case "freevars":
|
||||
return freevars(q)
|
||||
case "implements":
|
||||
return implements(q)
|
||||
case "referrers":
|
||||
return referrers(q)
|
||||
case "what":
|
||||
return what(q)
|
||||
default:
|
||||
return fmt.Errorf("invalid mode: %q", q.Mode)
|
||||
}
|
||||
}
|
||||
|
||||
func setPTAScope(lconf *loader.Config, scope []string) error {
|
||||
if len(scope) == 0 {
|
||||
return fmt.Errorf("no packages specified for pointer analysis scope")
|
||||
}
|
||||
|
||||
// Determine initial packages for PTA.
|
||||
args, err := lconf.FromArgs(scope, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(args) > 0 {
|
||||
return fmt.Errorf("surplus arguments: %q", args)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create a pointer.Config whose scope is the initial packages of lprog
|
||||
// and their dependencies.
|
||||
func setupPTA(prog *ssa.Program, lprog *loader.Program, ptaLog io.Writer, reflection bool) (*pointer.Config, error) {
|
||||
// TODO(adonovan): the body of this function is essentially
|
||||
// duplicated in all go/pointer clients. Refactor.
|
||||
|
||||
// For each initial package (specified on the command line),
|
||||
// if it has a main function, analyze that,
|
||||
// otherwise analyze its tests, if any.
|
||||
var testPkgs, mains []*ssa.Package
|
||||
for _, info := range lprog.InitialPackages() {
|
||||
initialPkg := prog.Package(info.Pkg)
|
||||
|
||||
// Add package to the pointer analysis scope.
|
||||
if initialPkg.Func("main") != nil {
|
||||
mains = append(mains, initialPkg)
|
||||
} else {
|
||||
testPkgs = append(testPkgs, initialPkg)
|
||||
}
|
||||
}
|
||||
if testPkgs != nil {
|
||||
if p := prog.CreateTestMainPackage(testPkgs...); p != nil {
|
||||
mains = append(mains, p)
|
||||
}
|
||||
}
|
||||
if mains == nil {
|
||||
return nil, fmt.Errorf("analysis scope has no main and no tests")
|
||||
}
|
||||
return &pointer.Config{
|
||||
Log: ptaLog,
|
||||
Reflection: reflection,
|
||||
Mains: mains,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// importQueryPackage finds the package P containing the
|
||||
// query position and tells conf to import it.
|
||||
// It returns the package's path.
|
||||
func importQueryPackage(pos string, conf *loader.Config) (string, error) {
|
||||
fqpos, err := fastQueryPos(pos)
|
||||
if err != nil {
|
||||
return "", err // bad query
|
||||
}
|
||||
filename := fqpos.fset.File(fqpos.start).Name()
|
||||
|
||||
// This will not work for ad-hoc packages
|
||||
// such as $GOROOT/src/net/http/triv.go.
|
||||
// TODO(adonovan): ensure we report a clear error.
|
||||
_, importPath, err := guessImportPath(filename, conf.Build)
|
||||
if err != nil {
|
||||
return "", err // can't find GOPATH dir
|
||||
}
|
||||
if importPath == "" {
|
||||
return "", fmt.Errorf("can't guess import path from %s", filename)
|
||||
}
|
||||
|
||||
// Check that it's possible to load the queried package.
|
||||
// (e.g. oracle tests contain different 'package' decls in same dir.)
|
||||
// Keep consistent with logic in loader/util.go!
|
||||
cfg2 := *conf.Build
|
||||
cfg2.CgoEnabled = false
|
||||
bp, err := cfg2.Import(importPath, "", 0)
|
||||
if err != nil {
|
||||
return "", err // no files for package
|
||||
}
|
||||
|
||||
switch pkgContainsFile(bp, filename) {
|
||||
case 'T':
|
||||
conf.ImportWithTests(importPath)
|
||||
case 'X':
|
||||
conf.ImportWithTests(importPath)
|
||||
importPath += "_test" // for TypeCheckFuncBodies
|
||||
case 'G':
|
||||
conf.Import(importPath)
|
||||
default:
|
||||
return "", fmt.Errorf("package %q doesn't contain file %s",
|
||||
importPath, filename)
|
||||
}
|
||||
|
||||
conf.TypeCheckFuncBodies = func(p string) bool { return p == importPath }
|
||||
|
||||
return importPath, nil
|
||||
}
|
||||
|
||||
// pkgContainsFile reports whether file was among the packages Go
|
||||
// files, Test files, eXternal test files, or not found.
|
||||
func pkgContainsFile(bp *build.Package, filename string) byte {
|
||||
for i, files := range [][]string{bp.GoFiles, bp.TestGoFiles, bp.XTestGoFiles} {
|
||||
for _, file := range files {
|
||||
if sameFile(filepath.Join(bp.Dir, file), filename) {
|
||||
return "GTX"[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0 // not found
|
||||
}
|
||||
|
||||
// ParseQueryPos parses the source query position pos and returns the
|
||||
// AST node of the loaded program lprog that it identifies.
|
||||
// If needExact, it must identify a single AST subtree;
|
||||
// this is appropriate for queries that allow fairly arbitrary syntax,
|
||||
// e.g. "describe".
|
||||
//
|
||||
func parseQueryPos(lprog *loader.Program, posFlag string, needExact bool) (*queryPos, error) {
|
||||
filename, startOffset, endOffset, err := parsePosFlag(posFlag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
start, end, err := findQueryPos(lprog.Fset, filename, startOffset, endOffset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info, path, exact := lprog.PathEnclosingInterval(start, end)
|
||||
if path == nil {
|
||||
return nil, fmt.Errorf("no syntax here")
|
||||
}
|
||||
if needExact && !exact {
|
||||
return nil, fmt.Errorf("ambiguous selection within %s", astutil.NodeDescription(path[0]))
|
||||
}
|
||||
return &queryPos{lprog.Fset, start, end, path, exact, info}, nil
|
||||
}
|
||||
|
||||
// ---------- Utilities ----------
|
||||
|
||||
// allowErrors causes type errors to be silently ignored.
|
||||
// (Not suitable if SSA construction follows.)
|
||||
func allowErrors(lconf *loader.Config) {
|
||||
ctxt := *lconf.Build // copy
|
||||
ctxt.CgoEnabled = false
|
||||
lconf.Build = &ctxt
|
||||
lconf.AllowErrors = true
|
||||
// AllErrors makes the parser always return an AST instead of
|
||||
// bailing out after 10 errors and returning an empty ast.File.
|
||||
lconf.ParserMode = parser.AllErrors
|
||||
lconf.TypeChecker.Error = func(err error) {}
|
||||
}
|
||||
|
||||
// ptrAnalysis runs the pointer analysis and returns its result.
|
||||
func ptrAnalysis(conf *pointer.Config) *pointer.Result {
|
||||
result, err := pointer.Analyze(conf)
|
||||
if err != nil {
|
||||
panic(err) // pointer analysis internal error
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func unparen(e ast.Expr) ast.Expr { return astutil.Unparen(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.
|
||||
// - a QueryPos, denoting the extent 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 fprintf(w io.Writer, fset *token.FileSet, 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 *queryPos:
|
||||
start = pos.start
|
||||
end = pos.end
|
||||
case nil:
|
||||
// no-op
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid pos: %T", pos))
|
||||
}
|
||||
|
||||
if sp := fset.Position(start); start == end {
|
||||
// (prints "-: " for token.NoPos)
|
||||
fmt.Fprintf(w, "%s: ", sp)
|
||||
} else {
|
||||
ep := 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")
|
||||
}
|
||||
|
|
@ -1,278 +0,0 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package 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 golang.org/x/tools/oracle -update
|
||||
// to update the golden files.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/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
|
||||
queryPos string // value of -pos flag
|
||||
}
|
||||
|
||||
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.
|
||||
fset := token.NewFileSet()
|
||||
f, err := parser.ParseFile(fset, filename, filedata, 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
|
||||
}
|
||||
|
||||
q := &query{
|
||||
id: id,
|
||||
verb: match[1],
|
||||
filename: filename,
|
||||
posn: posn,
|
||||
}
|
||||
|
||||
if match[3] != `"nopos"` {
|
||||
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.queryPos = fmt.Sprintf("%s:#%d,#%d",
|
||||
filename, linestart+loc[0], linestart+loc[1])
|
||||
}
|
||||
|
||||
queries = append(queries, q)
|
||||
queriesById[id] = q
|
||||
}
|
||||
|
||||
// Return the slice, not map, for deterministic iteration.
|
||||
return queries
|
||||
}
|
||||
|
||||
// WriteResult writes res (-format=plain) to w, stripping file locations.
|
||||
func WriteResult(w io.Writer, q *oracle.Query) {
|
||||
capture := new(bytes.Buffer) // capture standard output
|
||||
q.WriteTo(capture)
|
||||
for _, line := range strings.Split(capture.String(), "\n") {
|
||||
// Remove a "file:line: " prefix.
|
||||
if i := strings.Index(line, ": "); i >= 0 {
|
||||
line = line[i+2:]
|
||||
}
|
||||
fmt.Fprintf(w, "%s\n", 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, useJson bool) {
|
||||
fmt.Fprintf(out, "-------- @%s %s --------\n", q.verb, q.id)
|
||||
|
||||
var buildContext = build.Default
|
||||
buildContext.GOPATH = "testdata"
|
||||
query := oracle.Query{
|
||||
Mode: q.verb,
|
||||
Pos: q.queryPos,
|
||||
Build: &buildContext,
|
||||
Scope: []string{q.filename},
|
||||
Reflection: true,
|
||||
}
|
||||
if err := oracle.Run(&query); err != nil {
|
||||
fmt.Fprintf(out, "\nError: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if useJson {
|
||||
// JSON output
|
||||
b, err := json.MarshalIndent(query.Serial(), "", "\t")
|
||||
if err != nil {
|
||||
fmt.Fprintf(out, "JSON error: %s\n", err.Error())
|
||||
return
|
||||
}
|
||||
out.Write(b)
|
||||
fmt.Fprintln(out)
|
||||
} else {
|
||||
// "plain" (compiler diagnostic format) output
|
||||
WriteResult(out, &query)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOracle(t *testing.T) {
|
||||
switch runtime.GOOS {
|
||||
case "android":
|
||||
t.Skipf("skipping test on %q (no testdata dir)", runtime.GOOS)
|
||||
case "windows":
|
||||
t.Skipf("skipping test on %q (no /usr/bin/diff)", runtime.GOOS)
|
||||
}
|
||||
|
||||
for _, filename := range []string{
|
||||
"testdata/src/calls/main.go",
|
||||
"testdata/src/describe/main.go",
|
||||
"testdata/src/freevars/main.go",
|
||||
"testdata/src/implements/main.go",
|
||||
"testdata/src/implements-methods/main.go",
|
||||
"testdata/src/imports/main.go",
|
||||
"testdata/src/peers/main.go",
|
||||
"testdata/src/pointsto/main.go",
|
||||
"testdata/src/referrers/main.go",
|
||||
"testdata/src/reflection/main.go",
|
||||
"testdata/src/what/main.go",
|
||||
"testdata/src/whicherrs/main.go",
|
||||
// JSON:
|
||||
// TODO(adonovan): most of these are very similar; combine them.
|
||||
"testdata/src/calls-json/main.go",
|
||||
"testdata/src/peers-json/main.go",
|
||||
"testdata/src/describe-json/main.go",
|
||||
"testdata/src/implements-json/main.go",
|
||||
"testdata/src/implements-methods-json/main.go",
|
||||
"testdata/src/pointsto-json/main.go",
|
||||
"testdata/src/referrers-json/main.go",
|
||||
"testdata/src/what-json/main.go",
|
||||
} {
|
||||
if filename == "testdata/src/referrers/main.go" && runtime.GOOS == "plan9" {
|
||||
// Disable this test on plan9 since it expects a particular
|
||||
// wording for a "no such file or directory" error.
|
||||
continue
|
||||
}
|
||||
|
||||
useJson := strings.Contains(filename, "-json/")
|
||||
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()
|
||||
defer os.Remove(got)
|
||||
|
||||
// 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, useJson)
|
||||
}
|
||||
|
||||
// Compare foo.got with foo.golden.
|
||||
var cmd *exec.Cmd
|
||||
switch runtime.GOOS {
|
||||
case "plan9":
|
||||
cmd = exec.Command("/bin/diff", "-c", golden, got)
|
||||
default:
|
||||
cmd = exec.Command("/usr/bin/diff", "-u", golden, got)
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
cmd.Stdout = buf
|
||||
cmd.Stderr = os.Stderr
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
254
oracle/peers.go
254
oracle/peers.go
|
|
@ -1,254 +0,0 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package oracle
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"sort"
|
||||
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/ssa/ssautil"
|
||||
"golang.org/x/tools/oracle/serial"
|
||||
)
|
||||
|
||||
// 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,Close}.
|
||||
// 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(q *Query) error {
|
||||
lconf := loader.Config{Build: q.Build}
|
||||
|
||||
if err := setPTAScope(&lconf, q.Scope); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load/parse/type-check the program.
|
||||
lprog, err := lconf.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.Fset = lprog.Fset
|
||||
|
||||
qpos, err := parseQueryPos(lprog, q.Pos, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug)
|
||||
|
||||
ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
opPos := findOp(qpos)
|
||||
if opPos == token.NoPos {
|
||||
return fmt.Errorf("there is no channel operation here")
|
||||
}
|
||||
|
||||
// Defer SSA construction till after errors are reported.
|
||||
prog.Build()
|
||||
|
||||
var queryOp chanOp // the originating send or receive operation
|
||||
var ops []chanOp // all sends/receives of opposite direction
|
||||
|
||||
// Look at all channel operations in the whole ssa.Program.
|
||||
// Build a list of those of same type as the query.
|
||||
allFuncs := ssautil.AllFunctions(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 == opPos {
|
||||
queryOp = op // we found the query op
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if queryOp.ch == nil {
|
||||
return fmt.Errorf("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.
|
||||
// We compare channels by element types, not channel types, to
|
||||
// ignore both directionality and type names.
|
||||
queryType := queryOp.ch.Type()
|
||||
queryElemType := queryType.Underlying().(*types.Chan).Elem()
|
||||
ptaConfig.AddQuery(queryOp.ch)
|
||||
i := 0
|
||||
for _, op := range ops {
|
||||
if types.Identical(op.ch.Type().Underlying().(*types.Chan).Elem(), queryElemType) {
|
||||
ptaConfig.AddQuery(op.ch)
|
||||
ops[i] = op
|
||||
i++
|
||||
}
|
||||
}
|
||||
ops = ops[:i]
|
||||
|
||||
// Run the pointer analysis.
|
||||
ptares := ptrAnalysis(ptaConfig)
|
||||
|
||||
// Find the points-to set.
|
||||
queryChanPtr := ptares.Queries[queryOp.ch]
|
||||
|
||||
// Ascertain which make(chan) labels the query's channel can alias.
|
||||
var makes []token.Pos
|
||||
for _, label := range queryChanPtr.PointsTo().Labels() {
|
||||
makes = append(makes, label.Pos())
|
||||
}
|
||||
sort.Sort(byPos(makes))
|
||||
|
||||
// Ascertain which channel operations can alias the same make(chan) labels.
|
||||
var sends, receives, closes []token.Pos
|
||||
for _, op := range ops {
|
||||
if ptr, ok := ptares.Queries[op.ch]; ok && ptr.MayAlias(queryChanPtr) {
|
||||
switch op.dir {
|
||||
case types.SendOnly:
|
||||
sends = append(sends, op.pos)
|
||||
case types.RecvOnly:
|
||||
receives = append(receives, op.pos)
|
||||
case types.SendRecv:
|
||||
closes = append(closes, op.pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Sort(byPos(sends))
|
||||
sort.Sort(byPos(receives))
|
||||
sort.Sort(byPos(closes))
|
||||
|
||||
q.result = &peersResult{
|
||||
queryPos: opPos,
|
||||
queryType: queryType,
|
||||
makes: makes,
|
||||
sends: sends,
|
||||
receives: receives,
|
||||
closes: closes,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// findOp returns the position of the enclosing send/receive/close op.
|
||||
// For send and receive operations, this is the position of the <- token;
|
||||
// for close operations, it's the Lparen of the function call.
|
||||
//
|
||||
// TODO(adonovan): handle implicit receive operations from 'for...range chan' statements.
|
||||
func findOp(qpos *queryPos) token.Pos {
|
||||
for _, n := range qpos.path {
|
||||
switch n := n.(type) {
|
||||
case *ast.UnaryExpr:
|
||||
if n.Op == token.ARROW {
|
||||
return n.OpPos
|
||||
}
|
||||
case *ast.SendStmt:
|
||||
return n.Arrow
|
||||
case *ast.CallExpr:
|
||||
// close function call can only exist as a direct identifier
|
||||
if close, ok := unparen(n.Fun).(*ast.Ident); ok {
|
||||
if b, ok := qpos.info.Info.Uses[close].(*types.Builtin); ok && b.Name() == "close" {
|
||||
return n.Lparen
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return token.NoPos
|
||||
}
|
||||
|
||||
// chanOp abstracts an ssa.Send, ssa.Unop(ARROW), or a SelectState.
|
||||
type chanOp struct {
|
||||
ch ssa.Value
|
||||
dir types.ChanDir // SendOnly=send, RecvOnly=recv, SendRecv=close
|
||||
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,Close} too.
|
||||
var ops []chanOp
|
||||
switch instr := instr.(type) {
|
||||
case *ssa.UnOp:
|
||||
if instr.Op == token.ARROW {
|
||||
ops = append(ops, chanOp{instr.X, types.RecvOnly, instr.Pos()})
|
||||
}
|
||||
case *ssa.Send:
|
||||
ops = append(ops, chanOp{instr.Chan, types.SendOnly, instr.Pos()})
|
||||
case *ssa.Select:
|
||||
for _, st := range instr.States {
|
||||
ops = append(ops, chanOp{st.Chan, st.Dir, st.Pos})
|
||||
}
|
||||
case ssa.CallInstruction:
|
||||
cc := instr.Common()
|
||||
if b, ok := cc.Value.(*ssa.Builtin); ok && b.Name() == "close" {
|
||||
ops = append(ops, chanOp{cc.Args[0], types.SendRecv, cc.Pos()})
|
||||
}
|
||||
}
|
||||
return ops
|
||||
}
|
||||
|
||||
type peersResult struct {
|
||||
queryPos token.Pos // of queried channel op
|
||||
queryType types.Type // type of queried channel
|
||||
makes, sends, receives, closes []token.Pos // positions of aliased makechan/send/receive/close instrs
|
||||
}
|
||||
|
||||
func (r *peersResult) display(printf printfFunc) {
|
||||
if len(r.makes) == 0 {
|
||||
printf(r.queryPos, "This channel can't point to anything.")
|
||||
return
|
||||
}
|
||||
printf(r.queryPos, "This channel of type %s may be:", r.queryType)
|
||||
for _, alloc := range r.makes {
|
||||
printf(alloc, "\tallocated here")
|
||||
}
|
||||
for _, send := range r.sends {
|
||||
printf(send, "\tsent to, here")
|
||||
}
|
||||
for _, receive := range r.receives {
|
||||
printf(receive, "\treceived from, here")
|
||||
}
|
||||
for _, clos := range r.closes {
|
||||
printf(clos, "\tclosed, here")
|
||||
}
|
||||
}
|
||||
|
||||
func (r *peersResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
||||
peers := &serial.Peers{
|
||||
Pos: fset.Position(r.queryPos).String(),
|
||||
Type: r.queryType.String(),
|
||||
}
|
||||
for _, alloc := range r.makes {
|
||||
peers.Allocs = append(peers.Allocs, fset.Position(alloc).String())
|
||||
}
|
||||
for _, send := range r.sends {
|
||||
peers.Sends = append(peers.Sends, fset.Position(send).String())
|
||||
}
|
||||
for _, receive := range r.receives {
|
||||
peers.Receives = append(peers.Receives, fset.Position(receive).String())
|
||||
}
|
||||
for _, clos := range r.closes {
|
||||
peers.Closes = append(peers.Closes, fset.Position(clos).String())
|
||||
}
|
||||
res.Peers = peers
|
||||
}
|
||||
|
||||
// -------- utils --------
|
||||
|
||||
// NB: byPos is not deterministic across packages since it depends on load order.
|
||||
// Use lessPos if the tests need it.
|
||||
type byPos []token.Pos
|
||||
|
||||
func (p byPos) Len() int { return len(p) }
|
||||
func (p byPos) Less(i, j int) bool { return p[i] < p[j] }
|
||||
func (p byPos) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||
|
|
@ -1,293 +0,0 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package oracle
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"sort"
|
||||
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/pointer"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/ssa/ssautil"
|
||||
"golang.org/x/tools/oracle/serial"
|
||||
)
|
||||
|
||||
// pointsto runs the pointer analysis on the selected expression,
|
||||
// and reports its points-to set (for a pointer-like expression)
|
||||
// or its dynamic types (for an interface, reflect.Value, or
|
||||
// reflect.Type expression) and their points-to sets.
|
||||
//
|
||||
// All printed sets are sorted to ensure determinism.
|
||||
//
|
||||
func pointsto(q *Query) error {
|
||||
lconf := loader.Config{Build: q.Build}
|
||||
|
||||
if err := setPTAScope(&lconf, q.Scope); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load/parse/type-check the program.
|
||||
lprog, err := lconf.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.Fset = lprog.Fset
|
||||
|
||||
qpos, err := parseQueryPos(lprog, q.Pos, true) // needs exact pos
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug)
|
||||
|
||||
ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
path, action := findInterestingNode(qpos.info, qpos.path)
|
||||
if action != actionExpr {
|
||||
return fmt.Errorf("pointer analysis wants an expression; got %s",
|
||||
astutil.NodeDescription(qpos.path[0]))
|
||||
}
|
||||
|
||||
var expr ast.Expr
|
||||
var obj types.Object
|
||||
switch n := path[0].(type) {
|
||||
case *ast.ValueSpec:
|
||||
// ambiguous ValueSpec containing multiple names
|
||||
return fmt.Errorf("multiple value specification")
|
||||
case *ast.Ident:
|
||||
obj = qpos.info.ObjectOf(n)
|
||||
expr = n
|
||||
case ast.Expr:
|
||||
expr = n
|
||||
default:
|
||||
// TODO(adonovan): is this reachable?
|
||||
return fmt.Errorf("unexpected AST for expr: %T", n)
|
||||
}
|
||||
|
||||
// Reject non-pointerlike types (includes all constants---except nil).
|
||||
// TODO(adonovan): reject nil too.
|
||||
typ := qpos.info.TypeOf(expr)
|
||||
if !pointer.CanPoint(typ) {
|
||||
return fmt.Errorf("pointer analysis wants an expression of reference type; got %s", typ)
|
||||
}
|
||||
|
||||
// Determine the ssa.Value for the expression.
|
||||
var value ssa.Value
|
||||
var isAddr bool
|
||||
if obj != nil {
|
||||
// def/ref of func/var object
|
||||
value, isAddr, err = ssaValueForIdent(prog, qpos.info, obj, path)
|
||||
} else {
|
||||
value, isAddr, err = ssaValueForExpr(prog, qpos.info, path)
|
||||
}
|
||||
if err != nil {
|
||||
return err // e.g. trivially dead code
|
||||
}
|
||||
|
||||
// Defer SSA construction till after errors are reported.
|
||||
prog.Build()
|
||||
|
||||
// Run the pointer analysis.
|
||||
ptrs, err := runPTA(ptaConfig, value, isAddr)
|
||||
if err != nil {
|
||||
return err // e.g. analytically unreachable
|
||||
}
|
||||
|
||||
q.result = &pointstoResult{
|
||||
qpos: qpos,
|
||||
typ: typ,
|
||||
ptrs: ptrs,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ssaValueForIdent returns the ssa.Value for the ast.Ident whose path
|
||||
// to the root of the AST is path. isAddr reports whether the
|
||||
// ssa.Value is the address denoted by the ast.Ident, not its value.
|
||||
//
|
||||
func ssaValueForIdent(prog *ssa.Program, qinfo *loader.PackageInfo, obj types.Object, path []ast.Node) (value ssa.Value, isAddr bool, err error) {
|
||||
switch obj := obj.(type) {
|
||||
case *types.Var:
|
||||
pkg := prog.Package(qinfo.Pkg)
|
||||
pkg.Build()
|
||||
if v, addr := prog.VarValue(obj, pkg, path); v != nil {
|
||||
return v, addr, nil
|
||||
}
|
||||
return nil, false, fmt.Errorf("can't locate SSA Value for var %s", obj.Name())
|
||||
|
||||
case *types.Func:
|
||||
fn := prog.FuncValue(obj)
|
||||
if fn == nil {
|
||||
return nil, false, fmt.Errorf("%s is an interface method", obj)
|
||||
}
|
||||
// TODO(adonovan): there's no point running PTA on a *Func ident.
|
||||
// Eliminate this feature.
|
||||
return fn, false, nil
|
||||
}
|
||||
panic(obj)
|
||||
}
|
||||
|
||||
// ssaValueForExpr returns the ssa.Value of the non-ast.Ident
|
||||
// expression whose path to the root of the AST is path.
|
||||
//
|
||||
func ssaValueForExpr(prog *ssa.Program, qinfo *loader.PackageInfo, path []ast.Node) (value ssa.Value, isAddr bool, err error) {
|
||||
pkg := prog.Package(qinfo.Pkg)
|
||||
pkg.SetDebugMode(true)
|
||||
pkg.Build()
|
||||
|
||||
fn := ssa.EnclosingFunction(pkg, path)
|
||||
if fn == nil {
|
||||
return nil, false, fmt.Errorf("no SSA function built for this location (dead code?)")
|
||||
}
|
||||
|
||||
if v, addr := fn.ValueForExpr(path[0].(ast.Expr)); v != nil {
|
||||
return v, addr, nil
|
||||
}
|
||||
|
||||
return nil, false, fmt.Errorf("can't locate SSA Value for expression in %s", fn)
|
||||
}
|
||||
|
||||
// runPTA runs the pointer analysis of the selected SSA value or address.
|
||||
func runPTA(conf *pointer.Config, v ssa.Value, isAddr bool) (ptrs []pointerResult, err error) {
|
||||
T := v.Type()
|
||||
if isAddr {
|
||||
conf.AddIndirectQuery(v)
|
||||
T = deref(T)
|
||||
} else {
|
||||
conf.AddQuery(v)
|
||||
}
|
||||
ptares := ptrAnalysis(conf)
|
||||
|
||||
var ptr pointer.Pointer
|
||||
if isAddr {
|
||||
ptr = ptares.IndirectQueries[v]
|
||||
} else {
|
||||
ptr = ptares.Queries[v]
|
||||
}
|
||||
if ptr == (pointer.Pointer{}) {
|
||||
return nil, fmt.Errorf("pointer analysis did not find expression (dead code?)")
|
||||
}
|
||||
pts := ptr.PointsTo()
|
||||
|
||||
if pointer.CanHaveDynamicTypes(T) {
|
||||
// Show concrete types for interface/reflect.Value expression.
|
||||
if concs := pts.DynamicTypes(); concs.Len() > 0 {
|
||||
concs.Iterate(func(conc types.Type, pta interface{}) {
|
||||
labels := pta.(pointer.PointsToSet).Labels()
|
||||
sort.Sort(byPosAndString(labels)) // to ensure determinism
|
||||
ptrs = append(ptrs, pointerResult{conc, labels})
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// Show labels for other expressions.
|
||||
labels := pts.Labels()
|
||||
sort.Sort(byPosAndString(labels)) // to ensure determinism
|
||||
ptrs = append(ptrs, pointerResult{T, labels})
|
||||
}
|
||||
sort.Sort(byTypeString(ptrs)) // to ensure determinism
|
||||
return ptrs, nil
|
||||
}
|
||||
|
||||
type pointerResult struct {
|
||||
typ types.Type // type of the pointer (always concrete)
|
||||
labels []*pointer.Label // set of labels
|
||||
}
|
||||
|
||||
type pointstoResult struct {
|
||||
qpos *queryPos
|
||||
typ types.Type // type of expression
|
||||
ptrs []pointerResult // pointer info (typ is concrete => len==1)
|
||||
}
|
||||
|
||||
func (r *pointstoResult) display(printf printfFunc) {
|
||||
if pointer.CanHaveDynamicTypes(r.typ) {
|
||||
// Show concrete types for interface, reflect.Type or
|
||||
// reflect.Value expression.
|
||||
|
||||
if len(r.ptrs) > 0 {
|
||||
printf(r.qpos, "this %s may contain these dynamic types:", r.qpos.typeString(r.typ))
|
||||
for _, ptr := range r.ptrs {
|
||||
var obj types.Object
|
||||
if nt, ok := deref(ptr.typ).(*types.Named); ok {
|
||||
obj = nt.Obj()
|
||||
}
|
||||
if len(ptr.labels) > 0 {
|
||||
printf(obj, "\t%s, may point to:", r.qpos.typeString(ptr.typ))
|
||||
printLabels(printf, ptr.labels, "\t\t")
|
||||
} else {
|
||||
printf(obj, "\t%s", r.qpos.typeString(ptr.typ))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
printf(r.qpos, "this %s cannot contain any dynamic types.", r.typ)
|
||||
}
|
||||
} else {
|
||||
// Show labels for other expressions.
|
||||
if ptr := r.ptrs[0]; len(ptr.labels) > 0 {
|
||||
printf(r.qpos, "this %s may point to these objects:",
|
||||
r.qpos.typeString(r.typ))
|
||||
printLabels(printf, ptr.labels, "\t")
|
||||
} else {
|
||||
printf(r.qpos, "this %s may not point to anything.",
|
||||
r.qpos.typeString(r.typ))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *pointstoResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
||||
var pts []serial.PointsTo
|
||||
for _, ptr := range r.ptrs {
|
||||
var namePos string
|
||||
if nt, ok := deref(ptr.typ).(*types.Named); ok {
|
||||
namePos = fset.Position(nt.Obj().Pos()).String()
|
||||
}
|
||||
var labels []serial.PointsToLabel
|
||||
for _, l := range ptr.labels {
|
||||
labels = append(labels, serial.PointsToLabel{
|
||||
Pos: fset.Position(l.Pos()).String(),
|
||||
Desc: l.String(),
|
||||
})
|
||||
}
|
||||
pts = append(pts, serial.PointsTo{
|
||||
Type: r.qpos.typeString(ptr.typ),
|
||||
NamePos: namePos,
|
||||
Labels: labels,
|
||||
})
|
||||
}
|
||||
res.PointsTo = pts
|
||||
}
|
||||
|
||||
type byTypeString []pointerResult
|
||||
|
||||
func (a byTypeString) Len() int { return len(a) }
|
||||
func (a byTypeString) Less(i, j int) bool { return a[i].typ.String() < a[j].typ.String() }
|
||||
func (a byTypeString) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
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(printf printfFunc, labels []*pointer.Label, prefix string) {
|
||||
// TODO(adonovan): due to context-sensitivity, many of these
|
||||
// labels may differ only by context, which isn't apparent.
|
||||
for _, label := range labels {
|
||||
printf(label, "%s%s", prefix, label)
|
||||
}
|
||||
}
|
||||
143
oracle/pos.go
143
oracle/pos.go
|
|
@ -1,143 +0,0 @@
|
|||
package oracle
|
||||
|
||||
// This file defines utilities for working with file positions.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
)
|
||||
|
||||
// parseOctothorpDecimal returns the numeric value if s matches "#%d",
|
||||
// otherwise -1.
|
||||
func parseOctothorpDecimal(s string) int {
|
||||
if s != "" && s[0] == '#' {
|
||||
if s, err := strconv.ParseInt(s[1:], 10, 32); err == nil {
|
||||
return int(s)
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// parsePosFlag parses a string of the form "file:pos" or
|
||||
// file:start,end" where pos, start, end match #%d and represent byte
|
||||
// offsets, and returns its components.
|
||||
//
|
||||
// (Numbers without a '#' prefix are reserved for future use,
|
||||
// e.g. to indicate line/column positions.)
|
||||
//
|
||||
func parsePosFlag(posFlag string) (filename string, startOffset, endOffset int, err error) {
|
||||
if posFlag == "" {
|
||||
err = fmt.Errorf("no source position specified (-pos flag)")
|
||||
return
|
||||
}
|
||||
|
||||
colon := strings.LastIndex(posFlag, ":")
|
||||
if colon < 0 {
|
||||
err = fmt.Errorf("invalid source position -pos=%q", posFlag)
|
||||
return
|
||||
}
|
||||
filename, offset := posFlag[:colon], posFlag[colon+1:]
|
||||
startOffset = -1
|
||||
endOffset = -1
|
||||
if hyphen := strings.Index(offset, ","); hyphen < 0 {
|
||||
// e.g. "foo.go:#123"
|
||||
startOffset = parseOctothorpDecimal(offset)
|
||||
endOffset = startOffset
|
||||
} else {
|
||||
// e.g. "foo.go:#123,#456"
|
||||
startOffset = parseOctothorpDecimal(offset[:hyphen])
|
||||
endOffset = parseOctothorpDecimal(offset[hyphen+1:])
|
||||
}
|
||||
if startOffset < 0 || endOffset < 0 {
|
||||
err = fmt.Errorf("invalid -pos offset %q", offset)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// findQueryPos searches fset for filename and translates the
|
||||
// specified file-relative byte offsets into token.Pos form. It
|
||||
// returns an error if the file was not found or the offsets were out
|
||||
// of bounds.
|
||||
//
|
||||
func findQueryPos(fset *token.FileSet, filename string, startOffset, endOffset int) (start, end token.Pos, err error) {
|
||||
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")
|
||||
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")
|
||||
return
|
||||
}
|
||||
|
||||
if 0 <= endOffset && endOffset <= file.Size() {
|
||||
end = file.Pos(int(endOffset))
|
||||
} else {
|
||||
err = fmt.Errorf("end position is beyond end of file")
|
||||
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
|
||||
}
|
||||
|
||||
// fastQueryPos parses the -pos flag and returns a QueryPos.
|
||||
// It parses only a single file, and does not run the type checker.
|
||||
func fastQueryPos(posFlag string) (*queryPos, error) {
|
||||
filename, startOffset, endOffset, err := parsePosFlag(posFlag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fset := token.NewFileSet()
|
||||
f, err := parser.ParseFile(fset, filename, nil, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
start, end, err := findQueryPos(fset, filename, startOffset, endOffset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
path, exact := astutil.PathEnclosingInterval(f, start, end)
|
||||
if path == nil {
|
||||
return nil, fmt.Errorf("no syntax here")
|
||||
}
|
||||
|
||||
return &queryPos{fset, start, end, path, exact, nil}, nil
|
||||
}
|
||||
|
|
@ -1,243 +0,0 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package oracle
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"io/ioutil"
|
||||
"sort"
|
||||
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/oracle/serial"
|
||||
"golang.org/x/tools/refactor/importgraph"
|
||||
)
|
||||
|
||||
// Referrers reports all identifiers that resolve to the same object
|
||||
// as the queried identifier, within any package in the analysis scope.
|
||||
func referrers(q *Query) error {
|
||||
lconf := loader.Config{Build: q.Build}
|
||||
allowErrors(&lconf)
|
||||
|
||||
if _, err := importQueryPackage(q.Pos, &lconf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var id *ast.Ident
|
||||
var obj types.Object
|
||||
var lprog *loader.Program
|
||||
var pass2 bool
|
||||
var qpos *queryPos
|
||||
for {
|
||||
// Load/parse/type-check the program.
|
||||
var err error
|
||||
lprog, err = lconf.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.Fset = lprog.Fset
|
||||
|
||||
qpos, err = parseQueryPos(lprog, q.Pos, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
id, _ = qpos.path[0].(*ast.Ident)
|
||||
if id == nil {
|
||||
return fmt.Errorf("no identifier here")
|
||||
}
|
||||
|
||||
obj = qpos.info.ObjectOf(id)
|
||||
if obj == nil {
|
||||
// Happens for y in "switch y := x.(type)",
|
||||
// the package declaration,
|
||||
// and unresolved identifiers.
|
||||
if _, ok := qpos.path[1].(*ast.File); ok { // package decl?
|
||||
pkg := qpos.info.Pkg
|
||||
obj = types.NewPkgName(id.Pos(), pkg, pkg.Name(), pkg)
|
||||
} else {
|
||||
return fmt.Errorf("no object for identifier: %T", qpos.path[1])
|
||||
}
|
||||
}
|
||||
|
||||
if pass2 {
|
||||
break
|
||||
}
|
||||
|
||||
// If the identifier is exported, we must load all packages that
|
||||
// depend transitively upon the package that defines it.
|
||||
// Treat PkgNames as exported, even though they're lowercase.
|
||||
if _, isPkg := obj.(*types.PkgName); !(isPkg || obj.Exported()) {
|
||||
break // not exported
|
||||
}
|
||||
|
||||
// Scan the workspace and build the import graph.
|
||||
// Ignore broken packages.
|
||||
_, rev, _ := importgraph.Build(q.Build)
|
||||
|
||||
// Re-load the larger program.
|
||||
// Create a new file set so that ...
|
||||
// External test packages are never imported,
|
||||
// so they will never appear in the graph.
|
||||
// (We must reset the Config here, not just reset the Fset field.)
|
||||
lconf = loader.Config{
|
||||
Fset: token.NewFileSet(),
|
||||
Build: q.Build,
|
||||
}
|
||||
allowErrors(&lconf)
|
||||
for path := range rev.Search(obj.Pkg().Path()) {
|
||||
lconf.ImportWithTests(path)
|
||||
}
|
||||
pass2 = true
|
||||
}
|
||||
|
||||
// Iterate over all go/types' Uses facts for the entire program.
|
||||
var refs []*ast.Ident
|
||||
for _, info := range lprog.AllPackages {
|
||||
for id2, obj2 := range info.Uses {
|
||||
if sameObj(obj, obj2) {
|
||||
refs = append(refs, id2)
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Sort(byNamePos{q.Fset, refs})
|
||||
|
||||
q.result = &referrersResult{
|
||||
qpos: qpos,
|
||||
query: id,
|
||||
obj: obj,
|
||||
refs: refs,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// same reports whether x and y are identical, or both are PkgNames
|
||||
// that import the same Package.
|
||||
//
|
||||
func sameObj(x, y types.Object) bool {
|
||||
if x == y {
|
||||
return true
|
||||
}
|
||||
if x, ok := x.(*types.PkgName); ok {
|
||||
if y, ok := y.(*types.PkgName); ok {
|
||||
return x.Imported() == y.Imported()
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// -------- utils --------
|
||||
|
||||
// An deterministic ordering for token.Pos that doesn't
|
||||
// depend on the order in which packages were loaded.
|
||||
func lessPos(fset *token.FileSet, x, y token.Pos) bool {
|
||||
fx := fset.File(x)
|
||||
fy := fset.File(y)
|
||||
if fx != fy {
|
||||
return fx.Name() < fy.Name()
|
||||
}
|
||||
return x < y
|
||||
}
|
||||
|
||||
type byNamePos struct {
|
||||
fset *token.FileSet
|
||||
ids []*ast.Ident
|
||||
}
|
||||
|
||||
func (p byNamePos) Len() int { return len(p.ids) }
|
||||
func (p byNamePos) Swap(i, j int) { p.ids[i], p.ids[j] = p.ids[j], p.ids[i] }
|
||||
func (p byNamePos) Less(i, j int) bool {
|
||||
return lessPos(p.fset, p.ids[i].NamePos, p.ids[j].NamePos)
|
||||
}
|
||||
|
||||
type referrersResult struct {
|
||||
qpos *queryPos
|
||||
query *ast.Ident // identifier of query
|
||||
obj types.Object // object it denotes
|
||||
refs []*ast.Ident // set of all other references to it
|
||||
}
|
||||
|
||||
func (r *referrersResult) display(printf printfFunc) {
|
||||
printf(r.obj, "%d references to %s", len(r.refs), r.qpos.objectString(r.obj))
|
||||
|
||||
// Show referring lines, like grep.
|
||||
type fileinfo struct {
|
||||
refs []*ast.Ident
|
||||
linenums []int // line number of refs[i]
|
||||
data chan interface{} // file contents or error
|
||||
}
|
||||
var fileinfos []*fileinfo
|
||||
fileinfosByName := make(map[string]*fileinfo)
|
||||
|
||||
// First pass: start the file reads concurrently.
|
||||
sema := make(chan struct{}, 20) // counting semaphore to limit I/O concurrency
|
||||
for _, ref := range r.refs {
|
||||
posn := r.qpos.fset.Position(ref.Pos())
|
||||
fi := fileinfosByName[posn.Filename]
|
||||
if fi == nil {
|
||||
fi = &fileinfo{data: make(chan interface{})}
|
||||
fileinfosByName[posn.Filename] = fi
|
||||
fileinfos = append(fileinfos, fi)
|
||||
|
||||
// First request for this file:
|
||||
// start asynchronous read.
|
||||
go func() {
|
||||
sema <- struct{}{} // acquire token
|
||||
content, err := ioutil.ReadFile(posn.Filename)
|
||||
<-sema // release token
|
||||
if err != nil {
|
||||
fi.data <- err
|
||||
} else {
|
||||
fi.data <- content
|
||||
}
|
||||
}()
|
||||
}
|
||||
fi.refs = append(fi.refs, ref)
|
||||
fi.linenums = append(fi.linenums, posn.Line)
|
||||
}
|
||||
|
||||
// Second pass: print refs in original order.
|
||||
// One line may have several refs at different columns.
|
||||
for _, fi := range fileinfos {
|
||||
v := <-fi.data // wait for I/O completion
|
||||
|
||||
// Print one item for all refs in a file that could not
|
||||
// be loaded (perhaps due to //line directives).
|
||||
if err, ok := v.(error); ok {
|
||||
var suffix string
|
||||
if more := len(fi.refs) - 1; more > 0 {
|
||||
suffix = fmt.Sprintf(" (+ %d more refs in this file)", more)
|
||||
}
|
||||
printf(fi.refs[0], "%v%s", err, suffix)
|
||||
continue
|
||||
}
|
||||
|
||||
lines := bytes.Split(v.([]byte), []byte("\n"))
|
||||
for i, ref := range fi.refs {
|
||||
printf(ref, "%s", lines[fi.linenums[i]-1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(adonovan): encode extent, not just Pos info, in Serial form.
|
||||
|
||||
func (r *referrersResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
||||
referrers := &serial.Referrers{
|
||||
Pos: fset.Position(r.query.Pos()).String(),
|
||||
Desc: r.obj.String(),
|
||||
}
|
||||
if pos := r.obj.Pos(); pos != token.NoPos { // Package objects have no Pos()
|
||||
referrers.ObjPos = fset.Position(pos).String()
|
||||
}
|
||||
for _, ref := range r.refs {
|
||||
referrers.Refs = append(referrers.Refs, fset.Position(ref.NamePos).String())
|
||||
}
|
||||
res.Referrers = referrers
|
||||
}
|
||||
|
|
@ -1,258 +0,0 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package serial defines the oracle's schema for structured data
|
||||
// serialization using JSON, XML, etc.
|
||||
package serial
|
||||
|
||||
// All 'pos' strings are of the form "file:line:col".
|
||||
// TODO(adonovan): improve performance by sharing filename strings.
|
||||
// TODO(adonovan): improve precision by providing the start/end
|
||||
// interval when available.
|
||||
//
|
||||
// TODO(adonovan): consider richer encodings of types, functions,
|
||||
// methods, etc.
|
||||
|
||||
// A Peers is the result of a 'peers' query.
|
||||
// If Allocs is empty, the selected channel can't point to anything.
|
||||
type Peers struct {
|
||||
Pos string `json:"pos"` // location of the selected channel op (<-)
|
||||
Type string `json:"type"` // type of the selected channel
|
||||
Allocs []string `json:"allocs,omitempty"` // locations of aliased make(chan) ops
|
||||
Sends []string `json:"sends,omitempty"` // locations of aliased ch<-x ops
|
||||
Receives []string `json:"receives,omitempty"` // locations of aliased <-ch ops
|
||||
Closes []string `json:"closes,omitempty"` // locations of aliased close(ch) ops
|
||||
}
|
||||
|
||||
// A Referrers is the result of a 'referrers' query.
|
||||
type Referrers struct {
|
||||
Pos string `json:"pos"` // location of the query reference
|
||||
ObjPos string `json:"objpos,omitempty"` // location of the definition
|
||||
Desc string `json:"desc"` // description of the denoted object
|
||||
Refs []string `json:"refs,omitempty"` // locations of all references
|
||||
}
|
||||
|
||||
// A Definition is the result of a 'definition' query.
|
||||
type Definition struct {
|
||||
ObjPos string `json:"objpos,omitempty"` // location of the definition
|
||||
Desc string `json:"desc"` // description of the denoted object
|
||||
}
|
||||
|
||||
type CalleesItem struct {
|
||||
Name string `json:"name"` // full name of called function
|
||||
Pos string `json:"pos"` // location of called function
|
||||
}
|
||||
|
||||
// A Callees is the result of a 'callees' query.
|
||||
//
|
||||
// Callees is nonempty unless the call was a dynamic call on a
|
||||
// provably nil func or interface value.
|
||||
type Callees struct {
|
||||
Pos string `json:"pos"` // location of selected call site
|
||||
Desc string `json:"desc"` // description of call site
|
||||
Callees []*CalleesItem `json:"callees,omitempty"` // set of possible call targets
|
||||
}
|
||||
|
||||
// A Caller is one element of the slice returned by a 'callers' query.
|
||||
// (Callstack also contains a similar slice.)
|
||||
//
|
||||
// The root of the callgraph has an unspecified "Caller" string.
|
||||
type Caller struct {
|
||||
Pos string `json:"pos,omitempty"` // location of the calling function
|
||||
Desc string `json:"desc"` // description of call site
|
||||
Caller string `json:"caller"` // full name of calling function
|
||||
}
|
||||
|
||||
// A CallStack is the result of a 'callstack' query.
|
||||
// It indicates an arbitrary path from the root of the callgraph to
|
||||
// the query function.
|
||||
//
|
||||
// If the Callers slice is empty, the function was unreachable in this
|
||||
// analysis scope.
|
||||
type CallStack struct {
|
||||
Pos string `json:"pos"` // location of the selected function
|
||||
Target string `json:"target"` // the selected function
|
||||
Callers []Caller `json:"callers"` // enclosing calls, innermost first.
|
||||
}
|
||||
|
||||
// A FreeVar is one element of the slice returned by a 'freevars'
|
||||
// query. Each one identifies an expression referencing a local
|
||||
// identifier defined outside the selected region.
|
||||
type FreeVar struct {
|
||||
Pos string `json:"pos"` // location of the identifier's definition
|
||||
Kind string `json:"kind"` // one of {var,func,type,const,label}
|
||||
Ref string `json:"ref"` // referring expression (e.g. "x" or "x.y.z")
|
||||
Type string `json:"type"` // type of the expression
|
||||
}
|
||||
|
||||
// An Implements contains the result of an 'implements' query.
|
||||
// It describes the queried type, the set of named non-empty interface
|
||||
// types to which it is assignable, and the set of named/*named types
|
||||
// (concrete or non-empty interface) which may be assigned to it.
|
||||
//
|
||||
type Implements struct {
|
||||
T ImplementsType `json:"type,omitempty"` // the queried type
|
||||
AssignableTo []ImplementsType `json:"to,omitempty"` // types assignable to T
|
||||
AssignableFrom []ImplementsType `json:"from,omitempty"` // interface types assignable from T
|
||||
AssignableFromPtr []ImplementsType `json:"fromptr,omitempty"` // interface types assignable only from *T
|
||||
|
||||
// The following fields are set only if the query was a method.
|
||||
// Assignable{To,From,FromPtr}Method[i] is the corresponding
|
||||
// method of type Assignable{To,From,FromPtr}[i], or blank
|
||||
// {"",""} if that type lacks the method.
|
||||
Method *DescribeMethod `json:"method,omitempty"` // the queried method
|
||||
AssignableToMethod []DescribeMethod `json:"to_method,omitempty"`
|
||||
AssignableFromMethod []DescribeMethod `json:"from_method,omitempty"`
|
||||
AssignableFromPtrMethod []DescribeMethod `json:"fromptr_method,omitempty"`
|
||||
}
|
||||
|
||||
// An ImplementsType describes a single type as part of an 'implements' query.
|
||||
type ImplementsType struct {
|
||||
Name string `json:"name"` // full name of the type
|
||||
Pos string `json:"pos"` // location of its definition
|
||||
Kind string `json:"kind"` // "basic", "array", etc
|
||||
}
|
||||
|
||||
// A SyntaxNode is one element of a stack of enclosing syntax nodes in
|
||||
// a "what" query.
|
||||
type SyntaxNode struct {
|
||||
Description string `json:"desc"` // description of syntax tree
|
||||
Start int `json:"start"` // start byte offset, 0-based
|
||||
End int `json:"end"` // end byte offset
|
||||
}
|
||||
|
||||
// A What is the result of the "what" query, which quickly identifies
|
||||
// the selection, parsing only a single file. It is intended for use
|
||||
// in low-latency GUIs.
|
||||
type What struct {
|
||||
Enclosing []SyntaxNode `json:"enclosing"` // enclosing nodes of syntax tree
|
||||
Modes []string `json:"modes"` // query modes enabled for this selection.
|
||||
SrcDir string `json:"srcdir,omitempty"` // $GOROOT src directory containing queried package
|
||||
ImportPath string `json:"importpath,omitempty"` // import path of queried package
|
||||
}
|
||||
|
||||
// A PointsToLabel describes a pointer analysis label.
|
||||
//
|
||||
// A "label" is an object that may be pointed to by a pointer, map,
|
||||
// channel, 'func', slice or interface. Labels include:
|
||||
// - functions
|
||||
// - globals
|
||||
// - arrays created by literals (e.g. []byte("foo")) and conversions ([]byte(s))
|
||||
// - stack- and heap-allocated variables (including composite literals)
|
||||
// - arrays allocated by append()
|
||||
// - channels, maps and arrays created by make()
|
||||
// - and their subelements, e.g. "alloc.y[*].z"
|
||||
//
|
||||
type PointsToLabel struct {
|
||||
Pos string `json:"pos"` // location of syntax that allocated the object
|
||||
Desc string `json:"desc"` // description of the label
|
||||
}
|
||||
|
||||
// A PointsTo is one element of the result of a 'pointsto' query on an
|
||||
// expression. It describes a single pointer: its type and the set of
|
||||
// "labels" it points to.
|
||||
//
|
||||
// If the pointer is of interface type, it will have one PTS entry
|
||||
// describing each concrete type that it may contain. For each
|
||||
// concrete type that is a pointer, the PTS entry describes the labels
|
||||
// it may point to. The same is true for reflect.Values, except the
|
||||
// dynamic types needn't be concrete.
|
||||
//
|
||||
type PointsTo struct {
|
||||
Type string `json:"type"` // (concrete) type of the pointer
|
||||
NamePos string `json:"namepos,omitempty"` // location of type defn, if Named
|
||||
Labels []PointsToLabel `json:"labels,omitempty"` // pointed-to objects
|
||||
}
|
||||
|
||||
// A DescribeValue is the additional result of a 'describe' query
|
||||
// if the selection indicates a value or expression.
|
||||
type DescribeValue struct {
|
||||
Type string `json:"type"` // type of the expression
|
||||
Value string `json:"value,omitempty"` // value of the expression, if constant
|
||||
ObjPos string `json:"objpos,omitempty"` // location of the definition, if an Ident
|
||||
}
|
||||
|
||||
type DescribeMethod struct {
|
||||
Name string `json:"name"` // method name, as defined by types.Selection.String()
|
||||
Pos string `json:"pos"` // location of the method's definition
|
||||
}
|
||||
|
||||
// A DescribeType is the additional result of a 'describe' query
|
||||
// if the selection indicates a type.
|
||||
type DescribeType struct {
|
||||
Type string `json:"type"` // the string form of the type
|
||||
NamePos string `json:"namepos,omitempty"` // location of definition of type, if named
|
||||
NameDef string `json:"namedef,omitempty"` // underlying definition of type, if named
|
||||
Methods []DescribeMethod `json:"methods,omitempty"` // methods of the type
|
||||
}
|
||||
|
||||
type DescribeMember struct {
|
||||
Name string `json:"name"` // name of member
|
||||
Type string `json:"type,omitempty"` // type of member (underlying, if 'type')
|
||||
Value string `json:"value,omitempty"` // value of member (if 'const')
|
||||
Pos string `json:"pos"` // location of definition of member
|
||||
Kind string `json:"kind"` // one of {var,const,func,type}
|
||||
Methods []DescribeMethod `json:"methods,omitempty"` // methods (if member is a type)
|
||||
}
|
||||
|
||||
// A DescribePackage is the additional result of a 'describe' if
|
||||
// the selection indicates a package.
|
||||
type DescribePackage struct {
|
||||
Path string `json:"path"` // import path of the package
|
||||
Members []*DescribeMember `json:"members,omitempty"` // accessible members of the package
|
||||
}
|
||||
|
||||
// A Describe is the result of a 'describe' query.
|
||||
// It may contain an element describing the selected semantic entity
|
||||
// in detail.
|
||||
type Describe struct {
|
||||
Desc string `json:"desc"` // description of the selected syntax node
|
||||
Pos string `json:"pos"` // location of the selected syntax node
|
||||
Detail string `json:"detail,omitempty"` // one of {package, type, value}, or "".
|
||||
|
||||
// At most one of the following fields is populated:
|
||||
// the one specified by 'detail'.
|
||||
Package *DescribePackage `json:"package,omitempty"`
|
||||
Type *DescribeType `json:"type,omitempty"`
|
||||
Value *DescribeValue `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
// A WhichErrs is the result of a 'whicherrs' query.
|
||||
// It contains the position of the queried error and the possible globals,
|
||||
// constants, and types it may point to.
|
||||
type WhichErrs struct {
|
||||
ErrPos string `json:"errpos,omitempty"` // location of queried error
|
||||
Globals []string `json:"globals,omitempty"` // locations of globals
|
||||
Constants []string `json:"constants,omitempty"` // locations of constants
|
||||
Types []WhichErrsType `json:"types,omitempty"` // Types
|
||||
}
|
||||
|
||||
type WhichErrsType struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
Position string `json:"position,omitempty"`
|
||||
}
|
||||
|
||||
// A Result is the common result of any oracle query.
|
||||
// It contains a query-specific result element.
|
||||
//
|
||||
// TODO(adonovan): perhaps include other info such as: analysis scope,
|
||||
// raw query position, stack of ast nodes, query package, etc.
|
||||
type Result struct {
|
||||
Mode string `json:"mode"` // mode of the query
|
||||
|
||||
// Exactly one of the following fields is populated:
|
||||
// the one specified by 'mode'.
|
||||
Callees *Callees `json:"callees,omitempty"`
|
||||
Callers []Caller `json:"callers,omitempty"`
|
||||
Callstack *CallStack `json:"callstack,omitempty"`
|
||||
Definition *Definition `json:"definition,omitempty"`
|
||||
Describe *Describe `json:"describe,omitempty"`
|
||||
Freevars []*FreeVar `json:"freevars,omitempty"`
|
||||
Implements *Implements `json:"implements,omitempty"`
|
||||
Peers *Peers `json:"peers,omitempty"`
|
||||
PointsTo []PointsTo `json:"pointsto,omitempty"`
|
||||
Referrers *Referrers `json:"referrers,omitempty"`
|
||||
What *What `json:"what,omitempty"`
|
||||
WhichErrs *WhichErrs `json:"whicherrs,omitempty"`
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
package main
|
||||
|
||||
// Tests of call-graph queries, -format=json.
|
||||
// See go.tools/oracle/oracle_test.go for explanation.
|
||||
// See calls-json.golden for expected query results.
|
||||
|
||||
func call(f func()) {
|
||||
f() // @callees @callees-f "f"
|
||||
}
|
||||
|
||||
func main() {
|
||||
call(func() {
|
||||
// @callers callers-main.anon "^"
|
||||
// @callstack callstack-main.anon "^"
|
||||
})
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
-------- @callees @callees-f --------
|
||||
{
|
||||
"mode": "callees",
|
||||
"callees": {
|
||||
"pos": "testdata/src/calls-json/main.go:8:3",
|
||||
"desc": "dynamic function call",
|
||||
"callees": [
|
||||
{
|
||||
"name": "main.main$1",
|
||||
"pos": "testdata/src/calls-json/main.go:12:7"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
-------- @callstack callstack-main.anon --------
|
||||
{
|
||||
"mode": "callstack",
|
||||
"callstack": {
|
||||
"pos": "testdata/src/calls-json/main.go:12:7",
|
||||
"target": "main.main$1",
|
||||
"callers": [
|
||||
{
|
||||
"pos": "testdata/src/calls-json/main.go:8:3",
|
||||
"desc": "dynamic function call",
|
||||
"caller": "main.call"
|
||||
},
|
||||
{
|
||||
"pos": "testdata/src/calls-json/main.go:12:6",
|
||||
"desc": "static function call",
|
||||
"caller": "main.main"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,129 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// 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) { // @pointsto pointsto-A-x "x"
|
||||
// @callers callers-A "^"
|
||||
// @callstack callstack-A "^"
|
||||
}
|
||||
|
||||
func B(x *int) { // @pointsto pointsto-B-x "x"
|
||||
// @callers callers-B "^"
|
||||
}
|
||||
|
||||
func foo() {
|
||||
}
|
||||
|
||||
// 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() // @pointsto pointsto-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
|
||||
go apply(A, &a) // @callees callees-main-apply1 "app"
|
||||
defer apply(B, &b)
|
||||
|
||||
var c, d int
|
||||
var pc, pd *int // @pointsto pointsto-pc "pc"
|
||||
store(&pc, &c)
|
||||
store(&pd, &d)
|
||||
_ = pd // @pointsto pointsto-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"
|
||||
|
||||
i = new(myint)
|
||||
i.f() // @callees callees-not-a-wrapper "f"
|
||||
|
||||
// statically dispatched calls. Handled specially by callees, so test that they work.
|
||||
foo() // @callees callees-static-call "foo"
|
||||
fmt.Println() // @callees callees-qualified-call "Println"
|
||||
m := new(method)
|
||||
m.f() // @callees callees-static-method-call "f"
|
||||
g := new(embeddedIface)
|
||||
g.iface = m
|
||||
g.f() // @callees callees-implicit-selection-method-call "f"
|
||||
}
|
||||
|
||||
type myint int
|
||||
|
||||
func (myint) f() {
|
||||
// @callers callers-not-a-wrapper "^"
|
||||
}
|
||||
|
||||
type method int
|
||||
|
||||
func (method) f() {
|
||||
}
|
||||
|
||||
type embeddedIface struct {
|
||||
iface
|
||||
}
|
||||
|
||||
type iface interface {
|
||||
f()
|
||||
}
|
||||
|
||||
var dynamic = func() {}
|
||||
|
||||
func deadcode() {
|
||||
main() // @callees callees-err-deadcode2 "main"
|
||||
// @callers callers-err-deadcode "^"
|
||||
// @callstack callstack-err-deadcode "^"
|
||||
|
||||
// Within dead code, dynamic calls have no callees.
|
||||
dynamic() // @callees callees-err-deadcode3 "dynamic"
|
||||
}
|
||||
|
||||
// This code belongs to init.
|
||||
var global = 123 // @callers callers-global "global"
|
||||
|
||||
// The package initializer may be called by other packages' inits, or
|
||||
// in this case, the root of the callgraph. The source-level init functions
|
||||
// are in turn called by it.
|
||||
func init() {
|
||||
// @callstack callstack-init "^"
|
||||
}
|
||||
|
|
@ -1,125 +0,0 @@
|
|||
-------- @pointsto pointsto-A-x --------
|
||||
this *int may point to these objects:
|
||||
a
|
||||
b
|
||||
|
||||
-------- @callstack callstack-A --------
|
||||
Found a call path from root to main.A
|
||||
main.A
|
||||
dynamic function call from main.apply
|
||||
concurrent static function call from main.main
|
||||
|
||||
-------- @pointsto pointsto-B-x --------
|
||||
this *int may point to these objects:
|
||||
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:
|
||||
concurrent static function call from main.main
|
||||
deferred 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
|
||||
|
||||
-------- @pointsto pointsto-result-f --------
|
||||
this func() *int may point to these objects:
|
||||
main.main$1
|
||||
|
||||
-------- @callees callees-main.call-f --------
|
||||
this dynamic function call dispatches to:
|
||||
main.main$1
|
||||
|
||||
-------- @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
|
||||
|
||||
-------- @pointsto pointsto-pc --------
|
||||
this *int may point to these objects:
|
||||
c
|
||||
|
||||
-------- @pointsto pointsto-pd --------
|
||||
this *int may point to these objects:
|
||||
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 static function call dispatches to:
|
||||
main.main
|
||||
|
||||
-------- @callees callees-err-nil-func --------
|
||||
dynamic function call on nil value
|
||||
|
||||
-------- @callees callees-err-nil-interface --------
|
||||
dynamic method call on nil value
|
||||
|
||||
-------- @callees callees-not-a-wrapper --------
|
||||
this dynamic method call dispatches to:
|
||||
(main.myint).f
|
||||
|
||||
-------- @callees callees-static-call --------
|
||||
this static function call dispatches to:
|
||||
main.foo
|
||||
|
||||
-------- @callees callees-qualified-call --------
|
||||
this static function call dispatches to:
|
||||
fmt.Println
|
||||
|
||||
-------- @callees callees-static-method-call --------
|
||||
this static function call dispatches to:
|
||||
(main.method).f
|
||||
|
||||
-------- @callees callees-implicit-selection-method-call --------
|
||||
this dynamic method call dispatches to:
|
||||
(main.method).f
|
||||
|
||||
-------- @callers callers-not-a-wrapper --------
|
||||
(main.myint).f is called from these 1 sites:
|
||||
dynamic method call from main.main
|
||||
|
||||
-------- @callees callees-err-deadcode2 --------
|
||||
this static function call dispatches to:
|
||||
main.main
|
||||
|
||||
-------- @callstack callstack-err-deadcode --------
|
||||
main.deadcode is unreachable in this analysis scope
|
||||
|
||||
-------- @callees callees-err-deadcode3 --------
|
||||
|
||||
Error: this call site is unreachable in this analysis
|
||||
-------- @callers callers-global --------
|
||||
main.init is called from these 1 sites:
|
||||
the root of the call graph
|
||||
|
||||
-------- @callstack callstack-init --------
|
||||
Found a call path from root to main.init#1
|
||||
main.init#1
|
||||
static function call from main.init
|
||||
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
package describe // @describe pkgdecl "describe"
|
||||
|
||||
// Tests of 'describe' query, -format=json.
|
||||
// See go.tools/oracle/oracle_test.go for explanation.
|
||||
// See describe-json.golden for expected query results.
|
||||
|
||||
func main() {
|
||||
var s struct{ x [3]int }
|
||||
p := &s.x[0] // @describe desc-val-p "p"
|
||||
_ = p
|
||||
|
||||
var i I = C(0)
|
||||
if i == nil {
|
||||
i = new(D)
|
||||
}
|
||||
print(i) // @describe desc-val-i "\\bi\\b"
|
||||
|
||||
go main() // @describe desc-stmt "go"
|
||||
}
|
||||
|
||||
type I interface {
|
||||
f()
|
||||
}
|
||||
|
||||
type C int // @describe desc-type-C "C"
|
||||
type D struct{}
|
||||
|
||||
func (c C) f() {}
|
||||
func (d *D) f() {}
|
||||
|
|
@ -1,111 +0,0 @@
|
|||
-------- @describe pkgdecl --------
|
||||
{
|
||||
"mode": "describe",
|
||||
"describe": {
|
||||
"desc": "definition of package \"describe-json\"",
|
||||
"pos": "testdata/src/describe-json/main.go:1:9",
|
||||
"detail": "package",
|
||||
"package": {
|
||||
"path": "describe-json",
|
||||
"members": [
|
||||
{
|
||||
"name": "C",
|
||||
"type": "int",
|
||||
"pos": "testdata/src/describe-json/main.go:25:6",
|
||||
"kind": "type",
|
||||
"methods": [
|
||||
{
|
||||
"name": "method (C) f()",
|
||||
"pos": "testdata/src/describe-json/main.go:28:12"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "D",
|
||||
"type": "struct{}",
|
||||
"pos": "testdata/src/describe-json/main.go:26:6",
|
||||
"kind": "type",
|
||||
"methods": [
|
||||
{
|
||||
"name": "method (*D) f()",
|
||||
"pos": "testdata/src/describe-json/main.go:29:13"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "I",
|
||||
"type": "interface{f()}",
|
||||
"pos": "testdata/src/describe-json/main.go:21:6",
|
||||
"kind": "type",
|
||||
"methods": [
|
||||
{
|
||||
"name": "method (I) f()",
|
||||
"pos": "testdata/src/describe-json/main.go:22:2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "main",
|
||||
"type": "func()",
|
||||
"pos": "testdata/src/describe-json/main.go:7:6",
|
||||
"kind": "func"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
-------- @describe desc-val-p --------
|
||||
{
|
||||
"mode": "describe",
|
||||
"describe": {
|
||||
"desc": "identifier",
|
||||
"pos": "testdata/src/describe-json/main.go:9:2",
|
||||
"detail": "value",
|
||||
"value": {
|
||||
"type": "*int",
|
||||
"objpos": "testdata/src/describe-json/main.go:9:2"
|
||||
}
|
||||
}
|
||||
}
|
||||
-------- @describe desc-val-i --------
|
||||
{
|
||||
"mode": "describe",
|
||||
"describe": {
|
||||
"desc": "identifier",
|
||||
"pos": "testdata/src/describe-json/main.go:16:8",
|
||||
"detail": "value",
|
||||
"value": {
|
||||
"type": "I",
|
||||
"objpos": "testdata/src/describe-json/main.go:12:6"
|
||||
}
|
||||
}
|
||||
}
|
||||
-------- @describe desc-stmt --------
|
||||
{
|
||||
"mode": "describe",
|
||||
"describe": {
|
||||
"desc": "go statement",
|
||||
"pos": "testdata/src/describe-json/main.go:18:2",
|
||||
"detail": "unknown"
|
||||
}
|
||||
}
|
||||
-------- @describe desc-type-C --------
|
||||
{
|
||||
"mode": "describe",
|
||||
"describe": {
|
||||
"desc": "definition of type C (size 8, align 8)",
|
||||
"pos": "testdata/src/describe-json/main.go:25:6",
|
||||
"detail": "type",
|
||||
"type": {
|
||||
"type": "C",
|
||||
"namepos": "testdata/src/describe-json/main.go:25:6",
|
||||
"namedef": "int",
|
||||
"methods": [
|
||||
{
|
||||
"name": "method (C) f()",
|
||||
"pos": "testdata/src/describe-json/main.go:28:12"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
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.
|
||||
|
||||
import (
|
||||
"nosuchpkg" // @describe badimport1 "nosuchpkg"
|
||||
nosuchpkg2 "nosuchpkg" // @describe badimport2 "nosuchpkg2"
|
||||
_ "unsafe" // @describe unsafe "unsafe"
|
||||
)
|
||||
|
||||
var _ nosuchpkg.T
|
||||
var _ nosuchpkg2.T
|
||||
|
||||
type cake float64 // @describe type-ref-builtin "float64"
|
||||
|
||||
const c = iota // @describe const-ref-iota "iota"
|
||||
|
||||
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"
|
||||
|
||||
var global = new(string) // NB: ssa.Global is indirect, i.e. **string
|
||||
|
||||
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"
|
||||
_ = global // @describe ref-global "global"
|
||||
|
||||
// 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"
|
||||
|
||||
i = new(C) // @describe var-ref-i-C "i"
|
||||
if i != nil {
|
||||
i = D{} // @describe var-ref-i-D "i"
|
||||
}
|
||||
print(i) // @describe var-ref-i "\\bi\\b"
|
||||
|
||||
// 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"
|
||||
_ = three
|
||||
|
||||
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}
|
||||
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"
|
||||
|
||||
panic(3) // @describe builtin-ref-panic "panic"
|
||||
|
||||
var a2 int // @describe var-decl-stmt "var a2 int"
|
||||
_ = a2
|
||||
var _ int // @describe var-decl-stmt2 "var _ int"
|
||||
var _ int // @describe var-def-blank "_"
|
||||
}
|
||||
|
||||
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() {}
|
||||
|
|
@ -1,184 +0,0 @@
|
|||
-------- @describe pkgdecl --------
|
||||
definition of package "describe"
|
||||
type C int
|
||||
method (*C) f()
|
||||
type D struct{}
|
||||
method (D) f()
|
||||
type I interface{f()}
|
||||
method (I) f()
|
||||
const c untyped int = 0
|
||||
type cake float64
|
||||
var global *string
|
||||
func main func()
|
||||
const pi untyped float = 3.141
|
||||
const pie cake = 3.141
|
||||
|
||||
-------- @describe badimport1 --------
|
||||
|
||||
Error: can't import package "nosuchpkg"
|
||||
-------- @describe badimport2 --------
|
||||
|
||||
Error: can't import package "nosuchpkg"
|
||||
-------- @describe unsafe --------
|
||||
import of package "unsafe"
|
||||
builtin Alignof
|
||||
builtin Offsetof
|
||||
type Pointer unsafe.Pointer
|
||||
builtin Sizeof
|
||||
|
||||
-------- @describe type-ref-builtin --------
|
||||
reference to built-in type float64
|
||||
|
||||
-------- @describe const-ref-iota --------
|
||||
reference to const iota untyped int of constant value 0
|
||||
|
||||
-------- @describe const-def-pi --------
|
||||
definition of const pi untyped float
|
||||
|
||||
-------- @describe const-def-pie --------
|
||||
definition of const pie cake
|
||||
|
||||
-------- @describe const-ref-pi --------
|
||||
reference to const pi untyped float of constant value 3.141
|
||||
defined here
|
||||
|
||||
-------- @describe func-def-main --------
|
||||
definition of func main()
|
||||
|
||||
-------- @describe func-ref-main --------
|
||||
reference to func main()
|
||||
defined here
|
||||
|
||||
-------- @describe func-ref-*C.f --------
|
||||
reference to method func (*C).f()
|
||||
defined here
|
||||
|
||||
-------- @describe func-ref-D.f --------
|
||||
reference to method func (D).f()
|
||||
defined here
|
||||
|
||||
-------- @describe func-ref-I.f --------
|
||||
reference to interface method func (I).f()
|
||||
defined here
|
||||
|
||||
-------- @describe type-D --------
|
||||
reference to type D (size 0, align 1)
|
||||
defined as struct{}
|
||||
Method set:
|
||||
method (D) f()
|
||||
|
||||
-------- @describe type-I --------
|
||||
reference to type I (size 16, align 8)
|
||||
defined as interface{f()}
|
||||
Method set:
|
||||
method (I) f()
|
||||
|
||||
-------- @describe func-ref-d.f --------
|
||||
reference to method func (D).f()
|
||||
defined here
|
||||
|
||||
-------- @describe func-ref-i.f --------
|
||||
reference to interface method func (I).f()
|
||||
defined here
|
||||
|
||||
-------- @describe ref-lexical-d --------
|
||||
reference to var d D
|
||||
defined here
|
||||
|
||||
-------- @describe ref-anon --------
|
||||
reference to var anon func()
|
||||
defined here
|
||||
|
||||
-------- @describe ref-global --------
|
||||
reference to var global *string
|
||||
defined here
|
||||
|
||||
-------- @describe var-def-x-1 --------
|
||||
definition of var x *int
|
||||
|
||||
-------- @describe var-ref-x-1 --------
|
||||
reference to var x *int
|
||||
defined here
|
||||
|
||||
-------- @describe var-def-x-2 --------
|
||||
reference to var x *int
|
||||
defined here
|
||||
|
||||
-------- @describe var-ref-x-2 --------
|
||||
reference to var x *int
|
||||
defined here
|
||||
|
||||
-------- @describe var-ref-i-C --------
|
||||
reference to var i I
|
||||
defined here
|
||||
|
||||
-------- @describe var-ref-i-D --------
|
||||
reference to var i I
|
||||
defined here
|
||||
|
||||
-------- @describe var-ref-i --------
|
||||
reference to var i I
|
||||
defined here
|
||||
|
||||
-------- @describe const-local-pi --------
|
||||
definition of const localpi untyped float
|
||||
|
||||
-------- @describe const-local-pie --------
|
||||
definition of const localpie cake
|
||||
|
||||
-------- @describe const-ref-localpi --------
|
||||
reference to const localpi untyped float of constant value 3.141
|
||||
defined here
|
||||
|
||||
-------- @describe type-def-T --------
|
||||
definition of type T (size 8, align 8)
|
||||
No methods.
|
||||
|
||||
-------- @describe type-ref-T --------
|
||||
reference to type T (size 8, align 8)
|
||||
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)
|
||||
|
||||
-------- @describe mapval --------
|
||||
reference to var mapval *int
|
||||
defined here
|
||||
|
||||
-------- @describe m --------
|
||||
reference to var m map[string]*int
|
||||
defined here
|
||||
|
||||
-------- @describe defer-stmt --------
|
||||
defer statement
|
||||
|
||||
-------- @describe go-stmt --------
|
||||
go statement
|
||||
|
||||
-------- @describe builtin-ref-panic --------
|
||||
function call (or conversion) of type ()
|
||||
|
||||
-------- @describe var-decl-stmt --------
|
||||
definition of var a2 int
|
||||
|
||||
-------- @describe var-decl-stmt2 --------
|
||||
definition of var _ int
|
||||
|
||||
-------- @describe var-def-blank --------
|
||||
definition of var _ int
|
||||
|
||||
-------- @describe def-iface-I --------
|
||||
definition of type I (size 16, align 8)
|
||||
Method set:
|
||||
method (I) f()
|
||||
|
||||
-------- @describe def-imethod-I.f --------
|
||||
definition of interface method func (I).f()
|
||||
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
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 f(int) {}
|
||||
|
||||
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."
|
||||
}
|
||||
|
||||
f(x) // @freevars fv3 "f.x."
|
||||
|
||||
// TODO(adonovan): enable when go/types supports labels.
|
||||
loop: // #@freevars fv-def-label "loop:"
|
||||
for {
|
||||
break loop // #@freevars fv-ref-label "break loop"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
-------- @freevars fv1 --------
|
||||
Free identifiers:
|
||||
type C
|
||||
const exp int
|
||||
var x int
|
||||
|
||||
-------- @freevars fv2 --------
|
||||
Free identifiers:
|
||||
var s.t.a int
|
||||
var s.t.b int
|
||||
var s.x int
|
||||
var x int
|
||||
var y rune
|
||||
|
||||
-------- @freevars fv3 --------
|
||||
Free identifiers:
|
||||
var x int
|
||||
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
package main
|
||||
|
||||
// Tests of 'implements' query, -output=json.
|
||||
// See go.tools/oracle/oracle_test.go for explanation.
|
||||
// See implements.golden for expected query results.
|
||||
|
||||
func main() {
|
||||
}
|
||||
|
||||
type E interface{} // @implements E "E"
|
||||
|
||||
type F interface { // @implements F "F"
|
||||
f()
|
||||
}
|
||||
|
||||
type FG interface { // @implements FG "FG"
|
||||
f()
|
||||
g() []int // @implements slice "..int"
|
||||
}
|
||||
|
||||
type C int // @implements C "C"
|
||||
type D struct{}
|
||||
|
||||
func (c *C) f() {} // @implements starC ".C"
|
||||
func (d D) f() {} // @implements D "D"
|
||||
|
||||
func (d *D) g() []int { return nil } // @implements starD ".D"
|
||||
|
|
@ -1,159 +0,0 @@
|
|||
-------- @implements E --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "implements-json.E",
|
||||
"pos": "testdata/src/implements-json/main.go:10:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
}
|
||||
}
|
||||
-------- @implements F --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "implements-json.F",
|
||||
"pos": "testdata/src/implements-json/main.go:12:6",
|
||||
"kind": "interface"
|
||||
},
|
||||
"to": [
|
||||
{
|
||||
"name": "*implements-json.C",
|
||||
"pos": "testdata/src/implements-json/main.go:21:6",
|
||||
"kind": "pointer"
|
||||
},
|
||||
{
|
||||
"name": "implements-json.D",
|
||||
"pos": "testdata/src/implements-json/main.go:22:6",
|
||||
"kind": "struct"
|
||||
},
|
||||
{
|
||||
"name": "implements-json.FG",
|
||||
"pos": "testdata/src/implements-json/main.go:16:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
-------- @implements FG --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "implements-json.FG",
|
||||
"pos": "testdata/src/implements-json/main.go:16:6",
|
||||
"kind": "interface"
|
||||
},
|
||||
"to": [
|
||||
{
|
||||
"name": "*implements-json.D",
|
||||
"pos": "testdata/src/implements-json/main.go:22:6",
|
||||
"kind": "pointer"
|
||||
}
|
||||
],
|
||||
"from": [
|
||||
{
|
||||
"name": "implements-json.F",
|
||||
"pos": "testdata/src/implements-json/main.go:12:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
-------- @implements slice --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "[]int",
|
||||
"pos": "-",
|
||||
"kind": "slice"
|
||||
}
|
||||
}
|
||||
}
|
||||
-------- @implements C --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "implements-json.C",
|
||||
"pos": "testdata/src/implements-json/main.go:21:6",
|
||||
"kind": "basic"
|
||||
},
|
||||
"fromptr": [
|
||||
{
|
||||
"name": "implements-json.F",
|
||||
"pos": "testdata/src/implements-json/main.go:12:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
-------- @implements starC --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "*implements-json.C",
|
||||
"pos": "testdata/src/implements-json/main.go:21:6",
|
||||
"kind": "pointer"
|
||||
},
|
||||
"from": [
|
||||
{
|
||||
"name": "implements-json.F",
|
||||
"pos": "testdata/src/implements-json/main.go:12:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
-------- @implements D --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "implements-json.D",
|
||||
"pos": "testdata/src/implements-json/main.go:22:6",
|
||||
"kind": "struct"
|
||||
},
|
||||
"from": [
|
||||
{
|
||||
"name": "implements-json.F",
|
||||
"pos": "testdata/src/implements-json/main.go:12:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
],
|
||||
"fromptr": [
|
||||
{
|
||||
"name": "implements-json.FG",
|
||||
"pos": "testdata/src/implements-json/main.go:16:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
-------- @implements starD --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "*implements-json.D",
|
||||
"pos": "testdata/src/implements-json/main.go:22:6",
|
||||
"kind": "pointer"
|
||||
},
|
||||
"from": [
|
||||
{
|
||||
"name": "implements-json.F",
|
||||
"pos": "testdata/src/implements-json/main.go:12:6",
|
||||
"kind": "interface"
|
||||
},
|
||||
{
|
||||
"name": "implements-json.FG",
|
||||
"pos": "testdata/src/implements-json/main.go:16:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
package main
|
||||
|
||||
// Tests of 'implements' query applied to methods, -output=json.
|
||||
// See go.tools/oracle/oracle_test.go for explanation.
|
||||
// See implements-methods.golden for expected query results.
|
||||
|
||||
import _ "lib"
|
||||
|
||||
func main() {
|
||||
}
|
||||
|
||||
type F interface {
|
||||
f() // @implements F.f "f"
|
||||
}
|
||||
|
||||
type FG interface {
|
||||
f() // @implements FG.f "f"
|
||||
g() []int // @implements FG.g "g"
|
||||
}
|
||||
|
||||
type C int
|
||||
type D struct{}
|
||||
|
||||
func (c *C) f() {} // @implements *C.f "f"
|
||||
func (d D) f() {} // @implements D.f "f"
|
||||
|
||||
func (d *D) g() []int { return nil } // @implements *D.g "g"
|
||||
|
||||
type sorter []int
|
||||
|
||||
func (sorter) Len() int { return 0 } // @implements Len "Len"
|
||||
func (sorter) Less(i, j int) bool { return false }
|
||||
func (sorter) Swap(i, j int) {}
|
||||
|
||||
type I interface {
|
||||
Method(*int) *int // @implements I.Method "Method"
|
||||
}
|
||||
|
|
@ -1,312 +0,0 @@
|
|||
-------- @implements F.f --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "implements-methods-json.F",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:12:6",
|
||||
"kind": "interface"
|
||||
},
|
||||
"to": [
|
||||
{
|
||||
"name": "*implements-methods-json.C",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:21:6",
|
||||
"kind": "pointer"
|
||||
},
|
||||
{
|
||||
"name": "implements-methods-json.D",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:22:6",
|
||||
"kind": "struct"
|
||||
},
|
||||
{
|
||||
"name": "implements-methods-json.FG",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:16:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
],
|
||||
"method": {
|
||||
"name": "func (F).f()",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:13:2"
|
||||
},
|
||||
"to_method": [
|
||||
{
|
||||
"name": "method (*C) f()",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:24:13"
|
||||
},
|
||||
{
|
||||
"name": "method (D) f()",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:25:12"
|
||||
},
|
||||
{
|
||||
"name": "method (FG) f()",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:17:2"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
-------- @implements FG.f --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "implements-methods-json.FG",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:16:6",
|
||||
"kind": "interface"
|
||||
},
|
||||
"to": [
|
||||
{
|
||||
"name": "*implements-methods-json.D",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:22:6",
|
||||
"kind": "pointer"
|
||||
}
|
||||
],
|
||||
"from": [
|
||||
{
|
||||
"name": "implements-methods-json.F",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:12:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
],
|
||||
"method": {
|
||||
"name": "func (FG).f()",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:17:2"
|
||||
},
|
||||
"to_method": [
|
||||
{
|
||||
"name": "method (*D) f()",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:25:12"
|
||||
}
|
||||
],
|
||||
"from_method": [
|
||||
{
|
||||
"name": "method (F) f()",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:13:2"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
-------- @implements FG.g --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "implements-methods-json.FG",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:16:6",
|
||||
"kind": "interface"
|
||||
},
|
||||
"to": [
|
||||
{
|
||||
"name": "*implements-methods-json.D",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:22:6",
|
||||
"kind": "pointer"
|
||||
}
|
||||
],
|
||||
"from": [
|
||||
{
|
||||
"name": "implements-methods-json.F",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:12:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
],
|
||||
"method": {
|
||||
"name": "func (FG).g() []int",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:18:2"
|
||||
},
|
||||
"to_method": [
|
||||
{
|
||||
"name": "method (*D) g() []int",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:27:13"
|
||||
}
|
||||
],
|
||||
"from_method": [
|
||||
{
|
||||
"name": "",
|
||||
"pos": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
-------- @implements *C.f --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "*implements-methods-json.C",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:21:6",
|
||||
"kind": "pointer"
|
||||
},
|
||||
"from": [
|
||||
{
|
||||
"name": "implements-methods-json.F",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:12:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
],
|
||||
"method": {
|
||||
"name": "func (*C).f()",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:24:13"
|
||||
},
|
||||
"from_method": [
|
||||
{
|
||||
"name": "method (F) f()",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:13:2"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
-------- @implements D.f --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "implements-methods-json.D",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:22:6",
|
||||
"kind": "struct"
|
||||
},
|
||||
"from": [
|
||||
{
|
||||
"name": "implements-methods-json.F",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:12:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
],
|
||||
"fromptr": [
|
||||
{
|
||||
"name": "implements-methods-json.FG",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:16:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
],
|
||||
"method": {
|
||||
"name": "func (D).f()",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:25:12"
|
||||
},
|
||||
"from_method": [
|
||||
{
|
||||
"name": "method (F) f()",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:13:2"
|
||||
}
|
||||
],
|
||||
"fromptr_method": [
|
||||
{
|
||||
"name": "method (FG) f()",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:17:2"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
-------- @implements *D.g --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "*implements-methods-json.D",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:22:6",
|
||||
"kind": "pointer"
|
||||
},
|
||||
"from": [
|
||||
{
|
||||
"name": "implements-methods-json.F",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:12:6",
|
||||
"kind": "interface"
|
||||
},
|
||||
{
|
||||
"name": "implements-methods-json.FG",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:16:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
],
|
||||
"method": {
|
||||
"name": "func (*D).g() []int",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:27:13"
|
||||
},
|
||||
"from_method": [
|
||||
{
|
||||
"name": "",
|
||||
"pos": ""
|
||||
},
|
||||
{
|
||||
"name": "method (FG) g() []int",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:18:2"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
-------- @implements Len --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "implements-methods-json.sorter",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:29:6",
|
||||
"kind": "slice"
|
||||
},
|
||||
"from": [
|
||||
{
|
||||
"name": "lib.Sorter",
|
||||
"pos": "testdata/src/lib/lib.go:16:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
],
|
||||
"method": {
|
||||
"name": "func (sorter).Len() int",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:31:15"
|
||||
},
|
||||
"from_method": [
|
||||
{
|
||||
"name": "method (lib.Sorter) Len() int",
|
||||
"pos": "testdata/src/lib/lib.go:17:2"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
-------- @implements I.Method --------
|
||||
{
|
||||
"mode": "implements",
|
||||
"implements": {
|
||||
"type": {
|
||||
"name": "implements-methods-json.I",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:35:6",
|
||||
"kind": "interface"
|
||||
},
|
||||
"to": [
|
||||
{
|
||||
"name": "lib.Type",
|
||||
"pos": "testdata/src/lib/lib.go:3:6",
|
||||
"kind": "basic"
|
||||
},
|
||||
{
|
||||
"name": "main.I",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:35:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
],
|
||||
"from": [
|
||||
{
|
||||
"name": "main.I",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:35:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
],
|
||||
"method": {
|
||||
"name": "func (I).Method(*int) *int",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:36:2"
|
||||
},
|
||||
"to_method": [
|
||||
{
|
||||
"name": "method (lib.Type) Method(x *int) *int",
|
||||
"pos": "testdata/src/lib/lib.go:5:13"
|
||||
},
|
||||
{
|
||||
"name": "method (main.I) Method(*int) *int",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:36:2"
|
||||
}
|
||||
],
|
||||
"from_method": [
|
||||
{
|
||||
"name": "method (main.I) Method(*int) *int",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:36:2"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
package main
|
||||
|
||||
// Tests of 'implements' query applied to methods.
|
||||
// See go.tools/oracle/oracle_test.go for explanation.
|
||||
// See implements-methods.golden for expected query results.
|
||||
|
||||
import _ "lib"
|
||||
|
||||
func main() {
|
||||
}
|
||||
|
||||
type F interface {
|
||||
f() // @implements F.f "f"
|
||||
}
|
||||
|
||||
type FG interface {
|
||||
f() // @implements FG.f "f"
|
||||
g() []int // @implements FG.g "g"
|
||||
}
|
||||
|
||||
type C int
|
||||
type D struct{}
|
||||
|
||||
func (c *C) f() {} // @implements *C.f "f"
|
||||
func (d D) f() {} // @implements D.f "f"
|
||||
|
||||
func (d *D) g() []int { return nil } // @implements *D.g "g"
|
||||
|
||||
type sorter []int
|
||||
|
||||
func (sorter) Len() int { return 0 } // @implements Len "Len"
|
||||
func (sorter) Less(i, j int) bool { return false }
|
||||
func (sorter) Swap(i, j int) {}
|
||||
|
||||
type I interface {
|
||||
Method(*int) *int // @implements I.Method "Method"
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
-------- @implements F.f --------
|
||||
abstract method func (F).f()
|
||||
is implemented by method (*C).f
|
||||
is implemented by method (D).f
|
||||
is implemented by method (FG).f
|
||||
|
||||
-------- @implements FG.f --------
|
||||
abstract method func (FG).f()
|
||||
is implemented by method (*D).f
|
||||
implements method (F).f
|
||||
|
||||
-------- @implements FG.g --------
|
||||
abstract method func (FG).g() []int
|
||||
is implemented by method (*D).g
|
||||
|
||||
-------- @implements *C.f --------
|
||||
concrete method func (*C).f()
|
||||
implements method (F).f
|
||||
|
||||
-------- @implements D.f --------
|
||||
concrete method func (D).f()
|
||||
implements method (F).f
|
||||
concrete method func (D).f()
|
||||
implements method (FG).f
|
||||
|
||||
-------- @implements *D.g --------
|
||||
concrete method func (*D).g() []int
|
||||
implements method (FG).g
|
||||
|
||||
-------- @implements Len --------
|
||||
concrete method func (sorter).Len() int
|
||||
implements method (lib.Sorter).Len
|
||||
|
||||
-------- @implements I.Method --------
|
||||
abstract method func (I).Method(*int) *int
|
||||
is implemented by method (lib.Type).Method
|
||||
is implemented by method (main.I).Method
|
||||
implements method (main.I).Method
|
||||
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
package main
|
||||
|
||||
// Tests of 'implements' query.
|
||||
// See go.tools/oracle/oracle_test.go for explanation.
|
||||
// See implements.golden for expected query results.
|
||||
|
||||
import _ "lib"
|
||||
|
||||
func main() {
|
||||
}
|
||||
|
||||
type E interface{} // @implements E "E"
|
||||
|
||||
type F interface { // @implements F "F"
|
||||
f()
|
||||
}
|
||||
|
||||
type FG interface { // @implements FG "FG"
|
||||
f()
|
||||
g() []int // @implements slice "..int"
|
||||
}
|
||||
|
||||
type C int // @implements C "C"
|
||||
type D struct{}
|
||||
|
||||
func (c *C) f() {} // @implements starC ".C"
|
||||
func (d D) f() {} // @implements D "D"
|
||||
|
||||
func (d *D) g() []int { return nil } // @implements starD ".D"
|
||||
|
||||
type sorter []int // @implements sorter "sorter"
|
||||
|
||||
func (sorter) Len() int { return 0 }
|
||||
func (sorter) Less(i, j int) bool { return false }
|
||||
func (sorter) Swap(i, j int) {}
|
||||
|
||||
type I interface { // @implements I "I"
|
||||
Method(*int) *int
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
-------- @implements E --------
|
||||
empty interface type E
|
||||
|
||||
-------- @implements F --------
|
||||
interface type F
|
||||
is implemented by pointer type *C
|
||||
is implemented by struct type D
|
||||
is implemented by interface type FG
|
||||
|
||||
-------- @implements FG --------
|
||||
interface type FG
|
||||
is implemented by pointer type *D
|
||||
implements F
|
||||
|
||||
-------- @implements slice --------
|
||||
slice type []int implements only interface{}
|
||||
|
||||
-------- @implements C --------
|
||||
pointer type *C
|
||||
implements F
|
||||
|
||||
-------- @implements starC --------
|
||||
pointer type *C
|
||||
implements F
|
||||
|
||||
-------- @implements D --------
|
||||
struct type D
|
||||
implements F
|
||||
pointer type *D
|
||||
implements FG
|
||||
|
||||
-------- @implements starD --------
|
||||
pointer type *D
|
||||
implements F
|
||||
implements FG
|
||||
|
||||
-------- @implements sorter --------
|
||||
slice type sorter
|
||||
implements lib.Sorter
|
||||
|
||||
-------- @implements I --------
|
||||
interface type I
|
||||
is implemented by basic type lib.Type
|
||||
is implemented by interface type main.I
|
||||
implements main.I
|
||||
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"hash/fnv" // @describe ref-pkg-import2 "fnv"
|
||||
"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) // @pointsto p "p "
|
||||
|
||||
var _ lib.Type // @describe ref-pkg "lib"
|
||||
|
||||
fnv.New32()
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
-------- @describe ref-pkg-import2 --------
|
||||
import of package "hash/fnv"
|
||||
func New32 func() hash.Hash32
|
||||
func New32a func() hash.Hash32
|
||||
func New64 func() hash.Hash64
|
||||
func New64a func() hash.Hash64
|
||||
|
||||
-------- @describe ref-pkg-import --------
|
||||
import of package "lib"
|
||||
const Const untyped int = 3
|
||||
func Func func()
|
||||
type Sorter interface{...}
|
||||
method (Sorter) Len() int
|
||||
method (Sorter) Less(i int, j int) bool
|
||||
method (Sorter) Swap(i int, j int)
|
||||
type Type int
|
||||
method (Type) Method(x *int) *int
|
||||
var Var int
|
||||
|
||||
-------- @describe ref-const --------
|
||||
reference to const lib.Const untyped int
|
||||
defined here
|
||||
|
||||
-------- @describe ref-func --------
|
||||
reference to func lib.Func()
|
||||
defined here
|
||||
|
||||
-------- @describe ref-var --------
|
||||
reference to var lib.Var int
|
||||
defined here
|
||||
|
||||
-------- @describe ref-type --------
|
||||
reference to type lib.Type (size 8, align 8)
|
||||
defined as int
|
||||
Method set:
|
||||
method (lib.Type) Method(x *int) *int
|
||||
|
||||
-------- @describe ref-method --------
|
||||
reference to method func (lib.Type).Method(x *int) *int
|
||||
defined here
|
||||
|
||||
-------- @pointsto p --------
|
||||
this *int may point to these objects:
|
||||
main.a
|
||||
|
||||
-------- @describe ref-pkg --------
|
||||
reference to package "lib"
|
||||
const Const untyped int = 3
|
||||
func Func func()
|
||||
type Sorter interface{...}
|
||||
method (Sorter) Len() int
|
||||
method (Sorter) Less(i int, j int) bool
|
||||
method (Sorter) Swap(i int, j int)
|
||||
type Type int
|
||||
method (Type) Method(x *int) *int
|
||||
var Var int
|
||||
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
package lib
|
||||
|
||||
type Type int
|
||||
|
||||
func (Type) Method(x *int) *int {
|
||||
return x
|
||||
}
|
||||
|
||||
func Func() {
|
||||
}
|
||||
|
||||
const Const = 3
|
||||
|
||||
var Var = 0
|
||||
|
||||
type Sorter interface {
|
||||
Len() int
|
||||
Less(i, j int) bool
|
||||
Swap(i, j int)
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
package main
|
||||
|
||||
func g(x int) {
|
||||
}
|
||||
|
||||
func f() {
|
||||
x := 1
|
||||
g(x) // "g(x)" is the selection for multiple queries
|
||||
}
|
||||
|
||||
func main() {
|
||||
f()
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
package main
|
||||
|
||||
// Tests of channel 'peers' query, -format=json.
|
||||
// See go.tools/oracle/oracle_test.go for explanation.
|
||||
// See peers-json.golden for expected query results.
|
||||
|
||||
func main() {
|
||||
chA := make(chan *int)
|
||||
<-chA
|
||||
select {
|
||||
case <-chA: // @peers peer-recv-chA "<-"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
-------- @peers peer-recv-chA --------
|
||||
{
|
||||
"mode": "peers",
|
||||
"peers": {
|
||||
"pos": "testdata/src/peers-json/main.go:11:7",
|
||||
"type": "chan *int",
|
||||
"allocs": [
|
||||
"testdata/src/peers-json/main.go:8:13"
|
||||
],
|
||||
"receives": [
|
||||
"testdata/src/peers-json/main.go:9:2",
|
||||
"testdata/src/peers-json/main.go:11:7"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
package main
|
||||
|
||||
// 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 // @pointsto pointsto-chA "chA"
|
||||
<-chA2 // @pointsto pointsto-chA2 "chA2"
|
||||
<-chB // @pointsto pointsto-chB "chB"
|
||||
|
||||
select {
|
||||
case rA := <-chA: // @peers peer-recv-chA "<-"
|
||||
_ = rA // @pointsto pointsto-rA "rA"
|
||||
case rB := <-chB: // @peers peer-recv-chB "<-"
|
||||
_ = rB // @pointsto pointsto-rB "rB"
|
||||
|
||||
case <-chA: // @peers peer-recv-chA' "<-"
|
||||
|
||||
case chA2 <- &a2: // @peers peer-send-chA' "<-"
|
||||
}
|
||||
|
||||
for _ = range chA {
|
||||
}
|
||||
|
||||
close(chA) // @peers peer-close-chA "chA"
|
||||
|
||||
chC := make(chan *int)
|
||||
(close)(chC) // @peers peer-close-chC "chC"
|
||||
|
||||
close := func(ch chan *int) chan *int {
|
||||
return ch
|
||||
}
|
||||
|
||||
close(chC) <- &b // @peers peer-send-chC "chC"
|
||||
<-close(chC) // @peers peer-recv-chC "chC"
|
||||
}
|
||||
|
|
@ -1,100 +0,0 @@
|
|||
-------- @pointsto pointsto-chA --------
|
||||
this chan *int may point to these objects:
|
||||
makechan
|
||||
makechan
|
||||
|
||||
-------- @pointsto pointsto-chA2 --------
|
||||
this chan *int may point to these objects:
|
||||
makechan
|
||||
|
||||
-------- @pointsto pointsto-chB --------
|
||||
this chan *int may point to these objects:
|
||||
makechan
|
||||
|
||||
-------- @peers peer-recv-chA --------
|
||||
This channel of type chan *int may be:
|
||||
allocated here
|
||||
allocated here
|
||||
sent to, here
|
||||
sent to, here
|
||||
received from, here
|
||||
received from, here
|
||||
received from, here
|
||||
received from, here
|
||||
received from, here
|
||||
closed, here
|
||||
|
||||
-------- @pointsto pointsto-rA --------
|
||||
this *int may point to these objects:
|
||||
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
|
||||
|
||||
-------- @pointsto pointsto-rB --------
|
||||
this *int may point to these objects:
|
||||
b
|
||||
|
||||
-------- @peers peer-recv-chA' --------
|
||||
This channel of type chan *int may be:
|
||||
allocated here
|
||||
allocated here
|
||||
sent to, here
|
||||
sent to, here
|
||||
received from, here
|
||||
received from, here
|
||||
received from, here
|
||||
received from, here
|
||||
received from, here
|
||||
closed, here
|
||||
|
||||
-------- @peers peer-send-chA' --------
|
||||
This channel of type chan *int may be:
|
||||
allocated here
|
||||
sent to, here
|
||||
received from, here
|
||||
received from, here
|
||||
received from, here
|
||||
received from, here
|
||||
received from, here
|
||||
closed, here
|
||||
|
||||
-------- @peers peer-close-chA --------
|
||||
This channel of type chan *int may be:
|
||||
allocated here
|
||||
allocated here
|
||||
sent to, here
|
||||
sent to, here
|
||||
received from, here
|
||||
received from, here
|
||||
received from, here
|
||||
received from, here
|
||||
received from, here
|
||||
closed, here
|
||||
|
||||
-------- @peers peer-close-chC --------
|
||||
This channel of type chan *int may be:
|
||||
allocated here
|
||||
sent to, here
|
||||
received from, here
|
||||
closed, here
|
||||
|
||||
-------- @peers peer-send-chC --------
|
||||
This channel of type chan *int may be:
|
||||
allocated here
|
||||
sent to, here
|
||||
received from, here
|
||||
closed, here
|
||||
|
||||
-------- @peers peer-recv-chC --------
|
||||
This channel of type chan *int may be:
|
||||
allocated here
|
||||
sent to, here
|
||||
received from, here
|
||||
closed, here
|
||||
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
package main
|
||||
|
||||
// Tests of 'pointsto' queries, -format=json.
|
||||
// See go.tools/oracle/oracle_test.go for explanation.
|
||||
// See pointsto-json.golden for expected query results.
|
||||
|
||||
func main() { //
|
||||
var s struct{ x [3]int }
|
||||
p := &s.x[0] // @pointsto val-p "p"
|
||||
_ = p
|
||||
|
||||
var i I = C(0)
|
||||
if i == nil {
|
||||
i = new(D)
|
||||
}
|
||||
print(i) // @pointsto val-i "\\bi\\b"
|
||||
}
|
||||
|
||||
type I interface {
|
||||
f()
|
||||
}
|
||||
|
||||
type C int
|
||||
type D struct{}
|
||||
|
||||
func (c C) f() {}
|
||||
func (d *D) f() {}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
-------- @pointsto val-p --------
|
||||
{
|
||||
"mode": "pointsto",
|
||||
"pointsto": [
|
||||
{
|
||||
"type": "*int",
|
||||
"labels": [
|
||||
{
|
||||
"pos": "testdata/src/pointsto-json/main.go:8:6",
|
||||
"desc": "s.x[*]"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
-------- @pointsto val-i --------
|
||||
{
|
||||
"mode": "pointsto",
|
||||
"pointsto": [
|
||||
{
|
||||
"type": "*D",
|
||||
"namepos": "testdata/src/pointsto-json/main.go:24:6",
|
||||
"labels": [
|
||||
{
|
||||
"pos": "testdata/src/pointsto-json/main.go:14:10",
|
||||
"desc": "new"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "C",
|
||||
"namepos": "testdata/src/pointsto-json/main.go:23:6"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
package main
|
||||
|
||||
// Tests of 'pointsto' query.
|
||||
// See go.tools/oracle/oracle_test.go for explanation.
|
||||
// See pointsto.golden for expected query results.
|
||||
|
||||
const pi = 3.141 // @pointsto const "pi"
|
||||
|
||||
var global = new(string) // NB: ssa.Global is indirect, i.e. **string
|
||||
|
||||
func main() {
|
||||
livecode()
|
||||
|
||||
// func objects
|
||||
_ = main // @pointsto func-ref-main "main"
|
||||
_ = (*C).f // @pointsto func-ref-*C.f "..C..f"
|
||||
_ = D.f // @pointsto func-ref-D.f "D.f"
|
||||
_ = I.f // @pointsto func-ref-I.f "I.f"
|
||||
var d D
|
||||
var i I
|
||||
_ = d.f // @pointsto func-ref-d.f "d.f"
|
||||
_ = i.f // @pointsto func-ref-i.f "i.f"
|
||||
|
||||
// var objects
|
||||
anon := func() {
|
||||
_ = d.f // @pointsto ref-lexical-d.f "d.f"
|
||||
}
|
||||
_ = anon // @pointsto ref-anon "anon"
|
||||
_ = global // @pointsto ref-global "global"
|
||||
|
||||
// SSA affords some local flow sensitivity.
|
||||
var a, b int
|
||||
var x = &a // @pointsto var-def-x-1 "x"
|
||||
_ = x // @pointsto var-ref-x-1 "x"
|
||||
x = &b // @pointsto var-def-x-2 "x"
|
||||
_ = x // @pointsto var-ref-x-2 "x"
|
||||
|
||||
i = new(C) // @pointsto var-ref-i-C "i"
|
||||
if i != nil {
|
||||
i = D{} // @pointsto var-ref-i-D "i"
|
||||
}
|
||||
print(i) // @pointsto var-ref-i "\\bi\\b"
|
||||
|
||||
m := map[string]*int{"a": &a}
|
||||
mapval, _ := m["a"] // @pointsto map-lookup,ok "m..a.."
|
||||
_ = mapval // @pointsto mapval "mapval"
|
||||
_ = m // @pointsto m "m"
|
||||
|
||||
if false {
|
||||
panic(3) // @pointsto builtin-panic "panic"
|
||||
}
|
||||
|
||||
// NB: s.f is addressable per (*ssa.Program).VarValue,
|
||||
// but our query concerns the object, not its address.
|
||||
s := struct{ f interface{} }{f: make(chan bool)}
|
||||
print(s.f) // @pointsto var-ref-s-f "s.f"
|
||||
}
|
||||
|
||||
func livecode() {} // @pointsto func-live "livecode"
|
||||
|
||||
func deadcode() { // @pointsto func-dead "deadcode"
|
||||
// Pointer analysis can't run on dead code.
|
||||
var b = new(int) // @pointsto b "b"
|
||||
_ = b
|
||||
}
|
||||
|
||||
type I interface {
|
||||
f()
|
||||
}
|
||||
|
||||
type C int
|
||||
type D struct{}
|
||||
|
||||
func (c *C) f() {}
|
||||
func (d D) f() {}
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
-------- @pointsto const --------
|
||||
|
||||
Error: pointer analysis wants an expression of reference type; got untyped float
|
||||
-------- @pointsto func-ref-main --------
|
||||
this func() may point to these objects:
|
||||
main.main
|
||||
|
||||
-------- @pointsto func-ref-*C.f --------
|
||||
this func() may point to these objects:
|
||||
(*main.C).f
|
||||
|
||||
-------- @pointsto func-ref-D.f --------
|
||||
this func() may point to these objects:
|
||||
(main.D).f
|
||||
|
||||
-------- @pointsto func-ref-I.f --------
|
||||
|
||||
Error: func (main.I).f() is an interface method
|
||||
-------- @pointsto func-ref-d.f --------
|
||||
this func() may point to these objects:
|
||||
(main.D).f
|
||||
|
||||
-------- @pointsto func-ref-i.f --------
|
||||
|
||||
Error: func (main.I).f() is an interface method
|
||||
-------- @pointsto ref-lexical-d.f --------
|
||||
this func() may point to these objects:
|
||||
(main.D).f
|
||||
|
||||
-------- @pointsto ref-anon --------
|
||||
this func() may point to these objects:
|
||||
main.main$1
|
||||
|
||||
-------- @pointsto ref-global --------
|
||||
this *string may point to these objects:
|
||||
new
|
||||
|
||||
-------- @pointsto var-def-x-1 --------
|
||||
this *int may point to these objects:
|
||||
a
|
||||
|
||||
-------- @pointsto var-ref-x-1 --------
|
||||
this *int may point to these objects:
|
||||
a
|
||||
|
||||
-------- @pointsto var-def-x-2 --------
|
||||
this *int may point to these objects:
|
||||
b
|
||||
|
||||
-------- @pointsto var-ref-x-2 --------
|
||||
this *int may point to these objects:
|
||||
b
|
||||
|
||||
-------- @pointsto var-ref-i-C --------
|
||||
this I may contain these dynamic types:
|
||||
*C, may point to:
|
||||
new
|
||||
|
||||
-------- @pointsto var-ref-i-D --------
|
||||
this I may contain these dynamic types:
|
||||
D
|
||||
|
||||
-------- @pointsto var-ref-i --------
|
||||
this I may contain these dynamic types:
|
||||
*C, may point to:
|
||||
new
|
||||
D
|
||||
|
||||
-------- @pointsto map-lookup,ok --------
|
||||
|
||||
Error: pointer analysis wants an expression of reference type; got (*int, bool)
|
||||
-------- @pointsto mapval --------
|
||||
this *int may point to these objects:
|
||||
a
|
||||
|
||||
-------- @pointsto m --------
|
||||
this map[string]*int may point to these objects:
|
||||
makemap
|
||||
|
||||
-------- @pointsto builtin-panic --------
|
||||
|
||||
Error: pointer analysis wants an expression of reference type; got ()
|
||||
-------- @pointsto var-ref-s-f --------
|
||||
this interface{} may contain these dynamic types:
|
||||
chan bool, may point to:
|
||||
makechan
|
||||
|
||||
-------- @pointsto func-live --------
|
||||
|
||||
Error: pointer analysis did not find expression (dead code?)
|
||||
-------- @pointsto func-dead --------
|
||||
|
||||
Error: pointer analysis did not find expression (dead code?)
|
||||
-------- @pointsto b --------
|
||||
|
||||
Error: pointer analysis did not find expression (dead code?)
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
package main
|
||||
|
||||
// Tests of 'referrers' query.
|
||||
// See go.tools/oracle/oracle_test.go for explanation.
|
||||
// See referrers.golden for expected query results.
|
||||
|
||||
import "lib"
|
||||
|
||||
type s struct {
|
||||
f int
|
||||
}
|
||||
|
||||
func main() {
|
||||
var v lib.Type = lib.Const // @referrers ref-package "lib"
|
||||
_ = v.Method // @referrers ref-method "Method"
|
||||
_ = v.Method
|
||||
v++ //@referrers ref-local "v"
|
||||
v++
|
||||
|
||||
_ = s{}.f // @referrers ref-field "f"
|
||||
|
||||
var s2 s
|
||||
s2.f = 1
|
||||
}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
-------- @referrers ref-package --------
|
||||
{
|
||||
"mode": "referrers",
|
||||
"referrers": {
|
||||
"pos": "testdata/src/referrers-json/main.go:14:8",
|
||||
"objpos": "testdata/src/referrers-json/main.go:7:8",
|
||||
"desc": "package lib",
|
||||
"refs": [
|
||||
"testdata/src/referrers-json/main.go:14:8",
|
||||
"testdata/src/referrers-json/main.go:14:19"
|
||||
]
|
||||
}
|
||||
}
|
||||
-------- @referrers ref-method --------
|
||||
{
|
||||
"mode": "referrers",
|
||||
"referrers": {
|
||||
"pos": "testdata/src/referrers-json/main.go:15:8",
|
||||
"objpos": "testdata/src/lib/lib.go:5:13",
|
||||
"desc": "func (lib.Type).Method(x *int) *int",
|
||||
"refs": [
|
||||
"testdata/src/imports/main.go:22:9",
|
||||
"testdata/src/referrers-json/main.go:15:8",
|
||||
"testdata/src/referrers-json/main.go:16:8",
|
||||
"testdata/src/referrers/ext_test.go:10:17",
|
||||
"testdata/src/referrers/int_test.go:7:17",
|
||||
"testdata/src/referrers/main.go:17:8",
|
||||
"testdata/src/referrers/main.go:18:8"
|
||||
]
|
||||
}
|
||||
}
|
||||
-------- @referrers ref-local --------
|
||||
{
|
||||
"mode": "referrers",
|
||||
"referrers": {
|
||||
"pos": "testdata/src/referrers-json/main.go:17:2",
|
||||
"objpos": "testdata/src/referrers-json/main.go:14:6",
|
||||
"desc": "var v lib.Type",
|
||||
"refs": [
|
||||
"testdata/src/referrers-json/main.go:15:6",
|
||||
"testdata/src/referrers-json/main.go:16:6",
|
||||
"testdata/src/referrers-json/main.go:17:2",
|
||||
"testdata/src/referrers-json/main.go:18:2"
|
||||
]
|
||||
}
|
||||
}
|
||||
-------- @referrers ref-field --------
|
||||
{
|
||||
"mode": "referrers",
|
||||
"referrers": {
|
||||
"pos": "testdata/src/referrers-json/main.go:20:10",
|
||||
"objpos": "testdata/src/referrers-json/main.go:10:2",
|
||||
"desc": "field f int",
|
||||
"refs": [
|
||||
"testdata/src/referrers-json/main.go:20:10",
|
||||
"testdata/src/referrers-json/main.go:23:5"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
package main_test
|
||||
|
||||
import (
|
||||
"lib"
|
||||
renamed "referrers" // package has name "main", path "referrers", local name "renamed"
|
||||
)
|
||||
|
||||
func _() {
|
||||
// This reference should be found by the ref-method query.
|
||||
_ = (lib.Type).Method // ref from external test package
|
||||
var _ renamed.T
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
package main
|
||||
|
||||
import "lib"
|
||||
|
||||
func _() {
|
||||
// This reference should be found by the ref-method query.
|
||||
_ = (lib.Type).Method // ref from internal test package
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
package main // @referrers package-decl "main"
|
||||
|
||||
// Tests of 'referrers' query.
|
||||
// See go.tools/oracle/oracle_test.go for explanation.
|
||||
// See referrers.golden for expected query results.
|
||||
|
||||
import "lib"
|
||||
|
||||
type s struct { // @referrers type " s "
|
||||
f int
|
||||
}
|
||||
|
||||
type T int
|
||||
|
||||
func main() {
|
||||
var v lib.Type = lib.Const // @referrers ref-package "lib"
|
||||
_ = v.Method // @referrers ref-method "Method"
|
||||
_ = v.Method
|
||||
v++ //@referrers ref-local "v"
|
||||
v++
|
||||
|
||||
_ = s{}.f // @referrers ref-field "f"
|
||||
|
||||
var s2 s
|
||||
s2.f = 1
|
||||
}
|
||||
|
||||
// Test //line directives:
|
||||
|
||||
type U int // @referrers ref-type-U "U"
|
||||
|
||||
//line nosuchfile.y:123
|
||||
var u1 U
|
||||
var u2 U
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
-------- @referrers package-decl --------
|
||||
1 references to package main ("referrers")
|
||||
var _ renamed.T
|
||||
|
||||
-------- @referrers type --------
|
||||
2 references to type s struct{f int}
|
||||
_ = s{}.f // @referrers ref-field "f"
|
||||
var s2 s
|
||||
|
||||
-------- @referrers ref-package --------
|
||||
4 references to package lib
|
||||
_ = (lib.Type).Method // ref from external test package
|
||||
_ = (lib.Type).Method // ref from internal test package
|
||||
var v lib.Type = lib.Const // @referrers ref-package "lib"
|
||||
var v lib.Type = lib.Const // @referrers ref-package "lib"
|
||||
|
||||
-------- @referrers ref-method --------
|
||||
7 references to func (lib.Type).Method(x *int) *int
|
||||
p := t.Method(&a) // @describe ref-method "Method"
|
||||
_ = v.Method // @referrers ref-method "Method"
|
||||
_ = v.Method
|
||||
_ = (lib.Type).Method // ref from external test package
|
||||
_ = (lib.Type).Method // ref from internal test package
|
||||
_ = v.Method // @referrers ref-method "Method"
|
||||
_ = v.Method
|
||||
|
||||
-------- @referrers ref-local --------
|
||||
4 references to var v lib.Type
|
||||
_ = v.Method // @referrers ref-method "Method"
|
||||
_ = v.Method
|
||||
v++ //@referrers ref-local "v"
|
||||
v++
|
||||
|
||||
-------- @referrers ref-field --------
|
||||
2 references to field f int
|
||||
_ = s{}.f // @referrers ref-field "f"
|
||||
s2.f = 1
|
||||
|
||||
-------- @referrers ref-type-U --------
|
||||
2 references to type U int
|
||||
open testdata/src/referrers/nosuchfile.y: no such file or directory (+ 1 more refs in this file)
|
||||
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
package main
|
||||
|
||||
// This is a test of 'pointsto', but we split it into a separate file
|
||||
// so that pointsto.go doesn't have to import "reflect" each time.
|
||||
|
||||
import "reflect"
|
||||
|
||||
var a int
|
||||
var b bool
|
||||
|
||||
func main() {
|
||||
m := make(map[*int]*bool)
|
||||
m[&a] = &b
|
||||
|
||||
mrv := reflect.ValueOf(m)
|
||||
if a > 0 {
|
||||
mrv = reflect.ValueOf(&b)
|
||||
}
|
||||
if a > 0 {
|
||||
mrv = reflect.ValueOf(&a)
|
||||
}
|
||||
|
||||
_ = mrv // @pointsto mrv "mrv"
|
||||
p1 := mrv.Interface() // @pointsto p1 "p1"
|
||||
p2 := mrv.MapKeys() // @pointsto p2 "p2"
|
||||
p3 := p2[0] // @pointsto p3 "p3"
|
||||
p4 := reflect.TypeOf(p1) // @pointsto p4 "p4"
|
||||
|
||||
_, _, _, _ = p1, p2, p3, p4
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
-------- @pointsto mrv --------
|
||||
this reflect.Value may contain these dynamic types:
|
||||
*bool, may point to:
|
||||
main.b
|
||||
*int, may point to:
|
||||
main.a
|
||||
map[*int]*bool, may point to:
|
||||
makemap
|
||||
|
||||
-------- @pointsto p1 --------
|
||||
this interface{} may contain these dynamic types:
|
||||
*bool, may point to:
|
||||
main.b
|
||||
*int, may point to:
|
||||
main.a
|
||||
map[*int]*bool, may point to:
|
||||
makemap
|
||||
|
||||
-------- @pointsto p2 --------
|
||||
this []reflect.Value may point to these objects:
|
||||
<alloc in (reflect.Value).MapKeys>
|
||||
|
||||
-------- @pointsto p3 --------
|
||||
this reflect.Value may contain these dynamic types:
|
||||
*int, may point to:
|
||||
main.a
|
||||
|
||||
-------- @pointsto p4 --------
|
||||
this reflect.Type may contain these dynamic types:
|
||||
*reflect.rtype, may point to:
|
||||
*bool
|
||||
*int
|
||||
map[*int]*bool
|
||||
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
package main
|
||||
|
||||
// Tests of 'what' queries, -format=json.
|
||||
// See go.tools/oracle/oracle_test.go for explanation.
|
||||
// See what-json.golden for expected query results.
|
||||
|
||||
func main() {
|
||||
f() // @what call "f"
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
-------- @what call --------
|
||||
{
|
||||
"mode": "what",
|
||||
"what": {
|
||||
"enclosing": [
|
||||
{
|
||||
"desc": "identifier",
|
||||
"start": 179,
|
||||
"end": 180
|
||||
},
|
||||
{
|
||||
"desc": "function call",
|
||||
"start": 179,
|
||||
"end": 182
|
||||
},
|
||||
{
|
||||
"desc": "expression statement",
|
||||
"start": 179,
|
||||
"end": 182
|
||||
},
|
||||
{
|
||||
"desc": "block",
|
||||
"start": 176,
|
||||
"end": 202
|
||||
},
|
||||
{
|
||||
"desc": "function declaration",
|
||||
"start": 164,
|
||||
"end": 202
|
||||
},
|
||||
{
|
||||
"desc": "source file",
|
||||
"start": 0,
|
||||
"end": 202
|
||||
}
|
||||
],
|
||||
"modes": [
|
||||
"callees",
|
||||
"callers",
|
||||
"callstack",
|
||||
"definition",
|
||||
"describe",
|
||||
"freevars",
|
||||
"implements",
|
||||
"pointsto",
|
||||
"referrers"
|
||||
],
|
||||
"srcdir": "testdata/src",
|
||||
"importpath": "what-json"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
package main // @what pkgdecl "main"
|
||||
|
||||
// Tests of 'what' queries.
|
||||
// See go.tools/oracle/oracle_test.go for explanation.
|
||||
// See what.golden for expected query results.
|
||||
|
||||
func main() {
|
||||
f() // @what call "f"
|
||||
var ch chan int // @what var "var"
|
||||
<-ch // @what recv "ch"
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
-------- @what pkgdecl --------
|
||||
identifier
|
||||
source file
|
||||
modes: [definition describe freevars implements pointsto referrers]
|
||||
srcdir: testdata/src
|
||||
import path: what
|
||||
|
||||
-------- @what call --------
|
||||
identifier
|
||||
function call
|
||||
expression statement
|
||||
block
|
||||
function declaration
|
||||
source file
|
||||
modes: [callees callers callstack definition describe freevars implements pointsto referrers]
|
||||
srcdir: testdata/src
|
||||
import path: what
|
||||
|
||||
-------- @what var --------
|
||||
variable declaration
|
||||
variable declaration statement
|
||||
block
|
||||
function declaration
|
||||
source file
|
||||
modes: [callers callstack describe freevars pointsto]
|
||||
srcdir: testdata/src
|
||||
import path: what
|
||||
|
||||
-------- @what recv --------
|
||||
identifier
|
||||
unary <- operation
|
||||
expression statement
|
||||
block
|
||||
function declaration
|
||||
source file
|
||||
modes: [callers callstack definition describe freevars implements peers pointsto referrers]
|
||||
srcdir: testdata/src
|
||||
import path: what
|
||||
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
package main
|
||||
|
||||
type errType string
|
||||
|
||||
const constErr errType = "blah"
|
||||
|
||||
func (et errType) Error() string {
|
||||
return string(et)
|
||||
}
|
||||
|
||||
var errVar error = errType("foo")
|
||||
|
||||
func genErr(i int) error {
|
||||
switch i {
|
||||
case 0:
|
||||
return constErr
|
||||
case 1:
|
||||
return errVar
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
err := genErr(0) // @whicherrs localerrs "err"
|
||||
_ = err
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
-------- @whicherrs localerrs --------
|
||||
this error may point to these globals:
|
||||
errVar
|
||||
this error may contain these constants:
|
||||
constErr
|
||||
this error may contain these dynamic types:
|
||||
errType
|
||||
|
||||
210
oracle/what.go
210
oracle/what.go
|
|
@ -1,210 +0,0 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package oracle
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/token"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
"golang.org/x/tools/oracle/serial"
|
||||
)
|
||||
|
||||
// what reports all the information about the query selection that can be
|
||||
// obtained from parsing only its containing source file.
|
||||
// It is intended to be a very low-latency query callable from GUI
|
||||
// tools, e.g. to populate a menu of options of slower queries about
|
||||
// the selected location.
|
||||
//
|
||||
func what(q *Query) error {
|
||||
qpos, err := fastQueryPos(q.Pos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.Fset = qpos.fset
|
||||
|
||||
// (ignore errors)
|
||||
srcdir, importPath, _ := guessImportPath(q.Fset.File(qpos.start).Name(), q.Build)
|
||||
|
||||
// Determine which query modes are applicable to the selection.
|
||||
enable := map[string]bool{
|
||||
"describe": true, // any syntax; always enabled
|
||||
}
|
||||
|
||||
if qpos.end > qpos.start {
|
||||
enable["freevars"] = true // nonempty selection?
|
||||
}
|
||||
|
||||
for _, n := range qpos.path {
|
||||
switch n := n.(type) {
|
||||
case *ast.Ident:
|
||||
enable["definition"] = true
|
||||
enable["referrers"] = true
|
||||
enable["implements"] = true
|
||||
case *ast.CallExpr:
|
||||
enable["callees"] = true
|
||||
case *ast.FuncDecl:
|
||||
enable["callers"] = true
|
||||
enable["callstack"] = true
|
||||
case *ast.SendStmt:
|
||||
enable["peers"] = true
|
||||
case *ast.UnaryExpr:
|
||||
if n.Op == token.ARROW {
|
||||
enable["peers"] = true
|
||||
}
|
||||
}
|
||||
|
||||
// For implements, we approximate findInterestingNode.
|
||||
if _, ok := enable["implements"]; !ok {
|
||||
switch n.(type) {
|
||||
case *ast.ArrayType,
|
||||
*ast.StructType,
|
||||
*ast.FuncType,
|
||||
*ast.InterfaceType,
|
||||
*ast.MapType,
|
||||
*ast.ChanType:
|
||||
enable["implements"] = true
|
||||
}
|
||||
}
|
||||
|
||||
// For pointsto, we approximate findInterestingNode.
|
||||
if _, ok := enable["pointsto"]; !ok {
|
||||
switch n.(type) {
|
||||
case ast.Stmt,
|
||||
*ast.ArrayType,
|
||||
*ast.StructType,
|
||||
*ast.FuncType,
|
||||
*ast.InterfaceType,
|
||||
*ast.MapType,
|
||||
*ast.ChanType:
|
||||
enable["pointsto"] = false // not an expr
|
||||
|
||||
case ast.Expr, ast.Decl, *ast.ValueSpec:
|
||||
enable["pointsto"] = true // an expr, maybe
|
||||
|
||||
default:
|
||||
// Comment, Field, KeyValueExpr, etc: ascend.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we don't have an exact selection, disable modes that need one.
|
||||
if !qpos.exact {
|
||||
enable["callees"] = false
|
||||
enable["pointsto"] = false
|
||||
enable["whicherrs"] = false
|
||||
enable["describe"] = false
|
||||
}
|
||||
|
||||
var modes []string
|
||||
for mode := range enable {
|
||||
modes = append(modes, mode)
|
||||
}
|
||||
sort.Strings(modes)
|
||||
|
||||
q.result = &whatResult{
|
||||
path: qpos.path,
|
||||
srcdir: srcdir,
|
||||
importPath: importPath,
|
||||
modes: modes,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// guessImportPath finds the package containing filename, and returns
|
||||
// its source directory (an element of $GOPATH) and its import path
|
||||
// relative to it.
|
||||
//
|
||||
// TODO(adonovan): what about _test.go files that are not part of the
|
||||
// package?
|
||||
//
|
||||
func guessImportPath(filename string, buildContext *build.Context) (srcdir, importPath string, err error) {
|
||||
absFile, err := filepath.Abs(filename)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("can't form absolute path of %s", filename)
|
||||
return
|
||||
}
|
||||
absFileDir := segments(filepath.Dir(absFile))
|
||||
|
||||
// Find the innermost directory in $GOPATH that encloses filename.
|
||||
minD := 1024
|
||||
for _, gopathDir := range buildContext.SrcDirs() {
|
||||
absDir, err := filepath.Abs(gopathDir)
|
||||
if err != nil {
|
||||
continue // e.g. non-existent dir on $GOPATH
|
||||
}
|
||||
d := prefixLen(segments(absDir), absFileDir)
|
||||
// If there are multiple matches,
|
||||
// prefer the innermost enclosing directory
|
||||
// (smallest d).
|
||||
if d >= 0 && d < minD {
|
||||
minD = d
|
||||
srcdir = gopathDir
|
||||
importPath = strings.Join(absFileDir[len(absFileDir)-minD:], string(os.PathSeparator))
|
||||
}
|
||||
}
|
||||
if srcdir == "" {
|
||||
err = fmt.Errorf("directory %s is not beneath any of these GOROOT/GOPATH directories: %s",
|
||||
filepath.Dir(absFile), strings.Join(buildContext.SrcDirs(), ", "))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func segments(path string) []string {
|
||||
return strings.Split(path, string(os.PathSeparator))
|
||||
}
|
||||
|
||||
// prefixLen returns the length of the remainder of y if x is a prefix
|
||||
// of y, a negative number otherwise.
|
||||
func prefixLen(x, y []string) int {
|
||||
d := len(y) - len(x)
|
||||
if d >= 0 {
|
||||
for i := range x {
|
||||
if y[i] != x[i] {
|
||||
return -1 // not a prefix
|
||||
}
|
||||
}
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
type whatResult struct {
|
||||
path []ast.Node
|
||||
modes []string
|
||||
srcdir string
|
||||
importPath string
|
||||
}
|
||||
|
||||
func (r *whatResult) display(printf printfFunc) {
|
||||
for _, n := range r.path {
|
||||
printf(n, "%s", astutil.NodeDescription(n))
|
||||
}
|
||||
printf(nil, "modes: %s", r.modes)
|
||||
printf(nil, "srcdir: %s", r.srcdir)
|
||||
printf(nil, "import path: %s", r.importPath)
|
||||
}
|
||||
|
||||
func (r *whatResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
||||
var enclosing []serial.SyntaxNode
|
||||
for _, n := range r.path {
|
||||
enclosing = append(enclosing, serial.SyntaxNode{
|
||||
Description: astutil.NodeDescription(n),
|
||||
Start: fset.Position(n.Pos()).Offset,
|
||||
End: fset.Position(n.End()).Offset,
|
||||
})
|
||||
}
|
||||
res.What = &serial.What{
|
||||
Modes: r.modes,
|
||||
SrcDir: r.srcdir,
|
||||
ImportPath: r.importPath,
|
||||
Enclosing: enclosing,
|
||||
}
|
||||
}
|
||||
|
|
@ -1,328 +0,0 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package oracle
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"sort"
|
||||
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/ssa/ssautil"
|
||||
"golang.org/x/tools/oracle/serial"
|
||||
)
|
||||
|
||||
var builtinErrorType = types.Universe.Lookup("error").Type()
|
||||
|
||||
// whicherrs takes an position to an error and tries to find all types, constants
|
||||
// and global value which a given error can point to and which can be checked from the
|
||||
// scope where the error lives.
|
||||
// In short, it returns a list of things that can be checked against in order to handle
|
||||
// an error properly.
|
||||
//
|
||||
// TODO(dmorsing): figure out if fields in errors like *os.PathError.Err
|
||||
// can be queried recursively somehow.
|
||||
func whicherrs(q *Query) error {
|
||||
lconf := loader.Config{Build: q.Build}
|
||||
|
||||
if err := setPTAScope(&lconf, q.Scope); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load/parse/type-check the program.
|
||||
lprog, err := lconf.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.Fset = lprog.Fset
|
||||
|
||||
qpos, err := parseQueryPos(lprog, q.Pos, true) // needs exact pos
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug)
|
||||
|
||||
ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
path, action := findInterestingNode(qpos.info, qpos.path)
|
||||
if action != actionExpr {
|
||||
return fmt.Errorf("whicherrs wants an expression; got %s",
|
||||
astutil.NodeDescription(qpos.path[0]))
|
||||
}
|
||||
var expr ast.Expr
|
||||
var obj types.Object
|
||||
switch n := path[0].(type) {
|
||||
case *ast.ValueSpec:
|
||||
// ambiguous ValueSpec containing multiple names
|
||||
return fmt.Errorf("multiple value specification")
|
||||
case *ast.Ident:
|
||||
obj = qpos.info.ObjectOf(n)
|
||||
expr = n
|
||||
case ast.Expr:
|
||||
expr = n
|
||||
default:
|
||||
return fmt.Errorf("unexpected AST for expr: %T", n)
|
||||
}
|
||||
|
||||
typ := qpos.info.TypeOf(expr)
|
||||
if !types.Identical(typ, builtinErrorType) {
|
||||
return fmt.Errorf("selection is not an expression of type 'error'")
|
||||
}
|
||||
// Determine the ssa.Value for the expression.
|
||||
var value ssa.Value
|
||||
if obj != nil {
|
||||
// def/ref of func/var object
|
||||
value, _, err = ssaValueForIdent(prog, qpos.info, obj, path)
|
||||
} else {
|
||||
value, _, err = ssaValueForExpr(prog, qpos.info, path)
|
||||
}
|
||||
if err != nil {
|
||||
return err // e.g. trivially dead code
|
||||
}
|
||||
|
||||
// Defer SSA construction till after errors are reported.
|
||||
prog.Build()
|
||||
|
||||
globals := findVisibleErrs(prog, qpos)
|
||||
constants := findVisibleConsts(prog, qpos)
|
||||
|
||||
res := &whicherrsResult{
|
||||
qpos: qpos,
|
||||
errpos: expr.Pos(),
|
||||
}
|
||||
|
||||
// TODO(adonovan): the following code is heavily duplicated
|
||||
// w.r.t. "pointsto". Refactor?
|
||||
|
||||
// Find the instruction which initialized the
|
||||
// global error. If more than one instruction has stored to the global
|
||||
// remove the global from the set of values that we want to query.
|
||||
allFuncs := ssautil.AllFunctions(prog)
|
||||
for fn := range allFuncs {
|
||||
for _, b := range fn.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
store, ok := instr.(*ssa.Store)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
gval, ok := store.Addr.(*ssa.Global)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
gbl, ok := globals[gval]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
// we already found a store to this global
|
||||
// The normal error define is just one store in the init
|
||||
// so we just remove this global from the set we want to query
|
||||
if gbl != nil {
|
||||
delete(globals, gval)
|
||||
}
|
||||
globals[gval] = store.Val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ptaConfig.AddQuery(value)
|
||||
for _, v := range globals {
|
||||
ptaConfig.AddQuery(v)
|
||||
}
|
||||
|
||||
ptares := ptrAnalysis(ptaConfig)
|
||||
valueptr := ptares.Queries[value]
|
||||
for g, v := range globals {
|
||||
ptr, ok := ptares.Queries[v]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if !ptr.MayAlias(valueptr) {
|
||||
continue
|
||||
}
|
||||
res.globals = append(res.globals, g)
|
||||
}
|
||||
pts := valueptr.PointsTo()
|
||||
dedup := make(map[*ssa.NamedConst]bool)
|
||||
for _, label := range pts.Labels() {
|
||||
// These values are either MakeInterfaces or reflect
|
||||
// generated interfaces. For the purposes of this
|
||||
// analysis, we don't care about reflect generated ones
|
||||
makeiface, ok := label.Value().(*ssa.MakeInterface)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
constval, ok := makeiface.X.(*ssa.Const)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
c := constants[*constval]
|
||||
if c != nil && !dedup[c] {
|
||||
dedup[c] = true
|
||||
res.consts = append(res.consts, c)
|
||||
}
|
||||
}
|
||||
concs := pts.DynamicTypes()
|
||||
concs.Iterate(func(conc types.Type, _ interface{}) {
|
||||
// go/types is a bit annoying here.
|
||||
// We want to find all the types that we can
|
||||
// typeswitch or assert to. This means finding out
|
||||
// if the type pointed to can be seen by us.
|
||||
//
|
||||
// For the purposes of this analysis, the type is always
|
||||
// either a Named type or a pointer to one.
|
||||
// There are cases where error can be implemented
|
||||
// by unnamed types, but in that case, we can't assert to
|
||||
// it, so we don't care about it for this analysis.
|
||||
var name *types.TypeName
|
||||
switch t := conc.(type) {
|
||||
case *types.Pointer:
|
||||
named, ok := t.Elem().(*types.Named)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
name = named.Obj()
|
||||
case *types.Named:
|
||||
name = t.Obj()
|
||||
default:
|
||||
return
|
||||
}
|
||||
if !isAccessibleFrom(name, qpos.info.Pkg) {
|
||||
return
|
||||
}
|
||||
res.types = append(res.types, &errorType{conc, name})
|
||||
})
|
||||
sort.Sort(membersByPosAndString(res.globals))
|
||||
sort.Sort(membersByPosAndString(res.consts))
|
||||
sort.Sort(sorterrorType(res.types))
|
||||
|
||||
q.result = res
|
||||
return nil
|
||||
}
|
||||
|
||||
// findVisibleErrs returns a mapping from each package-level variable of type "error" to nil.
|
||||
func findVisibleErrs(prog *ssa.Program, qpos *queryPos) map[*ssa.Global]ssa.Value {
|
||||
globals := make(map[*ssa.Global]ssa.Value)
|
||||
for _, pkg := range prog.AllPackages() {
|
||||
for _, mem := range pkg.Members {
|
||||
gbl, ok := mem.(*ssa.Global)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
gbltype := gbl.Type()
|
||||
// globals are always pointers
|
||||
if !types.Identical(deref(gbltype), builtinErrorType) {
|
||||
continue
|
||||
}
|
||||
if !isAccessibleFrom(gbl.Object(), qpos.info.Pkg) {
|
||||
continue
|
||||
}
|
||||
globals[gbl] = nil
|
||||
}
|
||||
}
|
||||
return globals
|
||||
}
|
||||
|
||||
// findVisibleConsts returns a mapping from each package-level constant assignable to type "error", to nil.
|
||||
func findVisibleConsts(prog *ssa.Program, qpos *queryPos) map[ssa.Const]*ssa.NamedConst {
|
||||
constants := make(map[ssa.Const]*ssa.NamedConst)
|
||||
for _, pkg := range prog.AllPackages() {
|
||||
for _, mem := range pkg.Members {
|
||||
obj, ok := mem.(*ssa.NamedConst)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
consttype := obj.Type()
|
||||
if !types.AssignableTo(consttype, builtinErrorType) {
|
||||
continue
|
||||
}
|
||||
if !isAccessibleFrom(obj.Object(), qpos.info.Pkg) {
|
||||
continue
|
||||
}
|
||||
constants[*obj.Value] = obj
|
||||
}
|
||||
}
|
||||
|
||||
return constants
|
||||
}
|
||||
|
||||
type membersByPosAndString []ssa.Member
|
||||
|
||||
func (a membersByPosAndString) Len() int { return len(a) }
|
||||
func (a membersByPosAndString) 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 membersByPosAndString) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
type sorterrorType []*errorType
|
||||
|
||||
func (a sorterrorType) Len() int { return len(a) }
|
||||
func (a sorterrorType) Less(i, j int) bool {
|
||||
cmp := a[i].obj.Pos() - a[j].obj.Pos()
|
||||
return cmp < 0 || cmp == 0 && a[i].typ.String() < a[j].typ.String()
|
||||
}
|
||||
func (a sorterrorType) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
type errorType struct {
|
||||
typ types.Type // concrete type N or *N that implements error
|
||||
obj *types.TypeName // the named type N
|
||||
}
|
||||
|
||||
type whicherrsResult struct {
|
||||
qpos *queryPos
|
||||
errpos token.Pos
|
||||
globals []ssa.Member
|
||||
consts []ssa.Member
|
||||
types []*errorType
|
||||
}
|
||||
|
||||
func (r *whicherrsResult) display(printf printfFunc) {
|
||||
if len(r.globals) > 0 {
|
||||
printf(r.qpos, "this error may point to these globals:")
|
||||
for _, g := range r.globals {
|
||||
printf(g.Pos(), "\t%s", g.RelString(r.qpos.info.Pkg))
|
||||
}
|
||||
}
|
||||
if len(r.consts) > 0 {
|
||||
printf(r.qpos, "this error may contain these constants:")
|
||||
for _, c := range r.consts {
|
||||
printf(c.Pos(), "\t%s", c.RelString(r.qpos.info.Pkg))
|
||||
}
|
||||
}
|
||||
if len(r.types) > 0 {
|
||||
printf(r.qpos, "this error may contain these dynamic types:")
|
||||
for _, t := range r.types {
|
||||
printf(t.obj.Pos(), "\t%s", r.qpos.typeString(t.typ))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *whicherrsResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
||||
we := &serial.WhichErrs{}
|
||||
we.ErrPos = fset.Position(r.errpos).String()
|
||||
for _, g := range r.globals {
|
||||
we.Globals = append(we.Globals, fset.Position(g.Pos()).String())
|
||||
}
|
||||
for _, c := range r.consts {
|
||||
we.Constants = append(we.Constants, fset.Position(c.Pos()).String())
|
||||
}
|
||||
for _, t := range r.types {
|
||||
var et serial.WhichErrsType
|
||||
et.Type = r.qpos.typeString(t.typ)
|
||||
et.Position = fset.Position(t.obj.Pos()).String()
|
||||
we.Types = append(we.Types, et)
|
||||
}
|
||||
res.WhichErrs = we
|
||||
}
|
||||
Loading…
Reference in New Issue