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:
Alan Donovan 2016-10-13 11:53:44 -04:00
parent b44548ae6a
commit d9f9484612
66 changed files with 0 additions and 8170 deletions

View File

@ -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"

View File

@ -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)
}
}

View File

@ -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)

View File

@ -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>)

View File

@ -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.

View File

@ -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] }

View File

@ -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
}

View File

@ -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,
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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()
}

View File

@ -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()
}

View File

@ -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] }

View File

@ -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")
}

View File

@ -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)
}
}
}
}
}

View File

@ -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] }

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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"`
}

View File

@ -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 "^"
})
}

View File

@ -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"
}
]
}
}

View File

@ -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 "^"
}

View File

@ -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

View File

@ -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() {}

View File

@ -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"
}
]
}
}
}

View File

@ -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() {}

View File

@ -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()

View File

@ -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"
}
}

View File

@ -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

View File

@ -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"

View File

@ -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"
}
]
}
}

View File

@ -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"
}

View File

@ -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"
}
]
}
}

View File

@ -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"
}

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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()
}

View File

@ -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

View File

@ -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)
}

View File

@ -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()
}

View File

@ -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 "<-"
}
}

View File

@ -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"
]
}
}

View File

@ -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"
}

View File

@ -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

View File

@ -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() {}

View File

@ -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"
}
]
}

View File

@ -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() {}

View File

@ -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?)

View File

@ -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
}

View File

@ -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"
]
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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

View File

@ -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)

View 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
}

View File

@ -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

View File

@ -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"
}

View File

@ -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"
}
}

View File

@ -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"
}

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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,
}
}

View File

@ -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
}