cmd/oracle: oracle is dead, long live guru
Change-Id: I0f729b1477d8b14f255538414087d25f99b20c1e Reviewed-on: https://go-review.googlesource.com/30982 Reviewed-by: Robert Griesemer <gri@golang.org>
This commit is contained in:
parent
b44548ae6a
commit
d9f9484612
|
|
@ -1,50 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
#
|
|
||||||
# Simple test of Go oracle/Emacs integration.
|
|
||||||
# Requires that GOROOT and GOPATH are set.
|
|
||||||
# Side effect: builds and installs oracle in $GOROOT.
|
|
||||||
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
[ -z "$GOROOT" ] && { echo "Error: GOROOT is unset." >&2; exit 1; }
|
|
||||||
[ -z "$GOPATH" ] && { echo "Error: GOPATH is unset." >&2; exit 1; }
|
|
||||||
|
|
||||||
log=/tmp/$(basename $0)-$$.log
|
|
||||||
thisdir=$(dirname $0)
|
|
||||||
|
|
||||||
function die() {
|
|
||||||
echo "Error: $@."
|
|
||||||
cat $log
|
|
||||||
exit 1
|
|
||||||
} >&2
|
|
||||||
|
|
||||||
trap "rm -f $log" EXIT
|
|
||||||
|
|
||||||
# Build and install oracle.
|
|
||||||
go get golang.org/x/tools/cmd/oracle || die "'go get' failed"
|
|
||||||
mv -f $GOPATH/bin/oracle $GOROOT/bin/
|
|
||||||
$GOROOT/bin/oracle >$log 2>&1 || true # (prints usage and exits 1)
|
|
||||||
grep -q "Run.*help" $log || die "$GOROOT/bin/oracle not installed"
|
|
||||||
|
|
||||||
|
|
||||||
# Run Emacs, set the scope to the oracle tool itself,
|
|
||||||
# load ./main.go, and describe the "fmt" import.
|
|
||||||
emacs --batch --no-splash --no-window-system --no-init \
|
|
||||||
--load $GOROOT/misc/emacs/go-mode.el \
|
|
||||||
--load $thisdir/oracle.el \
|
|
||||||
--eval '
|
|
||||||
(progn
|
|
||||||
(setq go-oracle-scope "golang.org/x/tools/cmd/oracle")
|
|
||||||
(find-file "'$thisdir'/main.go")
|
|
||||||
(search-forward "\"fmt\"")
|
|
||||||
(backward-char)
|
|
||||||
(go-oracle-describe)
|
|
||||||
(princ (with-current-buffer "*go-oracle*"
|
|
||||||
(buffer-substring-no-properties (point-min) (point-max))))
|
|
||||||
(kill-emacs 0))
|
|
||||||
' main.go >$log 2>&1 || die "emacs command failed"
|
|
||||||
|
|
||||||
# Check that Println is mentioned.
|
|
||||||
grep -q "fmt/print.go.*func Println" $log || die "didn't find expected lines in log; got:"
|
|
||||||
|
|
||||||
echo "PASS"
|
|
||||||
|
|
@ -1,208 +0,0 @@
|
||||||
// Copyright 2013 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// oracle: a tool for answering questions about Go source code.
|
|
||||||
// http://golang.org/s/oracle-design
|
|
||||||
// http://golang.org/s/oracle-user-manual
|
|
||||||
//
|
|
||||||
// DEPRECATED: oracle has been superseded by guru;
|
|
||||||
// see https://golang.org/s/using-guru for details.
|
|
||||||
// This package will be deleted on October 1, 2016.
|
|
||||||
//
|
|
||||||
// Run with -help flag or help subcommand for usage information.
|
|
||||||
//
|
|
||||||
package main // import "golang.org/x/tools/cmd/oracle"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"encoding/json"
|
|
||||||
"encoding/xml"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"go/build"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"runtime/pprof"
|
|
||||||
|
|
||||||
"golang.org/x/tools/go/buildutil"
|
|
||||||
"golang.org/x/tools/go/loader"
|
|
||||||
"golang.org/x/tools/oracle"
|
|
||||||
)
|
|
||||||
|
|
||||||
var posFlag = flag.String("pos", "",
|
|
||||||
"Filename and byte offset or extent of a syntax element about which to query, "+
|
|
||||||
"e.g. foo.go:#123,#456, bar.go:#123.")
|
|
||||||
|
|
||||||
var ptalogFlag = flag.String("ptalog", "",
|
|
||||||
"Location of the points-to analysis log file, or empty to disable logging.")
|
|
||||||
|
|
||||||
var formatFlag = flag.String("format", "plain", "Output format. One of {plain,json,xml}.")
|
|
||||||
|
|
||||||
var reflectFlag = flag.Bool("reflect", false, "Analyze reflection soundly (slow).")
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
flag.Var((*buildutil.TagsFlag)(&build.Default.BuildTags), "tags", buildutil.TagsFlagDoc)
|
|
||||||
}
|
|
||||||
|
|
||||||
const useHelp = "Run 'oracle -help' for more information.\n"
|
|
||||||
|
|
||||||
const helpMessage = `Go source code oracle.
|
|
||||||
Usage: oracle [<flag> ...] <mode> <args> ...
|
|
||||||
|
|
||||||
The -format flag controls the output format:
|
|
||||||
plain an editor-friendly format in which every line of output
|
|
||||||
is of the form "pos: text", where pos is "-" if unknown.
|
|
||||||
json structured data in JSON syntax.
|
|
||||||
xml structured data in XML syntax.
|
|
||||||
|
|
||||||
The -pos flag is required in all modes.
|
|
||||||
|
|
||||||
The mode argument determines the query to perform:
|
|
||||||
|
|
||||||
callees show possible targets of selected function call
|
|
||||||
callers show possible callers of selected function
|
|
||||||
callstack show path from callgraph root to selected function
|
|
||||||
definition show declaration of selected identifier
|
|
||||||
describe describe selected syntax: definition, methods, etc
|
|
||||||
freevars show free variables of selection
|
|
||||||
implements show 'implements' relation for selected type or method
|
|
||||||
peers show send/receive corresponding to selected channel op
|
|
||||||
pointsto show variables to which the selected pointer may point
|
|
||||||
referrers show all refs to entity denoted by selected identifier
|
|
||||||
what show basic information about the selected syntax node
|
|
||||||
whicherrs show possible values of the selected error variable
|
|
||||||
|
|
||||||
The user manual is available here: http://golang.org/s/oracle-user-manual
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
|
|
||||||
Describe the syntax at offset 530 in this file (an import spec):
|
|
||||||
% oracle -pos=src/golang.org/x/tools/cmd/oracle/main.go:#530 describe \
|
|
||||||
golang.org/x/tools/cmd/oracle
|
|
||||||
|
|
||||||
` + loader.FromArgsUsage
|
|
||||||
|
|
||||||
var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// If $GOMAXPROCS isn't set, use the full capacity of the machine.
|
|
||||||
// For small machines, use at least 4 threads.
|
|
||||||
if os.Getenv("GOMAXPROCS") == "" {
|
|
||||||
n := runtime.NumCPU()
|
|
||||||
if n < 4 {
|
|
||||||
n = 4
|
|
||||||
}
|
|
||||||
runtime.GOMAXPROCS(n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func printHelp() {
|
|
||||||
fmt.Fprintln(os.Stderr, helpMessage)
|
|
||||||
fmt.Fprintln(os.Stderr, "Flags:")
|
|
||||||
flag.PrintDefaults()
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// Don't print full help unless -help was requested.
|
|
||||||
// Just gently remind users that it's there.
|
|
||||||
flag.Usage = func() { fmt.Fprint(os.Stderr, useHelp) }
|
|
||||||
flag.CommandLine.Init(os.Args[0], flag.ContinueOnError) // hack
|
|
||||||
if err := flag.CommandLine.Parse(os.Args[1:]); err != nil {
|
|
||||||
// (err has already been printed)
|
|
||||||
if err == flag.ErrHelp {
|
|
||||||
printHelp()
|
|
||||||
}
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
args := flag.Args()
|
|
||||||
if len(args) == 0 || args[0] == "" {
|
|
||||||
fmt.Fprint(os.Stderr, "oracle: a mode argument is required.\n"+useHelp)
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
mode := args[0]
|
|
||||||
args = args[1:]
|
|
||||||
if mode == "help" {
|
|
||||||
printHelp()
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up points-to analysis log file.
|
|
||||||
var ptalog io.Writer
|
|
||||||
if *ptalogFlag != "" {
|
|
||||||
if f, err := os.Create(*ptalogFlag); err != nil {
|
|
||||||
log.Fatalf("Failed to create PTA log file: %s", err)
|
|
||||||
} else {
|
|
||||||
buf := bufio.NewWriter(f)
|
|
||||||
ptalog = buf
|
|
||||||
defer func() {
|
|
||||||
if err := buf.Flush(); err != nil {
|
|
||||||
log.Printf("flush: %s", err)
|
|
||||||
}
|
|
||||||
if err := f.Close(); err != nil {
|
|
||||||
log.Printf("close: %s", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Profiling support.
|
|
||||||
if *cpuprofile != "" {
|
|
||||||
f, err := os.Create(*cpuprofile)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
pprof.StartCPUProfile(f)
|
|
||||||
defer pprof.StopCPUProfile()
|
|
||||||
}
|
|
||||||
|
|
||||||
// -format flag
|
|
||||||
switch *formatFlag {
|
|
||||||
case "json", "plain", "xml":
|
|
||||||
// ok
|
|
||||||
default:
|
|
||||||
fmt.Fprintf(os.Stderr, "oracle: illegal -format value: %q.\n"+useHelp, *formatFlag)
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ask the oracle.
|
|
||||||
query := oracle.Query{
|
|
||||||
Mode: mode,
|
|
||||||
Pos: *posFlag,
|
|
||||||
Build: &build.Default,
|
|
||||||
Scope: args,
|
|
||||||
PTALog: ptalog,
|
|
||||||
Reflection: *reflectFlag,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := oracle.Run(&query); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "oracle: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print the result.
|
|
||||||
switch *formatFlag {
|
|
||||||
case "json":
|
|
||||||
b, err := json.MarshalIndent(query.Serial(), "", "\t")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "oracle: JSON error: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
os.Stdout.Write(b)
|
|
||||||
|
|
||||||
case "xml":
|
|
||||||
b, err := xml.MarshalIndent(query.Serial(), "", "\t")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "oracle: XML error: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
os.Stdout.Write(b)
|
|
||||||
|
|
||||||
case "plain":
|
|
||||||
query.WriteTo(os.Stdout)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,230 +0,0 @@
|
||||||
;;;
|
|
||||||
;;; Integration of the Go 'oracle' analysis tool into Emacs.
|
|
||||||
;;;
|
|
||||||
;;; To install the Go oracle, run:
|
|
||||||
;;; % export GOROOT=... GOPATH=...
|
|
||||||
;;; % go get golang.org/x/tools/cmd/oracle
|
|
||||||
;;; % mv $GOPATH/bin/oracle $GOROOT/bin/
|
|
||||||
;;;
|
|
||||||
;;; Load this file into Emacs and set go-oracle-scope to your
|
|
||||||
;;; configuration. Then, find a file of Go source code, enable
|
|
||||||
;;; go-oracle-mode, select an expression of interest, and press `C-c C-o d'
|
|
||||||
;;; (for "describe") or run one of the other go-oracle-xxx commands.
|
|
||||||
;;;
|
|
||||||
;;; TODO(adonovan): simplify installation and configuration by making
|
|
||||||
;;; oracle a subcommand of 'go tool'.
|
|
||||||
|
|
||||||
(require 'compile)
|
|
||||||
(require 'go-mode)
|
|
||||||
(require 'cl)
|
|
||||||
|
|
||||||
(defgroup go-oracle nil
|
|
||||||
"Options specific to the Go oracle."
|
|
||||||
:group 'go)
|
|
||||||
|
|
||||||
(defcustom go-oracle-command "oracle"
|
|
||||||
"The Go oracle command."
|
|
||||||
:type 'string
|
|
||||||
:group 'go-oracle)
|
|
||||||
|
|
||||||
(defcustom go-oracle-scope ""
|
|
||||||
"The scope of the analysis. See `go-oracle-set-scope'."
|
|
||||||
:type 'string
|
|
||||||
:group 'go-oracle)
|
|
||||||
|
|
||||||
(defvar go-oracle--scope-history
|
|
||||||
nil
|
|
||||||
"History of values supplied to `go-oracle-set-scope'.")
|
|
||||||
|
|
||||||
;; Extend go-mode-map.
|
|
||||||
(let ((m go-mode-map))
|
|
||||||
(define-key m (kbd "C-c C-o t") #'go-oracle-describe) ; t for type
|
|
||||||
(define-key m (kbd "C-c C-o f") #'go-oracle-freevars)
|
|
||||||
(define-key m (kbd "C-c C-o g") #'go-oracle-callgraph)
|
|
||||||
(define-key m (kbd "C-c C-o i") #'go-oracle-implements)
|
|
||||||
(define-key m (kbd "C-c C-o c") #'go-oracle-peers) ; c for channel
|
|
||||||
(define-key m (kbd "C-c C-o r") #'go-oracle-referrers)
|
|
||||||
(define-key m (kbd "C-c C-o d") #'go-oracle-definition)
|
|
||||||
(define-key m (kbd "C-c C-o p") #'go-oracle-pointsto)
|
|
||||||
(define-key m (kbd "C-c C-o s") #'go-oracle-callstack)
|
|
||||||
(define-key m (kbd "C-c C-o <") #'go-oracle-callers)
|
|
||||||
(define-key m (kbd "C-c C-o >") #'go-oracle-callees)
|
|
||||||
(define-key m (kbd "<f5>") #'go-oracle-describe)
|
|
||||||
(define-key m (kbd "<f6>") #'go-oracle-referrers))
|
|
||||||
|
|
||||||
;; TODO(dominikh): Rethink set-scope some. Setting it to a file is
|
|
||||||
;; painful because it doesn't use find-file, and variables/~ aren't
|
|
||||||
;; expanded. Setting it to an import path is somewhat painful because
|
|
||||||
;; it doesn't make use of go-mode's import path completion. One option
|
|
||||||
;; would be having two different functions, but then we can't
|
|
||||||
;; automatically call it when no scope has been set. Also it wouldn't
|
|
||||||
;; easily allow specifying more than one file/package.
|
|
||||||
(defun go-oracle-set-scope ()
|
|
||||||
"Set the scope for the Go oracle, prompting the user to edit the
|
|
||||||
previous scope.
|
|
||||||
|
|
||||||
The scope specifies a set of arguments, separated by spaces.
|
|
||||||
It may be:
|
|
||||||
1) a set of packages whose main() functions will be analyzed.
|
|
||||||
2) a list of *.go filenames; they will treated like as a single
|
|
||||||
package (see #3).
|
|
||||||
3) a single package whose main() function and/or Test* functions
|
|
||||||
will be analyzed.
|
|
||||||
|
|
||||||
In the common case, this is similar to the argument(s) you would
|
|
||||||
specify to 'go build'."
|
|
||||||
(interactive)
|
|
||||||
(let ((scope (read-from-minibuffer "Go oracle scope: "
|
|
||||||
go-oracle-scope
|
|
||||||
nil
|
|
||||||
nil
|
|
||||||
'go-oracle--scope-history)))
|
|
||||||
(if (string-equal "" scope)
|
|
||||||
(error "You must specify a non-empty scope for the Go oracle"))
|
|
||||||
(setq go-oracle-scope scope)))
|
|
||||||
|
|
||||||
(defun go-oracle--run (mode &optional need-scope)
|
|
||||||
"Run the Go oracle in the specified MODE, passing it the
|
|
||||||
selected region of the current buffer. If NEED-SCOPE, prompt for
|
|
||||||
a scope if not already set. Process the output to replace each
|
|
||||||
file name with a small hyperlink. Display the result."
|
|
||||||
(if (not buffer-file-name)
|
|
||||||
(error "Cannot use oracle on a buffer without a file name"))
|
|
||||||
;; It's not sufficient to save a modified buffer since if
|
|
||||||
;; gofmt-before-save is on the before-save-hook, saving will
|
|
||||||
;; disturb the selected region.
|
|
||||||
(if (buffer-modified-p)
|
|
||||||
(error "Please save the buffer before invoking go-oracle"))
|
|
||||||
(and need-scope
|
|
||||||
(string-equal "" go-oracle-scope)
|
|
||||||
(go-oracle-set-scope))
|
|
||||||
(let* ((filename (file-truename buffer-file-name))
|
|
||||||
(posflag (if (use-region-p)
|
|
||||||
(format "-pos=%s:#%d,#%d"
|
|
||||||
filename
|
|
||||||
(1- (go--position-bytes (region-beginning)))
|
|
||||||
(1- (go--position-bytes (region-end))))
|
|
||||||
(format "-pos=%s:#%d"
|
|
||||||
filename
|
|
||||||
(1- (position-bytes (point))))))
|
|
||||||
(env-vars (go-root-and-paths))
|
|
||||||
(goroot-env (concat "GOROOT=" (car env-vars)))
|
|
||||||
(gopath-env (concat "GOPATH=" (mapconcat #'identity (cdr env-vars) ":"))))
|
|
||||||
(with-current-buffer (get-buffer-create "*go-oracle*")
|
|
||||||
(setq buffer-read-only nil)
|
|
||||||
(erase-buffer)
|
|
||||||
(insert "Go Oracle\n")
|
|
||||||
(let ((args (append (list go-oracle-command nil t nil posflag mode)
|
|
||||||
(split-string go-oracle-scope " " t))))
|
|
||||||
;; Log the command to *Messages*, for debugging.
|
|
||||||
(message "Command: %s:" args)
|
|
||||||
(message nil) ; clears/shrinks minibuffer
|
|
||||||
|
|
||||||
(message "Running oracle...")
|
|
||||||
;; Use dynamic binding to modify/restore the environment
|
|
||||||
(let ((process-environment (list* goroot-env gopath-env process-environment)))
|
|
||||||
(apply #'call-process args)))
|
|
||||||
(insert "\n")
|
|
||||||
(compilation-mode)
|
|
||||||
(setq compilation-error-screen-columns nil)
|
|
||||||
|
|
||||||
;; Hide the file/line info to save space.
|
|
||||||
;; Replace each with a little widget.
|
|
||||||
;; compilation-mode + this loop = slooow.
|
|
||||||
;; TODO(adonovan): have oracle give us JSON
|
|
||||||
;; and we'll do the markup directly.
|
|
||||||
(let ((buffer-read-only nil)
|
|
||||||
(p 1))
|
|
||||||
(while (not (null p))
|
|
||||||
(let ((np (compilation-next-single-property-change p 'compilation-message)))
|
|
||||||
(if np
|
|
||||||
(when (equal (line-number-at-pos p) (line-number-at-pos np))
|
|
||||||
;; Using a fixed width greatly improves readability, so
|
|
||||||
;; if the filename is longer than 20, show ".../last/17chars.go".
|
|
||||||
;; This usually includes the last segment of the package name.
|
|
||||||
;; Don't show the line or column number.
|
|
||||||
(let* ((loc (buffer-substring p np)) ; "/home/foo/go/pkg/file.go:1:2-3:4"
|
|
||||||
(i (search ":" loc)))
|
|
||||||
(setq loc (cond
|
|
||||||
((null i) "...")
|
|
||||||
((>= i 17) (concat "..." (substring loc (- i 17) i)))
|
|
||||||
(t (substring loc 0 i))))
|
|
||||||
;; np is (typically) the space following ":"; consume it too.
|
|
||||||
(put-text-property p np 'display (concat loc ":")))
|
|
||||||
(goto-char np)
|
|
||||||
(insert " ")
|
|
||||||
(incf np))) ; so we don't get stuck (e.g. on a panic stack dump)
|
|
||||||
(setq p np)))
|
|
||||||
(message nil))
|
|
||||||
|
|
||||||
(let ((w (display-buffer (current-buffer))))
|
|
||||||
(balance-windows)
|
|
||||||
(shrink-window-if-larger-than-buffer w)
|
|
||||||
(set-window-point w (point-min))))))
|
|
||||||
|
|
||||||
(defun go-oracle-callees ()
|
|
||||||
"Show possible callees of the function call at the current point."
|
|
||||||
(interactive)
|
|
||||||
(go-oracle--run "callees" t))
|
|
||||||
|
|
||||||
(defun go-oracle-callers ()
|
|
||||||
"Show the set of callers of the function containing the current point."
|
|
||||||
(interactive)
|
|
||||||
(go-oracle--run "callers" t))
|
|
||||||
|
|
||||||
(defun go-oracle-callgraph ()
|
|
||||||
"Show the callgraph of the current program."
|
|
||||||
(interactive)
|
|
||||||
(go-oracle--run "callgraph" t))
|
|
||||||
|
|
||||||
(defun go-oracle-callstack ()
|
|
||||||
"Show an arbitrary path from a root of the call graph to the
|
|
||||||
function containing the current point."
|
|
||||||
(interactive)
|
|
||||||
(go-oracle--run "callstack" t))
|
|
||||||
|
|
||||||
(defun go-oracle-definition ()
|
|
||||||
"Show the definition of the selected identifier."
|
|
||||||
(interactive)
|
|
||||||
(go-oracle--run "definition"))
|
|
||||||
|
|
||||||
(defun go-oracle-describe ()
|
|
||||||
"Describe the selected syntax, its kind, type and methods."
|
|
||||||
(interactive)
|
|
||||||
(go-oracle--run "describe"))
|
|
||||||
|
|
||||||
(defun go-oracle-pointsto ()
|
|
||||||
"Show what the selected expression points to."
|
|
||||||
(interactive)
|
|
||||||
(go-oracle--run "pointsto" t))
|
|
||||||
|
|
||||||
(defun go-oracle-implements ()
|
|
||||||
"Describe the 'implements' relation for types in the package
|
|
||||||
containing the current point."
|
|
||||||
(interactive)
|
|
||||||
(go-oracle--run "implements"))
|
|
||||||
|
|
||||||
(defun go-oracle-freevars ()
|
|
||||||
"Enumerate the free variables of the current selection."
|
|
||||||
(interactive)
|
|
||||||
(go-oracle--run "freevars"))
|
|
||||||
|
|
||||||
(defun go-oracle-peers ()
|
|
||||||
"Enumerate the set of possible corresponding sends/receives for
|
|
||||||
this channel receive/send operation."
|
|
||||||
(interactive)
|
|
||||||
(go-oracle--run "peers" t))
|
|
||||||
|
|
||||||
(defun go-oracle-referrers ()
|
|
||||||
"Enumerate all references to the object denoted by the selected
|
|
||||||
identifier."
|
|
||||||
(interactive)
|
|
||||||
(go-oracle--run "referrers"))
|
|
||||||
|
|
||||||
(defun go-oracle-whicherrs ()
|
|
||||||
"Show globals, constants and types to which the selected
|
|
||||||
expression (of type 'error') may refer."
|
|
||||||
(interactive)
|
|
||||||
(go-oracle--run "whicherrs" t))
|
|
||||||
|
|
||||||
(provide 'go-oracle)
|
|
||||||
|
|
@ -1,107 +0,0 @@
|
||||||
" -*- text -*-
|
|
||||||
" oracle.vim -- Vim integration for the Go oracle.
|
|
||||||
"
|
|
||||||
" Load with (e.g.) :source oracle.vim
|
|
||||||
" Call with (e.g.) :GoOracleDescribe
|
|
||||||
" while cursor or selection is over syntax of interest.
|
|
||||||
" Run :copen to show the quick-fix file.
|
|
||||||
"
|
|
||||||
" This is an absolutely rudimentary integration of the Go Oracle into
|
|
||||||
" Vim's quickfix mechanism and it needs a number of usability
|
|
||||||
" improvements before it can be practically useful to Vim users.
|
|
||||||
" Voluntary contributions welcomed!
|
|
||||||
"
|
|
||||||
" TODO(adonovan):
|
|
||||||
" - reject buffers with no filename.
|
|
||||||
" - hide all filenames in quickfix buffer.
|
|
||||||
|
|
||||||
" Get the path to the Go oracle executable.
|
|
||||||
func! s:go_oracle_bin()
|
|
||||||
let [ext, sep] = (has('win32') || has('win64') ? ['.exe', ';'] : ['', ':'])
|
|
||||||
let go_oracle = globpath(join(split($GOPATH, sep), ','), '/bin/oracle' . ext)
|
|
||||||
if go_oracle == ''
|
|
||||||
let go_oracle = globpath($GOROOT, '/bin/oracle' . ext)
|
|
||||||
endif
|
|
||||||
return go_oracle
|
|
||||||
endfunction
|
|
||||||
|
|
||||||
let s:go_oracle = s:go_oracle_bin()
|
|
||||||
|
|
||||||
func! s:qflist(output)
|
|
||||||
let qflist = []
|
|
||||||
" Parse GNU-style 'file:line.col-line.col: message' format.
|
|
||||||
let mx = '^\(\a:[\\/][^:]\+\|[^:]\+\):\(\d\+\):\(\d\+\):\(.*\)$'
|
|
||||||
for line in split(a:output, "\n")
|
|
||||||
let ml = matchlist(line, mx)
|
|
||||||
" Ignore non-match lines or warnings
|
|
||||||
if ml == [] || ml[4] =~ '^ warning:'
|
|
||||||
continue
|
|
||||||
endif
|
|
||||||
let item = {
|
|
||||||
\ 'filename': ml[1],
|
|
||||||
\ 'text': ml[4],
|
|
||||||
\ 'lnum': ml[2],
|
|
||||||
\ 'col': ml[3],
|
|
||||||
\}
|
|
||||||
let bnr = bufnr(fnameescape(ml[1]))
|
|
||||||
if bnr != -1
|
|
||||||
let item['bufnr'] = bnr
|
|
||||||
endif
|
|
||||||
call add(qflist, item)
|
|
||||||
endfor
|
|
||||||
call setqflist(qflist)
|
|
||||||
cwindow
|
|
||||||
endfun
|
|
||||||
|
|
||||||
func! s:getpos(l, c)
|
|
||||||
if &encoding != 'utf-8'
|
|
||||||
let buf = a:l == 1 ? '' : (join(getline(1, a:l-1), "\n") . "\n")
|
|
||||||
let buf .= a:c == 1 ? '' : getline('.')[:a:c-2]
|
|
||||||
return len(iconv(buf, &encoding, 'utf-8'))
|
|
||||||
endif
|
|
||||||
return line2byte(a:l) + (a:c-2)
|
|
||||||
endfun
|
|
||||||
|
|
||||||
func! s:RunOracle(mode, selected) range abort
|
|
||||||
let fname = expand('%:p')
|
|
||||||
let sname = get(g:, 'go_oracle_scope_file', fname)
|
|
||||||
if a:selected != -1
|
|
||||||
let pos1 = s:getpos(line("'<"), col("'<"))
|
|
||||||
let pos2 = s:getpos(line("'>"), col("'>"))
|
|
||||||
let cmd = printf('%s -pos=%s:#%d,#%d %s %s',
|
|
||||||
\ s:go_oracle,
|
|
||||||
\ shellescape(fname), pos1, pos2, a:mode, shellescape(sname))
|
|
||||||
else
|
|
||||||
let pos = s:getpos(line('.'), col('.'))
|
|
||||||
let cmd = printf('%s -pos=%s:#%d %s %s',
|
|
||||||
\ s:go_oracle,
|
|
||||||
\ shellescape(fname), pos, a:mode, shellescape(sname))
|
|
||||||
endif
|
|
||||||
call s:qflist(system(cmd))
|
|
||||||
endfun
|
|
||||||
|
|
||||||
" Describe the expression at the current point.
|
|
||||||
command! -range=% GoOracleDescribe
|
|
||||||
\ call s:RunOracle('describe', <count>)
|
|
||||||
|
|
||||||
" Show possible callees of the function call at the current point.
|
|
||||||
command! -range=% GoOracleCallees
|
|
||||||
\ call s:RunOracle('callees', <count>)
|
|
||||||
|
|
||||||
" Show the set of callers of the function containing the current point.
|
|
||||||
command! -range=% GoOracleCallers
|
|
||||||
\ call s:RunOracle('callers', <count>)
|
|
||||||
|
|
||||||
" Show the callgraph of the current program.
|
|
||||||
command! -range=% GoOracleCallgraph
|
|
||||||
\ call s:RunOracle('callgraph', <count>)
|
|
||||||
|
|
||||||
" Describe the 'implements' relation for types in the
|
|
||||||
" package containing the current point.
|
|
||||||
command! -range=% GoOracleImplements
|
|
||||||
\ call s:RunOracle('implements', <count>)
|
|
||||||
|
|
||||||
" Enumerate the set of possible corresponding sends/receives for
|
|
||||||
" this channel receive/send operation.
|
|
||||||
command! -range=% GoOracleChannelPeers
|
|
||||||
\ call s:RunOracle('peers', <count>)
|
|
||||||
83
oracle/TODO
83
oracle/TODO
|
|
@ -1,83 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
ORACLE TODO
|
|
||||||
===========
|
|
||||||
|
|
||||||
General
|
|
||||||
=======
|
|
||||||
|
|
||||||
Save unsaved editor buffers into an archive and provide that to the
|
|
||||||
tools, which should act as if they were saved.
|
|
||||||
|
|
||||||
Include complete pos/end information Serial output.
|
|
||||||
But beware that sometimes a single token (e.g. +) is more helpful
|
|
||||||
than the pos/end of the containing expression (e.g. x \n + \n y).
|
|
||||||
|
|
||||||
Specific queries
|
|
||||||
================
|
|
||||||
|
|
||||||
callers, callees
|
|
||||||
|
|
||||||
Use a type-based (e.g. RTA) callgraph when a callers/callees query is
|
|
||||||
outside the analysis scope.
|
|
||||||
|
|
||||||
implements
|
|
||||||
|
|
||||||
Make it require that the selection is a type, and show only the
|
|
||||||
implements relation as it applies to that type.
|
|
||||||
|
|
||||||
definition, referrers
|
|
||||||
|
|
||||||
definition: Make it work with qualified identifiers (SelectorExpr) too.
|
|
||||||
|
|
||||||
references: Make it work on things that are implicit idents, like
|
|
||||||
import specs, perhaps?
|
|
||||||
|
|
||||||
what
|
|
||||||
|
|
||||||
Report def/ref info if available.
|
|
||||||
Editors could use it to highlight all idents of the same local var.
|
|
||||||
|
|
||||||
More tests.
|
|
||||||
|
|
||||||
pointsto
|
|
||||||
|
|
||||||
When invoked on a function Ident, we get an error.
|
|
||||||
|
|
||||||
When invoked on a named return parameter, we get an error.
|
|
||||||
|
|
||||||
describe
|
|
||||||
|
|
||||||
When invoked on a var, we want to see the type and its methods.
|
|
||||||
|
|
||||||
Split "show type" and "describe syntax" into separate commands?
|
|
||||||
|
|
||||||
peers
|
|
||||||
|
|
||||||
Permit querying from a makechan, for...range, or reflective op.
|
|
||||||
|
|
||||||
Report aliasing reflect.{Send,Recv,Close} and close() operations.
|
|
||||||
|
|
||||||
New queries
|
|
||||||
|
|
||||||
"updaters": show all statements that may update the selected lvalue
|
|
||||||
(local, global, field, etc).
|
|
||||||
|
|
||||||
"creators": show all places where an object of type T is created
|
|
||||||
(&T{}, var t T, new(T), new(struct{array [3]T}), etc.
|
|
||||||
(Useful for datatypes whose zero value is not safe)
|
|
||||||
|
|
||||||
|
|
||||||
Editor-specific
|
|
||||||
===============
|
|
||||||
|
|
||||||
Add support for "what" to .el; clean up.
|
|
||||||
|
|
||||||
Emacs: use JSON to get the raw information from the oracle. Don't
|
|
||||||
open an editor buffer for simpler queries, just jump to the result
|
|
||||||
and/or display it in the modeline.
|
|
||||||
|
|
||||||
Emacs: go-root-and-paths depends on the current buffer, so be sure to
|
|
||||||
call it from within the source file, not the *go-oracle* buffer:
|
|
||||||
the user may have switched workspaces and the oracle should run in
|
|
||||||
the new one.
|
|
||||||
|
|
@ -1,260 +0,0 @@
|
||||||
// Copyright 2013 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build go1.5
|
|
||||||
|
|
||||||
package oracle
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"go/ast"
|
|
||||||
"go/token"
|
|
||||||
"go/types"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"golang.org/x/tools/go/loader"
|
|
||||||
"golang.org/x/tools/go/pointer"
|
|
||||||
"golang.org/x/tools/go/ssa"
|
|
||||||
"golang.org/x/tools/go/ssa/ssautil"
|
|
||||||
"golang.org/x/tools/oracle/serial"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Callees reports the possible callees of the function call site
|
|
||||||
// identified by the specified source location.
|
|
||||||
func callees(q *Query) error {
|
|
||||||
lconf := loader.Config{Build: q.Build}
|
|
||||||
|
|
||||||
if err := setPTAScope(&lconf, q.Scope); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load/parse/type-check the program.
|
|
||||||
lprog, err := lconf.Load()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
q.Fset = lprog.Fset
|
|
||||||
|
|
||||||
qpos, err := parseQueryPos(lprog, q.Pos, true) // needs exact pos
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine the enclosing call for the specified position.
|
|
||||||
var e *ast.CallExpr
|
|
||||||
for _, n := range qpos.path {
|
|
||||||
if e, _ = n.(*ast.CallExpr); e != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if e == nil {
|
|
||||||
return fmt.Errorf("there is no function call here")
|
|
||||||
}
|
|
||||||
// TODO(adonovan): issue an error if the call is "too far
|
|
||||||
// away" from the current selection, as this most likely is
|
|
||||||
// not what the user intended.
|
|
||||||
|
|
||||||
// Reject type conversions.
|
|
||||||
if qpos.info.Types[e.Fun].IsType() {
|
|
||||||
return fmt.Errorf("this is a type conversion, not a function call")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deal with obviously static calls before constructing SSA form.
|
|
||||||
// Some static calls may yet require SSA construction,
|
|
||||||
// e.g. f := func(){}; f().
|
|
||||||
switch funexpr := unparen(e.Fun).(type) {
|
|
||||||
case *ast.Ident:
|
|
||||||
switch obj := qpos.info.Uses[funexpr].(type) {
|
|
||||||
case *types.Builtin:
|
|
||||||
// Reject calls to built-ins.
|
|
||||||
return fmt.Errorf("this is a call to the built-in '%s' operator", obj.Name())
|
|
||||||
case *types.Func:
|
|
||||||
// This is a static function call
|
|
||||||
q.result = &calleesTypesResult{
|
|
||||||
site: e,
|
|
||||||
callee: obj,
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
case *ast.SelectorExpr:
|
|
||||||
sel := qpos.info.Selections[funexpr]
|
|
||||||
if sel == nil {
|
|
||||||
// qualified identifier.
|
|
||||||
// May refer to top level function variable
|
|
||||||
// or to top level function.
|
|
||||||
callee := qpos.info.Uses[funexpr.Sel]
|
|
||||||
if obj, ok := callee.(*types.Func); ok {
|
|
||||||
q.result = &calleesTypesResult{
|
|
||||||
site: e,
|
|
||||||
callee: obj,
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
} else if sel.Kind() == types.MethodVal {
|
|
||||||
// Inspect the receiver type of the selected method.
|
|
||||||
// If it is concrete, the call is statically dispatched.
|
|
||||||
// (Due to implicit field selections, it is not enough to look
|
|
||||||
// at sel.Recv(), the type of the actual receiver expression.)
|
|
||||||
method := sel.Obj().(*types.Func)
|
|
||||||
recvtype := method.Type().(*types.Signature).Recv().Type()
|
|
||||||
if !types.IsInterface(recvtype) {
|
|
||||||
// static method call
|
|
||||||
q.result = &calleesTypesResult{
|
|
||||||
site: e,
|
|
||||||
callee: method,
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug)
|
|
||||||
|
|
||||||
ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
pkg := prog.Package(qpos.info.Pkg)
|
|
||||||
if pkg == nil {
|
|
||||||
return fmt.Errorf("no SSA package")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defer SSA construction till after errors are reported.
|
|
||||||
prog.Build()
|
|
||||||
|
|
||||||
// Ascertain calling function and call site.
|
|
||||||
callerFn := ssa.EnclosingFunction(pkg, qpos.path)
|
|
||||||
if callerFn == nil {
|
|
||||||
return fmt.Errorf("no SSA function built for this location (dead code?)")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the call site.
|
|
||||||
site, err := findCallSite(callerFn, e)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
funcs, err := findCallees(ptaConfig, site)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
q.result = &calleesSSAResult{
|
|
||||||
site: site,
|
|
||||||
funcs: funcs,
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func findCallSite(fn *ssa.Function, call *ast.CallExpr) (ssa.CallInstruction, error) {
|
|
||||||
instr, _ := fn.ValueForExpr(call)
|
|
||||||
callInstr, _ := instr.(ssa.CallInstruction)
|
|
||||||
if instr == nil {
|
|
||||||
return nil, fmt.Errorf("this call site is unreachable in this analysis")
|
|
||||||
}
|
|
||||||
return callInstr, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func findCallees(conf *pointer.Config, site ssa.CallInstruction) ([]*ssa.Function, error) {
|
|
||||||
// Avoid running the pointer analysis for static calls.
|
|
||||||
if callee := site.Common().StaticCallee(); callee != nil {
|
|
||||||
switch callee.String() {
|
|
||||||
case "runtime.SetFinalizer", "(reflect.Value).Call":
|
|
||||||
// The PTA treats calls to these intrinsics as dynamic.
|
|
||||||
// TODO(adonovan): avoid reliance on PTA internals.
|
|
||||||
|
|
||||||
default:
|
|
||||||
return []*ssa.Function{callee}, nil // singleton
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dynamic call: use pointer analysis.
|
|
||||||
conf.BuildCallGraph = true
|
|
||||||
cg := ptrAnalysis(conf).CallGraph
|
|
||||||
cg.DeleteSyntheticNodes()
|
|
||||||
|
|
||||||
// Find all call edges from the site.
|
|
||||||
n := cg.Nodes[site.Parent()]
|
|
||||||
if n == nil {
|
|
||||||
return nil, fmt.Errorf("this call site is unreachable in this analysis")
|
|
||||||
}
|
|
||||||
calleesMap := make(map[*ssa.Function]bool)
|
|
||||||
for _, edge := range n.Out {
|
|
||||||
if edge.Site == site {
|
|
||||||
calleesMap[edge.Callee.Func] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// De-duplicate and sort.
|
|
||||||
funcs := make([]*ssa.Function, 0, len(calleesMap))
|
|
||||||
for f := range calleesMap {
|
|
||||||
funcs = append(funcs, f)
|
|
||||||
}
|
|
||||||
sort.Sort(byFuncPos(funcs))
|
|
||||||
return funcs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type calleesSSAResult struct {
|
|
||||||
site ssa.CallInstruction
|
|
||||||
funcs []*ssa.Function
|
|
||||||
}
|
|
||||||
|
|
||||||
type calleesTypesResult struct {
|
|
||||||
site *ast.CallExpr
|
|
||||||
callee *types.Func
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *calleesSSAResult) display(printf printfFunc) {
|
|
||||||
if len(r.funcs) == 0 {
|
|
||||||
// dynamic call on a provably nil func/interface
|
|
||||||
printf(r.site, "%s on nil value", r.site.Common().Description())
|
|
||||||
} else {
|
|
||||||
printf(r.site, "this %s dispatches to:", r.site.Common().Description())
|
|
||||||
for _, callee := range r.funcs {
|
|
||||||
printf(callee, "\t%s", callee)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *calleesSSAResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
|
||||||
j := &serial.Callees{
|
|
||||||
Pos: fset.Position(r.site.Pos()).String(),
|
|
||||||
Desc: r.site.Common().Description(),
|
|
||||||
}
|
|
||||||
for _, callee := range r.funcs {
|
|
||||||
j.Callees = append(j.Callees, &serial.CalleesItem{
|
|
||||||
Name: callee.String(),
|
|
||||||
Pos: fset.Position(callee.Pos()).String(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
res.Callees = j
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *calleesTypesResult) display(printf printfFunc) {
|
|
||||||
printf(r.site, "this static function call dispatches to:")
|
|
||||||
printf(r.callee, "\t%s", r.callee.FullName())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *calleesTypesResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
|
||||||
j := &serial.Callees{
|
|
||||||
Pos: fset.Position(r.site.Pos()).String(),
|
|
||||||
Desc: "static function call",
|
|
||||||
}
|
|
||||||
j.Callees = []*serial.CalleesItem{
|
|
||||||
&serial.CalleesItem{
|
|
||||||
Name: r.callee.FullName(),
|
|
||||||
Pos: fset.Position(r.callee.Pos()).String(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
res.Callees = j
|
|
||||||
}
|
|
||||||
|
|
||||||
// NB: byFuncPos is not deterministic across packages since it depends on load order.
|
|
||||||
// Use lessPos if the tests need it.
|
|
||||||
type byFuncPos []*ssa.Function
|
|
||||||
|
|
||||||
func (a byFuncPos) Len() int { return len(a) }
|
|
||||||
func (a byFuncPos) Less(i, j int) bool { return a[i].Pos() < a[j].Pos() }
|
|
||||||
func (a byFuncPos) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
||||||
|
|
@ -1,115 +0,0 @@
|
||||||
// Copyright 2013 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package oracle
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"go/token"
|
|
||||||
|
|
||||||
"golang.org/x/tools/go/callgraph"
|
|
||||||
"golang.org/x/tools/go/loader"
|
|
||||||
"golang.org/x/tools/go/ssa"
|
|
||||||
"golang.org/x/tools/go/ssa/ssautil"
|
|
||||||
"golang.org/x/tools/oracle/serial"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Callers reports the possible callers of the function
|
|
||||||
// immediately enclosing the specified source location.
|
|
||||||
//
|
|
||||||
func callers(q *Query) error {
|
|
||||||
lconf := loader.Config{Build: q.Build}
|
|
||||||
|
|
||||||
if err := setPTAScope(&lconf, q.Scope); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load/parse/type-check the program.
|
|
||||||
lprog, err := lconf.Load()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
q.Fset = lprog.Fset
|
|
||||||
|
|
||||||
qpos, err := parseQueryPos(lprog, q.Pos, false)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
prog := ssautil.CreateProgram(lprog, 0)
|
|
||||||
|
|
||||||
ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
pkg := prog.Package(qpos.info.Pkg)
|
|
||||||
if pkg == nil {
|
|
||||||
return fmt.Errorf("no SSA package")
|
|
||||||
}
|
|
||||||
if !ssa.HasEnclosingFunction(pkg, qpos.path) {
|
|
||||||
return fmt.Errorf("this position is not inside a function")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defer SSA construction till after errors are reported.
|
|
||||||
prog.Build()
|
|
||||||
|
|
||||||
target := ssa.EnclosingFunction(pkg, qpos.path)
|
|
||||||
if target == nil {
|
|
||||||
return fmt.Errorf("no SSA function built for this location (dead code?)")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(adonovan): opt: if function is never address-taken, skip
|
|
||||||
// the pointer analysis. Just look for direct calls. This can
|
|
||||||
// be done in a single pass over the SSA.
|
|
||||||
|
|
||||||
// Run the pointer analysis, recording each
|
|
||||||
// call found to originate from target.
|
|
||||||
ptaConfig.BuildCallGraph = true
|
|
||||||
cg := ptrAnalysis(ptaConfig).CallGraph
|
|
||||||
cg.DeleteSyntheticNodes()
|
|
||||||
edges := cg.CreateNode(target).In
|
|
||||||
// TODO(adonovan): sort + dedup calls to ensure test determinism.
|
|
||||||
|
|
||||||
q.result = &callersResult{
|
|
||||||
target: target,
|
|
||||||
callgraph: cg,
|
|
||||||
edges: edges,
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type callersResult struct {
|
|
||||||
target *ssa.Function
|
|
||||||
callgraph *callgraph.Graph
|
|
||||||
edges []*callgraph.Edge
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *callersResult) display(printf printfFunc) {
|
|
||||||
root := r.callgraph.Root
|
|
||||||
if r.edges == nil {
|
|
||||||
printf(r.target, "%s is not reachable in this program.", r.target)
|
|
||||||
} else {
|
|
||||||
printf(r.target, "%s is called from these %d sites:", r.target, len(r.edges))
|
|
||||||
for _, edge := range r.edges {
|
|
||||||
if edge.Caller == root {
|
|
||||||
printf(r.target, "the root of the call graph")
|
|
||||||
} else {
|
|
||||||
printf(edge, "\t%s from %s", edge.Description(), edge.Caller.Func)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *callersResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
|
||||||
var callers []serial.Caller
|
|
||||||
for _, edge := range r.edges {
|
|
||||||
callers = append(callers, serial.Caller{
|
|
||||||
Caller: edge.Caller.Func.String(),
|
|
||||||
Pos: fset.Position(edge.Pos()).String(),
|
|
||||||
Desc: edge.Description(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
res.Callers = callers
|
|
||||||
}
|
|
||||||
|
|
@ -1,126 +0,0 @@
|
||||||
// Copyright 2013 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package oracle
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"go/token"
|
|
||||||
|
|
||||||
"golang.org/x/tools/go/callgraph"
|
|
||||||
"golang.org/x/tools/go/loader"
|
|
||||||
"golang.org/x/tools/go/ssa"
|
|
||||||
"golang.org/x/tools/go/ssa/ssautil"
|
|
||||||
"golang.org/x/tools/oracle/serial"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Callstack displays an arbitrary path from a root of the callgraph
|
|
||||||
// to the function at the current position.
|
|
||||||
//
|
|
||||||
// The information may be misleading in a context-insensitive
|
|
||||||
// analysis. e.g. the call path X->Y->Z might be infeasible if Y never
|
|
||||||
// calls Z when it is called from X. TODO(adonovan): think about UI.
|
|
||||||
//
|
|
||||||
// TODO(adonovan): permit user to specify a starting point other than
|
|
||||||
// the analysis root.
|
|
||||||
//
|
|
||||||
func callstack(q *Query) error {
|
|
||||||
fset := token.NewFileSet()
|
|
||||||
lconf := loader.Config{Fset: fset, Build: q.Build}
|
|
||||||
|
|
||||||
if err := setPTAScope(&lconf, q.Scope); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load/parse/type-check the program.
|
|
||||||
lprog, err := lconf.Load()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
qpos, err := parseQueryPos(lprog, q.Pos, false)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
prog := ssautil.CreateProgram(lprog, 0)
|
|
||||||
|
|
||||||
ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
pkg := prog.Package(qpos.info.Pkg)
|
|
||||||
if pkg == nil {
|
|
||||||
return fmt.Errorf("no SSA package")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ssa.HasEnclosingFunction(pkg, qpos.path) {
|
|
||||||
return fmt.Errorf("this position is not inside a function")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defer SSA construction till after errors are reported.
|
|
||||||
prog.Build()
|
|
||||||
|
|
||||||
target := ssa.EnclosingFunction(pkg, qpos.path)
|
|
||||||
if target == nil {
|
|
||||||
return fmt.Errorf("no SSA function built for this location (dead code?)")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the pointer analysis and build the complete call graph.
|
|
||||||
ptaConfig.BuildCallGraph = true
|
|
||||||
cg := ptrAnalysis(ptaConfig).CallGraph
|
|
||||||
cg.DeleteSyntheticNodes()
|
|
||||||
|
|
||||||
// Search for an arbitrary path from a root to the target function.
|
|
||||||
isEnd := func(n *callgraph.Node) bool { return n.Func == target }
|
|
||||||
callpath := callgraph.PathSearch(cg.Root, isEnd)
|
|
||||||
if callpath != nil {
|
|
||||||
callpath = callpath[1:] // remove synthetic edge from <root>
|
|
||||||
}
|
|
||||||
|
|
||||||
q.Fset = fset
|
|
||||||
q.result = &callstackResult{
|
|
||||||
qpos: qpos,
|
|
||||||
target: target,
|
|
||||||
callpath: callpath,
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type callstackResult struct {
|
|
||||||
qpos *queryPos
|
|
||||||
target *ssa.Function
|
|
||||||
callpath []*callgraph.Edge
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *callstackResult) display(printf printfFunc) {
|
|
||||||
if r.callpath != nil {
|
|
||||||
printf(r.qpos, "Found a call path from root to %s", r.target)
|
|
||||||
printf(r.target, "%s", r.target)
|
|
||||||
for i := len(r.callpath) - 1; i >= 0; i-- {
|
|
||||||
edge := r.callpath[i]
|
|
||||||
printf(edge, "%s from %s", edge.Description(), edge.Caller.Func)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
printf(r.target, "%s is unreachable in this analysis scope", r.target)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *callstackResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
|
||||||
var callers []serial.Caller
|
|
||||||
for i := len(r.callpath) - 1; i >= 0; i-- { // (innermost first)
|
|
||||||
edge := r.callpath[i]
|
|
||||||
callers = append(callers, serial.Caller{
|
|
||||||
Pos: fset.Position(edge.Pos()).String(),
|
|
||||||
Caller: edge.Caller.Func.String(),
|
|
||||||
Desc: edge.Description(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
res.Callstack = &serial.CallStack{
|
|
||||||
Pos: fset.Position(r.target.Pos()).String(),
|
|
||||||
Target: r.target.String(),
|
|
||||||
Callers: callers,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
// Copyright 2013 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build go1.5
|
|
||||||
|
|
||||||
package oracle
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"go/ast"
|
|
||||||
"go/token"
|
|
||||||
"go/types"
|
|
||||||
|
|
||||||
"golang.org/x/tools/go/loader"
|
|
||||||
"golang.org/x/tools/oracle/serial"
|
|
||||||
)
|
|
||||||
|
|
||||||
// definition reports the location of the definition of an identifier.
|
|
||||||
//
|
|
||||||
// TODO(adonovan): opt: for intra-file references, the parser's
|
|
||||||
// resolution might be enough; we should start with that.
|
|
||||||
//
|
|
||||||
func definition(q *Query) error {
|
|
||||||
lconf := loader.Config{Build: q.Build}
|
|
||||||
allowErrors(&lconf)
|
|
||||||
|
|
||||||
if _, err := importQueryPackage(q.Pos, &lconf); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load/parse/type-check the program.
|
|
||||||
lprog, err := lconf.Load()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
q.Fset = lprog.Fset
|
|
||||||
|
|
||||||
qpos, err := parseQueryPos(lprog, q.Pos, false)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
id, _ := qpos.path[0].(*ast.Ident)
|
|
||||||
if id == nil {
|
|
||||||
return fmt.Errorf("no identifier here")
|
|
||||||
}
|
|
||||||
|
|
||||||
obj := qpos.info.ObjectOf(id)
|
|
||||||
if obj == nil {
|
|
||||||
// Happens for y in "switch y := x.(type)",
|
|
||||||
// and the package declaration,
|
|
||||||
// but I think that's all.
|
|
||||||
return fmt.Errorf("no object for identifier")
|
|
||||||
}
|
|
||||||
|
|
||||||
q.result = &definitionResult{qpos, obj}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type definitionResult struct {
|
|
||||||
qpos *queryPos
|
|
||||||
obj types.Object // object it denotes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *definitionResult) display(printf printfFunc) {
|
|
||||||
printf(r.obj, "defined here as %s", r.qpos.objectString(r.obj))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *definitionResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
|
||||||
definition := &serial.Definition{
|
|
||||||
Desc: r.obj.String(),
|
|
||||||
}
|
|
||||||
if pos := r.obj.Pos(); pos != token.NoPos { // Package objects have no Pos()
|
|
||||||
definition.ObjPos = fset.Position(pos).String()
|
|
||||||
}
|
|
||||||
res.Definition = definition
|
|
||||||
}
|
|
||||||
|
|
@ -1,775 +0,0 @@
|
||||||
// Copyright 2013 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build go1.6
|
|
||||||
|
|
||||||
package oracle
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"go/ast"
|
|
||||||
exact "go/constant"
|
|
||||||
"go/token"
|
|
||||||
"go/types"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/tools/go/ast/astutil"
|
|
||||||
"golang.org/x/tools/go/loader"
|
|
||||||
"golang.org/x/tools/go/types/typeutil"
|
|
||||||
"golang.org/x/tools/oracle/serial"
|
|
||||||
)
|
|
||||||
|
|
||||||
// describe describes the syntax node denoted by the query position,
|
|
||||||
// including:
|
|
||||||
// - its syntactic category
|
|
||||||
// - the definition of its referent (for identifiers) [now redundant]
|
|
||||||
// - its type and method set (for an expression or type expression)
|
|
||||||
//
|
|
||||||
func describe(q *Query) error {
|
|
||||||
lconf := loader.Config{Build: q.Build}
|
|
||||||
allowErrors(&lconf)
|
|
||||||
|
|
||||||
if _, err := importQueryPackage(q.Pos, &lconf); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load/parse/type-check the program.
|
|
||||||
lprog, err := lconf.Load()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
q.Fset = lprog.Fset
|
|
||||||
|
|
||||||
qpos, err := parseQueryPos(lprog, q.Pos, true) // (need exact pos)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if false { // debugging
|
|
||||||
fprintf(os.Stderr, lprog.Fset, qpos.path[0], "you selected: %s %s",
|
|
||||||
astutil.NodeDescription(qpos.path[0]), pathToString(qpos.path))
|
|
||||||
}
|
|
||||||
|
|
||||||
path, action := findInterestingNode(qpos.info, qpos.path)
|
|
||||||
switch action {
|
|
||||||
case actionExpr:
|
|
||||||
q.result, err = describeValue(qpos, path)
|
|
||||||
|
|
||||||
case actionType:
|
|
||||||
q.result, err = describeType(qpos, path)
|
|
||||||
|
|
||||||
case actionPackage:
|
|
||||||
q.result, err = describePackage(qpos, path)
|
|
||||||
|
|
||||||
case actionStmt:
|
|
||||||
q.result, err = describeStmt(qpos, path)
|
|
||||||
|
|
||||||
case actionUnknown:
|
|
||||||
q.result = &describeUnknownResult{path[0]}
|
|
||||||
|
|
||||||
default:
|
|
||||||
panic(action) // unreachable
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
type describeUnknownResult struct {
|
|
||||||
node ast.Node
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *describeUnknownResult) display(printf printfFunc) {
|
|
||||||
// Nothing much to say about misc syntax.
|
|
||||||
printf(r.node, "%s", astutil.NodeDescription(r.node))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *describeUnknownResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
|
||||||
res.Describe = &serial.Describe{
|
|
||||||
Desc: astutil.NodeDescription(r.node),
|
|
||||||
Pos: fset.Position(r.node.Pos()).String(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type action int
|
|
||||||
|
|
||||||
const (
|
|
||||||
actionUnknown action = iota // None of the below
|
|
||||||
actionExpr // FuncDecl, true Expr or Ident(types.{Const,Var})
|
|
||||||
actionType // type Expr or Ident(types.TypeName).
|
|
||||||
actionStmt // Stmt or Ident(types.Label)
|
|
||||||
actionPackage // Ident(types.Package) or ImportSpec
|
|
||||||
)
|
|
||||||
|
|
||||||
// findInterestingNode classifies the syntax node denoted by path as one of:
|
|
||||||
// - an expression, part of an expression or a reference to a constant
|
|
||||||
// or variable;
|
|
||||||
// - a type, part of a type, or a reference to a named type;
|
|
||||||
// - a statement, part of a statement, or a label referring to a statement;
|
|
||||||
// - part of a package declaration or import spec.
|
|
||||||
// - none of the above.
|
|
||||||
// and returns the most "interesting" associated node, which may be
|
|
||||||
// the same node, an ancestor or a descendent.
|
|
||||||
//
|
|
||||||
func findInterestingNode(pkginfo *loader.PackageInfo, path []ast.Node) ([]ast.Node, action) {
|
|
||||||
// TODO(adonovan): integrate with go/types/stdlib_test.go and
|
|
||||||
// apply this to every AST node we can find to make sure it
|
|
||||||
// doesn't crash.
|
|
||||||
|
|
||||||
// TODO(adonovan): audit for ParenExpr safety, esp. since we
|
|
||||||
// traverse up and down.
|
|
||||||
|
|
||||||
// TODO(adonovan): if the users selects the "." in
|
|
||||||
// "fmt.Fprintf()", they'll get an ambiguous selection error;
|
|
||||||
// we won't even reach here. Can we do better?
|
|
||||||
|
|
||||||
// TODO(adonovan): describing a field within 'type T struct {...}'
|
|
||||||
// describes the (anonymous) struct type and concludes "no methods".
|
|
||||||
// We should ascend to the enclosing type decl, if any.
|
|
||||||
|
|
||||||
for len(path) > 0 {
|
|
||||||
switch n := path[0].(type) {
|
|
||||||
case *ast.GenDecl:
|
|
||||||
if len(n.Specs) == 1 {
|
|
||||||
// Descend to sole {Import,Type,Value}Spec child.
|
|
||||||
path = append([]ast.Node{n.Specs[0]}, path...)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return path, actionUnknown // uninteresting
|
|
||||||
|
|
||||||
case *ast.FuncDecl:
|
|
||||||
// Descend to function name.
|
|
||||||
path = append([]ast.Node{n.Name}, path...)
|
|
||||||
continue
|
|
||||||
|
|
||||||
case *ast.ImportSpec:
|
|
||||||
return path, actionPackage
|
|
||||||
|
|
||||||
case *ast.ValueSpec:
|
|
||||||
if len(n.Names) == 1 {
|
|
||||||
// Descend to sole Ident child.
|
|
||||||
path = append([]ast.Node{n.Names[0]}, path...)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return path, actionUnknown // uninteresting
|
|
||||||
|
|
||||||
case *ast.TypeSpec:
|
|
||||||
// Descend to type name.
|
|
||||||
path = append([]ast.Node{n.Name}, path...)
|
|
||||||
continue
|
|
||||||
|
|
||||||
case ast.Stmt:
|
|
||||||
return path, actionStmt
|
|
||||||
|
|
||||||
case *ast.ArrayType,
|
|
||||||
*ast.StructType,
|
|
||||||
*ast.FuncType,
|
|
||||||
*ast.InterfaceType,
|
|
||||||
*ast.MapType,
|
|
||||||
*ast.ChanType:
|
|
||||||
return path, actionType
|
|
||||||
|
|
||||||
case *ast.Comment, *ast.CommentGroup, *ast.File, *ast.KeyValueExpr, *ast.CommClause:
|
|
||||||
return path, actionUnknown // uninteresting
|
|
||||||
|
|
||||||
case *ast.Ellipsis:
|
|
||||||
// Continue to enclosing node.
|
|
||||||
// e.g. [...]T in ArrayType
|
|
||||||
// f(x...) in CallExpr
|
|
||||||
// f(x...T) in FuncType
|
|
||||||
|
|
||||||
case *ast.Field:
|
|
||||||
// TODO(adonovan): this needs more thought,
|
|
||||||
// since fields can be so many things.
|
|
||||||
if len(n.Names) == 1 {
|
|
||||||
// Descend to sole Ident child.
|
|
||||||
path = append([]ast.Node{n.Names[0]}, path...)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Zero names (e.g. anon field in struct)
|
|
||||||
// or multiple field or param names:
|
|
||||||
// continue to enclosing field list.
|
|
||||||
|
|
||||||
case *ast.FieldList:
|
|
||||||
// Continue to enclosing node:
|
|
||||||
// {Struct,Func,Interface}Type or FuncDecl.
|
|
||||||
|
|
||||||
case *ast.BasicLit:
|
|
||||||
if _, ok := path[1].(*ast.ImportSpec); ok {
|
|
||||||
return path[1:], actionPackage
|
|
||||||
}
|
|
||||||
return path, actionExpr
|
|
||||||
|
|
||||||
case *ast.SelectorExpr:
|
|
||||||
// TODO(adonovan): use Selections info directly.
|
|
||||||
if pkginfo.Uses[n.Sel] == nil {
|
|
||||||
// TODO(adonovan): is this reachable?
|
|
||||||
return path, actionUnknown
|
|
||||||
}
|
|
||||||
// Descend to .Sel child.
|
|
||||||
path = append([]ast.Node{n.Sel}, path...)
|
|
||||||
continue
|
|
||||||
|
|
||||||
case *ast.Ident:
|
|
||||||
switch pkginfo.ObjectOf(n).(type) {
|
|
||||||
case *types.PkgName:
|
|
||||||
return path, actionPackage
|
|
||||||
|
|
||||||
case *types.Const:
|
|
||||||
return path, actionExpr
|
|
||||||
|
|
||||||
case *types.Label:
|
|
||||||
return path, actionStmt
|
|
||||||
|
|
||||||
case *types.TypeName:
|
|
||||||
return path, actionType
|
|
||||||
|
|
||||||
case *types.Var:
|
|
||||||
// For x in 'struct {x T}', return struct type, for now.
|
|
||||||
if _, ok := path[1].(*ast.Field); ok {
|
|
||||||
_ = path[2].(*ast.FieldList) // assertion
|
|
||||||
if _, ok := path[3].(*ast.StructType); ok {
|
|
||||||
return path[3:], actionType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return path, actionExpr
|
|
||||||
|
|
||||||
case *types.Func:
|
|
||||||
return path, actionExpr
|
|
||||||
|
|
||||||
case *types.Builtin:
|
|
||||||
// For reference to built-in function, return enclosing call.
|
|
||||||
path = path[1:] // ascend to enclosing function call
|
|
||||||
continue
|
|
||||||
|
|
||||||
case *types.Nil:
|
|
||||||
return path, actionExpr
|
|
||||||
}
|
|
||||||
|
|
||||||
// No object.
|
|
||||||
switch path[1].(type) {
|
|
||||||
case *ast.SelectorExpr:
|
|
||||||
// Return enclosing selector expression.
|
|
||||||
return path[1:], actionExpr
|
|
||||||
|
|
||||||
case *ast.Field:
|
|
||||||
// TODO(adonovan): test this.
|
|
||||||
// e.g. all f in:
|
|
||||||
// struct { f, g int }
|
|
||||||
// interface { f() }
|
|
||||||
// func (f T) method(f, g int) (f, g bool)
|
|
||||||
//
|
|
||||||
// switch path[3].(type) {
|
|
||||||
// case *ast.FuncDecl:
|
|
||||||
// case *ast.StructType:
|
|
||||||
// case *ast.InterfaceType:
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return path[1:], actionExpr
|
|
||||||
//
|
|
||||||
// Unclear what to do with these.
|
|
||||||
// Struct.Fields -- field
|
|
||||||
// Interface.Methods -- field
|
|
||||||
// FuncType.{Params.Results} -- actionExpr
|
|
||||||
// FuncDecl.Recv -- actionExpr
|
|
||||||
|
|
||||||
case *ast.File:
|
|
||||||
// 'package foo'
|
|
||||||
return path, actionPackage
|
|
||||||
|
|
||||||
case *ast.ImportSpec:
|
|
||||||
// TODO(adonovan): fix: why no package object? go/types bug?
|
|
||||||
return path[1:], actionPackage
|
|
||||||
|
|
||||||
default:
|
|
||||||
// e.g. blank identifier
|
|
||||||
// or y in "switch y := x.(type)"
|
|
||||||
// or code in a _test.go file that's not part of the package.
|
|
||||||
log.Printf("unknown reference %s in %T\n", n, path[1])
|
|
||||||
return path, actionUnknown
|
|
||||||
}
|
|
||||||
|
|
||||||
case *ast.StarExpr:
|
|
||||||
if pkginfo.Types[n].IsType() {
|
|
||||||
return path, actionType
|
|
||||||
}
|
|
||||||
return path, actionExpr
|
|
||||||
|
|
||||||
case ast.Expr:
|
|
||||||
// All Expr but {BasicLit,Ident,StarExpr} are
|
|
||||||
// "true" expressions that evaluate to a value.
|
|
||||||
return path, actionExpr
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ascend to parent.
|
|
||||||
path = path[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, actionUnknown // unreachable
|
|
||||||
}
|
|
||||||
|
|
||||||
func describeValue(qpos *queryPos, path []ast.Node) (*describeValueResult, error) {
|
|
||||||
var expr ast.Expr
|
|
||||||
var obj types.Object
|
|
||||||
switch n := path[0].(type) {
|
|
||||||
case *ast.ValueSpec:
|
|
||||||
// ambiguous ValueSpec containing multiple names
|
|
||||||
return nil, fmt.Errorf("multiple value specification")
|
|
||||||
case *ast.Ident:
|
|
||||||
obj = qpos.info.ObjectOf(n)
|
|
||||||
expr = n
|
|
||||||
case ast.Expr:
|
|
||||||
expr = n
|
|
||||||
default:
|
|
||||||
// TODO(adonovan): is this reachable?
|
|
||||||
return nil, fmt.Errorf("unexpected AST for expr: %T", n)
|
|
||||||
}
|
|
||||||
|
|
||||||
typ := qpos.info.TypeOf(expr)
|
|
||||||
constVal := qpos.info.Types[expr].Value
|
|
||||||
|
|
||||||
return &describeValueResult{
|
|
||||||
qpos: qpos,
|
|
||||||
expr: expr,
|
|
||||||
typ: typ,
|
|
||||||
constVal: constVal,
|
|
||||||
obj: obj,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type describeValueResult struct {
|
|
||||||
qpos *queryPos
|
|
||||||
expr ast.Expr // query node
|
|
||||||
typ types.Type // type of expression
|
|
||||||
constVal exact.Value // value of expression, if constant
|
|
||||||
obj types.Object // var/func/const object, if expr was Ident
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *describeValueResult) display(printf printfFunc) {
|
|
||||||
var prefix, suffix string
|
|
||||||
if r.constVal != nil {
|
|
||||||
suffix = fmt.Sprintf(" of constant value %s", r.constVal)
|
|
||||||
}
|
|
||||||
switch obj := r.obj.(type) {
|
|
||||||
case *types.Func:
|
|
||||||
if recv := obj.Type().(*types.Signature).Recv(); recv != nil {
|
|
||||||
if _, ok := recv.Type().Underlying().(*types.Interface); ok {
|
|
||||||
prefix = "interface method "
|
|
||||||
} else {
|
|
||||||
prefix = "method "
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Describe the expression.
|
|
||||||
if r.obj != nil {
|
|
||||||
if r.obj.Pos() == r.expr.Pos() {
|
|
||||||
// defining ident
|
|
||||||
printf(r.expr, "definition of %s%s%s", prefix, r.qpos.objectString(r.obj), suffix)
|
|
||||||
} else {
|
|
||||||
// referring ident
|
|
||||||
printf(r.expr, "reference to %s%s%s", prefix, r.qpos.objectString(r.obj), suffix)
|
|
||||||
if def := r.obj.Pos(); def != token.NoPos {
|
|
||||||
printf(def, "defined here")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
desc := astutil.NodeDescription(r.expr)
|
|
||||||
if suffix != "" {
|
|
||||||
// constant expression
|
|
||||||
printf(r.expr, "%s%s", desc, suffix)
|
|
||||||
} else {
|
|
||||||
// non-constant expression
|
|
||||||
printf(r.expr, "%s of type %s", desc, r.qpos.typeString(r.typ))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *describeValueResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
|
||||||
var value, objpos string
|
|
||||||
if r.constVal != nil {
|
|
||||||
value = r.constVal.String()
|
|
||||||
}
|
|
||||||
if r.obj != nil {
|
|
||||||
objpos = fset.Position(r.obj.Pos()).String()
|
|
||||||
}
|
|
||||||
|
|
||||||
res.Describe = &serial.Describe{
|
|
||||||
Desc: astutil.NodeDescription(r.expr),
|
|
||||||
Pos: fset.Position(r.expr.Pos()).String(),
|
|
||||||
Detail: "value",
|
|
||||||
Value: &serial.DescribeValue{
|
|
||||||
Type: r.qpos.typeString(r.typ),
|
|
||||||
Value: value,
|
|
||||||
ObjPos: objpos,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- TYPE ------------------------------------------------------------
|
|
||||||
|
|
||||||
func describeType(qpos *queryPos, path []ast.Node) (*describeTypeResult, error) {
|
|
||||||
var description string
|
|
||||||
var t types.Type
|
|
||||||
switch n := path[0].(type) {
|
|
||||||
case *ast.Ident:
|
|
||||||
t = qpos.info.TypeOf(n)
|
|
||||||
switch t := t.(type) {
|
|
||||||
case *types.Basic:
|
|
||||||
description = "reference to built-in "
|
|
||||||
|
|
||||||
case *types.Named:
|
|
||||||
isDef := t.Obj().Pos() == n.Pos() // see caveats at isDef above
|
|
||||||
if isDef {
|
|
||||||
description = "definition of "
|
|
||||||
} else {
|
|
||||||
description = "reference to "
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case ast.Expr:
|
|
||||||
t = qpos.info.TypeOf(n)
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Unreachable?
|
|
||||||
return nil, fmt.Errorf("unexpected AST for type: %T", n)
|
|
||||||
}
|
|
||||||
|
|
||||||
description = description + "type " + qpos.typeString(t)
|
|
||||||
|
|
||||||
// Show sizes for structs and named types (it's fairly obvious for others).
|
|
||||||
switch t.(type) {
|
|
||||||
case *types.Named, *types.Struct:
|
|
||||||
szs := types.StdSizes{WordSize: 8, MaxAlign: 8} // assume amd64
|
|
||||||
description = fmt.Sprintf("%s (size %d, align %d)", description,
|
|
||||||
szs.Sizeof(t), szs.Alignof(t))
|
|
||||||
}
|
|
||||||
|
|
||||||
return &describeTypeResult{
|
|
||||||
qpos: qpos,
|
|
||||||
node: path[0],
|
|
||||||
description: description,
|
|
||||||
typ: t,
|
|
||||||
methods: accessibleMethods(t, qpos.info.Pkg),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type describeTypeResult struct {
|
|
||||||
qpos *queryPos
|
|
||||||
node ast.Node
|
|
||||||
description string
|
|
||||||
typ types.Type
|
|
||||||
methods []*types.Selection
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *describeTypeResult) display(printf printfFunc) {
|
|
||||||
printf(r.node, "%s", r.description)
|
|
||||||
|
|
||||||
// Show the underlying type for a reference to a named type.
|
|
||||||
if nt, ok := r.typ.(*types.Named); ok && r.node.Pos() != nt.Obj().Pos() {
|
|
||||||
printf(nt.Obj(), "defined as %s", r.qpos.typeString(nt.Underlying()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print the method set, if the type kind is capable of bearing methods.
|
|
||||||
switch r.typ.(type) {
|
|
||||||
case *types.Interface, *types.Struct, *types.Named:
|
|
||||||
if len(r.methods) > 0 {
|
|
||||||
printf(r.node, "Method set:")
|
|
||||||
for _, meth := range r.methods {
|
|
||||||
// TODO(adonovan): print these relative
|
|
||||||
// to the owning package, not the
|
|
||||||
// query package.
|
|
||||||
printf(meth.Obj(), "\t%s", r.qpos.selectionString(meth))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
printf(r.node, "No methods.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *describeTypeResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
|
||||||
var namePos, nameDef string
|
|
||||||
if nt, ok := r.typ.(*types.Named); ok {
|
|
||||||
namePos = fset.Position(nt.Obj().Pos()).String()
|
|
||||||
nameDef = nt.Underlying().String()
|
|
||||||
}
|
|
||||||
res.Describe = &serial.Describe{
|
|
||||||
Desc: r.description,
|
|
||||||
Pos: fset.Position(r.node.Pos()).String(),
|
|
||||||
Detail: "type",
|
|
||||||
Type: &serial.DescribeType{
|
|
||||||
Type: r.qpos.typeString(r.typ),
|
|
||||||
NamePos: namePos,
|
|
||||||
NameDef: nameDef,
|
|
||||||
Methods: methodsToSerial(r.qpos.info.Pkg, r.methods, fset),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- PACKAGE ------------------------------------------------------------
|
|
||||||
|
|
||||||
func describePackage(qpos *queryPos, path []ast.Node) (*describePackageResult, error) {
|
|
||||||
var description string
|
|
||||||
var pkg *types.Package
|
|
||||||
switch n := path[0].(type) {
|
|
||||||
case *ast.ImportSpec:
|
|
||||||
var obj types.Object
|
|
||||||
if n.Name != nil {
|
|
||||||
obj = qpos.info.Defs[n.Name]
|
|
||||||
} else {
|
|
||||||
obj = qpos.info.Implicits[n]
|
|
||||||
}
|
|
||||||
pkgname, _ := obj.(*types.PkgName)
|
|
||||||
if pkgname == nil {
|
|
||||||
return nil, fmt.Errorf("can't import package %s", n.Path.Value)
|
|
||||||
}
|
|
||||||
pkg = pkgname.Imported()
|
|
||||||
description = fmt.Sprintf("import of package %q", pkg.Path())
|
|
||||||
|
|
||||||
case *ast.Ident:
|
|
||||||
if _, isDef := path[1].(*ast.File); isDef {
|
|
||||||
// e.g. package id
|
|
||||||
pkg = qpos.info.Pkg
|
|
||||||
description = fmt.Sprintf("definition of package %q", pkg.Path())
|
|
||||||
} else {
|
|
||||||
// e.g. import id "..."
|
|
||||||
// or id.F()
|
|
||||||
pkg = qpos.info.ObjectOf(n).(*types.PkgName).Imported()
|
|
||||||
description = fmt.Sprintf("reference to package %q", pkg.Path())
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Unreachable?
|
|
||||||
return nil, fmt.Errorf("unexpected AST for package: %T", n)
|
|
||||||
}
|
|
||||||
|
|
||||||
var members []*describeMember
|
|
||||||
// NB: "unsafe" has no types.Package
|
|
||||||
if pkg != nil {
|
|
||||||
// Enumerate the accessible package members
|
|
||||||
// in lexicographic order.
|
|
||||||
for _, name := range pkg.Scope().Names() {
|
|
||||||
if pkg == qpos.info.Pkg || ast.IsExported(name) {
|
|
||||||
mem := pkg.Scope().Lookup(name)
|
|
||||||
var methods []*types.Selection
|
|
||||||
if mem, ok := mem.(*types.TypeName); ok {
|
|
||||||
methods = accessibleMethods(mem.Type(), qpos.info.Pkg)
|
|
||||||
}
|
|
||||||
members = append(members, &describeMember{
|
|
||||||
mem,
|
|
||||||
methods,
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &describePackageResult{qpos.fset, path[0], description, pkg, members}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type describePackageResult struct {
|
|
||||||
fset *token.FileSet
|
|
||||||
node ast.Node
|
|
||||||
description string
|
|
||||||
pkg *types.Package
|
|
||||||
members []*describeMember // in lexicographic name order
|
|
||||||
}
|
|
||||||
|
|
||||||
type describeMember struct {
|
|
||||||
obj types.Object
|
|
||||||
methods []*types.Selection // in types.MethodSet order
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *describePackageResult) display(printf printfFunc) {
|
|
||||||
printf(r.node, "%s", r.description)
|
|
||||||
|
|
||||||
// Compute max width of name "column".
|
|
||||||
maxname := 0
|
|
||||||
for _, mem := range r.members {
|
|
||||||
if l := len(mem.obj.Name()); l > maxname {
|
|
||||||
maxname = l
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, mem := range r.members {
|
|
||||||
printf(mem.obj, "\t%s", formatMember(mem.obj, maxname))
|
|
||||||
for _, meth := range mem.methods {
|
|
||||||
printf(meth.Obj(), "\t\t%s", types.SelectionString(meth, types.RelativeTo(r.pkg)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatMember(obj types.Object, maxname int) string {
|
|
||||||
qualifier := types.RelativeTo(obj.Pkg())
|
|
||||||
var buf bytes.Buffer
|
|
||||||
fmt.Fprintf(&buf, "%-5s %-*s", tokenOf(obj), maxname, obj.Name())
|
|
||||||
switch obj := obj.(type) {
|
|
||||||
case *types.Const:
|
|
||||||
fmt.Fprintf(&buf, " %s = %s", types.TypeString(obj.Type(), qualifier), obj.Val().String())
|
|
||||||
|
|
||||||
case *types.Func:
|
|
||||||
fmt.Fprintf(&buf, " %s", types.TypeString(obj.Type(), qualifier))
|
|
||||||
|
|
||||||
case *types.TypeName:
|
|
||||||
// Abbreviate long aggregate type names.
|
|
||||||
var abbrev string
|
|
||||||
switch t := obj.Type().Underlying().(type) {
|
|
||||||
case *types.Interface:
|
|
||||||
if t.NumMethods() > 1 {
|
|
||||||
abbrev = "interface{...}"
|
|
||||||
}
|
|
||||||
case *types.Struct:
|
|
||||||
if t.NumFields() > 1 {
|
|
||||||
abbrev = "struct{...}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if abbrev == "" {
|
|
||||||
fmt.Fprintf(&buf, " %s", types.TypeString(obj.Type().Underlying(), qualifier))
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(&buf, " %s", abbrev)
|
|
||||||
}
|
|
||||||
|
|
||||||
case *types.Var:
|
|
||||||
fmt.Fprintf(&buf, " %s", types.TypeString(obj.Type(), qualifier))
|
|
||||||
}
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *describePackageResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
|
||||||
var members []*serial.DescribeMember
|
|
||||||
for _, mem := range r.members {
|
|
||||||
typ := mem.obj.Type()
|
|
||||||
var val string
|
|
||||||
switch mem := mem.obj.(type) {
|
|
||||||
case *types.Const:
|
|
||||||
val = mem.Val().String()
|
|
||||||
case *types.TypeName:
|
|
||||||
typ = typ.Underlying()
|
|
||||||
}
|
|
||||||
members = append(members, &serial.DescribeMember{
|
|
||||||
Name: mem.obj.Name(),
|
|
||||||
Type: typ.String(),
|
|
||||||
Value: val,
|
|
||||||
Pos: fset.Position(mem.obj.Pos()).String(),
|
|
||||||
Kind: tokenOf(mem.obj),
|
|
||||||
Methods: methodsToSerial(r.pkg, mem.methods, fset),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
res.Describe = &serial.Describe{
|
|
||||||
Desc: r.description,
|
|
||||||
Pos: fset.Position(r.node.Pos()).String(),
|
|
||||||
Detail: "package",
|
|
||||||
Package: &serial.DescribePackage{
|
|
||||||
Path: r.pkg.Path(),
|
|
||||||
Members: members,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func tokenOf(o types.Object) string {
|
|
||||||
switch o.(type) {
|
|
||||||
case *types.Func:
|
|
||||||
return "func"
|
|
||||||
case *types.Var:
|
|
||||||
return "var"
|
|
||||||
case *types.TypeName:
|
|
||||||
return "type"
|
|
||||||
case *types.Const:
|
|
||||||
return "const"
|
|
||||||
case *types.PkgName:
|
|
||||||
return "package"
|
|
||||||
case *types.Builtin:
|
|
||||||
return "builtin" // e.g. when describing package "unsafe"
|
|
||||||
case *types.Nil:
|
|
||||||
return "nil"
|
|
||||||
case *types.Label:
|
|
||||||
return "label"
|
|
||||||
}
|
|
||||||
panic(o)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- STATEMENT ------------------------------------------------------------
|
|
||||||
|
|
||||||
func describeStmt(qpos *queryPos, path []ast.Node) (*describeStmtResult, error) {
|
|
||||||
var description string
|
|
||||||
switch n := path[0].(type) {
|
|
||||||
case *ast.Ident:
|
|
||||||
if qpos.info.Defs[n] != nil {
|
|
||||||
description = "labelled statement"
|
|
||||||
} else {
|
|
||||||
description = "reference to labelled statement"
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Nothing much to say about statements.
|
|
||||||
description = astutil.NodeDescription(n)
|
|
||||||
}
|
|
||||||
return &describeStmtResult{qpos.fset, path[0], description}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type describeStmtResult struct {
|
|
||||||
fset *token.FileSet
|
|
||||||
node ast.Node
|
|
||||||
description string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *describeStmtResult) display(printf printfFunc) {
|
|
||||||
printf(r.node, "%s", r.description)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *describeStmtResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
|
||||||
res.Describe = &serial.Describe{
|
|
||||||
Desc: r.description,
|
|
||||||
Pos: fset.Position(r.node.Pos()).String(),
|
|
||||||
Detail: "unknown",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------- Utilities -------------------
|
|
||||||
|
|
||||||
// pathToString returns a string containing the concrete types of the
|
|
||||||
// nodes in path.
|
|
||||||
func pathToString(path []ast.Node) string {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
fmt.Fprint(&buf, "[")
|
|
||||||
for i, n := range path {
|
|
||||||
if i > 0 {
|
|
||||||
fmt.Fprint(&buf, " ")
|
|
||||||
}
|
|
||||||
fmt.Fprint(&buf, strings.TrimPrefix(fmt.Sprintf("%T", n), "*ast."))
|
|
||||||
}
|
|
||||||
fmt.Fprint(&buf, "]")
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func accessibleMethods(t types.Type, from *types.Package) []*types.Selection {
|
|
||||||
var methods []*types.Selection
|
|
||||||
for _, meth := range typeutil.IntuitiveMethodSet(t, nil) {
|
|
||||||
if isAccessibleFrom(meth.Obj(), from) {
|
|
||||||
methods = append(methods, meth)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return methods
|
|
||||||
}
|
|
||||||
|
|
||||||
func isAccessibleFrom(obj types.Object, pkg *types.Package) bool {
|
|
||||||
return ast.IsExported(obj.Name()) || obj.Pkg() == pkg
|
|
||||||
}
|
|
||||||
|
|
||||||
func methodsToSerial(this *types.Package, methods []*types.Selection, fset *token.FileSet) []serial.DescribeMethod {
|
|
||||||
qualifier := types.RelativeTo(this)
|
|
||||||
var jmethods []serial.DescribeMethod
|
|
||||||
for _, meth := range methods {
|
|
||||||
var ser serial.DescribeMethod
|
|
||||||
if meth != nil { // may contain nils when called by implements (on a method)
|
|
||||||
ser = serial.DescribeMethod{
|
|
||||||
Name: types.SelectionString(meth, qualifier),
|
|
||||||
Pos: fset.Position(meth.Obj().Pos()).String(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
jmethods = append(jmethods, ser)
|
|
||||||
}
|
|
||||||
return jmethods
|
|
||||||
}
|
|
||||||
|
|
@ -1,786 +0,0 @@
|
||||||
// Copyright 2013 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build go1.5,!go1.6
|
|
||||||
|
|
||||||
package oracle
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"go/ast"
|
|
||||||
exact "go/constant"
|
|
||||||
"go/token"
|
|
||||||
"go/types"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/tools/go/ast/astutil"
|
|
||||||
"golang.org/x/tools/go/loader"
|
|
||||||
"golang.org/x/tools/go/types/typeutil"
|
|
||||||
"golang.org/x/tools/oracle/serial"
|
|
||||||
)
|
|
||||||
|
|
||||||
// describe describes the syntax node denoted by the query position,
|
|
||||||
// including:
|
|
||||||
// - its syntactic category
|
|
||||||
// - the definition of its referent (for identifiers) [now redundant]
|
|
||||||
// - its type and method set (for an expression or type expression)
|
|
||||||
//
|
|
||||||
func describe(q *Query) error {
|
|
||||||
lconf := loader.Config{Build: q.Build}
|
|
||||||
allowErrors(&lconf)
|
|
||||||
|
|
||||||
if _, err := importQueryPackage(q.Pos, &lconf); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load/parse/type-check the program.
|
|
||||||
lprog, err := lconf.Load()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
q.Fset = lprog.Fset
|
|
||||||
|
|
||||||
qpos, err := parseQueryPos(lprog, q.Pos, true) // (need exact pos)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if false { // debugging
|
|
||||||
fprintf(os.Stderr, lprog.Fset, qpos.path[0], "you selected: %s %s",
|
|
||||||
astutil.NodeDescription(qpos.path[0]), pathToString(qpos.path))
|
|
||||||
}
|
|
||||||
|
|
||||||
path, action := findInterestingNode(qpos.info, qpos.path)
|
|
||||||
switch action {
|
|
||||||
case actionExpr:
|
|
||||||
q.result, err = describeValue(qpos, path)
|
|
||||||
|
|
||||||
case actionType:
|
|
||||||
q.result, err = describeType(qpos, path)
|
|
||||||
|
|
||||||
case actionPackage:
|
|
||||||
q.result, err = describePackage(qpos, path)
|
|
||||||
|
|
||||||
case actionStmt:
|
|
||||||
q.result, err = describeStmt(qpos, path)
|
|
||||||
|
|
||||||
case actionUnknown:
|
|
||||||
q.result = &describeUnknownResult{path[0]}
|
|
||||||
|
|
||||||
default:
|
|
||||||
panic(action) // unreachable
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
type describeUnknownResult struct {
|
|
||||||
node ast.Node
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *describeUnknownResult) display(printf printfFunc) {
|
|
||||||
// Nothing much to say about misc syntax.
|
|
||||||
printf(r.node, "%s", astutil.NodeDescription(r.node))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *describeUnknownResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
|
||||||
res.Describe = &serial.Describe{
|
|
||||||
Desc: astutil.NodeDescription(r.node),
|
|
||||||
Pos: fset.Position(r.node.Pos()).String(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type action int
|
|
||||||
|
|
||||||
const (
|
|
||||||
actionUnknown action = iota // None of the below
|
|
||||||
actionExpr // FuncDecl, true Expr or Ident(types.{Const,Var})
|
|
||||||
actionType // type Expr or Ident(types.TypeName).
|
|
||||||
actionStmt // Stmt or Ident(types.Label)
|
|
||||||
actionPackage // Ident(types.Package) or ImportSpec
|
|
||||||
)
|
|
||||||
|
|
||||||
// findInterestingNode classifies the syntax node denoted by path as one of:
|
|
||||||
// - an expression, part of an expression or a reference to a constant
|
|
||||||
// or variable;
|
|
||||||
// - a type, part of a type, or a reference to a named type;
|
|
||||||
// - a statement, part of a statement, or a label referring to a statement;
|
|
||||||
// - part of a package declaration or import spec.
|
|
||||||
// - none of the above.
|
|
||||||
// and returns the most "interesting" associated node, which may be
|
|
||||||
// the same node, an ancestor or a descendent.
|
|
||||||
//
|
|
||||||
func findInterestingNode(pkginfo *loader.PackageInfo, path []ast.Node) ([]ast.Node, action) {
|
|
||||||
// TODO(adonovan): integrate with go/types/stdlib_test.go and
|
|
||||||
// apply this to every AST node we can find to make sure it
|
|
||||||
// doesn't crash.
|
|
||||||
|
|
||||||
// TODO(adonovan): audit for ParenExpr safety, esp. since we
|
|
||||||
// traverse up and down.
|
|
||||||
|
|
||||||
// TODO(adonovan): if the users selects the "." in
|
|
||||||
// "fmt.Fprintf()", they'll get an ambiguous selection error;
|
|
||||||
// we won't even reach here. Can we do better?
|
|
||||||
|
|
||||||
// TODO(adonovan): describing a field within 'type T struct {...}'
|
|
||||||
// describes the (anonymous) struct type and concludes "no methods".
|
|
||||||
// We should ascend to the enclosing type decl, if any.
|
|
||||||
|
|
||||||
for len(path) > 0 {
|
|
||||||
switch n := path[0].(type) {
|
|
||||||
case *ast.GenDecl:
|
|
||||||
if len(n.Specs) == 1 {
|
|
||||||
// Descend to sole {Import,Type,Value}Spec child.
|
|
||||||
path = append([]ast.Node{n.Specs[0]}, path...)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return path, actionUnknown // uninteresting
|
|
||||||
|
|
||||||
case *ast.FuncDecl:
|
|
||||||
// Descend to function name.
|
|
||||||
path = append([]ast.Node{n.Name}, path...)
|
|
||||||
continue
|
|
||||||
|
|
||||||
case *ast.ImportSpec:
|
|
||||||
return path, actionPackage
|
|
||||||
|
|
||||||
case *ast.ValueSpec:
|
|
||||||
if len(n.Names) == 1 {
|
|
||||||
// Descend to sole Ident child.
|
|
||||||
path = append([]ast.Node{n.Names[0]}, path...)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return path, actionUnknown // uninteresting
|
|
||||||
|
|
||||||
case *ast.TypeSpec:
|
|
||||||
// Descend to type name.
|
|
||||||
path = append([]ast.Node{n.Name}, path...)
|
|
||||||
continue
|
|
||||||
|
|
||||||
case ast.Stmt:
|
|
||||||
return path, actionStmt
|
|
||||||
|
|
||||||
case *ast.ArrayType,
|
|
||||||
*ast.StructType,
|
|
||||||
*ast.FuncType,
|
|
||||||
*ast.InterfaceType,
|
|
||||||
*ast.MapType,
|
|
||||||
*ast.ChanType:
|
|
||||||
return path, actionType
|
|
||||||
|
|
||||||
case *ast.Comment, *ast.CommentGroup, *ast.File, *ast.KeyValueExpr, *ast.CommClause:
|
|
||||||
return path, actionUnknown // uninteresting
|
|
||||||
|
|
||||||
case *ast.Ellipsis:
|
|
||||||
// Continue to enclosing node.
|
|
||||||
// e.g. [...]T in ArrayType
|
|
||||||
// f(x...) in CallExpr
|
|
||||||
// f(x...T) in FuncType
|
|
||||||
|
|
||||||
case *ast.Field:
|
|
||||||
// TODO(adonovan): this needs more thought,
|
|
||||||
// since fields can be so many things.
|
|
||||||
if len(n.Names) == 1 {
|
|
||||||
// Descend to sole Ident child.
|
|
||||||
path = append([]ast.Node{n.Names[0]}, path...)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Zero names (e.g. anon field in struct)
|
|
||||||
// or multiple field or param names:
|
|
||||||
// continue to enclosing field list.
|
|
||||||
|
|
||||||
case *ast.FieldList:
|
|
||||||
// Continue to enclosing node:
|
|
||||||
// {Struct,Func,Interface}Type or FuncDecl.
|
|
||||||
|
|
||||||
case *ast.BasicLit:
|
|
||||||
if _, ok := path[1].(*ast.ImportSpec); ok {
|
|
||||||
return path[1:], actionPackage
|
|
||||||
}
|
|
||||||
return path, actionExpr
|
|
||||||
|
|
||||||
case *ast.SelectorExpr:
|
|
||||||
// TODO(adonovan): use Selections info directly.
|
|
||||||
if pkginfo.Uses[n.Sel] == nil {
|
|
||||||
// TODO(adonovan): is this reachable?
|
|
||||||
return path, actionUnknown
|
|
||||||
}
|
|
||||||
// Descend to .Sel child.
|
|
||||||
path = append([]ast.Node{n.Sel}, path...)
|
|
||||||
continue
|
|
||||||
|
|
||||||
case *ast.Ident:
|
|
||||||
switch pkginfo.ObjectOf(n).(type) {
|
|
||||||
case *types.PkgName:
|
|
||||||
return path, actionPackage
|
|
||||||
|
|
||||||
case *types.Const:
|
|
||||||
return path, actionExpr
|
|
||||||
|
|
||||||
case *types.Label:
|
|
||||||
return path, actionStmt
|
|
||||||
|
|
||||||
case *types.TypeName:
|
|
||||||
return path, actionType
|
|
||||||
|
|
||||||
case *types.Var:
|
|
||||||
// For x in 'struct {x T}', return struct type, for now.
|
|
||||||
if _, ok := path[1].(*ast.Field); ok {
|
|
||||||
_ = path[2].(*ast.FieldList) // assertion
|
|
||||||
if _, ok := path[3].(*ast.StructType); ok {
|
|
||||||
return path[3:], actionType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return path, actionExpr
|
|
||||||
|
|
||||||
case *types.Func:
|
|
||||||
return path, actionExpr
|
|
||||||
|
|
||||||
case *types.Builtin:
|
|
||||||
// For reference to built-in function, return enclosing call.
|
|
||||||
path = path[1:] // ascend to enclosing function call
|
|
||||||
continue
|
|
||||||
|
|
||||||
case *types.Nil:
|
|
||||||
return path, actionExpr
|
|
||||||
}
|
|
||||||
|
|
||||||
// No object.
|
|
||||||
switch path[1].(type) {
|
|
||||||
case *ast.SelectorExpr:
|
|
||||||
// Return enclosing selector expression.
|
|
||||||
return path[1:], actionExpr
|
|
||||||
|
|
||||||
case *ast.Field:
|
|
||||||
// TODO(adonovan): test this.
|
|
||||||
// e.g. all f in:
|
|
||||||
// struct { f, g int }
|
|
||||||
// interface { f() }
|
|
||||||
// func (f T) method(f, g int) (f, g bool)
|
|
||||||
//
|
|
||||||
// switch path[3].(type) {
|
|
||||||
// case *ast.FuncDecl:
|
|
||||||
// case *ast.StructType:
|
|
||||||
// case *ast.InterfaceType:
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return path[1:], actionExpr
|
|
||||||
//
|
|
||||||
// Unclear what to do with these.
|
|
||||||
// Struct.Fields -- field
|
|
||||||
// Interface.Methods -- field
|
|
||||||
// FuncType.{Params.Results} -- actionExpr
|
|
||||||
// FuncDecl.Recv -- actionExpr
|
|
||||||
|
|
||||||
case *ast.File:
|
|
||||||
// 'package foo'
|
|
||||||
return path, actionPackage
|
|
||||||
|
|
||||||
case *ast.ImportSpec:
|
|
||||||
// TODO(adonovan): fix: why no package object? go/types bug?
|
|
||||||
return path[1:], actionPackage
|
|
||||||
|
|
||||||
default:
|
|
||||||
// e.g. blank identifier
|
|
||||||
// or y in "switch y := x.(type)"
|
|
||||||
// or code in a _test.go file that's not part of the package.
|
|
||||||
log.Printf("unknown reference %s in %T\n", n, path[1])
|
|
||||||
return path, actionUnknown
|
|
||||||
}
|
|
||||||
|
|
||||||
case *ast.StarExpr:
|
|
||||||
if pkginfo.Types[n].IsType() {
|
|
||||||
return path, actionType
|
|
||||||
}
|
|
||||||
return path, actionExpr
|
|
||||||
|
|
||||||
case ast.Expr:
|
|
||||||
// All Expr but {BasicLit,Ident,StarExpr} are
|
|
||||||
// "true" expressions that evaluate to a value.
|
|
||||||
return path, actionExpr
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ascend to parent.
|
|
||||||
path = path[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, actionUnknown // unreachable
|
|
||||||
}
|
|
||||||
|
|
||||||
func describeValue(qpos *queryPos, path []ast.Node) (*describeValueResult, error) {
|
|
||||||
var expr ast.Expr
|
|
||||||
var obj types.Object
|
|
||||||
switch n := path[0].(type) {
|
|
||||||
case *ast.ValueSpec:
|
|
||||||
// ambiguous ValueSpec containing multiple names
|
|
||||||
return nil, fmt.Errorf("multiple value specification")
|
|
||||||
case *ast.Ident:
|
|
||||||
obj = qpos.info.ObjectOf(n)
|
|
||||||
expr = n
|
|
||||||
case ast.Expr:
|
|
||||||
expr = n
|
|
||||||
default:
|
|
||||||
// TODO(adonovan): is this reachable?
|
|
||||||
return nil, fmt.Errorf("unexpected AST for expr: %T", n)
|
|
||||||
}
|
|
||||||
|
|
||||||
typ := qpos.info.TypeOf(expr)
|
|
||||||
constVal := qpos.info.Types[expr].Value
|
|
||||||
|
|
||||||
return &describeValueResult{
|
|
||||||
qpos: qpos,
|
|
||||||
expr: expr,
|
|
||||||
typ: typ,
|
|
||||||
constVal: constVal,
|
|
||||||
obj: obj,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type describeValueResult struct {
|
|
||||||
qpos *queryPos
|
|
||||||
expr ast.Expr // query node
|
|
||||||
typ types.Type // type of expression
|
|
||||||
constVal exact.Value // value of expression, if constant
|
|
||||||
obj types.Object // var/func/const object, if expr was Ident
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *describeValueResult) display(printf printfFunc) {
|
|
||||||
var prefix, suffix string
|
|
||||||
if r.constVal != nil {
|
|
||||||
suffix = fmt.Sprintf(" of constant value %s", constValString(r.constVal))
|
|
||||||
}
|
|
||||||
switch obj := r.obj.(type) {
|
|
||||||
case *types.Func:
|
|
||||||
if recv := obj.Type().(*types.Signature).Recv(); recv != nil {
|
|
||||||
if _, ok := recv.Type().Underlying().(*types.Interface); ok {
|
|
||||||
prefix = "interface method "
|
|
||||||
} else {
|
|
||||||
prefix = "method "
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Describe the expression.
|
|
||||||
if r.obj != nil {
|
|
||||||
if r.obj.Pos() == r.expr.Pos() {
|
|
||||||
// defining ident
|
|
||||||
printf(r.expr, "definition of %s%s%s", prefix, r.qpos.objectString(r.obj), suffix)
|
|
||||||
} else {
|
|
||||||
// referring ident
|
|
||||||
printf(r.expr, "reference to %s%s%s", prefix, r.qpos.objectString(r.obj), suffix)
|
|
||||||
if def := r.obj.Pos(); def != token.NoPos {
|
|
||||||
printf(def, "defined here")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
desc := astutil.NodeDescription(r.expr)
|
|
||||||
if suffix != "" {
|
|
||||||
// constant expression
|
|
||||||
printf(r.expr, "%s%s", desc, suffix)
|
|
||||||
} else {
|
|
||||||
// non-constant expression
|
|
||||||
printf(r.expr, "%s of type %s", desc, r.qpos.typeString(r.typ))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *describeValueResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
|
||||||
var value, objpos string
|
|
||||||
if r.constVal != nil {
|
|
||||||
value = r.constVal.String()
|
|
||||||
}
|
|
||||||
if r.obj != nil {
|
|
||||||
objpos = fset.Position(r.obj.Pos()).String()
|
|
||||||
}
|
|
||||||
|
|
||||||
res.Describe = &serial.Describe{
|
|
||||||
Desc: astutil.NodeDescription(r.expr),
|
|
||||||
Pos: fset.Position(r.expr.Pos()).String(),
|
|
||||||
Detail: "value",
|
|
||||||
Value: &serial.DescribeValue{
|
|
||||||
Type: r.qpos.typeString(r.typ),
|
|
||||||
Value: value,
|
|
||||||
ObjPos: objpos,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- TYPE ------------------------------------------------------------
|
|
||||||
|
|
||||||
func describeType(qpos *queryPos, path []ast.Node) (*describeTypeResult, error) {
|
|
||||||
var description string
|
|
||||||
var t types.Type
|
|
||||||
switch n := path[0].(type) {
|
|
||||||
case *ast.Ident:
|
|
||||||
t = qpos.info.TypeOf(n)
|
|
||||||
switch t := t.(type) {
|
|
||||||
case *types.Basic:
|
|
||||||
description = "reference to built-in "
|
|
||||||
|
|
||||||
case *types.Named:
|
|
||||||
isDef := t.Obj().Pos() == n.Pos() // see caveats at isDef above
|
|
||||||
if isDef {
|
|
||||||
description = "definition of "
|
|
||||||
} else {
|
|
||||||
description = "reference to "
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case ast.Expr:
|
|
||||||
t = qpos.info.TypeOf(n)
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Unreachable?
|
|
||||||
return nil, fmt.Errorf("unexpected AST for type: %T", n)
|
|
||||||
}
|
|
||||||
|
|
||||||
description = description + "type " + qpos.typeString(t)
|
|
||||||
|
|
||||||
// Show sizes for structs and named types (it's fairly obvious for others).
|
|
||||||
switch t.(type) {
|
|
||||||
case *types.Named, *types.Struct:
|
|
||||||
szs := types.StdSizes{8, 8} // assume amd64
|
|
||||||
description = fmt.Sprintf("%s (size %d, align %d)", description,
|
|
||||||
szs.Sizeof(t), szs.Alignof(t))
|
|
||||||
}
|
|
||||||
|
|
||||||
return &describeTypeResult{
|
|
||||||
qpos: qpos,
|
|
||||||
node: path[0],
|
|
||||||
description: description,
|
|
||||||
typ: t,
|
|
||||||
methods: accessibleMethods(t, qpos.info.Pkg),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type describeTypeResult struct {
|
|
||||||
qpos *queryPos
|
|
||||||
node ast.Node
|
|
||||||
description string
|
|
||||||
typ types.Type
|
|
||||||
methods []*types.Selection
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *describeTypeResult) display(printf printfFunc) {
|
|
||||||
printf(r.node, "%s", r.description)
|
|
||||||
|
|
||||||
// Show the underlying type for a reference to a named type.
|
|
||||||
if nt, ok := r.typ.(*types.Named); ok && r.node.Pos() != nt.Obj().Pos() {
|
|
||||||
printf(nt.Obj(), "defined as %s", r.qpos.typeString(nt.Underlying()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print the method set, if the type kind is capable of bearing methods.
|
|
||||||
switch r.typ.(type) {
|
|
||||||
case *types.Interface, *types.Struct, *types.Named:
|
|
||||||
if len(r.methods) > 0 {
|
|
||||||
printf(r.node, "Method set:")
|
|
||||||
for _, meth := range r.methods {
|
|
||||||
// TODO(adonovan): print these relative
|
|
||||||
// to the owning package, not the
|
|
||||||
// query package.
|
|
||||||
printf(meth.Obj(), "\t%s", r.qpos.selectionString(meth))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
printf(r.node, "No methods.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *describeTypeResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
|
||||||
var namePos, nameDef string
|
|
||||||
if nt, ok := r.typ.(*types.Named); ok {
|
|
||||||
namePos = fset.Position(nt.Obj().Pos()).String()
|
|
||||||
nameDef = nt.Underlying().String()
|
|
||||||
}
|
|
||||||
res.Describe = &serial.Describe{
|
|
||||||
Desc: r.description,
|
|
||||||
Pos: fset.Position(r.node.Pos()).String(),
|
|
||||||
Detail: "type",
|
|
||||||
Type: &serial.DescribeType{
|
|
||||||
Type: r.qpos.typeString(r.typ),
|
|
||||||
NamePos: namePos,
|
|
||||||
NameDef: nameDef,
|
|
||||||
Methods: methodsToSerial(r.qpos.info.Pkg, r.methods, fset),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- PACKAGE ------------------------------------------------------------
|
|
||||||
|
|
||||||
func describePackage(qpos *queryPos, path []ast.Node) (*describePackageResult, error) {
|
|
||||||
var description string
|
|
||||||
var pkg *types.Package
|
|
||||||
switch n := path[0].(type) {
|
|
||||||
case *ast.ImportSpec:
|
|
||||||
var obj types.Object
|
|
||||||
if n.Name != nil {
|
|
||||||
obj = qpos.info.Defs[n.Name]
|
|
||||||
} else {
|
|
||||||
obj = qpos.info.Implicits[n]
|
|
||||||
}
|
|
||||||
pkgname, _ := obj.(*types.PkgName)
|
|
||||||
if pkgname == nil {
|
|
||||||
return nil, fmt.Errorf("can't import package %s", n.Path.Value)
|
|
||||||
}
|
|
||||||
pkg = pkgname.Imported()
|
|
||||||
description = fmt.Sprintf("import of package %q", pkg.Path())
|
|
||||||
|
|
||||||
case *ast.Ident:
|
|
||||||
if _, isDef := path[1].(*ast.File); isDef {
|
|
||||||
// e.g. package id
|
|
||||||
pkg = qpos.info.Pkg
|
|
||||||
description = fmt.Sprintf("definition of package %q", pkg.Path())
|
|
||||||
} else {
|
|
||||||
// e.g. import id "..."
|
|
||||||
// or id.F()
|
|
||||||
pkg = qpos.info.ObjectOf(n).(*types.PkgName).Imported()
|
|
||||||
description = fmt.Sprintf("reference to package %q", pkg.Path())
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Unreachable?
|
|
||||||
return nil, fmt.Errorf("unexpected AST for package: %T", n)
|
|
||||||
}
|
|
||||||
|
|
||||||
var members []*describeMember
|
|
||||||
// NB: "unsafe" has no types.Package
|
|
||||||
if pkg != nil {
|
|
||||||
// Enumerate the accessible package members
|
|
||||||
// in lexicographic order.
|
|
||||||
for _, name := range pkg.Scope().Names() {
|
|
||||||
if pkg == qpos.info.Pkg || ast.IsExported(name) {
|
|
||||||
mem := pkg.Scope().Lookup(name)
|
|
||||||
var methods []*types.Selection
|
|
||||||
if mem, ok := mem.(*types.TypeName); ok {
|
|
||||||
methods = accessibleMethods(mem.Type(), qpos.info.Pkg)
|
|
||||||
}
|
|
||||||
members = append(members, &describeMember{
|
|
||||||
mem,
|
|
||||||
methods,
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &describePackageResult{qpos.fset, path[0], description, pkg, members}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type describePackageResult struct {
|
|
||||||
fset *token.FileSet
|
|
||||||
node ast.Node
|
|
||||||
description string
|
|
||||||
pkg *types.Package
|
|
||||||
members []*describeMember // in lexicographic name order
|
|
||||||
}
|
|
||||||
|
|
||||||
type describeMember struct {
|
|
||||||
obj types.Object
|
|
||||||
methods []*types.Selection // in types.MethodSet order
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *describePackageResult) display(printf printfFunc) {
|
|
||||||
printf(r.node, "%s", r.description)
|
|
||||||
|
|
||||||
// Compute max width of name "column".
|
|
||||||
maxname := 0
|
|
||||||
for _, mem := range r.members {
|
|
||||||
if l := len(mem.obj.Name()); l > maxname {
|
|
||||||
maxname = l
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, mem := range r.members {
|
|
||||||
printf(mem.obj, "\t%s", formatMember(mem.obj, maxname))
|
|
||||||
for _, meth := range mem.methods {
|
|
||||||
printf(meth.Obj(), "\t\t%s", types.SelectionString(meth, types.RelativeTo(r.pkg)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatMember(obj types.Object, maxname int) string {
|
|
||||||
qualifier := types.RelativeTo(obj.Pkg())
|
|
||||||
var buf bytes.Buffer
|
|
||||||
fmt.Fprintf(&buf, "%-5s %-*s", tokenOf(obj), maxname, obj.Name())
|
|
||||||
switch obj := obj.(type) {
|
|
||||||
case *types.Const:
|
|
||||||
fmt.Fprintf(&buf, " %s = %s", types.TypeString(obj.Type(), qualifier), constValString(obj.Val()))
|
|
||||||
|
|
||||||
case *types.Func:
|
|
||||||
fmt.Fprintf(&buf, " %s", types.TypeString(obj.Type(), qualifier))
|
|
||||||
|
|
||||||
case *types.TypeName:
|
|
||||||
// Abbreviate long aggregate type names.
|
|
||||||
var abbrev string
|
|
||||||
switch t := obj.Type().Underlying().(type) {
|
|
||||||
case *types.Interface:
|
|
||||||
if t.NumMethods() > 1 {
|
|
||||||
abbrev = "interface{...}"
|
|
||||||
}
|
|
||||||
case *types.Struct:
|
|
||||||
if t.NumFields() > 1 {
|
|
||||||
abbrev = "struct{...}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if abbrev == "" {
|
|
||||||
fmt.Fprintf(&buf, " %s", types.TypeString(obj.Type().Underlying(), qualifier))
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(&buf, " %s", abbrev)
|
|
||||||
}
|
|
||||||
|
|
||||||
case *types.Var:
|
|
||||||
fmt.Fprintf(&buf, " %s", types.TypeString(obj.Type(), qualifier))
|
|
||||||
}
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *describePackageResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
|
||||||
var members []*serial.DescribeMember
|
|
||||||
for _, mem := range r.members {
|
|
||||||
typ := mem.obj.Type()
|
|
||||||
var val string
|
|
||||||
switch mem := mem.obj.(type) {
|
|
||||||
case *types.Const:
|
|
||||||
val = constValString(mem.Val())
|
|
||||||
case *types.TypeName:
|
|
||||||
typ = typ.Underlying()
|
|
||||||
}
|
|
||||||
members = append(members, &serial.DescribeMember{
|
|
||||||
Name: mem.obj.Name(),
|
|
||||||
Type: typ.String(),
|
|
||||||
Value: val,
|
|
||||||
Pos: fset.Position(mem.obj.Pos()).String(),
|
|
||||||
Kind: tokenOf(mem.obj),
|
|
||||||
Methods: methodsToSerial(r.pkg, mem.methods, fset),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
res.Describe = &serial.Describe{
|
|
||||||
Desc: r.description,
|
|
||||||
Pos: fset.Position(r.node.Pos()).String(),
|
|
||||||
Detail: "package",
|
|
||||||
Package: &serial.DescribePackage{
|
|
||||||
Path: r.pkg.Path(),
|
|
||||||
Members: members,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func tokenOf(o types.Object) string {
|
|
||||||
switch o.(type) {
|
|
||||||
case *types.Func:
|
|
||||||
return "func"
|
|
||||||
case *types.Var:
|
|
||||||
return "var"
|
|
||||||
case *types.TypeName:
|
|
||||||
return "type"
|
|
||||||
case *types.Const:
|
|
||||||
return "const"
|
|
||||||
case *types.PkgName:
|
|
||||||
return "package"
|
|
||||||
case *types.Builtin:
|
|
||||||
return "builtin" // e.g. when describing package "unsafe"
|
|
||||||
case *types.Nil:
|
|
||||||
return "nil"
|
|
||||||
case *types.Label:
|
|
||||||
return "label"
|
|
||||||
}
|
|
||||||
panic(o)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- STATEMENT ------------------------------------------------------------
|
|
||||||
|
|
||||||
func describeStmt(qpos *queryPos, path []ast.Node) (*describeStmtResult, error) {
|
|
||||||
var description string
|
|
||||||
switch n := path[0].(type) {
|
|
||||||
case *ast.Ident:
|
|
||||||
if qpos.info.Defs[n] != nil {
|
|
||||||
description = "labelled statement"
|
|
||||||
} else {
|
|
||||||
description = "reference to labelled statement"
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Nothing much to say about statements.
|
|
||||||
description = astutil.NodeDescription(n)
|
|
||||||
}
|
|
||||||
return &describeStmtResult{qpos.fset, path[0], description}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type describeStmtResult struct {
|
|
||||||
fset *token.FileSet
|
|
||||||
node ast.Node
|
|
||||||
description string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *describeStmtResult) display(printf printfFunc) {
|
|
||||||
printf(r.node, "%s", r.description)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *describeStmtResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
|
||||||
res.Describe = &serial.Describe{
|
|
||||||
Desc: r.description,
|
|
||||||
Pos: fset.Position(r.node.Pos()).String(),
|
|
||||||
Detail: "unknown",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------- Utilities -------------------
|
|
||||||
|
|
||||||
// pathToString returns a string containing the concrete types of the
|
|
||||||
// nodes in path.
|
|
||||||
func pathToString(path []ast.Node) string {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
fmt.Fprint(&buf, "[")
|
|
||||||
for i, n := range path {
|
|
||||||
if i > 0 {
|
|
||||||
fmt.Fprint(&buf, " ")
|
|
||||||
}
|
|
||||||
fmt.Fprint(&buf, strings.TrimPrefix(fmt.Sprintf("%T", n), "*ast."))
|
|
||||||
}
|
|
||||||
fmt.Fprint(&buf, "]")
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func accessibleMethods(t types.Type, from *types.Package) []*types.Selection {
|
|
||||||
var methods []*types.Selection
|
|
||||||
for _, meth := range typeutil.IntuitiveMethodSet(t, nil) {
|
|
||||||
if isAccessibleFrom(meth.Obj(), from) {
|
|
||||||
methods = append(methods, meth)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return methods
|
|
||||||
}
|
|
||||||
|
|
||||||
func isAccessibleFrom(obj types.Object, pkg *types.Package) bool {
|
|
||||||
return ast.IsExported(obj.Name()) || obj.Pkg() == pkg
|
|
||||||
}
|
|
||||||
|
|
||||||
func methodsToSerial(this *types.Package, methods []*types.Selection, fset *token.FileSet) []serial.DescribeMethod {
|
|
||||||
qualifier := types.RelativeTo(this)
|
|
||||||
var jmethods []serial.DescribeMethod
|
|
||||||
for _, meth := range methods {
|
|
||||||
var ser serial.DescribeMethod
|
|
||||||
if meth != nil { // may contain nils when called by implements (on a method)
|
|
||||||
ser = serial.DescribeMethod{
|
|
||||||
Name: types.SelectionString(meth, qualifier),
|
|
||||||
Pos: fset.Position(meth.Obj().Pos()).String(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
jmethods = append(jmethods, ser)
|
|
||||||
}
|
|
||||||
return jmethods
|
|
||||||
}
|
|
||||||
|
|
||||||
// constValString emulates Go 1.6's go/constant.ExactString well enough
|
|
||||||
// to make the tests pass. This is just a stopgap until we throw away
|
|
||||||
// all the *15.go files.
|
|
||||||
func constValString(v exact.Value) string {
|
|
||||||
if v.Kind() == exact.Float {
|
|
||||||
f, _ := exact.Float64Val(v)
|
|
||||||
return fmt.Sprintf("%g", f)
|
|
||||||
}
|
|
||||||
return v.String()
|
|
||||||
}
|
|
||||||
|
|
@ -1,224 +0,0 @@
|
||||||
// Copyright 2013 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build go1.5
|
|
||||||
|
|
||||||
package oracle
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"go/ast"
|
|
||||||
"go/printer"
|
|
||||||
"go/token"
|
|
||||||
"go/types"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"golang.org/x/tools/go/loader"
|
|
||||||
"golang.org/x/tools/oracle/serial"
|
|
||||||
)
|
|
||||||
|
|
||||||
// freevars displays the lexical (not package-level) free variables of
|
|
||||||
// the selection.
|
|
||||||
//
|
|
||||||
// It treats A.B.C as a separate variable from A to reveal the parts
|
|
||||||
// of an aggregate type that are actually needed.
|
|
||||||
// This aids refactoring.
|
|
||||||
//
|
|
||||||
// TODO(adonovan): optionally display the free references to
|
|
||||||
// file/package scope objects, and to objects from other packages.
|
|
||||||
// Depending on where the resulting function abstraction will go,
|
|
||||||
// these might be interesting. Perhaps group the results into three
|
|
||||||
// bands.
|
|
||||||
//
|
|
||||||
func freevars(q *Query) error {
|
|
||||||
lconf := loader.Config{Build: q.Build}
|
|
||||||
allowErrors(&lconf)
|
|
||||||
|
|
||||||
if _, err := importQueryPackage(q.Pos, &lconf); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load/parse/type-check the program.
|
|
||||||
lprog, err := lconf.Load()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
q.Fset = lprog.Fset
|
|
||||||
|
|
||||||
qpos, err := parseQueryPos(lprog, q.Pos, false)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
file := qpos.path[len(qpos.path)-1] // the enclosing file
|
|
||||||
fileScope := qpos.info.Scopes[file]
|
|
||||||
pkgScope := fileScope.Parent()
|
|
||||||
|
|
||||||
// The id and sel functions return non-nil if they denote an
|
|
||||||
// object o or selection o.x.y that is referenced by the
|
|
||||||
// selection but defined neither within the selection nor at
|
|
||||||
// file scope, i.e. it is in the lexical environment.
|
|
||||||
var id func(n *ast.Ident) types.Object
|
|
||||||
var sel func(n *ast.SelectorExpr) types.Object
|
|
||||||
|
|
||||||
sel = func(n *ast.SelectorExpr) types.Object {
|
|
||||||
switch x := unparen(n.X).(type) {
|
|
||||||
case *ast.SelectorExpr:
|
|
||||||
return sel(x)
|
|
||||||
case *ast.Ident:
|
|
||||||
return id(x)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
id = func(n *ast.Ident) types.Object {
|
|
||||||
obj := qpos.info.Uses[n]
|
|
||||||
if obj == nil {
|
|
||||||
return nil // not a reference
|
|
||||||
}
|
|
||||||
if _, ok := obj.(*types.PkgName); ok {
|
|
||||||
return nil // imported package
|
|
||||||
}
|
|
||||||
if !(file.Pos() <= obj.Pos() && obj.Pos() <= file.End()) {
|
|
||||||
return nil // not defined in this file
|
|
||||||
}
|
|
||||||
scope := obj.Parent()
|
|
||||||
if scope == nil {
|
|
||||||
return nil // e.g. interface method, struct field
|
|
||||||
}
|
|
||||||
if scope == fileScope || scope == pkgScope {
|
|
||||||
return nil // defined at file or package scope
|
|
||||||
}
|
|
||||||
if qpos.start <= obj.Pos() && obj.Pos() <= qpos.end {
|
|
||||||
return nil // defined within selection => not free
|
|
||||||
}
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
|
|
||||||
// Maps each reference that is free in the selection
|
|
||||||
// to the object it refers to.
|
|
||||||
// The map de-duplicates repeated references.
|
|
||||||
refsMap := make(map[string]freevarsRef)
|
|
||||||
|
|
||||||
// Visit all the identifiers in the selected ASTs.
|
|
||||||
ast.Inspect(qpos.path[0], func(n ast.Node) bool {
|
|
||||||
if n == nil {
|
|
||||||
return true // popping DFS stack
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is this node contained within the selection?
|
|
||||||
// (freevars permits inexact selections,
|
|
||||||
// like two stmts in a block.)
|
|
||||||
if qpos.start <= n.Pos() && n.End() <= qpos.end {
|
|
||||||
var obj types.Object
|
|
||||||
var prune bool
|
|
||||||
switch n := n.(type) {
|
|
||||||
case *ast.Ident:
|
|
||||||
obj = id(n)
|
|
||||||
|
|
||||||
case *ast.SelectorExpr:
|
|
||||||
obj = sel(n)
|
|
||||||
prune = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if obj != nil {
|
|
||||||
var kind string
|
|
||||||
switch obj.(type) {
|
|
||||||
case *types.Var:
|
|
||||||
kind = "var"
|
|
||||||
case *types.Func:
|
|
||||||
kind = "func"
|
|
||||||
case *types.TypeName:
|
|
||||||
kind = "type"
|
|
||||||
case *types.Const:
|
|
||||||
kind = "const"
|
|
||||||
case *types.Label:
|
|
||||||
kind = "label"
|
|
||||||
default:
|
|
||||||
panic(obj)
|
|
||||||
}
|
|
||||||
|
|
||||||
typ := qpos.info.TypeOf(n.(ast.Expr))
|
|
||||||
ref := freevarsRef{kind, printNode(lprog.Fset, n), typ, obj}
|
|
||||||
refsMap[ref.ref] = ref
|
|
||||||
|
|
||||||
if prune {
|
|
||||||
return false // don't descend
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true // descend
|
|
||||||
})
|
|
||||||
|
|
||||||
refs := make([]freevarsRef, 0, len(refsMap))
|
|
||||||
for _, ref := range refsMap {
|
|
||||||
refs = append(refs, ref)
|
|
||||||
}
|
|
||||||
sort.Sort(byRef(refs))
|
|
||||||
|
|
||||||
q.result = &freevarsResult{
|
|
||||||
qpos: qpos,
|
|
||||||
refs: refs,
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type freevarsResult struct {
|
|
||||||
qpos *queryPos
|
|
||||||
refs []freevarsRef
|
|
||||||
}
|
|
||||||
|
|
||||||
type freevarsRef struct {
|
|
||||||
kind string
|
|
||||||
ref string
|
|
||||||
typ types.Type
|
|
||||||
obj types.Object
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *freevarsResult) display(printf printfFunc) {
|
|
||||||
if len(r.refs) == 0 {
|
|
||||||
printf(r.qpos, "No free identifiers.")
|
|
||||||
} else {
|
|
||||||
printf(r.qpos, "Free identifiers:")
|
|
||||||
qualifier := types.RelativeTo(r.qpos.info.Pkg)
|
|
||||||
for _, ref := range r.refs {
|
|
||||||
// Avoid printing "type T T".
|
|
||||||
var typstr string
|
|
||||||
if ref.kind != "type" {
|
|
||||||
typstr = " " + types.TypeString(ref.typ, qualifier)
|
|
||||||
}
|
|
||||||
printf(ref.obj, "%s %s%s", ref.kind, ref.ref, typstr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *freevarsResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
|
||||||
var refs []*serial.FreeVar
|
|
||||||
for _, ref := range r.refs {
|
|
||||||
refs = append(refs,
|
|
||||||
&serial.FreeVar{
|
|
||||||
Pos: fset.Position(ref.obj.Pos()).String(),
|
|
||||||
Kind: ref.kind,
|
|
||||||
Ref: ref.ref,
|
|
||||||
Type: ref.typ.String(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
res.Freevars = refs
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------- utils --------
|
|
||||||
|
|
||||||
type byRef []freevarsRef
|
|
||||||
|
|
||||||
func (p byRef) Len() int { return len(p) }
|
|
||||||
func (p byRef) Less(i, j int) bool { return p[i].ref < p[j].ref }
|
|
||||||
func (p byRef) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
|
||||||
|
|
||||||
// printNode returns the pretty-printed syntax of n.
|
|
||||||
func printNode(fset *token.FileSet, n ast.Node) string {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
printer.Fprint(&buf, fset, n)
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
@ -1,354 +0,0 @@
|
||||||
// Copyright 2013 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build go1.5
|
|
||||||
|
|
||||||
package oracle
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"go/ast"
|
|
||||||
"go/token"
|
|
||||||
"go/types"
|
|
||||||
"reflect"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/tools/go/loader"
|
|
||||||
"golang.org/x/tools/go/types/typeutil"
|
|
||||||
"golang.org/x/tools/oracle/serial"
|
|
||||||
"golang.org/x/tools/refactor/importgraph"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Implements displays the "implements" relation as it pertains to the
|
|
||||||
// selected type.
|
|
||||||
// If the selection is a method, 'implements' displays
|
|
||||||
// the corresponding methods of the types that would have been reported
|
|
||||||
// by an implements query on the receiver type.
|
|
||||||
//
|
|
||||||
func implements(q *Query) error {
|
|
||||||
lconf := loader.Config{Build: q.Build}
|
|
||||||
allowErrors(&lconf)
|
|
||||||
|
|
||||||
qpkg, err := importQueryPackage(q.Pos, &lconf)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the packages to search.
|
|
||||||
if len(q.Scope) > 0 {
|
|
||||||
// Inspect all packages in the analysis scope, if specified.
|
|
||||||
if err := setPTAScope(&lconf, q.Scope); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Otherwise inspect the forward and reverse
|
|
||||||
// transitive closure of the selected package.
|
|
||||||
// (In theory even this is incomplete.)
|
|
||||||
_, rev, _ := importgraph.Build(q.Build)
|
|
||||||
for path := range rev.Search(qpkg) {
|
|
||||||
lconf.ImportWithTests(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(adonovan): for completeness, we should also
|
|
||||||
// type-check and inspect function bodies in all
|
|
||||||
// imported packages. This would be expensive, but we
|
|
||||||
// could optimize by skipping functions that do not
|
|
||||||
// contain type declarations. This would require
|
|
||||||
// changing the loader's TypeCheckFuncBodies hook to
|
|
||||||
// provide the []*ast.File.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load/parse/type-check the program.
|
|
||||||
lprog, err := lconf.Load()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
q.Fset = lprog.Fset
|
|
||||||
|
|
||||||
qpos, err := parseQueryPos(lprog, q.Pos, false)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the selected type.
|
|
||||||
path, action := findInterestingNode(qpos.info, qpos.path)
|
|
||||||
|
|
||||||
var method *types.Func
|
|
||||||
var T types.Type // selected type (receiver if method != nil)
|
|
||||||
|
|
||||||
switch action {
|
|
||||||
case actionExpr:
|
|
||||||
// method?
|
|
||||||
if id, ok := path[0].(*ast.Ident); ok {
|
|
||||||
if obj, ok := qpos.info.ObjectOf(id).(*types.Func); ok {
|
|
||||||
recv := obj.Type().(*types.Signature).Recv()
|
|
||||||
if recv == nil {
|
|
||||||
return fmt.Errorf("this function is not a method")
|
|
||||||
}
|
|
||||||
method = obj
|
|
||||||
T = recv.Type()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case actionType:
|
|
||||||
T = qpos.info.TypeOf(path[0].(ast.Expr))
|
|
||||||
}
|
|
||||||
if T == nil {
|
|
||||||
return fmt.Errorf("no type or method here")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find all named types, even local types (which can have
|
|
||||||
// methods via promotion) and the built-in "error".
|
|
||||||
var allNamed []types.Type
|
|
||||||
for _, info := range lprog.AllPackages {
|
|
||||||
for _, obj := range info.Defs {
|
|
||||||
if obj, ok := obj.(*types.TypeName); ok {
|
|
||||||
allNamed = append(allNamed, obj.Type())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
allNamed = append(allNamed, types.Universe.Lookup("error").Type())
|
|
||||||
|
|
||||||
var msets typeutil.MethodSetCache
|
|
||||||
|
|
||||||
// Test each named type.
|
|
||||||
var to, from, fromPtr []types.Type
|
|
||||||
for _, U := range allNamed {
|
|
||||||
if isInterface(T) {
|
|
||||||
if msets.MethodSet(T).Len() == 0 {
|
|
||||||
continue // empty interface
|
|
||||||
}
|
|
||||||
if isInterface(U) {
|
|
||||||
if msets.MethodSet(U).Len() == 0 {
|
|
||||||
continue // empty interface
|
|
||||||
}
|
|
||||||
|
|
||||||
// T interface, U interface
|
|
||||||
if !types.Identical(T, U) {
|
|
||||||
if types.AssignableTo(U, T) {
|
|
||||||
to = append(to, U)
|
|
||||||
}
|
|
||||||
if types.AssignableTo(T, U) {
|
|
||||||
from = append(from, U)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// T interface, U concrete
|
|
||||||
if types.AssignableTo(U, T) {
|
|
||||||
to = append(to, U)
|
|
||||||
} else if pU := types.NewPointer(U); types.AssignableTo(pU, T) {
|
|
||||||
to = append(to, pU)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if isInterface(U) {
|
|
||||||
if msets.MethodSet(U).Len() == 0 {
|
|
||||||
continue // empty interface
|
|
||||||
}
|
|
||||||
|
|
||||||
// T concrete, U interface
|
|
||||||
if types.AssignableTo(T, U) {
|
|
||||||
from = append(from, U)
|
|
||||||
} else if pT := types.NewPointer(T); types.AssignableTo(pT, U) {
|
|
||||||
fromPtr = append(fromPtr, U)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var pos interface{} = qpos
|
|
||||||
if nt, ok := deref(T).(*types.Named); ok {
|
|
||||||
pos = nt.Obj()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort types (arbitrarily) to ensure test determinism.
|
|
||||||
sort.Sort(typesByString(to))
|
|
||||||
sort.Sort(typesByString(from))
|
|
||||||
sort.Sort(typesByString(fromPtr))
|
|
||||||
|
|
||||||
var toMethod, fromMethod, fromPtrMethod []*types.Selection // contain nils
|
|
||||||
if method != nil {
|
|
||||||
for _, t := range to {
|
|
||||||
toMethod = append(toMethod,
|
|
||||||
types.NewMethodSet(t).Lookup(method.Pkg(), method.Name()))
|
|
||||||
}
|
|
||||||
for _, t := range from {
|
|
||||||
fromMethod = append(fromMethod,
|
|
||||||
types.NewMethodSet(t).Lookup(method.Pkg(), method.Name()))
|
|
||||||
}
|
|
||||||
for _, t := range fromPtr {
|
|
||||||
fromPtrMethod = append(fromPtrMethod,
|
|
||||||
types.NewMethodSet(t).Lookup(method.Pkg(), method.Name()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
q.result = &implementsResult{
|
|
||||||
qpos, T, pos, to, from, fromPtr, method, toMethod, fromMethod, fromPtrMethod,
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type implementsResult struct {
|
|
||||||
qpos *queryPos
|
|
||||||
|
|
||||||
t types.Type // queried type (not necessarily named)
|
|
||||||
pos interface{} // pos of t (*types.Name or *QueryPos)
|
|
||||||
to []types.Type // named or ptr-to-named types assignable to interface T
|
|
||||||
from []types.Type // named interfaces assignable from T
|
|
||||||
fromPtr []types.Type // named interfaces assignable only from *T
|
|
||||||
|
|
||||||
// if a method was queried:
|
|
||||||
method *types.Func // queried method
|
|
||||||
toMethod []*types.Selection // method of type to[i], if any
|
|
||||||
fromMethod []*types.Selection // method of type from[i], if any
|
|
||||||
fromPtrMethod []*types.Selection // method of type fromPtrMethod[i], if any
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *implementsResult) display(printf printfFunc) {
|
|
||||||
relation := "is implemented by"
|
|
||||||
|
|
||||||
meth := func(sel *types.Selection) {
|
|
||||||
if sel != nil {
|
|
||||||
printf(sel.Obj(), "\t%s method (%s).%s",
|
|
||||||
relation, r.qpos.typeString(sel.Recv()), sel.Obj().Name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if isInterface(r.t) {
|
|
||||||
if types.NewMethodSet(r.t).Len() == 0 { // TODO(adonovan): cache mset
|
|
||||||
printf(r.pos, "empty interface type %s", r.qpos.typeString(r.t))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.method == nil {
|
|
||||||
printf(r.pos, "interface type %s", r.qpos.typeString(r.t))
|
|
||||||
} else {
|
|
||||||
printf(r.method, "abstract method %s", r.qpos.objectString(r.method))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show concrete types (or methods) first; use two passes.
|
|
||||||
for i, sub := range r.to {
|
|
||||||
if !isInterface(sub) {
|
|
||||||
if r.method == nil {
|
|
||||||
printf(deref(sub).(*types.Named).Obj(), "\t%s %s type %s",
|
|
||||||
relation, typeKind(sub), r.qpos.typeString(sub))
|
|
||||||
} else {
|
|
||||||
meth(r.toMethod[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i, sub := range r.to {
|
|
||||||
if isInterface(sub) {
|
|
||||||
if r.method == nil {
|
|
||||||
printf(sub.(*types.Named).Obj(), "\t%s %s type %s",
|
|
||||||
relation, typeKind(sub), r.qpos.typeString(sub))
|
|
||||||
} else {
|
|
||||||
meth(r.toMethod[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
relation = "implements"
|
|
||||||
for i, super := range r.from {
|
|
||||||
if r.method == nil {
|
|
||||||
printf(super.(*types.Named).Obj(), "\t%s %s",
|
|
||||||
relation, r.qpos.typeString(super))
|
|
||||||
} else {
|
|
||||||
meth(r.fromMethod[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
relation = "implements"
|
|
||||||
|
|
||||||
if r.from != nil {
|
|
||||||
if r.method == nil {
|
|
||||||
printf(r.pos, "%s type %s",
|
|
||||||
typeKind(r.t), r.qpos.typeString(r.t))
|
|
||||||
} else {
|
|
||||||
printf(r.method, "concrete method %s",
|
|
||||||
r.qpos.objectString(r.method))
|
|
||||||
}
|
|
||||||
for i, super := range r.from {
|
|
||||||
if r.method == nil {
|
|
||||||
printf(super.(*types.Named).Obj(), "\t%s %s",
|
|
||||||
relation, r.qpos.typeString(super))
|
|
||||||
} else {
|
|
||||||
meth(r.fromMethod[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if r.fromPtr != nil {
|
|
||||||
if r.method == nil {
|
|
||||||
printf(r.pos, "pointer type *%s", r.qpos.typeString(r.t))
|
|
||||||
} else {
|
|
||||||
// TODO(adonovan): de-dup (C).f and (*C).f implementing (I).f.
|
|
||||||
printf(r.method, "concrete method %s",
|
|
||||||
r.qpos.objectString(r.method))
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, psuper := range r.fromPtr {
|
|
||||||
if r.method == nil {
|
|
||||||
printf(psuper.(*types.Named).Obj(), "\t%s %s",
|
|
||||||
relation, r.qpos.typeString(psuper))
|
|
||||||
} else {
|
|
||||||
meth(r.fromPtrMethod[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if r.from == nil {
|
|
||||||
printf(r.pos, "%s type %s implements only interface{}",
|
|
||||||
typeKind(r.t), r.qpos.typeString(r.t))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *implementsResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
|
||||||
res.Implements = &serial.Implements{
|
|
||||||
T: makeImplementsType(r.t, fset),
|
|
||||||
AssignableTo: makeImplementsTypes(r.to, fset),
|
|
||||||
AssignableFrom: makeImplementsTypes(r.from, fset),
|
|
||||||
AssignableFromPtr: makeImplementsTypes(r.fromPtr, fset),
|
|
||||||
AssignableToMethod: methodsToSerial(r.qpos.info.Pkg, r.toMethod, fset),
|
|
||||||
AssignableFromMethod: methodsToSerial(r.qpos.info.Pkg, r.fromMethod, fset),
|
|
||||||
AssignableFromPtrMethod: methodsToSerial(r.qpos.info.Pkg, r.fromPtrMethod, fset),
|
|
||||||
}
|
|
||||||
if r.method != nil {
|
|
||||||
res.Implements.Method = &serial.DescribeMethod{
|
|
||||||
Name: r.qpos.objectString(r.method),
|
|
||||||
Pos: fset.Position(r.method.Pos()).String(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeImplementsTypes(tt []types.Type, fset *token.FileSet) []serial.ImplementsType {
|
|
||||||
var r []serial.ImplementsType
|
|
||||||
for _, t := range tt {
|
|
||||||
r = append(r, makeImplementsType(t, fset))
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeImplementsType(T types.Type, fset *token.FileSet) serial.ImplementsType {
|
|
||||||
var pos token.Pos
|
|
||||||
if nt, ok := deref(T).(*types.Named); ok { // implementsResult.t may be non-named
|
|
||||||
pos = nt.Obj().Pos()
|
|
||||||
}
|
|
||||||
return serial.ImplementsType{
|
|
||||||
Name: T.String(),
|
|
||||||
Pos: fset.Position(pos).String(),
|
|
||||||
Kind: typeKind(T),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// typeKind returns a string describing the underlying kind of type,
|
|
||||||
// e.g. "slice", "array", "struct".
|
|
||||||
func typeKind(T types.Type) string {
|
|
||||||
s := reflect.TypeOf(T.Underlying()).String()
|
|
||||||
return strings.ToLower(strings.TrimPrefix(s, "*types."))
|
|
||||||
}
|
|
||||||
|
|
||||||
func isInterface(T types.Type) bool { return types.IsInterface(T) }
|
|
||||||
|
|
||||||
type typesByString []types.Type
|
|
||||||
|
|
||||||
func (p typesByString) Len() int { return len(p) }
|
|
||||||
func (p typesByString) Less(i, j int) bool { return p[i].String() < p[j].String() }
|
|
||||||
func (p typesByString) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
|
||||||
368
oracle/oracle.go
368
oracle/oracle.go
|
|
@ -1,368 +0,0 @@
|
||||||
// Copyright 2014 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build go1.5
|
|
||||||
|
|
||||||
// Package oracle contains the implementation of the oracle tool whose
|
|
||||||
// command-line is provided by golang.org/x/tools/cmd/oracle.
|
|
||||||
//
|
|
||||||
// DEPRECATED: oracle has been superseded by guru;
|
|
||||||
// see https://golang.org/s/using-guru for details.
|
|
||||||
// This package will be deleted on October 1, 2016.
|
|
||||||
//
|
|
||||||
package oracle // import "golang.org/x/tools/oracle"
|
|
||||||
|
|
||||||
// This file defines oracle.Query, the entry point for the oracle tool.
|
|
||||||
// The actual executable is defined in cmd/oracle.
|
|
||||||
|
|
||||||
// TODO(adonovan): new queries
|
|
||||||
// - show all statements that may update the selected lvalue
|
|
||||||
// (local, global, field, etc).
|
|
||||||
// - show all places where an object of type T is created
|
|
||||||
// (&T{}, var t T, new(T), new(struct{array [3]T}), etc.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"go/ast"
|
|
||||||
"go/build"
|
|
||||||
"go/parser"
|
|
||||||
"go/token"
|
|
||||||
"go/types"
|
|
||||||
"io"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"golang.org/x/tools/go/ast/astutil"
|
|
||||||
"golang.org/x/tools/go/loader"
|
|
||||||
"golang.org/x/tools/go/pointer"
|
|
||||||
"golang.org/x/tools/go/ssa"
|
|
||||||
"golang.org/x/tools/oracle/serial"
|
|
||||||
)
|
|
||||||
|
|
||||||
type printfFunc func(pos interface{}, format string, args ...interface{})
|
|
||||||
|
|
||||||
// queryResult is the interface of each query-specific result type.
|
|
||||||
type queryResult interface {
|
|
||||||
toSerial(res *serial.Result, fset *token.FileSet)
|
|
||||||
display(printf printfFunc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// A QueryPos represents the position provided as input to a query:
|
|
||||||
// a textual extent in the program's source code, the AST node it
|
|
||||||
// corresponds to, and the package to which it belongs.
|
|
||||||
// Instances are created by parseQueryPos.
|
|
||||||
type queryPos struct {
|
|
||||||
fset *token.FileSet
|
|
||||||
start, end token.Pos // source extent of query
|
|
||||||
path []ast.Node // AST path from query node to root of ast.File
|
|
||||||
exact bool // 2nd result of PathEnclosingInterval
|
|
||||||
info *loader.PackageInfo // type info for the queried package (nil for fastQueryPos)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TypeString prints type T relative to the query position.
|
|
||||||
func (qpos *queryPos) typeString(T types.Type) string {
|
|
||||||
return types.TypeString(T, types.RelativeTo(qpos.info.Pkg))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ObjectString prints object obj relative to the query position.
|
|
||||||
func (qpos *queryPos) objectString(obj types.Object) string {
|
|
||||||
return types.ObjectString(obj, types.RelativeTo(qpos.info.Pkg))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SelectionString prints selection sel relative to the query position.
|
|
||||||
func (qpos *queryPos) selectionString(sel *types.Selection) string {
|
|
||||||
return types.SelectionString(sel, types.RelativeTo(qpos.info.Pkg))
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Query specifies a single oracle query.
|
|
||||||
type Query struct {
|
|
||||||
Mode string // query mode ("callers", etc)
|
|
||||||
Pos string // query position
|
|
||||||
Build *build.Context // package loading configuration
|
|
||||||
|
|
||||||
// pointer analysis options
|
|
||||||
Scope []string // main packages in (*loader.Config).FromArgs syntax
|
|
||||||
PTALog io.Writer // (optional) pointer-analysis log file
|
|
||||||
Reflection bool // model reflection soundly (currently slow).
|
|
||||||
|
|
||||||
// Populated during Run()
|
|
||||||
Fset *token.FileSet
|
|
||||||
result queryResult
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serial returns an instance of serial.Result, which implements the
|
|
||||||
// {xml,json}.Marshaler interfaces so that query results can be
|
|
||||||
// serialized as JSON or XML.
|
|
||||||
//
|
|
||||||
func (q *Query) Serial() *serial.Result {
|
|
||||||
resj := &serial.Result{Mode: q.Mode}
|
|
||||||
q.result.toSerial(resj, q.Fset)
|
|
||||||
return resj
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteTo writes the oracle query result res to out in a compiler diagnostic format.
|
|
||||||
func (q *Query) WriteTo(out io.Writer) {
|
|
||||||
printf := func(pos interface{}, format string, args ...interface{}) {
|
|
||||||
fprintf(out, q.Fset, pos, format, args...)
|
|
||||||
}
|
|
||||||
q.result.display(printf)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run runs an oracle query and populates its Fset and Result.
|
|
||||||
func Run(q *Query) error {
|
|
||||||
switch q.Mode {
|
|
||||||
case "callees":
|
|
||||||
return callees(q)
|
|
||||||
case "callers":
|
|
||||||
return callers(q)
|
|
||||||
case "callstack":
|
|
||||||
return callstack(q)
|
|
||||||
case "peers":
|
|
||||||
return peers(q)
|
|
||||||
case "pointsto":
|
|
||||||
return pointsto(q)
|
|
||||||
case "whicherrs":
|
|
||||||
return whicherrs(q)
|
|
||||||
case "definition":
|
|
||||||
return definition(q)
|
|
||||||
case "describe":
|
|
||||||
return describe(q)
|
|
||||||
case "freevars":
|
|
||||||
return freevars(q)
|
|
||||||
case "implements":
|
|
||||||
return implements(q)
|
|
||||||
case "referrers":
|
|
||||||
return referrers(q)
|
|
||||||
case "what":
|
|
||||||
return what(q)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("invalid mode: %q", q.Mode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setPTAScope(lconf *loader.Config, scope []string) error {
|
|
||||||
if len(scope) == 0 {
|
|
||||||
return fmt.Errorf("no packages specified for pointer analysis scope")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine initial packages for PTA.
|
|
||||||
args, err := lconf.FromArgs(scope, true)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(args) > 0 {
|
|
||||||
return fmt.Errorf("surplus arguments: %q", args)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a pointer.Config whose scope is the initial packages of lprog
|
|
||||||
// and their dependencies.
|
|
||||||
func setupPTA(prog *ssa.Program, lprog *loader.Program, ptaLog io.Writer, reflection bool) (*pointer.Config, error) {
|
|
||||||
// TODO(adonovan): the body of this function is essentially
|
|
||||||
// duplicated in all go/pointer clients. Refactor.
|
|
||||||
|
|
||||||
// For each initial package (specified on the command line),
|
|
||||||
// if it has a main function, analyze that,
|
|
||||||
// otherwise analyze its tests, if any.
|
|
||||||
var testPkgs, mains []*ssa.Package
|
|
||||||
for _, info := range lprog.InitialPackages() {
|
|
||||||
initialPkg := prog.Package(info.Pkg)
|
|
||||||
|
|
||||||
// Add package to the pointer analysis scope.
|
|
||||||
if initialPkg.Func("main") != nil {
|
|
||||||
mains = append(mains, initialPkg)
|
|
||||||
} else {
|
|
||||||
testPkgs = append(testPkgs, initialPkg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if testPkgs != nil {
|
|
||||||
if p := prog.CreateTestMainPackage(testPkgs...); p != nil {
|
|
||||||
mains = append(mains, p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if mains == nil {
|
|
||||||
return nil, fmt.Errorf("analysis scope has no main and no tests")
|
|
||||||
}
|
|
||||||
return &pointer.Config{
|
|
||||||
Log: ptaLog,
|
|
||||||
Reflection: reflection,
|
|
||||||
Mains: mains,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// importQueryPackage finds the package P containing the
|
|
||||||
// query position and tells conf to import it.
|
|
||||||
// It returns the package's path.
|
|
||||||
func importQueryPackage(pos string, conf *loader.Config) (string, error) {
|
|
||||||
fqpos, err := fastQueryPos(pos)
|
|
||||||
if err != nil {
|
|
||||||
return "", err // bad query
|
|
||||||
}
|
|
||||||
filename := fqpos.fset.File(fqpos.start).Name()
|
|
||||||
|
|
||||||
// This will not work for ad-hoc packages
|
|
||||||
// such as $GOROOT/src/net/http/triv.go.
|
|
||||||
// TODO(adonovan): ensure we report a clear error.
|
|
||||||
_, importPath, err := guessImportPath(filename, conf.Build)
|
|
||||||
if err != nil {
|
|
||||||
return "", err // can't find GOPATH dir
|
|
||||||
}
|
|
||||||
if importPath == "" {
|
|
||||||
return "", fmt.Errorf("can't guess import path from %s", filename)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that it's possible to load the queried package.
|
|
||||||
// (e.g. oracle tests contain different 'package' decls in same dir.)
|
|
||||||
// Keep consistent with logic in loader/util.go!
|
|
||||||
cfg2 := *conf.Build
|
|
||||||
cfg2.CgoEnabled = false
|
|
||||||
bp, err := cfg2.Import(importPath, "", 0)
|
|
||||||
if err != nil {
|
|
||||||
return "", err // no files for package
|
|
||||||
}
|
|
||||||
|
|
||||||
switch pkgContainsFile(bp, filename) {
|
|
||||||
case 'T':
|
|
||||||
conf.ImportWithTests(importPath)
|
|
||||||
case 'X':
|
|
||||||
conf.ImportWithTests(importPath)
|
|
||||||
importPath += "_test" // for TypeCheckFuncBodies
|
|
||||||
case 'G':
|
|
||||||
conf.Import(importPath)
|
|
||||||
default:
|
|
||||||
return "", fmt.Errorf("package %q doesn't contain file %s",
|
|
||||||
importPath, filename)
|
|
||||||
}
|
|
||||||
|
|
||||||
conf.TypeCheckFuncBodies = func(p string) bool { return p == importPath }
|
|
||||||
|
|
||||||
return importPath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// pkgContainsFile reports whether file was among the packages Go
|
|
||||||
// files, Test files, eXternal test files, or not found.
|
|
||||||
func pkgContainsFile(bp *build.Package, filename string) byte {
|
|
||||||
for i, files := range [][]string{bp.GoFiles, bp.TestGoFiles, bp.XTestGoFiles} {
|
|
||||||
for _, file := range files {
|
|
||||||
if sameFile(filepath.Join(bp.Dir, file), filename) {
|
|
||||||
return "GTX"[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0 // not found
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseQueryPos parses the source query position pos and returns the
|
|
||||||
// AST node of the loaded program lprog that it identifies.
|
|
||||||
// If needExact, it must identify a single AST subtree;
|
|
||||||
// this is appropriate for queries that allow fairly arbitrary syntax,
|
|
||||||
// e.g. "describe".
|
|
||||||
//
|
|
||||||
func parseQueryPos(lprog *loader.Program, posFlag string, needExact bool) (*queryPos, error) {
|
|
||||||
filename, startOffset, endOffset, err := parsePosFlag(posFlag)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
start, end, err := findQueryPos(lprog.Fset, filename, startOffset, endOffset)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
info, path, exact := lprog.PathEnclosingInterval(start, end)
|
|
||||||
if path == nil {
|
|
||||||
return nil, fmt.Errorf("no syntax here")
|
|
||||||
}
|
|
||||||
if needExact && !exact {
|
|
||||||
return nil, fmt.Errorf("ambiguous selection within %s", astutil.NodeDescription(path[0]))
|
|
||||||
}
|
|
||||||
return &queryPos{lprog.Fset, start, end, path, exact, info}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------- Utilities ----------
|
|
||||||
|
|
||||||
// allowErrors causes type errors to be silently ignored.
|
|
||||||
// (Not suitable if SSA construction follows.)
|
|
||||||
func allowErrors(lconf *loader.Config) {
|
|
||||||
ctxt := *lconf.Build // copy
|
|
||||||
ctxt.CgoEnabled = false
|
|
||||||
lconf.Build = &ctxt
|
|
||||||
lconf.AllowErrors = true
|
|
||||||
// AllErrors makes the parser always return an AST instead of
|
|
||||||
// bailing out after 10 errors and returning an empty ast.File.
|
|
||||||
lconf.ParserMode = parser.AllErrors
|
|
||||||
lconf.TypeChecker.Error = func(err error) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ptrAnalysis runs the pointer analysis and returns its result.
|
|
||||||
func ptrAnalysis(conf *pointer.Config) *pointer.Result {
|
|
||||||
result, err := pointer.Analyze(conf)
|
|
||||||
if err != nil {
|
|
||||||
panic(err) // pointer analysis internal error
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func unparen(e ast.Expr) ast.Expr { return astutil.Unparen(e) }
|
|
||||||
|
|
||||||
// deref returns a pointer's element type; otherwise it returns typ.
|
|
||||||
func deref(typ types.Type) types.Type {
|
|
||||||
if p, ok := typ.Underlying().(*types.Pointer); ok {
|
|
||||||
return p.Elem()
|
|
||||||
}
|
|
||||||
return typ
|
|
||||||
}
|
|
||||||
|
|
||||||
// fprintf prints to w a message of the form "location: message\n"
|
|
||||||
// where location is derived from pos.
|
|
||||||
//
|
|
||||||
// pos must be one of:
|
|
||||||
// - a token.Pos, denoting a position
|
|
||||||
// - an ast.Node, denoting an interval
|
|
||||||
// - anything with a Pos() method:
|
|
||||||
// ssa.Member, ssa.Value, ssa.Instruction, types.Object, pointer.Label, etc.
|
|
||||||
// - a QueryPos, denoting the extent of the user's query.
|
|
||||||
// - nil, meaning no position at all.
|
|
||||||
//
|
|
||||||
// The output format is is compatible with the 'gnu'
|
|
||||||
// compilation-error-regexp in Emacs' compilation mode.
|
|
||||||
// TODO(adonovan): support other editors.
|
|
||||||
//
|
|
||||||
func fprintf(w io.Writer, fset *token.FileSet, pos interface{}, format string, args ...interface{}) {
|
|
||||||
var start, end token.Pos
|
|
||||||
switch pos := pos.(type) {
|
|
||||||
case ast.Node:
|
|
||||||
start = pos.Pos()
|
|
||||||
end = pos.End()
|
|
||||||
case token.Pos:
|
|
||||||
start = pos
|
|
||||||
end = start
|
|
||||||
case interface {
|
|
||||||
Pos() token.Pos
|
|
||||||
}:
|
|
||||||
start = pos.Pos()
|
|
||||||
end = start
|
|
||||||
case *queryPos:
|
|
||||||
start = pos.start
|
|
||||||
end = pos.end
|
|
||||||
case nil:
|
|
||||||
// no-op
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("invalid pos: %T", pos))
|
|
||||||
}
|
|
||||||
|
|
||||||
if sp := fset.Position(start); start == end {
|
|
||||||
// (prints "-: " for token.NoPos)
|
|
||||||
fmt.Fprintf(w, "%s: ", sp)
|
|
||||||
} else {
|
|
||||||
ep := fset.Position(end)
|
|
||||||
// The -1 below is a concession to Emacs's broken use of
|
|
||||||
// inclusive (not half-open) intervals.
|
|
||||||
// Other editors may not want it.
|
|
||||||
// TODO(adonovan): add an -editor=vim|emacs|acme|auto
|
|
||||||
// flag; auto uses EMACS=t / VIM=... / etc env vars.
|
|
||||||
fmt.Fprintf(w, "%s:%d.%d-%d.%d: ",
|
|
||||||
sp.Filename, sp.Line, sp.Column, ep.Line, ep.Column-1)
|
|
||||||
}
|
|
||||||
fmt.Fprintf(w, format, args...)
|
|
||||||
io.WriteString(w, "\n")
|
|
||||||
}
|
|
||||||
|
|
@ -1,278 +0,0 @@
|
||||||
// Copyright 2013 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package oracle_test
|
|
||||||
|
|
||||||
// This file defines a test framework for oracle queries.
|
|
||||||
//
|
|
||||||
// The files beneath testdata/src/main contain Go programs containing
|
|
||||||
// query annotations of the form:
|
|
||||||
//
|
|
||||||
// @verb id "select"
|
|
||||||
//
|
|
||||||
// where verb is the query mode (e.g. "callers"), id is a unique name
|
|
||||||
// for this query, and "select" is a regular expression matching the
|
|
||||||
// substring of the current line that is the query's input selection.
|
|
||||||
//
|
|
||||||
// The expected output for each query is provided in the accompanying
|
|
||||||
// .golden file.
|
|
||||||
//
|
|
||||||
// (Location information is not included because it's too fragile to
|
|
||||||
// display as text. TODO(adonovan): think about how we can test its
|
|
||||||
// correctness, since it is critical information.)
|
|
||||||
//
|
|
||||||
// Run this test with:
|
|
||||||
// % go test golang.org/x/tools/oracle -update
|
|
||||||
// to update the golden files.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"go/build"
|
|
||||||
"go/parser"
|
|
||||||
"go/token"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"regexp"
|
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"golang.org/x/tools/oracle"
|
|
||||||
)
|
|
||||||
|
|
||||||
var updateFlag = flag.Bool("update", false, "Update the golden files.")
|
|
||||||
|
|
||||||
type query struct {
|
|
||||||
id string // unique id
|
|
||||||
verb string // query mode, e.g. "callees"
|
|
||||||
posn token.Position // position of of query
|
|
||||||
filename string
|
|
||||||
queryPos string // value of -pos flag
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseRegexp(text string) (*regexp.Regexp, error) {
|
|
||||||
pattern, err := strconv.Unquote(text)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("can't unquote %s", text)
|
|
||||||
}
|
|
||||||
return regexp.Compile(pattern)
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseQueries parses and returns the queries in the named file.
|
|
||||||
func parseQueries(t *testing.T, filename string) []*query {
|
|
||||||
filedata, err := ioutil.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the file once to discover the test queries.
|
|
||||||
fset := token.NewFileSet()
|
|
||||||
f, err := parser.ParseFile(fset, filename, filedata, parser.ParseComments)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
lines := bytes.Split(filedata, []byte("\n"))
|
|
||||||
|
|
||||||
var queries []*query
|
|
||||||
queriesById := make(map[string]*query)
|
|
||||||
|
|
||||||
// Find all annotations of these forms:
|
|
||||||
expectRe := regexp.MustCompile(`@([a-z]+)\s+(\S+)\s+(\".*)$`) // @verb id "regexp"
|
|
||||||
for _, c := range f.Comments {
|
|
||||||
text := strings.TrimSpace(c.Text())
|
|
||||||
if text == "" || text[0] != '@' {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
posn := fset.Position(c.Pos())
|
|
||||||
|
|
||||||
// @verb id "regexp"
|
|
||||||
match := expectRe.FindStringSubmatch(text)
|
|
||||||
if match == nil {
|
|
||||||
t.Errorf("%s: ill-formed query: %s", posn, text)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
id := match[2]
|
|
||||||
if prev, ok := queriesById[id]; ok {
|
|
||||||
t.Errorf("%s: duplicate id %s", posn, id)
|
|
||||||
t.Errorf("%s: previously used here", prev.posn)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
q := &query{
|
|
||||||
id: id,
|
|
||||||
verb: match[1],
|
|
||||||
filename: filename,
|
|
||||||
posn: posn,
|
|
||||||
}
|
|
||||||
|
|
||||||
if match[3] != `"nopos"` {
|
|
||||||
selectRe, err := parseRegexp(match[3])
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("%s: %s", posn, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find text of the current line, sans query.
|
|
||||||
// (Queries must be // not /**/ comments.)
|
|
||||||
line := lines[posn.Line-1][:posn.Column-1]
|
|
||||||
|
|
||||||
// Apply regexp to current line to find input selection.
|
|
||||||
loc := selectRe.FindIndex(line)
|
|
||||||
if loc == nil {
|
|
||||||
t.Errorf("%s: selection pattern %s doesn't match line %q",
|
|
||||||
posn, match[3], string(line))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assumes ASCII. TODO(adonovan): test on UTF-8.
|
|
||||||
linestart := posn.Offset - (posn.Column - 1)
|
|
||||||
|
|
||||||
// Compute the file offsets.
|
|
||||||
q.queryPos = fmt.Sprintf("%s:#%d,#%d",
|
|
||||||
filename, linestart+loc[0], linestart+loc[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
queries = append(queries, q)
|
|
||||||
queriesById[id] = q
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the slice, not map, for deterministic iteration.
|
|
||||||
return queries
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteResult writes res (-format=plain) to w, stripping file locations.
|
|
||||||
func WriteResult(w io.Writer, q *oracle.Query) {
|
|
||||||
capture := new(bytes.Buffer) // capture standard output
|
|
||||||
q.WriteTo(capture)
|
|
||||||
for _, line := range strings.Split(capture.String(), "\n") {
|
|
||||||
// Remove a "file:line: " prefix.
|
|
||||||
if i := strings.Index(line, ": "); i >= 0 {
|
|
||||||
line = line[i+2:]
|
|
||||||
}
|
|
||||||
fmt.Fprintf(w, "%s\n", line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// doQuery poses query q to the oracle and writes its response and
|
|
||||||
// error (if any) to out.
|
|
||||||
func doQuery(out io.Writer, q *query, useJson bool) {
|
|
||||||
fmt.Fprintf(out, "-------- @%s %s --------\n", q.verb, q.id)
|
|
||||||
|
|
||||||
var buildContext = build.Default
|
|
||||||
buildContext.GOPATH = "testdata"
|
|
||||||
query := oracle.Query{
|
|
||||||
Mode: q.verb,
|
|
||||||
Pos: q.queryPos,
|
|
||||||
Build: &buildContext,
|
|
||||||
Scope: []string{q.filename},
|
|
||||||
Reflection: true,
|
|
||||||
}
|
|
||||||
if err := oracle.Run(&query); err != nil {
|
|
||||||
fmt.Fprintf(out, "\nError: %s\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if useJson {
|
|
||||||
// JSON output
|
|
||||||
b, err := json.MarshalIndent(query.Serial(), "", "\t")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(out, "JSON error: %s\n", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
out.Write(b)
|
|
||||||
fmt.Fprintln(out)
|
|
||||||
} else {
|
|
||||||
// "plain" (compiler diagnostic format) output
|
|
||||||
WriteResult(out, &query)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOracle(t *testing.T) {
|
|
||||||
switch runtime.GOOS {
|
|
||||||
case "android":
|
|
||||||
t.Skipf("skipping test on %q (no testdata dir)", runtime.GOOS)
|
|
||||||
case "windows":
|
|
||||||
t.Skipf("skipping test on %q (no /usr/bin/diff)", runtime.GOOS)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, filename := range []string{
|
|
||||||
"testdata/src/calls/main.go",
|
|
||||||
"testdata/src/describe/main.go",
|
|
||||||
"testdata/src/freevars/main.go",
|
|
||||||
"testdata/src/implements/main.go",
|
|
||||||
"testdata/src/implements-methods/main.go",
|
|
||||||
"testdata/src/imports/main.go",
|
|
||||||
"testdata/src/peers/main.go",
|
|
||||||
"testdata/src/pointsto/main.go",
|
|
||||||
"testdata/src/referrers/main.go",
|
|
||||||
"testdata/src/reflection/main.go",
|
|
||||||
"testdata/src/what/main.go",
|
|
||||||
"testdata/src/whicherrs/main.go",
|
|
||||||
// JSON:
|
|
||||||
// TODO(adonovan): most of these are very similar; combine them.
|
|
||||||
"testdata/src/calls-json/main.go",
|
|
||||||
"testdata/src/peers-json/main.go",
|
|
||||||
"testdata/src/describe-json/main.go",
|
|
||||||
"testdata/src/implements-json/main.go",
|
|
||||||
"testdata/src/implements-methods-json/main.go",
|
|
||||||
"testdata/src/pointsto-json/main.go",
|
|
||||||
"testdata/src/referrers-json/main.go",
|
|
||||||
"testdata/src/what-json/main.go",
|
|
||||||
} {
|
|
||||||
if filename == "testdata/src/referrers/main.go" && runtime.GOOS == "plan9" {
|
|
||||||
// Disable this test on plan9 since it expects a particular
|
|
||||||
// wording for a "no such file or directory" error.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
useJson := strings.Contains(filename, "-json/")
|
|
||||||
queries := parseQueries(t, filename)
|
|
||||||
golden := filename + "lden"
|
|
||||||
got := filename + "t"
|
|
||||||
gotfh, err := os.Create(got)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Create(%s) failed: %s", got, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
defer gotfh.Close()
|
|
||||||
defer os.Remove(got)
|
|
||||||
|
|
||||||
// Run the oracle on each query, redirecting its output
|
|
||||||
// and error (if any) to the foo.got file.
|
|
||||||
for _, q := range queries {
|
|
||||||
doQuery(gotfh, q, useJson)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compare foo.got with foo.golden.
|
|
||||||
var cmd *exec.Cmd
|
|
||||||
switch runtime.GOOS {
|
|
||||||
case "plan9":
|
|
||||||
cmd = exec.Command("/bin/diff", "-c", golden, got)
|
|
||||||
default:
|
|
||||||
cmd = exec.Command("/usr/bin/diff", "-u", golden, got)
|
|
||||||
}
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
cmd.Stdout = buf
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
t.Errorf("Oracle tests for %s failed: %s.\n%s\n",
|
|
||||||
filename, err, buf)
|
|
||||||
|
|
||||||
if *updateFlag {
|
|
||||||
t.Logf("Updating %s...", golden)
|
|
||||||
if err := exec.Command("/bin/cp", got, golden).Run(); err != nil {
|
|
||||||
t.Errorf("Update failed: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
254
oracle/peers.go
254
oracle/peers.go
|
|
@ -1,254 +0,0 @@
|
||||||
// Copyright 2013 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build go1.5
|
|
||||||
|
|
||||||
package oracle
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"go/ast"
|
|
||||||
"go/token"
|
|
||||||
"go/types"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"golang.org/x/tools/go/loader"
|
|
||||||
"golang.org/x/tools/go/ssa"
|
|
||||||
"golang.org/x/tools/go/ssa/ssautil"
|
|
||||||
"golang.org/x/tools/oracle/serial"
|
|
||||||
)
|
|
||||||
|
|
||||||
// peers enumerates, for a given channel send (or receive) operation,
|
|
||||||
// the set of possible receives (or sends) that correspond to it.
|
|
||||||
//
|
|
||||||
// TODO(adonovan): support reflect.{Select,Recv,Send,Close}.
|
|
||||||
// TODO(adonovan): permit the user to query based on a MakeChan (not send/recv),
|
|
||||||
// or the implicit receive in "for v := range ch".
|
|
||||||
func peers(q *Query) error {
|
|
||||||
lconf := loader.Config{Build: q.Build}
|
|
||||||
|
|
||||||
if err := setPTAScope(&lconf, q.Scope); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load/parse/type-check the program.
|
|
||||||
lprog, err := lconf.Load()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
q.Fset = lprog.Fset
|
|
||||||
|
|
||||||
qpos, err := parseQueryPos(lprog, q.Pos, false)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug)
|
|
||||||
|
|
||||||
ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
opPos := findOp(qpos)
|
|
||||||
if opPos == token.NoPos {
|
|
||||||
return fmt.Errorf("there is no channel operation here")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defer SSA construction till after errors are reported.
|
|
||||||
prog.Build()
|
|
||||||
|
|
||||||
var queryOp chanOp // the originating send or receive operation
|
|
||||||
var ops []chanOp // all sends/receives of opposite direction
|
|
||||||
|
|
||||||
// Look at all channel operations in the whole ssa.Program.
|
|
||||||
// Build a list of those of same type as the query.
|
|
||||||
allFuncs := ssautil.AllFunctions(prog)
|
|
||||||
for fn := range allFuncs {
|
|
||||||
for _, b := range fn.Blocks {
|
|
||||||
for _, instr := range b.Instrs {
|
|
||||||
for _, op := range chanOps(instr) {
|
|
||||||
ops = append(ops, op)
|
|
||||||
if op.pos == opPos {
|
|
||||||
queryOp = op // we found the query op
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if queryOp.ch == nil {
|
|
||||||
return fmt.Errorf("ssa.Instruction for send/receive not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Discard operations of wrong channel element type.
|
|
||||||
// Build set of channel ssa.Values as query to pointer analysis.
|
|
||||||
// We compare channels by element types, not channel types, to
|
|
||||||
// ignore both directionality and type names.
|
|
||||||
queryType := queryOp.ch.Type()
|
|
||||||
queryElemType := queryType.Underlying().(*types.Chan).Elem()
|
|
||||||
ptaConfig.AddQuery(queryOp.ch)
|
|
||||||
i := 0
|
|
||||||
for _, op := range ops {
|
|
||||||
if types.Identical(op.ch.Type().Underlying().(*types.Chan).Elem(), queryElemType) {
|
|
||||||
ptaConfig.AddQuery(op.ch)
|
|
||||||
ops[i] = op
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ops = ops[:i]
|
|
||||||
|
|
||||||
// Run the pointer analysis.
|
|
||||||
ptares := ptrAnalysis(ptaConfig)
|
|
||||||
|
|
||||||
// Find the points-to set.
|
|
||||||
queryChanPtr := ptares.Queries[queryOp.ch]
|
|
||||||
|
|
||||||
// Ascertain which make(chan) labels the query's channel can alias.
|
|
||||||
var makes []token.Pos
|
|
||||||
for _, label := range queryChanPtr.PointsTo().Labels() {
|
|
||||||
makes = append(makes, label.Pos())
|
|
||||||
}
|
|
||||||
sort.Sort(byPos(makes))
|
|
||||||
|
|
||||||
// Ascertain which channel operations can alias the same make(chan) labels.
|
|
||||||
var sends, receives, closes []token.Pos
|
|
||||||
for _, op := range ops {
|
|
||||||
if ptr, ok := ptares.Queries[op.ch]; ok && ptr.MayAlias(queryChanPtr) {
|
|
||||||
switch op.dir {
|
|
||||||
case types.SendOnly:
|
|
||||||
sends = append(sends, op.pos)
|
|
||||||
case types.RecvOnly:
|
|
||||||
receives = append(receives, op.pos)
|
|
||||||
case types.SendRecv:
|
|
||||||
closes = append(closes, op.pos)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Sort(byPos(sends))
|
|
||||||
sort.Sort(byPos(receives))
|
|
||||||
sort.Sort(byPos(closes))
|
|
||||||
|
|
||||||
q.result = &peersResult{
|
|
||||||
queryPos: opPos,
|
|
||||||
queryType: queryType,
|
|
||||||
makes: makes,
|
|
||||||
sends: sends,
|
|
||||||
receives: receives,
|
|
||||||
closes: closes,
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// findOp returns the position of the enclosing send/receive/close op.
|
|
||||||
// For send and receive operations, this is the position of the <- token;
|
|
||||||
// for close operations, it's the Lparen of the function call.
|
|
||||||
//
|
|
||||||
// TODO(adonovan): handle implicit receive operations from 'for...range chan' statements.
|
|
||||||
func findOp(qpos *queryPos) token.Pos {
|
|
||||||
for _, n := range qpos.path {
|
|
||||||
switch n := n.(type) {
|
|
||||||
case *ast.UnaryExpr:
|
|
||||||
if n.Op == token.ARROW {
|
|
||||||
return n.OpPos
|
|
||||||
}
|
|
||||||
case *ast.SendStmt:
|
|
||||||
return n.Arrow
|
|
||||||
case *ast.CallExpr:
|
|
||||||
// close function call can only exist as a direct identifier
|
|
||||||
if close, ok := unparen(n.Fun).(*ast.Ident); ok {
|
|
||||||
if b, ok := qpos.info.Info.Uses[close].(*types.Builtin); ok && b.Name() == "close" {
|
|
||||||
return n.Lparen
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return token.NoPos
|
|
||||||
}
|
|
||||||
|
|
||||||
// chanOp abstracts an ssa.Send, ssa.Unop(ARROW), or a SelectState.
|
|
||||||
type chanOp struct {
|
|
||||||
ch ssa.Value
|
|
||||||
dir types.ChanDir // SendOnly=send, RecvOnly=recv, SendRecv=close
|
|
||||||
pos token.Pos
|
|
||||||
}
|
|
||||||
|
|
||||||
// chanOps returns a slice of all the channel operations in the instruction.
|
|
||||||
func chanOps(instr ssa.Instruction) []chanOp {
|
|
||||||
// TODO(adonovan): handle calls to reflect.{Select,Recv,Send,Close} too.
|
|
||||||
var ops []chanOp
|
|
||||||
switch instr := instr.(type) {
|
|
||||||
case *ssa.UnOp:
|
|
||||||
if instr.Op == token.ARROW {
|
|
||||||
ops = append(ops, chanOp{instr.X, types.RecvOnly, instr.Pos()})
|
|
||||||
}
|
|
||||||
case *ssa.Send:
|
|
||||||
ops = append(ops, chanOp{instr.Chan, types.SendOnly, instr.Pos()})
|
|
||||||
case *ssa.Select:
|
|
||||||
for _, st := range instr.States {
|
|
||||||
ops = append(ops, chanOp{st.Chan, st.Dir, st.Pos})
|
|
||||||
}
|
|
||||||
case ssa.CallInstruction:
|
|
||||||
cc := instr.Common()
|
|
||||||
if b, ok := cc.Value.(*ssa.Builtin); ok && b.Name() == "close" {
|
|
||||||
ops = append(ops, chanOp{cc.Args[0], types.SendRecv, cc.Pos()})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ops
|
|
||||||
}
|
|
||||||
|
|
||||||
type peersResult struct {
|
|
||||||
queryPos token.Pos // of queried channel op
|
|
||||||
queryType types.Type // type of queried channel
|
|
||||||
makes, sends, receives, closes []token.Pos // positions of aliased makechan/send/receive/close instrs
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *peersResult) display(printf printfFunc) {
|
|
||||||
if len(r.makes) == 0 {
|
|
||||||
printf(r.queryPos, "This channel can't point to anything.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
printf(r.queryPos, "This channel of type %s may be:", r.queryType)
|
|
||||||
for _, alloc := range r.makes {
|
|
||||||
printf(alloc, "\tallocated here")
|
|
||||||
}
|
|
||||||
for _, send := range r.sends {
|
|
||||||
printf(send, "\tsent to, here")
|
|
||||||
}
|
|
||||||
for _, receive := range r.receives {
|
|
||||||
printf(receive, "\treceived from, here")
|
|
||||||
}
|
|
||||||
for _, clos := range r.closes {
|
|
||||||
printf(clos, "\tclosed, here")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *peersResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
|
||||||
peers := &serial.Peers{
|
|
||||||
Pos: fset.Position(r.queryPos).String(),
|
|
||||||
Type: r.queryType.String(),
|
|
||||||
}
|
|
||||||
for _, alloc := range r.makes {
|
|
||||||
peers.Allocs = append(peers.Allocs, fset.Position(alloc).String())
|
|
||||||
}
|
|
||||||
for _, send := range r.sends {
|
|
||||||
peers.Sends = append(peers.Sends, fset.Position(send).String())
|
|
||||||
}
|
|
||||||
for _, receive := range r.receives {
|
|
||||||
peers.Receives = append(peers.Receives, fset.Position(receive).String())
|
|
||||||
}
|
|
||||||
for _, clos := range r.closes {
|
|
||||||
peers.Closes = append(peers.Closes, fset.Position(clos).String())
|
|
||||||
}
|
|
||||||
res.Peers = peers
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------- utils --------
|
|
||||||
|
|
||||||
// NB: byPos is not deterministic across packages since it depends on load order.
|
|
||||||
// Use lessPos if the tests need it.
|
|
||||||
type byPos []token.Pos
|
|
||||||
|
|
||||||
func (p byPos) Len() int { return len(p) }
|
|
||||||
func (p byPos) Less(i, j int) bool { return p[i] < p[j] }
|
|
||||||
func (p byPos) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
|
||||||
|
|
@ -1,293 +0,0 @@
|
||||||
// Copyright 2013 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build go1.5
|
|
||||||
|
|
||||||
package oracle
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"go/ast"
|
|
||||||
"go/token"
|
|
||||||
"go/types"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"golang.org/x/tools/go/ast/astutil"
|
|
||||||
"golang.org/x/tools/go/loader"
|
|
||||||
"golang.org/x/tools/go/pointer"
|
|
||||||
"golang.org/x/tools/go/ssa"
|
|
||||||
"golang.org/x/tools/go/ssa/ssautil"
|
|
||||||
"golang.org/x/tools/oracle/serial"
|
|
||||||
)
|
|
||||||
|
|
||||||
// pointsto runs the pointer analysis on the selected expression,
|
|
||||||
// and reports its points-to set (for a pointer-like expression)
|
|
||||||
// or its dynamic types (for an interface, reflect.Value, or
|
|
||||||
// reflect.Type expression) and their points-to sets.
|
|
||||||
//
|
|
||||||
// All printed sets are sorted to ensure determinism.
|
|
||||||
//
|
|
||||||
func pointsto(q *Query) error {
|
|
||||||
lconf := loader.Config{Build: q.Build}
|
|
||||||
|
|
||||||
if err := setPTAScope(&lconf, q.Scope); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load/parse/type-check the program.
|
|
||||||
lprog, err := lconf.Load()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
q.Fset = lprog.Fset
|
|
||||||
|
|
||||||
qpos, err := parseQueryPos(lprog, q.Pos, true) // needs exact pos
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug)
|
|
||||||
|
|
||||||
ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
path, action := findInterestingNode(qpos.info, qpos.path)
|
|
||||||
if action != actionExpr {
|
|
||||||
return fmt.Errorf("pointer analysis wants an expression; got %s",
|
|
||||||
astutil.NodeDescription(qpos.path[0]))
|
|
||||||
}
|
|
||||||
|
|
||||||
var expr ast.Expr
|
|
||||||
var obj types.Object
|
|
||||||
switch n := path[0].(type) {
|
|
||||||
case *ast.ValueSpec:
|
|
||||||
// ambiguous ValueSpec containing multiple names
|
|
||||||
return fmt.Errorf("multiple value specification")
|
|
||||||
case *ast.Ident:
|
|
||||||
obj = qpos.info.ObjectOf(n)
|
|
||||||
expr = n
|
|
||||||
case ast.Expr:
|
|
||||||
expr = n
|
|
||||||
default:
|
|
||||||
// TODO(adonovan): is this reachable?
|
|
||||||
return fmt.Errorf("unexpected AST for expr: %T", n)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reject non-pointerlike types (includes all constants---except nil).
|
|
||||||
// TODO(adonovan): reject nil too.
|
|
||||||
typ := qpos.info.TypeOf(expr)
|
|
||||||
if !pointer.CanPoint(typ) {
|
|
||||||
return fmt.Errorf("pointer analysis wants an expression of reference type; got %s", typ)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine the ssa.Value for the expression.
|
|
||||||
var value ssa.Value
|
|
||||||
var isAddr bool
|
|
||||||
if obj != nil {
|
|
||||||
// def/ref of func/var object
|
|
||||||
value, isAddr, err = ssaValueForIdent(prog, qpos.info, obj, path)
|
|
||||||
} else {
|
|
||||||
value, isAddr, err = ssaValueForExpr(prog, qpos.info, path)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err // e.g. trivially dead code
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defer SSA construction till after errors are reported.
|
|
||||||
prog.Build()
|
|
||||||
|
|
||||||
// Run the pointer analysis.
|
|
||||||
ptrs, err := runPTA(ptaConfig, value, isAddr)
|
|
||||||
if err != nil {
|
|
||||||
return err // e.g. analytically unreachable
|
|
||||||
}
|
|
||||||
|
|
||||||
q.result = &pointstoResult{
|
|
||||||
qpos: qpos,
|
|
||||||
typ: typ,
|
|
||||||
ptrs: ptrs,
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ssaValueForIdent returns the ssa.Value for the ast.Ident whose path
|
|
||||||
// to the root of the AST is path. isAddr reports whether the
|
|
||||||
// ssa.Value is the address denoted by the ast.Ident, not its value.
|
|
||||||
//
|
|
||||||
func ssaValueForIdent(prog *ssa.Program, qinfo *loader.PackageInfo, obj types.Object, path []ast.Node) (value ssa.Value, isAddr bool, err error) {
|
|
||||||
switch obj := obj.(type) {
|
|
||||||
case *types.Var:
|
|
||||||
pkg := prog.Package(qinfo.Pkg)
|
|
||||||
pkg.Build()
|
|
||||||
if v, addr := prog.VarValue(obj, pkg, path); v != nil {
|
|
||||||
return v, addr, nil
|
|
||||||
}
|
|
||||||
return nil, false, fmt.Errorf("can't locate SSA Value for var %s", obj.Name())
|
|
||||||
|
|
||||||
case *types.Func:
|
|
||||||
fn := prog.FuncValue(obj)
|
|
||||||
if fn == nil {
|
|
||||||
return nil, false, fmt.Errorf("%s is an interface method", obj)
|
|
||||||
}
|
|
||||||
// TODO(adonovan): there's no point running PTA on a *Func ident.
|
|
||||||
// Eliminate this feature.
|
|
||||||
return fn, false, nil
|
|
||||||
}
|
|
||||||
panic(obj)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ssaValueForExpr returns the ssa.Value of the non-ast.Ident
|
|
||||||
// expression whose path to the root of the AST is path.
|
|
||||||
//
|
|
||||||
func ssaValueForExpr(prog *ssa.Program, qinfo *loader.PackageInfo, path []ast.Node) (value ssa.Value, isAddr bool, err error) {
|
|
||||||
pkg := prog.Package(qinfo.Pkg)
|
|
||||||
pkg.SetDebugMode(true)
|
|
||||||
pkg.Build()
|
|
||||||
|
|
||||||
fn := ssa.EnclosingFunction(pkg, path)
|
|
||||||
if fn == nil {
|
|
||||||
return nil, false, fmt.Errorf("no SSA function built for this location (dead code?)")
|
|
||||||
}
|
|
||||||
|
|
||||||
if v, addr := fn.ValueForExpr(path[0].(ast.Expr)); v != nil {
|
|
||||||
return v, addr, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, false, fmt.Errorf("can't locate SSA Value for expression in %s", fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// runPTA runs the pointer analysis of the selected SSA value or address.
|
|
||||||
func runPTA(conf *pointer.Config, v ssa.Value, isAddr bool) (ptrs []pointerResult, err error) {
|
|
||||||
T := v.Type()
|
|
||||||
if isAddr {
|
|
||||||
conf.AddIndirectQuery(v)
|
|
||||||
T = deref(T)
|
|
||||||
} else {
|
|
||||||
conf.AddQuery(v)
|
|
||||||
}
|
|
||||||
ptares := ptrAnalysis(conf)
|
|
||||||
|
|
||||||
var ptr pointer.Pointer
|
|
||||||
if isAddr {
|
|
||||||
ptr = ptares.IndirectQueries[v]
|
|
||||||
} else {
|
|
||||||
ptr = ptares.Queries[v]
|
|
||||||
}
|
|
||||||
if ptr == (pointer.Pointer{}) {
|
|
||||||
return nil, fmt.Errorf("pointer analysis did not find expression (dead code?)")
|
|
||||||
}
|
|
||||||
pts := ptr.PointsTo()
|
|
||||||
|
|
||||||
if pointer.CanHaveDynamicTypes(T) {
|
|
||||||
// Show concrete types for interface/reflect.Value expression.
|
|
||||||
if concs := pts.DynamicTypes(); concs.Len() > 0 {
|
|
||||||
concs.Iterate(func(conc types.Type, pta interface{}) {
|
|
||||||
labels := pta.(pointer.PointsToSet).Labels()
|
|
||||||
sort.Sort(byPosAndString(labels)) // to ensure determinism
|
|
||||||
ptrs = append(ptrs, pointerResult{conc, labels})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Show labels for other expressions.
|
|
||||||
labels := pts.Labels()
|
|
||||||
sort.Sort(byPosAndString(labels)) // to ensure determinism
|
|
||||||
ptrs = append(ptrs, pointerResult{T, labels})
|
|
||||||
}
|
|
||||||
sort.Sort(byTypeString(ptrs)) // to ensure determinism
|
|
||||||
return ptrs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type pointerResult struct {
|
|
||||||
typ types.Type // type of the pointer (always concrete)
|
|
||||||
labels []*pointer.Label // set of labels
|
|
||||||
}
|
|
||||||
|
|
||||||
type pointstoResult struct {
|
|
||||||
qpos *queryPos
|
|
||||||
typ types.Type // type of expression
|
|
||||||
ptrs []pointerResult // pointer info (typ is concrete => len==1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *pointstoResult) display(printf printfFunc) {
|
|
||||||
if pointer.CanHaveDynamicTypes(r.typ) {
|
|
||||||
// Show concrete types for interface, reflect.Type or
|
|
||||||
// reflect.Value expression.
|
|
||||||
|
|
||||||
if len(r.ptrs) > 0 {
|
|
||||||
printf(r.qpos, "this %s may contain these dynamic types:", r.qpos.typeString(r.typ))
|
|
||||||
for _, ptr := range r.ptrs {
|
|
||||||
var obj types.Object
|
|
||||||
if nt, ok := deref(ptr.typ).(*types.Named); ok {
|
|
||||||
obj = nt.Obj()
|
|
||||||
}
|
|
||||||
if len(ptr.labels) > 0 {
|
|
||||||
printf(obj, "\t%s, may point to:", r.qpos.typeString(ptr.typ))
|
|
||||||
printLabels(printf, ptr.labels, "\t\t")
|
|
||||||
} else {
|
|
||||||
printf(obj, "\t%s", r.qpos.typeString(ptr.typ))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
printf(r.qpos, "this %s cannot contain any dynamic types.", r.typ)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Show labels for other expressions.
|
|
||||||
if ptr := r.ptrs[0]; len(ptr.labels) > 0 {
|
|
||||||
printf(r.qpos, "this %s may point to these objects:",
|
|
||||||
r.qpos.typeString(r.typ))
|
|
||||||
printLabels(printf, ptr.labels, "\t")
|
|
||||||
} else {
|
|
||||||
printf(r.qpos, "this %s may not point to anything.",
|
|
||||||
r.qpos.typeString(r.typ))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *pointstoResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
|
||||||
var pts []serial.PointsTo
|
|
||||||
for _, ptr := range r.ptrs {
|
|
||||||
var namePos string
|
|
||||||
if nt, ok := deref(ptr.typ).(*types.Named); ok {
|
|
||||||
namePos = fset.Position(nt.Obj().Pos()).String()
|
|
||||||
}
|
|
||||||
var labels []serial.PointsToLabel
|
|
||||||
for _, l := range ptr.labels {
|
|
||||||
labels = append(labels, serial.PointsToLabel{
|
|
||||||
Pos: fset.Position(l.Pos()).String(),
|
|
||||||
Desc: l.String(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
pts = append(pts, serial.PointsTo{
|
|
||||||
Type: r.qpos.typeString(ptr.typ),
|
|
||||||
NamePos: namePos,
|
|
||||||
Labels: labels,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
res.PointsTo = pts
|
|
||||||
}
|
|
||||||
|
|
||||||
type byTypeString []pointerResult
|
|
||||||
|
|
||||||
func (a byTypeString) Len() int { return len(a) }
|
|
||||||
func (a byTypeString) Less(i, j int) bool { return a[i].typ.String() < a[j].typ.String() }
|
|
||||||
func (a byTypeString) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
||||||
|
|
||||||
type byPosAndString []*pointer.Label
|
|
||||||
|
|
||||||
func (a byPosAndString) Len() int { return len(a) }
|
|
||||||
func (a byPosAndString) Less(i, j int) bool {
|
|
||||||
cmp := a[i].Pos() - a[j].Pos()
|
|
||||||
return cmp < 0 || (cmp == 0 && a[i].String() < a[j].String())
|
|
||||||
}
|
|
||||||
func (a byPosAndString) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
||||||
|
|
||||||
func printLabels(printf printfFunc, labels []*pointer.Label, prefix string) {
|
|
||||||
// TODO(adonovan): due to context-sensitivity, many of these
|
|
||||||
// labels may differ only by context, which isn't apparent.
|
|
||||||
for _, label := range labels {
|
|
||||||
printf(label, "%s%s", prefix, label)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
143
oracle/pos.go
143
oracle/pos.go
|
|
@ -1,143 +0,0 @@
|
||||||
package oracle
|
|
||||||
|
|
||||||
// This file defines utilities for working with file positions.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"go/parser"
|
|
||||||
"go/token"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/tools/go/ast/astutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
// parseOctothorpDecimal returns the numeric value if s matches "#%d",
|
|
||||||
// otherwise -1.
|
|
||||||
func parseOctothorpDecimal(s string) int {
|
|
||||||
if s != "" && s[0] == '#' {
|
|
||||||
if s, err := strconv.ParseInt(s[1:], 10, 32); err == nil {
|
|
||||||
return int(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
// parsePosFlag parses a string of the form "file:pos" or
|
|
||||||
// file:start,end" where pos, start, end match #%d and represent byte
|
|
||||||
// offsets, and returns its components.
|
|
||||||
//
|
|
||||||
// (Numbers without a '#' prefix are reserved for future use,
|
|
||||||
// e.g. to indicate line/column positions.)
|
|
||||||
//
|
|
||||||
func parsePosFlag(posFlag string) (filename string, startOffset, endOffset int, err error) {
|
|
||||||
if posFlag == "" {
|
|
||||||
err = fmt.Errorf("no source position specified (-pos flag)")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
colon := strings.LastIndex(posFlag, ":")
|
|
||||||
if colon < 0 {
|
|
||||||
err = fmt.Errorf("invalid source position -pos=%q", posFlag)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
filename, offset := posFlag[:colon], posFlag[colon+1:]
|
|
||||||
startOffset = -1
|
|
||||||
endOffset = -1
|
|
||||||
if hyphen := strings.Index(offset, ","); hyphen < 0 {
|
|
||||||
// e.g. "foo.go:#123"
|
|
||||||
startOffset = parseOctothorpDecimal(offset)
|
|
||||||
endOffset = startOffset
|
|
||||||
} else {
|
|
||||||
// e.g. "foo.go:#123,#456"
|
|
||||||
startOffset = parseOctothorpDecimal(offset[:hyphen])
|
|
||||||
endOffset = parseOctothorpDecimal(offset[hyphen+1:])
|
|
||||||
}
|
|
||||||
if startOffset < 0 || endOffset < 0 {
|
|
||||||
err = fmt.Errorf("invalid -pos offset %q", offset)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// findQueryPos searches fset for filename and translates the
|
|
||||||
// specified file-relative byte offsets into token.Pos form. It
|
|
||||||
// returns an error if the file was not found or the offsets were out
|
|
||||||
// of bounds.
|
|
||||||
//
|
|
||||||
func findQueryPos(fset *token.FileSet, filename string, startOffset, endOffset int) (start, end token.Pos, err error) {
|
|
||||||
var file *token.File
|
|
||||||
fset.Iterate(func(f *token.File) bool {
|
|
||||||
if sameFile(filename, f.Name()) {
|
|
||||||
// (f.Name() is absolute)
|
|
||||||
file = f
|
|
||||||
return false // done
|
|
||||||
}
|
|
||||||
return true // continue
|
|
||||||
})
|
|
||||||
if file == nil {
|
|
||||||
err = fmt.Errorf("couldn't find file containing position")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Range check [start..end], inclusive of both end-points.
|
|
||||||
|
|
||||||
if 0 <= startOffset && startOffset <= file.Size() {
|
|
||||||
start = file.Pos(int(startOffset))
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("start position is beyond end of file")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if 0 <= endOffset && endOffset <= file.Size() {
|
|
||||||
end = file.Pos(int(endOffset))
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("end position is beyond end of file")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// sameFile returns true if x and y have the same basename and denote
|
|
||||||
// the same file.
|
|
||||||
//
|
|
||||||
func sameFile(x, y string) bool {
|
|
||||||
if filepath.Base(x) == filepath.Base(y) { // (optimisation)
|
|
||||||
if xi, err := os.Stat(x); err == nil {
|
|
||||||
if yi, err := os.Stat(y); err == nil {
|
|
||||||
return os.SameFile(xi, yi)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// fastQueryPos parses the -pos flag and returns a QueryPos.
|
|
||||||
// It parses only a single file, and does not run the type checker.
|
|
||||||
func fastQueryPos(posFlag string) (*queryPos, error) {
|
|
||||||
filename, startOffset, endOffset, err := parsePosFlag(posFlag)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
fset := token.NewFileSet()
|
|
||||||
f, err := parser.ParseFile(fset, filename, nil, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
start, end, err := findQueryPos(fset, filename, startOffset, endOffset)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
path, exact := astutil.PathEnclosingInterval(f, start, end)
|
|
||||||
if path == nil {
|
|
||||||
return nil, fmt.Errorf("no syntax here")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &queryPos{fset, start, end, path, exact, nil}, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,243 +0,0 @@
|
||||||
// Copyright 2013 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build go1.5
|
|
||||||
|
|
||||||
package oracle
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"go/ast"
|
|
||||||
"go/token"
|
|
||||||
"go/types"
|
|
||||||
"io/ioutil"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"golang.org/x/tools/go/loader"
|
|
||||||
"golang.org/x/tools/oracle/serial"
|
|
||||||
"golang.org/x/tools/refactor/importgraph"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Referrers reports all identifiers that resolve to the same object
|
|
||||||
// as the queried identifier, within any package in the analysis scope.
|
|
||||||
func referrers(q *Query) error {
|
|
||||||
lconf := loader.Config{Build: q.Build}
|
|
||||||
allowErrors(&lconf)
|
|
||||||
|
|
||||||
if _, err := importQueryPackage(q.Pos, &lconf); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var id *ast.Ident
|
|
||||||
var obj types.Object
|
|
||||||
var lprog *loader.Program
|
|
||||||
var pass2 bool
|
|
||||||
var qpos *queryPos
|
|
||||||
for {
|
|
||||||
// Load/parse/type-check the program.
|
|
||||||
var err error
|
|
||||||
lprog, err = lconf.Load()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
q.Fset = lprog.Fset
|
|
||||||
|
|
||||||
qpos, err = parseQueryPos(lprog, q.Pos, false)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
id, _ = qpos.path[0].(*ast.Ident)
|
|
||||||
if id == nil {
|
|
||||||
return fmt.Errorf("no identifier here")
|
|
||||||
}
|
|
||||||
|
|
||||||
obj = qpos.info.ObjectOf(id)
|
|
||||||
if obj == nil {
|
|
||||||
// Happens for y in "switch y := x.(type)",
|
|
||||||
// the package declaration,
|
|
||||||
// and unresolved identifiers.
|
|
||||||
if _, ok := qpos.path[1].(*ast.File); ok { // package decl?
|
|
||||||
pkg := qpos.info.Pkg
|
|
||||||
obj = types.NewPkgName(id.Pos(), pkg, pkg.Name(), pkg)
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("no object for identifier: %T", qpos.path[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if pass2 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the identifier is exported, we must load all packages that
|
|
||||||
// depend transitively upon the package that defines it.
|
|
||||||
// Treat PkgNames as exported, even though they're lowercase.
|
|
||||||
if _, isPkg := obj.(*types.PkgName); !(isPkg || obj.Exported()) {
|
|
||||||
break // not exported
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scan the workspace and build the import graph.
|
|
||||||
// Ignore broken packages.
|
|
||||||
_, rev, _ := importgraph.Build(q.Build)
|
|
||||||
|
|
||||||
// Re-load the larger program.
|
|
||||||
// Create a new file set so that ...
|
|
||||||
// External test packages are never imported,
|
|
||||||
// so they will never appear in the graph.
|
|
||||||
// (We must reset the Config here, not just reset the Fset field.)
|
|
||||||
lconf = loader.Config{
|
|
||||||
Fset: token.NewFileSet(),
|
|
||||||
Build: q.Build,
|
|
||||||
}
|
|
||||||
allowErrors(&lconf)
|
|
||||||
for path := range rev.Search(obj.Pkg().Path()) {
|
|
||||||
lconf.ImportWithTests(path)
|
|
||||||
}
|
|
||||||
pass2 = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate over all go/types' Uses facts for the entire program.
|
|
||||||
var refs []*ast.Ident
|
|
||||||
for _, info := range lprog.AllPackages {
|
|
||||||
for id2, obj2 := range info.Uses {
|
|
||||||
if sameObj(obj, obj2) {
|
|
||||||
refs = append(refs, id2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Sort(byNamePos{q.Fset, refs})
|
|
||||||
|
|
||||||
q.result = &referrersResult{
|
|
||||||
qpos: qpos,
|
|
||||||
query: id,
|
|
||||||
obj: obj,
|
|
||||||
refs: refs,
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// same reports whether x and y are identical, or both are PkgNames
|
|
||||||
// that import the same Package.
|
|
||||||
//
|
|
||||||
func sameObj(x, y types.Object) bool {
|
|
||||||
if x == y {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if x, ok := x.(*types.PkgName); ok {
|
|
||||||
if y, ok := y.(*types.PkgName); ok {
|
|
||||||
return x.Imported() == y.Imported()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------- utils --------
|
|
||||||
|
|
||||||
// An deterministic ordering for token.Pos that doesn't
|
|
||||||
// depend on the order in which packages were loaded.
|
|
||||||
func lessPos(fset *token.FileSet, x, y token.Pos) bool {
|
|
||||||
fx := fset.File(x)
|
|
||||||
fy := fset.File(y)
|
|
||||||
if fx != fy {
|
|
||||||
return fx.Name() < fy.Name()
|
|
||||||
}
|
|
||||||
return x < y
|
|
||||||
}
|
|
||||||
|
|
||||||
type byNamePos struct {
|
|
||||||
fset *token.FileSet
|
|
||||||
ids []*ast.Ident
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p byNamePos) Len() int { return len(p.ids) }
|
|
||||||
func (p byNamePos) Swap(i, j int) { p.ids[i], p.ids[j] = p.ids[j], p.ids[i] }
|
|
||||||
func (p byNamePos) Less(i, j int) bool {
|
|
||||||
return lessPos(p.fset, p.ids[i].NamePos, p.ids[j].NamePos)
|
|
||||||
}
|
|
||||||
|
|
||||||
type referrersResult struct {
|
|
||||||
qpos *queryPos
|
|
||||||
query *ast.Ident // identifier of query
|
|
||||||
obj types.Object // object it denotes
|
|
||||||
refs []*ast.Ident // set of all other references to it
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *referrersResult) display(printf printfFunc) {
|
|
||||||
printf(r.obj, "%d references to %s", len(r.refs), r.qpos.objectString(r.obj))
|
|
||||||
|
|
||||||
// Show referring lines, like grep.
|
|
||||||
type fileinfo struct {
|
|
||||||
refs []*ast.Ident
|
|
||||||
linenums []int // line number of refs[i]
|
|
||||||
data chan interface{} // file contents or error
|
|
||||||
}
|
|
||||||
var fileinfos []*fileinfo
|
|
||||||
fileinfosByName := make(map[string]*fileinfo)
|
|
||||||
|
|
||||||
// First pass: start the file reads concurrently.
|
|
||||||
sema := make(chan struct{}, 20) // counting semaphore to limit I/O concurrency
|
|
||||||
for _, ref := range r.refs {
|
|
||||||
posn := r.qpos.fset.Position(ref.Pos())
|
|
||||||
fi := fileinfosByName[posn.Filename]
|
|
||||||
if fi == nil {
|
|
||||||
fi = &fileinfo{data: make(chan interface{})}
|
|
||||||
fileinfosByName[posn.Filename] = fi
|
|
||||||
fileinfos = append(fileinfos, fi)
|
|
||||||
|
|
||||||
// First request for this file:
|
|
||||||
// start asynchronous read.
|
|
||||||
go func() {
|
|
||||||
sema <- struct{}{} // acquire token
|
|
||||||
content, err := ioutil.ReadFile(posn.Filename)
|
|
||||||
<-sema // release token
|
|
||||||
if err != nil {
|
|
||||||
fi.data <- err
|
|
||||||
} else {
|
|
||||||
fi.data <- content
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
fi.refs = append(fi.refs, ref)
|
|
||||||
fi.linenums = append(fi.linenums, posn.Line)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Second pass: print refs in original order.
|
|
||||||
// One line may have several refs at different columns.
|
|
||||||
for _, fi := range fileinfos {
|
|
||||||
v := <-fi.data // wait for I/O completion
|
|
||||||
|
|
||||||
// Print one item for all refs in a file that could not
|
|
||||||
// be loaded (perhaps due to //line directives).
|
|
||||||
if err, ok := v.(error); ok {
|
|
||||||
var suffix string
|
|
||||||
if more := len(fi.refs) - 1; more > 0 {
|
|
||||||
suffix = fmt.Sprintf(" (+ %d more refs in this file)", more)
|
|
||||||
}
|
|
||||||
printf(fi.refs[0], "%v%s", err, suffix)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
lines := bytes.Split(v.([]byte), []byte("\n"))
|
|
||||||
for i, ref := range fi.refs {
|
|
||||||
printf(ref, "%s", lines[fi.linenums[i]-1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(adonovan): encode extent, not just Pos info, in Serial form.
|
|
||||||
|
|
||||||
func (r *referrersResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
|
||||||
referrers := &serial.Referrers{
|
|
||||||
Pos: fset.Position(r.query.Pos()).String(),
|
|
||||||
Desc: r.obj.String(),
|
|
||||||
}
|
|
||||||
if pos := r.obj.Pos(); pos != token.NoPos { // Package objects have no Pos()
|
|
||||||
referrers.ObjPos = fset.Position(pos).String()
|
|
||||||
}
|
|
||||||
for _, ref := range r.refs {
|
|
||||||
referrers.Refs = append(referrers.Refs, fset.Position(ref.NamePos).String())
|
|
||||||
}
|
|
||||||
res.Referrers = referrers
|
|
||||||
}
|
|
||||||
|
|
@ -1,258 +0,0 @@
|
||||||
// Copyright 2013 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package serial defines the oracle's schema for structured data
|
|
||||||
// serialization using JSON, XML, etc.
|
|
||||||
package serial
|
|
||||||
|
|
||||||
// All 'pos' strings are of the form "file:line:col".
|
|
||||||
// TODO(adonovan): improve performance by sharing filename strings.
|
|
||||||
// TODO(adonovan): improve precision by providing the start/end
|
|
||||||
// interval when available.
|
|
||||||
//
|
|
||||||
// TODO(adonovan): consider richer encodings of types, functions,
|
|
||||||
// methods, etc.
|
|
||||||
|
|
||||||
// A Peers is the result of a 'peers' query.
|
|
||||||
// If Allocs is empty, the selected channel can't point to anything.
|
|
||||||
type Peers struct {
|
|
||||||
Pos string `json:"pos"` // location of the selected channel op (<-)
|
|
||||||
Type string `json:"type"` // type of the selected channel
|
|
||||||
Allocs []string `json:"allocs,omitempty"` // locations of aliased make(chan) ops
|
|
||||||
Sends []string `json:"sends,omitempty"` // locations of aliased ch<-x ops
|
|
||||||
Receives []string `json:"receives,omitempty"` // locations of aliased <-ch ops
|
|
||||||
Closes []string `json:"closes,omitempty"` // locations of aliased close(ch) ops
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Referrers is the result of a 'referrers' query.
|
|
||||||
type Referrers struct {
|
|
||||||
Pos string `json:"pos"` // location of the query reference
|
|
||||||
ObjPos string `json:"objpos,omitempty"` // location of the definition
|
|
||||||
Desc string `json:"desc"` // description of the denoted object
|
|
||||||
Refs []string `json:"refs,omitempty"` // locations of all references
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Definition is the result of a 'definition' query.
|
|
||||||
type Definition struct {
|
|
||||||
ObjPos string `json:"objpos,omitempty"` // location of the definition
|
|
||||||
Desc string `json:"desc"` // description of the denoted object
|
|
||||||
}
|
|
||||||
|
|
||||||
type CalleesItem struct {
|
|
||||||
Name string `json:"name"` // full name of called function
|
|
||||||
Pos string `json:"pos"` // location of called function
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Callees is the result of a 'callees' query.
|
|
||||||
//
|
|
||||||
// Callees is nonempty unless the call was a dynamic call on a
|
|
||||||
// provably nil func or interface value.
|
|
||||||
type Callees struct {
|
|
||||||
Pos string `json:"pos"` // location of selected call site
|
|
||||||
Desc string `json:"desc"` // description of call site
|
|
||||||
Callees []*CalleesItem `json:"callees,omitempty"` // set of possible call targets
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Caller is one element of the slice returned by a 'callers' query.
|
|
||||||
// (Callstack also contains a similar slice.)
|
|
||||||
//
|
|
||||||
// The root of the callgraph has an unspecified "Caller" string.
|
|
||||||
type Caller struct {
|
|
||||||
Pos string `json:"pos,omitempty"` // location of the calling function
|
|
||||||
Desc string `json:"desc"` // description of call site
|
|
||||||
Caller string `json:"caller"` // full name of calling function
|
|
||||||
}
|
|
||||||
|
|
||||||
// A CallStack is the result of a 'callstack' query.
|
|
||||||
// It indicates an arbitrary path from the root of the callgraph to
|
|
||||||
// the query function.
|
|
||||||
//
|
|
||||||
// If the Callers slice is empty, the function was unreachable in this
|
|
||||||
// analysis scope.
|
|
||||||
type CallStack struct {
|
|
||||||
Pos string `json:"pos"` // location of the selected function
|
|
||||||
Target string `json:"target"` // the selected function
|
|
||||||
Callers []Caller `json:"callers"` // enclosing calls, innermost first.
|
|
||||||
}
|
|
||||||
|
|
||||||
// A FreeVar is one element of the slice returned by a 'freevars'
|
|
||||||
// query. Each one identifies an expression referencing a local
|
|
||||||
// identifier defined outside the selected region.
|
|
||||||
type FreeVar struct {
|
|
||||||
Pos string `json:"pos"` // location of the identifier's definition
|
|
||||||
Kind string `json:"kind"` // one of {var,func,type,const,label}
|
|
||||||
Ref string `json:"ref"` // referring expression (e.g. "x" or "x.y.z")
|
|
||||||
Type string `json:"type"` // type of the expression
|
|
||||||
}
|
|
||||||
|
|
||||||
// An Implements contains the result of an 'implements' query.
|
|
||||||
// It describes the queried type, the set of named non-empty interface
|
|
||||||
// types to which it is assignable, and the set of named/*named types
|
|
||||||
// (concrete or non-empty interface) which may be assigned to it.
|
|
||||||
//
|
|
||||||
type Implements struct {
|
|
||||||
T ImplementsType `json:"type,omitempty"` // the queried type
|
|
||||||
AssignableTo []ImplementsType `json:"to,omitempty"` // types assignable to T
|
|
||||||
AssignableFrom []ImplementsType `json:"from,omitempty"` // interface types assignable from T
|
|
||||||
AssignableFromPtr []ImplementsType `json:"fromptr,omitempty"` // interface types assignable only from *T
|
|
||||||
|
|
||||||
// The following fields are set only if the query was a method.
|
|
||||||
// Assignable{To,From,FromPtr}Method[i] is the corresponding
|
|
||||||
// method of type Assignable{To,From,FromPtr}[i], or blank
|
|
||||||
// {"",""} if that type lacks the method.
|
|
||||||
Method *DescribeMethod `json:"method,omitempty"` // the queried method
|
|
||||||
AssignableToMethod []DescribeMethod `json:"to_method,omitempty"`
|
|
||||||
AssignableFromMethod []DescribeMethod `json:"from_method,omitempty"`
|
|
||||||
AssignableFromPtrMethod []DescribeMethod `json:"fromptr_method,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// An ImplementsType describes a single type as part of an 'implements' query.
|
|
||||||
type ImplementsType struct {
|
|
||||||
Name string `json:"name"` // full name of the type
|
|
||||||
Pos string `json:"pos"` // location of its definition
|
|
||||||
Kind string `json:"kind"` // "basic", "array", etc
|
|
||||||
}
|
|
||||||
|
|
||||||
// A SyntaxNode is one element of a stack of enclosing syntax nodes in
|
|
||||||
// a "what" query.
|
|
||||||
type SyntaxNode struct {
|
|
||||||
Description string `json:"desc"` // description of syntax tree
|
|
||||||
Start int `json:"start"` // start byte offset, 0-based
|
|
||||||
End int `json:"end"` // end byte offset
|
|
||||||
}
|
|
||||||
|
|
||||||
// A What is the result of the "what" query, which quickly identifies
|
|
||||||
// the selection, parsing only a single file. It is intended for use
|
|
||||||
// in low-latency GUIs.
|
|
||||||
type What struct {
|
|
||||||
Enclosing []SyntaxNode `json:"enclosing"` // enclosing nodes of syntax tree
|
|
||||||
Modes []string `json:"modes"` // query modes enabled for this selection.
|
|
||||||
SrcDir string `json:"srcdir,omitempty"` // $GOROOT src directory containing queried package
|
|
||||||
ImportPath string `json:"importpath,omitempty"` // import path of queried package
|
|
||||||
}
|
|
||||||
|
|
||||||
// A PointsToLabel describes a pointer analysis label.
|
|
||||||
//
|
|
||||||
// A "label" is an object that may be pointed to by a pointer, map,
|
|
||||||
// channel, 'func', slice or interface. Labels include:
|
|
||||||
// - functions
|
|
||||||
// - globals
|
|
||||||
// - arrays created by literals (e.g. []byte("foo")) and conversions ([]byte(s))
|
|
||||||
// - stack- and heap-allocated variables (including composite literals)
|
|
||||||
// - arrays allocated by append()
|
|
||||||
// - channels, maps and arrays created by make()
|
|
||||||
// - and their subelements, e.g. "alloc.y[*].z"
|
|
||||||
//
|
|
||||||
type PointsToLabel struct {
|
|
||||||
Pos string `json:"pos"` // location of syntax that allocated the object
|
|
||||||
Desc string `json:"desc"` // description of the label
|
|
||||||
}
|
|
||||||
|
|
||||||
// A PointsTo is one element of the result of a 'pointsto' query on an
|
|
||||||
// expression. It describes a single pointer: its type and the set of
|
|
||||||
// "labels" it points to.
|
|
||||||
//
|
|
||||||
// If the pointer is of interface type, it will have one PTS entry
|
|
||||||
// describing each concrete type that it may contain. For each
|
|
||||||
// concrete type that is a pointer, the PTS entry describes the labels
|
|
||||||
// it may point to. The same is true for reflect.Values, except the
|
|
||||||
// dynamic types needn't be concrete.
|
|
||||||
//
|
|
||||||
type PointsTo struct {
|
|
||||||
Type string `json:"type"` // (concrete) type of the pointer
|
|
||||||
NamePos string `json:"namepos,omitempty"` // location of type defn, if Named
|
|
||||||
Labels []PointsToLabel `json:"labels,omitempty"` // pointed-to objects
|
|
||||||
}
|
|
||||||
|
|
||||||
// A DescribeValue is the additional result of a 'describe' query
|
|
||||||
// if the selection indicates a value or expression.
|
|
||||||
type DescribeValue struct {
|
|
||||||
Type string `json:"type"` // type of the expression
|
|
||||||
Value string `json:"value,omitempty"` // value of the expression, if constant
|
|
||||||
ObjPos string `json:"objpos,omitempty"` // location of the definition, if an Ident
|
|
||||||
}
|
|
||||||
|
|
||||||
type DescribeMethod struct {
|
|
||||||
Name string `json:"name"` // method name, as defined by types.Selection.String()
|
|
||||||
Pos string `json:"pos"` // location of the method's definition
|
|
||||||
}
|
|
||||||
|
|
||||||
// A DescribeType is the additional result of a 'describe' query
|
|
||||||
// if the selection indicates a type.
|
|
||||||
type DescribeType struct {
|
|
||||||
Type string `json:"type"` // the string form of the type
|
|
||||||
NamePos string `json:"namepos,omitempty"` // location of definition of type, if named
|
|
||||||
NameDef string `json:"namedef,omitempty"` // underlying definition of type, if named
|
|
||||||
Methods []DescribeMethod `json:"methods,omitempty"` // methods of the type
|
|
||||||
}
|
|
||||||
|
|
||||||
type DescribeMember struct {
|
|
||||||
Name string `json:"name"` // name of member
|
|
||||||
Type string `json:"type,omitempty"` // type of member (underlying, if 'type')
|
|
||||||
Value string `json:"value,omitempty"` // value of member (if 'const')
|
|
||||||
Pos string `json:"pos"` // location of definition of member
|
|
||||||
Kind string `json:"kind"` // one of {var,const,func,type}
|
|
||||||
Methods []DescribeMethod `json:"methods,omitempty"` // methods (if member is a type)
|
|
||||||
}
|
|
||||||
|
|
||||||
// A DescribePackage is the additional result of a 'describe' if
|
|
||||||
// the selection indicates a package.
|
|
||||||
type DescribePackage struct {
|
|
||||||
Path string `json:"path"` // import path of the package
|
|
||||||
Members []*DescribeMember `json:"members,omitempty"` // accessible members of the package
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Describe is the result of a 'describe' query.
|
|
||||||
// It may contain an element describing the selected semantic entity
|
|
||||||
// in detail.
|
|
||||||
type Describe struct {
|
|
||||||
Desc string `json:"desc"` // description of the selected syntax node
|
|
||||||
Pos string `json:"pos"` // location of the selected syntax node
|
|
||||||
Detail string `json:"detail,omitempty"` // one of {package, type, value}, or "".
|
|
||||||
|
|
||||||
// At most one of the following fields is populated:
|
|
||||||
// the one specified by 'detail'.
|
|
||||||
Package *DescribePackage `json:"package,omitempty"`
|
|
||||||
Type *DescribeType `json:"type,omitempty"`
|
|
||||||
Value *DescribeValue `json:"value,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// A WhichErrs is the result of a 'whicherrs' query.
|
|
||||||
// It contains the position of the queried error and the possible globals,
|
|
||||||
// constants, and types it may point to.
|
|
||||||
type WhichErrs struct {
|
|
||||||
ErrPos string `json:"errpos,omitempty"` // location of queried error
|
|
||||||
Globals []string `json:"globals,omitempty"` // locations of globals
|
|
||||||
Constants []string `json:"constants,omitempty"` // locations of constants
|
|
||||||
Types []WhichErrsType `json:"types,omitempty"` // Types
|
|
||||||
}
|
|
||||||
|
|
||||||
type WhichErrsType struct {
|
|
||||||
Type string `json:"type,omitempty"`
|
|
||||||
Position string `json:"position,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Result is the common result of any oracle query.
|
|
||||||
// It contains a query-specific result element.
|
|
||||||
//
|
|
||||||
// TODO(adonovan): perhaps include other info such as: analysis scope,
|
|
||||||
// raw query position, stack of ast nodes, query package, etc.
|
|
||||||
type Result struct {
|
|
||||||
Mode string `json:"mode"` // mode of the query
|
|
||||||
|
|
||||||
// Exactly one of the following fields is populated:
|
|
||||||
// the one specified by 'mode'.
|
|
||||||
Callees *Callees `json:"callees,omitempty"`
|
|
||||||
Callers []Caller `json:"callers,omitempty"`
|
|
||||||
Callstack *CallStack `json:"callstack,omitempty"`
|
|
||||||
Definition *Definition `json:"definition,omitempty"`
|
|
||||||
Describe *Describe `json:"describe,omitempty"`
|
|
||||||
Freevars []*FreeVar `json:"freevars,omitempty"`
|
|
||||||
Implements *Implements `json:"implements,omitempty"`
|
|
||||||
Peers *Peers `json:"peers,omitempty"`
|
|
||||||
PointsTo []PointsTo `json:"pointsto,omitempty"`
|
|
||||||
Referrers *Referrers `json:"referrers,omitempty"`
|
|
||||||
What *What `json:"what,omitempty"`
|
|
||||||
WhichErrs *WhichErrs `json:"whicherrs,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
// Tests of call-graph queries, -format=json.
|
|
||||||
// See go.tools/oracle/oracle_test.go for explanation.
|
|
||||||
// See calls-json.golden for expected query results.
|
|
||||||
|
|
||||||
func call(f func()) {
|
|
||||||
f() // @callees @callees-f "f"
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
call(func() {
|
|
||||||
// @callers callers-main.anon "^"
|
|
||||||
// @callstack callstack-main.anon "^"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
-------- @callees @callees-f --------
|
|
||||||
{
|
|
||||||
"mode": "callees",
|
|
||||||
"callees": {
|
|
||||||
"pos": "testdata/src/calls-json/main.go:8:3",
|
|
||||||
"desc": "dynamic function call",
|
|
||||||
"callees": [
|
|
||||||
{
|
|
||||||
"name": "main.main$1",
|
|
||||||
"pos": "testdata/src/calls-json/main.go:12:7"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-------- @callstack callstack-main.anon --------
|
|
||||||
{
|
|
||||||
"mode": "callstack",
|
|
||||||
"callstack": {
|
|
||||||
"pos": "testdata/src/calls-json/main.go:12:7",
|
|
||||||
"target": "main.main$1",
|
|
||||||
"callers": [
|
|
||||||
{
|
|
||||||
"pos": "testdata/src/calls-json/main.go:8:3",
|
|
||||||
"desc": "dynamic function call",
|
|
||||||
"caller": "main.call"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"pos": "testdata/src/calls-json/main.go:12:6",
|
|
||||||
"desc": "static function call",
|
|
||||||
"caller": "main.main"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,129 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Tests of call-graph queries.
|
|
||||||
// See go.tools/oracle/oracle_test.go for explanation.
|
|
||||||
// See calls.golden for expected query results.
|
|
||||||
|
|
||||||
func A(x *int) { // @pointsto pointsto-A-x "x"
|
|
||||||
// @callers callers-A "^"
|
|
||||||
// @callstack callstack-A "^"
|
|
||||||
}
|
|
||||||
|
|
||||||
func B(x *int) { // @pointsto pointsto-B-x "x"
|
|
||||||
// @callers callers-B "^"
|
|
||||||
}
|
|
||||||
|
|
||||||
func foo() {
|
|
||||||
}
|
|
||||||
|
|
||||||
// apply is not (yet) treated context-sensitively.
|
|
||||||
func apply(f func(x *int), x *int) {
|
|
||||||
f(x) // @callees callees-apply "f"
|
|
||||||
// @callers callers-apply "^"
|
|
||||||
}
|
|
||||||
|
|
||||||
// store *is* treated context-sensitively,
|
|
||||||
// so the points-to sets for pc, pd are precise.
|
|
||||||
func store(ptr **int, value *int) {
|
|
||||||
*ptr = value
|
|
||||||
// @callers callers-store "^"
|
|
||||||
}
|
|
||||||
|
|
||||||
func call(f func() *int) {
|
|
||||||
// Result points to anon function.
|
|
||||||
f() // @pointsto pointsto-result-f "f"
|
|
||||||
|
|
||||||
// Target of call is anon function.
|
|
||||||
f() // @callees callees-main.call-f "f"
|
|
||||||
|
|
||||||
// @callers callers-main.call "^"
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
var a, b int
|
|
||||||
go apply(A, &a) // @callees callees-main-apply1 "app"
|
|
||||||
defer apply(B, &b)
|
|
||||||
|
|
||||||
var c, d int
|
|
||||||
var pc, pd *int // @pointsto pointsto-pc "pc"
|
|
||||||
store(&pc, &c)
|
|
||||||
store(&pd, &d)
|
|
||||||
_ = pd // @pointsto pointsto-pd "pd"
|
|
||||||
|
|
||||||
call(func() *int {
|
|
||||||
// We are called twice from main.call
|
|
||||||
// @callers callers-main.anon "^"
|
|
||||||
return &a
|
|
||||||
})
|
|
||||||
|
|
||||||
// Errors
|
|
||||||
_ = "no function call here" // @callees callees-err-no-call "no"
|
|
||||||
print("builtin") // @callees callees-err-builtin "builtin"
|
|
||||||
_ = string("type conversion") // @callees callees-err-conversion "str"
|
|
||||||
call(nil) // @callees callees-err-bad-selection "call\\(nil"
|
|
||||||
if false {
|
|
||||||
main() // @callees callees-err-deadcode1 "main"
|
|
||||||
}
|
|
||||||
var nilFunc func()
|
|
||||||
nilFunc() // @callees callees-err-nil-func "nilFunc"
|
|
||||||
var i interface {
|
|
||||||
f()
|
|
||||||
}
|
|
||||||
i.f() // @callees callees-err-nil-interface "i.f"
|
|
||||||
|
|
||||||
i = new(myint)
|
|
||||||
i.f() // @callees callees-not-a-wrapper "f"
|
|
||||||
|
|
||||||
// statically dispatched calls. Handled specially by callees, so test that they work.
|
|
||||||
foo() // @callees callees-static-call "foo"
|
|
||||||
fmt.Println() // @callees callees-qualified-call "Println"
|
|
||||||
m := new(method)
|
|
||||||
m.f() // @callees callees-static-method-call "f"
|
|
||||||
g := new(embeddedIface)
|
|
||||||
g.iface = m
|
|
||||||
g.f() // @callees callees-implicit-selection-method-call "f"
|
|
||||||
}
|
|
||||||
|
|
||||||
type myint int
|
|
||||||
|
|
||||||
func (myint) f() {
|
|
||||||
// @callers callers-not-a-wrapper "^"
|
|
||||||
}
|
|
||||||
|
|
||||||
type method int
|
|
||||||
|
|
||||||
func (method) f() {
|
|
||||||
}
|
|
||||||
|
|
||||||
type embeddedIface struct {
|
|
||||||
iface
|
|
||||||
}
|
|
||||||
|
|
||||||
type iface interface {
|
|
||||||
f()
|
|
||||||
}
|
|
||||||
|
|
||||||
var dynamic = func() {}
|
|
||||||
|
|
||||||
func deadcode() {
|
|
||||||
main() // @callees callees-err-deadcode2 "main"
|
|
||||||
// @callers callers-err-deadcode "^"
|
|
||||||
// @callstack callstack-err-deadcode "^"
|
|
||||||
|
|
||||||
// Within dead code, dynamic calls have no callees.
|
|
||||||
dynamic() // @callees callees-err-deadcode3 "dynamic"
|
|
||||||
}
|
|
||||||
|
|
||||||
// This code belongs to init.
|
|
||||||
var global = 123 // @callers callers-global "global"
|
|
||||||
|
|
||||||
// The package initializer may be called by other packages' inits, or
|
|
||||||
// in this case, the root of the callgraph. The source-level init functions
|
|
||||||
// are in turn called by it.
|
|
||||||
func init() {
|
|
||||||
// @callstack callstack-init "^"
|
|
||||||
}
|
|
||||||
|
|
@ -1,125 +0,0 @@
|
||||||
-------- @pointsto pointsto-A-x --------
|
|
||||||
this *int may point to these objects:
|
|
||||||
a
|
|
||||||
b
|
|
||||||
|
|
||||||
-------- @callstack callstack-A --------
|
|
||||||
Found a call path from root to main.A
|
|
||||||
main.A
|
|
||||||
dynamic function call from main.apply
|
|
||||||
concurrent static function call from main.main
|
|
||||||
|
|
||||||
-------- @pointsto pointsto-B-x --------
|
|
||||||
this *int may point to these objects:
|
|
||||||
a
|
|
||||||
b
|
|
||||||
|
|
||||||
-------- @callers callers-B --------
|
|
||||||
main.B is called from these 1 sites:
|
|
||||||
dynamic function call from main.apply
|
|
||||||
|
|
||||||
-------- @callees callees-apply --------
|
|
||||||
this dynamic function call dispatches to:
|
|
||||||
main.A
|
|
||||||
main.B
|
|
||||||
|
|
||||||
-------- @callers callers-apply --------
|
|
||||||
main.apply is called from these 2 sites:
|
|
||||||
concurrent static function call from main.main
|
|
||||||
deferred static function call from main.main
|
|
||||||
|
|
||||||
-------- @callers callers-store --------
|
|
||||||
main.store is called from these 2 sites:
|
|
||||||
static function call from main.main
|
|
||||||
static function call from main.main
|
|
||||||
|
|
||||||
-------- @pointsto pointsto-result-f --------
|
|
||||||
this func() *int may point to these objects:
|
|
||||||
main.main$1
|
|
||||||
|
|
||||||
-------- @callees callees-main.call-f --------
|
|
||||||
this dynamic function call dispatches to:
|
|
||||||
main.main$1
|
|
||||||
|
|
||||||
-------- @callers callers-main.call --------
|
|
||||||
main.call is called from these 2 sites:
|
|
||||||
static function call from main.main
|
|
||||||
static function call from main.main
|
|
||||||
|
|
||||||
-------- @callees callees-main-apply1 --------
|
|
||||||
this static function call dispatches to:
|
|
||||||
main.apply
|
|
||||||
|
|
||||||
-------- @pointsto pointsto-pc --------
|
|
||||||
this *int may point to these objects:
|
|
||||||
c
|
|
||||||
|
|
||||||
-------- @pointsto pointsto-pd --------
|
|
||||||
this *int may point to these objects:
|
|
||||||
d
|
|
||||||
|
|
||||||
-------- @callees callees-err-no-call --------
|
|
||||||
|
|
||||||
Error: there is no function call here
|
|
||||||
-------- @callees callees-err-builtin --------
|
|
||||||
|
|
||||||
Error: this is a call to the built-in 'print' operator
|
|
||||||
-------- @callees callees-err-conversion --------
|
|
||||||
|
|
||||||
Error: this is a type conversion, not a function call
|
|
||||||
-------- @callees callees-err-bad-selection --------
|
|
||||||
|
|
||||||
Error: ambiguous selection within function call (or conversion)
|
|
||||||
-------- @callees callees-err-deadcode1 --------
|
|
||||||
this static function call dispatches to:
|
|
||||||
main.main
|
|
||||||
|
|
||||||
-------- @callees callees-err-nil-func --------
|
|
||||||
dynamic function call on nil value
|
|
||||||
|
|
||||||
-------- @callees callees-err-nil-interface --------
|
|
||||||
dynamic method call on nil value
|
|
||||||
|
|
||||||
-------- @callees callees-not-a-wrapper --------
|
|
||||||
this dynamic method call dispatches to:
|
|
||||||
(main.myint).f
|
|
||||||
|
|
||||||
-------- @callees callees-static-call --------
|
|
||||||
this static function call dispatches to:
|
|
||||||
main.foo
|
|
||||||
|
|
||||||
-------- @callees callees-qualified-call --------
|
|
||||||
this static function call dispatches to:
|
|
||||||
fmt.Println
|
|
||||||
|
|
||||||
-------- @callees callees-static-method-call --------
|
|
||||||
this static function call dispatches to:
|
|
||||||
(main.method).f
|
|
||||||
|
|
||||||
-------- @callees callees-implicit-selection-method-call --------
|
|
||||||
this dynamic method call dispatches to:
|
|
||||||
(main.method).f
|
|
||||||
|
|
||||||
-------- @callers callers-not-a-wrapper --------
|
|
||||||
(main.myint).f is called from these 1 sites:
|
|
||||||
dynamic method call from main.main
|
|
||||||
|
|
||||||
-------- @callees callees-err-deadcode2 --------
|
|
||||||
this static function call dispatches to:
|
|
||||||
main.main
|
|
||||||
|
|
||||||
-------- @callstack callstack-err-deadcode --------
|
|
||||||
main.deadcode is unreachable in this analysis scope
|
|
||||||
|
|
||||||
-------- @callees callees-err-deadcode3 --------
|
|
||||||
|
|
||||||
Error: this call site is unreachable in this analysis
|
|
||||||
-------- @callers callers-global --------
|
|
||||||
main.init is called from these 1 sites:
|
|
||||||
the root of the call graph
|
|
||||||
|
|
||||||
-------- @callstack callstack-init --------
|
|
||||||
Found a call path from root to main.init#1
|
|
||||||
main.init#1
|
|
||||||
static function call from main.init
|
|
||||||
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
package describe // @describe pkgdecl "describe"
|
|
||||||
|
|
||||||
// Tests of 'describe' query, -format=json.
|
|
||||||
// See go.tools/oracle/oracle_test.go for explanation.
|
|
||||||
// See describe-json.golden for expected query results.
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
var s struct{ x [3]int }
|
|
||||||
p := &s.x[0] // @describe desc-val-p "p"
|
|
||||||
_ = p
|
|
||||||
|
|
||||||
var i I = C(0)
|
|
||||||
if i == nil {
|
|
||||||
i = new(D)
|
|
||||||
}
|
|
||||||
print(i) // @describe desc-val-i "\\bi\\b"
|
|
||||||
|
|
||||||
go main() // @describe desc-stmt "go"
|
|
||||||
}
|
|
||||||
|
|
||||||
type I interface {
|
|
||||||
f()
|
|
||||||
}
|
|
||||||
|
|
||||||
type C int // @describe desc-type-C "C"
|
|
||||||
type D struct{}
|
|
||||||
|
|
||||||
func (c C) f() {}
|
|
||||||
func (d *D) f() {}
|
|
||||||
|
|
@ -1,111 +0,0 @@
|
||||||
-------- @describe pkgdecl --------
|
|
||||||
{
|
|
||||||
"mode": "describe",
|
|
||||||
"describe": {
|
|
||||||
"desc": "definition of package \"describe-json\"",
|
|
||||||
"pos": "testdata/src/describe-json/main.go:1:9",
|
|
||||||
"detail": "package",
|
|
||||||
"package": {
|
|
||||||
"path": "describe-json",
|
|
||||||
"members": [
|
|
||||||
{
|
|
||||||
"name": "C",
|
|
||||||
"type": "int",
|
|
||||||
"pos": "testdata/src/describe-json/main.go:25:6",
|
|
||||||
"kind": "type",
|
|
||||||
"methods": [
|
|
||||||
{
|
|
||||||
"name": "method (C) f()",
|
|
||||||
"pos": "testdata/src/describe-json/main.go:28:12"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "D",
|
|
||||||
"type": "struct{}",
|
|
||||||
"pos": "testdata/src/describe-json/main.go:26:6",
|
|
||||||
"kind": "type",
|
|
||||||
"methods": [
|
|
||||||
{
|
|
||||||
"name": "method (*D) f()",
|
|
||||||
"pos": "testdata/src/describe-json/main.go:29:13"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "I",
|
|
||||||
"type": "interface{f()}",
|
|
||||||
"pos": "testdata/src/describe-json/main.go:21:6",
|
|
||||||
"kind": "type",
|
|
||||||
"methods": [
|
|
||||||
{
|
|
||||||
"name": "method (I) f()",
|
|
||||||
"pos": "testdata/src/describe-json/main.go:22:2"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "main",
|
|
||||||
"type": "func()",
|
|
||||||
"pos": "testdata/src/describe-json/main.go:7:6",
|
|
||||||
"kind": "func"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-------- @describe desc-val-p --------
|
|
||||||
{
|
|
||||||
"mode": "describe",
|
|
||||||
"describe": {
|
|
||||||
"desc": "identifier",
|
|
||||||
"pos": "testdata/src/describe-json/main.go:9:2",
|
|
||||||
"detail": "value",
|
|
||||||
"value": {
|
|
||||||
"type": "*int",
|
|
||||||
"objpos": "testdata/src/describe-json/main.go:9:2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-------- @describe desc-val-i --------
|
|
||||||
{
|
|
||||||
"mode": "describe",
|
|
||||||
"describe": {
|
|
||||||
"desc": "identifier",
|
|
||||||
"pos": "testdata/src/describe-json/main.go:16:8",
|
|
||||||
"detail": "value",
|
|
||||||
"value": {
|
|
||||||
"type": "I",
|
|
||||||
"objpos": "testdata/src/describe-json/main.go:12:6"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-------- @describe desc-stmt --------
|
|
||||||
{
|
|
||||||
"mode": "describe",
|
|
||||||
"describe": {
|
|
||||||
"desc": "go statement",
|
|
||||||
"pos": "testdata/src/describe-json/main.go:18:2",
|
|
||||||
"detail": "unknown"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-------- @describe desc-type-C --------
|
|
||||||
{
|
|
||||||
"mode": "describe",
|
|
||||||
"describe": {
|
|
||||||
"desc": "definition of type C (size 8, align 8)",
|
|
||||||
"pos": "testdata/src/describe-json/main.go:25:6",
|
|
||||||
"detail": "type",
|
|
||||||
"type": {
|
|
||||||
"type": "C",
|
|
||||||
"namepos": "testdata/src/describe-json/main.go:25:6",
|
|
||||||
"namedef": "int",
|
|
||||||
"methods": [
|
|
||||||
{
|
|
||||||
"name": "method (C) f()",
|
|
||||||
"pos": "testdata/src/describe-json/main.go:28:12"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,96 +0,0 @@
|
||||||
package describe // @describe pkgdecl "describe"
|
|
||||||
|
|
||||||
// Tests of 'describe' query.
|
|
||||||
// See go.tools/oracle/oracle_test.go for explanation.
|
|
||||||
// See describe.golden for expected query results.
|
|
||||||
|
|
||||||
// TODO(adonovan): more coverage of the (extensive) logic.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"nosuchpkg" // @describe badimport1 "nosuchpkg"
|
|
||||||
nosuchpkg2 "nosuchpkg" // @describe badimport2 "nosuchpkg2"
|
|
||||||
_ "unsafe" // @describe unsafe "unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ nosuchpkg.T
|
|
||||||
var _ nosuchpkg2.T
|
|
||||||
|
|
||||||
type cake float64 // @describe type-ref-builtin "float64"
|
|
||||||
|
|
||||||
const c = iota // @describe const-ref-iota "iota"
|
|
||||||
|
|
||||||
const pi = 3.141 // @describe const-def-pi "pi"
|
|
||||||
const pie = cake(pi) // @describe const-def-pie "pie"
|
|
||||||
const _ = pi // @describe const-ref-pi "pi"
|
|
||||||
|
|
||||||
var global = new(string) // NB: ssa.Global is indirect, i.e. **string
|
|
||||||
|
|
||||||
func main() { // @describe func-def-main "main"
|
|
||||||
// func objects
|
|
||||||
_ = main // @describe func-ref-main "main"
|
|
||||||
_ = (*C).f // @describe func-ref-*C.f "..C..f"
|
|
||||||
_ = D.f // @describe func-ref-D.f "D.f"
|
|
||||||
_ = I.f // @describe func-ref-I.f "I.f"
|
|
||||||
var d D // @describe type-D "D"
|
|
||||||
var i I // @describe type-I "I"
|
|
||||||
_ = d.f // @describe func-ref-d.f "d.f"
|
|
||||||
_ = i.f // @describe func-ref-i.f "i.f"
|
|
||||||
|
|
||||||
// var objects
|
|
||||||
anon := func() {
|
|
||||||
_ = d // @describe ref-lexical-d "d"
|
|
||||||
}
|
|
||||||
_ = anon // @describe ref-anon "anon"
|
|
||||||
_ = global // @describe ref-global "global"
|
|
||||||
|
|
||||||
// SSA affords some local flow sensitivity.
|
|
||||||
var a, b int
|
|
||||||
var x = &a // @describe var-def-x-1 "x"
|
|
||||||
_ = x // @describe var-ref-x-1 "x"
|
|
||||||
x = &b // @describe var-def-x-2 "x"
|
|
||||||
_ = x // @describe var-ref-x-2 "x"
|
|
||||||
|
|
||||||
i = new(C) // @describe var-ref-i-C "i"
|
|
||||||
if i != nil {
|
|
||||||
i = D{} // @describe var-ref-i-D "i"
|
|
||||||
}
|
|
||||||
print(i) // @describe var-ref-i "\\bi\\b"
|
|
||||||
|
|
||||||
// const objects
|
|
||||||
const localpi = 3.141 // @describe const-local-pi "localpi"
|
|
||||||
const localpie = cake(pi) // @describe const-local-pie "localpie"
|
|
||||||
const _ = localpi // @describe const-ref-localpi "localpi"
|
|
||||||
|
|
||||||
// type objects
|
|
||||||
type T int // @describe type-def-T "T"
|
|
||||||
var three T = 3 // @describe type-ref-T "T"
|
|
||||||
_ = three
|
|
||||||
|
|
||||||
print(1 + 2*3) // @describe const-expr " 2.3"
|
|
||||||
print(real(1+2i) - 3) // @describe const-expr2 "real.*3"
|
|
||||||
|
|
||||||
m := map[string]*int{"a": &a}
|
|
||||||
mapval, _ := m["a"] // @describe map-lookup,ok "m..a.."
|
|
||||||
_ = mapval // @describe mapval "mapval"
|
|
||||||
_ = m // @describe m "m"
|
|
||||||
|
|
||||||
defer main() // @describe defer-stmt "defer"
|
|
||||||
go main() // @describe go-stmt "go"
|
|
||||||
|
|
||||||
panic(3) // @describe builtin-ref-panic "panic"
|
|
||||||
|
|
||||||
var a2 int // @describe var-decl-stmt "var a2 int"
|
|
||||||
_ = a2
|
|
||||||
var _ int // @describe var-decl-stmt2 "var _ int"
|
|
||||||
var _ int // @describe var-def-blank "_"
|
|
||||||
}
|
|
||||||
|
|
||||||
type I interface { // @describe def-iface-I "I"
|
|
||||||
f() // @describe def-imethod-I.f "f"
|
|
||||||
}
|
|
||||||
|
|
||||||
type C int
|
|
||||||
type D struct{}
|
|
||||||
|
|
||||||
func (c *C) f() {}
|
|
||||||
func (d D) f() {}
|
|
||||||
|
|
@ -1,184 +0,0 @@
|
||||||
-------- @describe pkgdecl --------
|
|
||||||
definition of package "describe"
|
|
||||||
type C int
|
|
||||||
method (*C) f()
|
|
||||||
type D struct{}
|
|
||||||
method (D) f()
|
|
||||||
type I interface{f()}
|
|
||||||
method (I) f()
|
|
||||||
const c untyped int = 0
|
|
||||||
type cake float64
|
|
||||||
var global *string
|
|
||||||
func main func()
|
|
||||||
const pi untyped float = 3.141
|
|
||||||
const pie cake = 3.141
|
|
||||||
|
|
||||||
-------- @describe badimport1 --------
|
|
||||||
|
|
||||||
Error: can't import package "nosuchpkg"
|
|
||||||
-------- @describe badimport2 --------
|
|
||||||
|
|
||||||
Error: can't import package "nosuchpkg"
|
|
||||||
-------- @describe unsafe --------
|
|
||||||
import of package "unsafe"
|
|
||||||
builtin Alignof
|
|
||||||
builtin Offsetof
|
|
||||||
type Pointer unsafe.Pointer
|
|
||||||
builtin Sizeof
|
|
||||||
|
|
||||||
-------- @describe type-ref-builtin --------
|
|
||||||
reference to built-in type float64
|
|
||||||
|
|
||||||
-------- @describe const-ref-iota --------
|
|
||||||
reference to const iota untyped int of constant value 0
|
|
||||||
|
|
||||||
-------- @describe const-def-pi --------
|
|
||||||
definition of const pi untyped float
|
|
||||||
|
|
||||||
-------- @describe const-def-pie --------
|
|
||||||
definition of const pie cake
|
|
||||||
|
|
||||||
-------- @describe const-ref-pi --------
|
|
||||||
reference to const pi untyped float of constant value 3.141
|
|
||||||
defined here
|
|
||||||
|
|
||||||
-------- @describe func-def-main --------
|
|
||||||
definition of func main()
|
|
||||||
|
|
||||||
-------- @describe func-ref-main --------
|
|
||||||
reference to func main()
|
|
||||||
defined here
|
|
||||||
|
|
||||||
-------- @describe func-ref-*C.f --------
|
|
||||||
reference to method func (*C).f()
|
|
||||||
defined here
|
|
||||||
|
|
||||||
-------- @describe func-ref-D.f --------
|
|
||||||
reference to method func (D).f()
|
|
||||||
defined here
|
|
||||||
|
|
||||||
-------- @describe func-ref-I.f --------
|
|
||||||
reference to interface method func (I).f()
|
|
||||||
defined here
|
|
||||||
|
|
||||||
-------- @describe type-D --------
|
|
||||||
reference to type D (size 0, align 1)
|
|
||||||
defined as struct{}
|
|
||||||
Method set:
|
|
||||||
method (D) f()
|
|
||||||
|
|
||||||
-------- @describe type-I --------
|
|
||||||
reference to type I (size 16, align 8)
|
|
||||||
defined as interface{f()}
|
|
||||||
Method set:
|
|
||||||
method (I) f()
|
|
||||||
|
|
||||||
-------- @describe func-ref-d.f --------
|
|
||||||
reference to method func (D).f()
|
|
||||||
defined here
|
|
||||||
|
|
||||||
-------- @describe func-ref-i.f --------
|
|
||||||
reference to interface method func (I).f()
|
|
||||||
defined here
|
|
||||||
|
|
||||||
-------- @describe ref-lexical-d --------
|
|
||||||
reference to var d D
|
|
||||||
defined here
|
|
||||||
|
|
||||||
-------- @describe ref-anon --------
|
|
||||||
reference to var anon func()
|
|
||||||
defined here
|
|
||||||
|
|
||||||
-------- @describe ref-global --------
|
|
||||||
reference to var global *string
|
|
||||||
defined here
|
|
||||||
|
|
||||||
-------- @describe var-def-x-1 --------
|
|
||||||
definition of var x *int
|
|
||||||
|
|
||||||
-------- @describe var-ref-x-1 --------
|
|
||||||
reference to var x *int
|
|
||||||
defined here
|
|
||||||
|
|
||||||
-------- @describe var-def-x-2 --------
|
|
||||||
reference to var x *int
|
|
||||||
defined here
|
|
||||||
|
|
||||||
-------- @describe var-ref-x-2 --------
|
|
||||||
reference to var x *int
|
|
||||||
defined here
|
|
||||||
|
|
||||||
-------- @describe var-ref-i-C --------
|
|
||||||
reference to var i I
|
|
||||||
defined here
|
|
||||||
|
|
||||||
-------- @describe var-ref-i-D --------
|
|
||||||
reference to var i I
|
|
||||||
defined here
|
|
||||||
|
|
||||||
-------- @describe var-ref-i --------
|
|
||||||
reference to var i I
|
|
||||||
defined here
|
|
||||||
|
|
||||||
-------- @describe const-local-pi --------
|
|
||||||
definition of const localpi untyped float
|
|
||||||
|
|
||||||
-------- @describe const-local-pie --------
|
|
||||||
definition of const localpie cake
|
|
||||||
|
|
||||||
-------- @describe const-ref-localpi --------
|
|
||||||
reference to const localpi untyped float of constant value 3.141
|
|
||||||
defined here
|
|
||||||
|
|
||||||
-------- @describe type-def-T --------
|
|
||||||
definition of type T (size 8, align 8)
|
|
||||||
No methods.
|
|
||||||
|
|
||||||
-------- @describe type-ref-T --------
|
|
||||||
reference to type T (size 8, align 8)
|
|
||||||
defined as int
|
|
||||||
No methods.
|
|
||||||
|
|
||||||
-------- @describe const-expr --------
|
|
||||||
binary * operation of constant value 6
|
|
||||||
|
|
||||||
-------- @describe const-expr2 --------
|
|
||||||
binary - operation of constant value -2
|
|
||||||
|
|
||||||
-------- @describe map-lookup,ok --------
|
|
||||||
index expression of type (*int, bool)
|
|
||||||
|
|
||||||
-------- @describe mapval --------
|
|
||||||
reference to var mapval *int
|
|
||||||
defined here
|
|
||||||
|
|
||||||
-------- @describe m --------
|
|
||||||
reference to var m map[string]*int
|
|
||||||
defined here
|
|
||||||
|
|
||||||
-------- @describe defer-stmt --------
|
|
||||||
defer statement
|
|
||||||
|
|
||||||
-------- @describe go-stmt --------
|
|
||||||
go statement
|
|
||||||
|
|
||||||
-------- @describe builtin-ref-panic --------
|
|
||||||
function call (or conversion) of type ()
|
|
||||||
|
|
||||||
-------- @describe var-decl-stmt --------
|
|
||||||
definition of var a2 int
|
|
||||||
|
|
||||||
-------- @describe var-decl-stmt2 --------
|
|
||||||
definition of var _ int
|
|
||||||
|
|
||||||
-------- @describe var-def-blank --------
|
|
||||||
definition of var _ int
|
|
||||||
|
|
||||||
-------- @describe def-iface-I --------
|
|
||||||
definition of type I (size 16, align 8)
|
|
||||||
Method set:
|
|
||||||
method (I) f()
|
|
||||||
|
|
||||||
-------- @describe def-imethod-I.f --------
|
|
||||||
definition of interface method func (I).f()
|
|
||||||
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
// Tests of 'freevars' query.
|
|
||||||
// See go.tools/oracle/oracle_test.go for explanation.
|
|
||||||
// See freevars.golden for expected query results.
|
|
||||||
|
|
||||||
// TODO(adonovan): it's hard to test this query in a single line of gofmt'd code.
|
|
||||||
|
|
||||||
type T struct {
|
|
||||||
a, b int
|
|
||||||
}
|
|
||||||
|
|
||||||
type S struct {
|
|
||||||
x int
|
|
||||||
t T
|
|
||||||
}
|
|
||||||
|
|
||||||
func f(int) {}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
type C int
|
|
||||||
x := 1
|
|
||||||
const exp = 6
|
|
||||||
if y := 2; x+y+int(C(3)) != exp { // @freevars fv1 "if.*{"
|
|
||||||
panic("expected 6")
|
|
||||||
}
|
|
||||||
|
|
||||||
var s S
|
|
||||||
|
|
||||||
for x, y := range "foo" {
|
|
||||||
println(s.x + s.t.a + s.t.b + x + int(y)) // @freevars fv2 "print.*y."
|
|
||||||
}
|
|
||||||
|
|
||||||
f(x) // @freevars fv3 "f.x."
|
|
||||||
|
|
||||||
// TODO(adonovan): enable when go/types supports labels.
|
|
||||||
loop: // #@freevars fv-def-label "loop:"
|
|
||||||
for {
|
|
||||||
break loop // #@freevars fv-ref-label "break loop"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
-------- @freevars fv1 --------
|
|
||||||
Free identifiers:
|
|
||||||
type C
|
|
||||||
const exp int
|
|
||||||
var x int
|
|
||||||
|
|
||||||
-------- @freevars fv2 --------
|
|
||||||
Free identifiers:
|
|
||||||
var s.t.a int
|
|
||||||
var s.t.b int
|
|
||||||
var s.x int
|
|
||||||
var x int
|
|
||||||
var y rune
|
|
||||||
|
|
||||||
-------- @freevars fv3 --------
|
|
||||||
Free identifiers:
|
|
||||||
var x int
|
|
||||||
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
// Tests of 'implements' query, -output=json.
|
|
||||||
// See go.tools/oracle/oracle_test.go for explanation.
|
|
||||||
// See implements.golden for expected query results.
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
}
|
|
||||||
|
|
||||||
type E interface{} // @implements E "E"
|
|
||||||
|
|
||||||
type F interface { // @implements F "F"
|
|
||||||
f()
|
|
||||||
}
|
|
||||||
|
|
||||||
type FG interface { // @implements FG "FG"
|
|
||||||
f()
|
|
||||||
g() []int // @implements slice "..int"
|
|
||||||
}
|
|
||||||
|
|
||||||
type C int // @implements C "C"
|
|
||||||
type D struct{}
|
|
||||||
|
|
||||||
func (c *C) f() {} // @implements starC ".C"
|
|
||||||
func (d D) f() {} // @implements D "D"
|
|
||||||
|
|
||||||
func (d *D) g() []int { return nil } // @implements starD ".D"
|
|
||||||
|
|
@ -1,159 +0,0 @@
|
||||||
-------- @implements E --------
|
|
||||||
{
|
|
||||||
"mode": "implements",
|
|
||||||
"implements": {
|
|
||||||
"type": {
|
|
||||||
"name": "implements-json.E",
|
|
||||||
"pos": "testdata/src/implements-json/main.go:10:6",
|
|
||||||
"kind": "interface"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-------- @implements F --------
|
|
||||||
{
|
|
||||||
"mode": "implements",
|
|
||||||
"implements": {
|
|
||||||
"type": {
|
|
||||||
"name": "implements-json.F",
|
|
||||||
"pos": "testdata/src/implements-json/main.go:12:6",
|
|
||||||
"kind": "interface"
|
|
||||||
},
|
|
||||||
"to": [
|
|
||||||
{
|
|
||||||
"name": "*implements-json.C",
|
|
||||||
"pos": "testdata/src/implements-json/main.go:21:6",
|
|
||||||
"kind": "pointer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "implements-json.D",
|
|
||||||
"pos": "testdata/src/implements-json/main.go:22:6",
|
|
||||||
"kind": "struct"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "implements-json.FG",
|
|
||||||
"pos": "testdata/src/implements-json/main.go:16:6",
|
|
||||||
"kind": "interface"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-------- @implements FG --------
|
|
||||||
{
|
|
||||||
"mode": "implements",
|
|
||||||
"implements": {
|
|
||||||
"type": {
|
|
||||||
"name": "implements-json.FG",
|
|
||||||
"pos": "testdata/src/implements-json/main.go:16:6",
|
|
||||||
"kind": "interface"
|
|
||||||
},
|
|
||||||
"to": [
|
|
||||||
{
|
|
||||||
"name": "*implements-json.D",
|
|
||||||
"pos": "testdata/src/implements-json/main.go:22:6",
|
|
||||||
"kind": "pointer"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"from": [
|
|
||||||
{
|
|
||||||
"name": "implements-json.F",
|
|
||||||
"pos": "testdata/src/implements-json/main.go:12:6",
|
|
||||||
"kind": "interface"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-------- @implements slice --------
|
|
||||||
{
|
|
||||||
"mode": "implements",
|
|
||||||
"implements": {
|
|
||||||
"type": {
|
|
||||||
"name": "[]int",
|
|
||||||
"pos": "-",
|
|
||||||
"kind": "slice"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-------- @implements C --------
|
|
||||||
{
|
|
||||||
"mode": "implements",
|
|
||||||
"implements": {
|
|
||||||
"type": {
|
|
||||||
"name": "implements-json.C",
|
|
||||||
"pos": "testdata/src/implements-json/main.go:21:6",
|
|
||||||
"kind": "basic"
|
|
||||||
},
|
|
||||||
"fromptr": [
|
|
||||||
{
|
|
||||||
"name": "implements-json.F",
|
|
||||||
"pos": "testdata/src/implements-json/main.go:12:6",
|
|
||||||
"kind": "interface"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-------- @implements starC --------
|
|
||||||
{
|
|
||||||
"mode": "implements",
|
|
||||||
"implements": {
|
|
||||||
"type": {
|
|
||||||
"name": "*implements-json.C",
|
|
||||||
"pos": "testdata/src/implements-json/main.go:21:6",
|
|
||||||
"kind": "pointer"
|
|
||||||
},
|
|
||||||
"from": [
|
|
||||||
{
|
|
||||||
"name": "implements-json.F",
|
|
||||||
"pos": "testdata/src/implements-json/main.go:12:6",
|
|
||||||
"kind": "interface"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-------- @implements D --------
|
|
||||||
{
|
|
||||||
"mode": "implements",
|
|
||||||
"implements": {
|
|
||||||
"type": {
|
|
||||||
"name": "implements-json.D",
|
|
||||||
"pos": "testdata/src/implements-json/main.go:22:6",
|
|
||||||
"kind": "struct"
|
|
||||||
},
|
|
||||||
"from": [
|
|
||||||
{
|
|
||||||
"name": "implements-json.F",
|
|
||||||
"pos": "testdata/src/implements-json/main.go:12:6",
|
|
||||||
"kind": "interface"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"fromptr": [
|
|
||||||
{
|
|
||||||
"name": "implements-json.FG",
|
|
||||||
"pos": "testdata/src/implements-json/main.go:16:6",
|
|
||||||
"kind": "interface"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-------- @implements starD --------
|
|
||||||
{
|
|
||||||
"mode": "implements",
|
|
||||||
"implements": {
|
|
||||||
"type": {
|
|
||||||
"name": "*implements-json.D",
|
|
||||||
"pos": "testdata/src/implements-json/main.go:22:6",
|
|
||||||
"kind": "pointer"
|
|
||||||
},
|
|
||||||
"from": [
|
|
||||||
{
|
|
||||||
"name": "implements-json.F",
|
|
||||||
"pos": "testdata/src/implements-json/main.go:12:6",
|
|
||||||
"kind": "interface"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "implements-json.FG",
|
|
||||||
"pos": "testdata/src/implements-json/main.go:16:6",
|
|
||||||
"kind": "interface"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
// Tests of 'implements' query applied to methods, -output=json.
|
|
||||||
// See go.tools/oracle/oracle_test.go for explanation.
|
|
||||||
// See implements-methods.golden for expected query results.
|
|
||||||
|
|
||||||
import _ "lib"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
}
|
|
||||||
|
|
||||||
type F interface {
|
|
||||||
f() // @implements F.f "f"
|
|
||||||
}
|
|
||||||
|
|
||||||
type FG interface {
|
|
||||||
f() // @implements FG.f "f"
|
|
||||||
g() []int // @implements FG.g "g"
|
|
||||||
}
|
|
||||||
|
|
||||||
type C int
|
|
||||||
type D struct{}
|
|
||||||
|
|
||||||
func (c *C) f() {} // @implements *C.f "f"
|
|
||||||
func (d D) f() {} // @implements D.f "f"
|
|
||||||
|
|
||||||
func (d *D) g() []int { return nil } // @implements *D.g "g"
|
|
||||||
|
|
||||||
type sorter []int
|
|
||||||
|
|
||||||
func (sorter) Len() int { return 0 } // @implements Len "Len"
|
|
||||||
func (sorter) Less(i, j int) bool { return false }
|
|
||||||
func (sorter) Swap(i, j int) {}
|
|
||||||
|
|
||||||
type I interface {
|
|
||||||
Method(*int) *int // @implements I.Method "Method"
|
|
||||||
}
|
|
||||||
|
|
@ -1,312 +0,0 @@
|
||||||
-------- @implements F.f --------
|
|
||||||
{
|
|
||||||
"mode": "implements",
|
|
||||||
"implements": {
|
|
||||||
"type": {
|
|
||||||
"name": "implements-methods-json.F",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:12:6",
|
|
||||||
"kind": "interface"
|
|
||||||
},
|
|
||||||
"to": [
|
|
||||||
{
|
|
||||||
"name": "*implements-methods-json.C",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:21:6",
|
|
||||||
"kind": "pointer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "implements-methods-json.D",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:22:6",
|
|
||||||
"kind": "struct"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "implements-methods-json.FG",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:16:6",
|
|
||||||
"kind": "interface"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"method": {
|
|
||||||
"name": "func (F).f()",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:13:2"
|
|
||||||
},
|
|
||||||
"to_method": [
|
|
||||||
{
|
|
||||||
"name": "method (*C) f()",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:24:13"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "method (D) f()",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:25:12"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "method (FG) f()",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:17:2"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-------- @implements FG.f --------
|
|
||||||
{
|
|
||||||
"mode": "implements",
|
|
||||||
"implements": {
|
|
||||||
"type": {
|
|
||||||
"name": "implements-methods-json.FG",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:16:6",
|
|
||||||
"kind": "interface"
|
|
||||||
},
|
|
||||||
"to": [
|
|
||||||
{
|
|
||||||
"name": "*implements-methods-json.D",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:22:6",
|
|
||||||
"kind": "pointer"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"from": [
|
|
||||||
{
|
|
||||||
"name": "implements-methods-json.F",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:12:6",
|
|
||||||
"kind": "interface"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"method": {
|
|
||||||
"name": "func (FG).f()",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:17:2"
|
|
||||||
},
|
|
||||||
"to_method": [
|
|
||||||
{
|
|
||||||
"name": "method (*D) f()",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:25:12"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"from_method": [
|
|
||||||
{
|
|
||||||
"name": "method (F) f()",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:13:2"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-------- @implements FG.g --------
|
|
||||||
{
|
|
||||||
"mode": "implements",
|
|
||||||
"implements": {
|
|
||||||
"type": {
|
|
||||||
"name": "implements-methods-json.FG",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:16:6",
|
|
||||||
"kind": "interface"
|
|
||||||
},
|
|
||||||
"to": [
|
|
||||||
{
|
|
||||||
"name": "*implements-methods-json.D",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:22:6",
|
|
||||||
"kind": "pointer"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"from": [
|
|
||||||
{
|
|
||||||
"name": "implements-methods-json.F",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:12:6",
|
|
||||||
"kind": "interface"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"method": {
|
|
||||||
"name": "func (FG).g() []int",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:18:2"
|
|
||||||
},
|
|
||||||
"to_method": [
|
|
||||||
{
|
|
||||||
"name": "method (*D) g() []int",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:27:13"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"from_method": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"pos": ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-------- @implements *C.f --------
|
|
||||||
{
|
|
||||||
"mode": "implements",
|
|
||||||
"implements": {
|
|
||||||
"type": {
|
|
||||||
"name": "*implements-methods-json.C",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:21:6",
|
|
||||||
"kind": "pointer"
|
|
||||||
},
|
|
||||||
"from": [
|
|
||||||
{
|
|
||||||
"name": "implements-methods-json.F",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:12:6",
|
|
||||||
"kind": "interface"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"method": {
|
|
||||||
"name": "func (*C).f()",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:24:13"
|
|
||||||
},
|
|
||||||
"from_method": [
|
|
||||||
{
|
|
||||||
"name": "method (F) f()",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:13:2"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-------- @implements D.f --------
|
|
||||||
{
|
|
||||||
"mode": "implements",
|
|
||||||
"implements": {
|
|
||||||
"type": {
|
|
||||||
"name": "implements-methods-json.D",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:22:6",
|
|
||||||
"kind": "struct"
|
|
||||||
},
|
|
||||||
"from": [
|
|
||||||
{
|
|
||||||
"name": "implements-methods-json.F",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:12:6",
|
|
||||||
"kind": "interface"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"fromptr": [
|
|
||||||
{
|
|
||||||
"name": "implements-methods-json.FG",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:16:6",
|
|
||||||
"kind": "interface"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"method": {
|
|
||||||
"name": "func (D).f()",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:25:12"
|
|
||||||
},
|
|
||||||
"from_method": [
|
|
||||||
{
|
|
||||||
"name": "method (F) f()",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:13:2"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"fromptr_method": [
|
|
||||||
{
|
|
||||||
"name": "method (FG) f()",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:17:2"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-------- @implements *D.g --------
|
|
||||||
{
|
|
||||||
"mode": "implements",
|
|
||||||
"implements": {
|
|
||||||
"type": {
|
|
||||||
"name": "*implements-methods-json.D",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:22:6",
|
|
||||||
"kind": "pointer"
|
|
||||||
},
|
|
||||||
"from": [
|
|
||||||
{
|
|
||||||
"name": "implements-methods-json.F",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:12:6",
|
|
||||||
"kind": "interface"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "implements-methods-json.FG",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:16:6",
|
|
||||||
"kind": "interface"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"method": {
|
|
||||||
"name": "func (*D).g() []int",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:27:13"
|
|
||||||
},
|
|
||||||
"from_method": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"pos": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "method (FG) g() []int",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:18:2"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-------- @implements Len --------
|
|
||||||
{
|
|
||||||
"mode": "implements",
|
|
||||||
"implements": {
|
|
||||||
"type": {
|
|
||||||
"name": "implements-methods-json.sorter",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:29:6",
|
|
||||||
"kind": "slice"
|
|
||||||
},
|
|
||||||
"from": [
|
|
||||||
{
|
|
||||||
"name": "lib.Sorter",
|
|
||||||
"pos": "testdata/src/lib/lib.go:16:6",
|
|
||||||
"kind": "interface"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"method": {
|
|
||||||
"name": "func (sorter).Len() int",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:31:15"
|
|
||||||
},
|
|
||||||
"from_method": [
|
|
||||||
{
|
|
||||||
"name": "method (lib.Sorter) Len() int",
|
|
||||||
"pos": "testdata/src/lib/lib.go:17:2"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-------- @implements I.Method --------
|
|
||||||
{
|
|
||||||
"mode": "implements",
|
|
||||||
"implements": {
|
|
||||||
"type": {
|
|
||||||
"name": "implements-methods-json.I",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:35:6",
|
|
||||||
"kind": "interface"
|
|
||||||
},
|
|
||||||
"to": [
|
|
||||||
{
|
|
||||||
"name": "lib.Type",
|
|
||||||
"pos": "testdata/src/lib/lib.go:3:6",
|
|
||||||
"kind": "basic"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "main.I",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:35:6",
|
|
||||||
"kind": "interface"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"from": [
|
|
||||||
{
|
|
||||||
"name": "main.I",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:35:6",
|
|
||||||
"kind": "interface"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"method": {
|
|
||||||
"name": "func (I).Method(*int) *int",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:36:2"
|
|
||||||
},
|
|
||||||
"to_method": [
|
|
||||||
{
|
|
||||||
"name": "method (lib.Type) Method(x *int) *int",
|
|
||||||
"pos": "testdata/src/lib/lib.go:5:13"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "method (main.I) Method(*int) *int",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:36:2"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"from_method": [
|
|
||||||
{
|
|
||||||
"name": "method (main.I) Method(*int) *int",
|
|
||||||
"pos": "testdata/src/implements-methods-json/main.go:36:2"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
// Tests of 'implements' query applied to methods.
|
|
||||||
// See go.tools/oracle/oracle_test.go for explanation.
|
|
||||||
// See implements-methods.golden for expected query results.
|
|
||||||
|
|
||||||
import _ "lib"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
}
|
|
||||||
|
|
||||||
type F interface {
|
|
||||||
f() // @implements F.f "f"
|
|
||||||
}
|
|
||||||
|
|
||||||
type FG interface {
|
|
||||||
f() // @implements FG.f "f"
|
|
||||||
g() []int // @implements FG.g "g"
|
|
||||||
}
|
|
||||||
|
|
||||||
type C int
|
|
||||||
type D struct{}
|
|
||||||
|
|
||||||
func (c *C) f() {} // @implements *C.f "f"
|
|
||||||
func (d D) f() {} // @implements D.f "f"
|
|
||||||
|
|
||||||
func (d *D) g() []int { return nil } // @implements *D.g "g"
|
|
||||||
|
|
||||||
type sorter []int
|
|
||||||
|
|
||||||
func (sorter) Len() int { return 0 } // @implements Len "Len"
|
|
||||||
func (sorter) Less(i, j int) bool { return false }
|
|
||||||
func (sorter) Swap(i, j int) {}
|
|
||||||
|
|
||||||
type I interface {
|
|
||||||
Method(*int) *int // @implements I.Method "Method"
|
|
||||||
}
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
-------- @implements F.f --------
|
|
||||||
abstract method func (F).f()
|
|
||||||
is implemented by method (*C).f
|
|
||||||
is implemented by method (D).f
|
|
||||||
is implemented by method (FG).f
|
|
||||||
|
|
||||||
-------- @implements FG.f --------
|
|
||||||
abstract method func (FG).f()
|
|
||||||
is implemented by method (*D).f
|
|
||||||
implements method (F).f
|
|
||||||
|
|
||||||
-------- @implements FG.g --------
|
|
||||||
abstract method func (FG).g() []int
|
|
||||||
is implemented by method (*D).g
|
|
||||||
|
|
||||||
-------- @implements *C.f --------
|
|
||||||
concrete method func (*C).f()
|
|
||||||
implements method (F).f
|
|
||||||
|
|
||||||
-------- @implements D.f --------
|
|
||||||
concrete method func (D).f()
|
|
||||||
implements method (F).f
|
|
||||||
concrete method func (D).f()
|
|
||||||
implements method (FG).f
|
|
||||||
|
|
||||||
-------- @implements *D.g --------
|
|
||||||
concrete method func (*D).g() []int
|
|
||||||
implements method (FG).g
|
|
||||||
|
|
||||||
-------- @implements Len --------
|
|
||||||
concrete method func (sorter).Len() int
|
|
||||||
implements method (lib.Sorter).Len
|
|
||||||
|
|
||||||
-------- @implements I.Method --------
|
|
||||||
abstract method func (I).Method(*int) *int
|
|
||||||
is implemented by method (lib.Type).Method
|
|
||||||
is implemented by method (main.I).Method
|
|
||||||
implements method (main.I).Method
|
|
||||||
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
// Tests of 'implements' query.
|
|
||||||
// See go.tools/oracle/oracle_test.go for explanation.
|
|
||||||
// See implements.golden for expected query results.
|
|
||||||
|
|
||||||
import _ "lib"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
}
|
|
||||||
|
|
||||||
type E interface{} // @implements E "E"
|
|
||||||
|
|
||||||
type F interface { // @implements F "F"
|
|
||||||
f()
|
|
||||||
}
|
|
||||||
|
|
||||||
type FG interface { // @implements FG "FG"
|
|
||||||
f()
|
|
||||||
g() []int // @implements slice "..int"
|
|
||||||
}
|
|
||||||
|
|
||||||
type C int // @implements C "C"
|
|
||||||
type D struct{}
|
|
||||||
|
|
||||||
func (c *C) f() {} // @implements starC ".C"
|
|
||||||
func (d D) f() {} // @implements D "D"
|
|
||||||
|
|
||||||
func (d *D) g() []int { return nil } // @implements starD ".D"
|
|
||||||
|
|
||||||
type sorter []int // @implements sorter "sorter"
|
|
||||||
|
|
||||||
func (sorter) Len() int { return 0 }
|
|
||||||
func (sorter) Less(i, j int) bool { return false }
|
|
||||||
func (sorter) Swap(i, j int) {}
|
|
||||||
|
|
||||||
type I interface { // @implements I "I"
|
|
||||||
Method(*int) *int
|
|
||||||
}
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
-------- @implements E --------
|
|
||||||
empty interface type E
|
|
||||||
|
|
||||||
-------- @implements F --------
|
|
||||||
interface type F
|
|
||||||
is implemented by pointer type *C
|
|
||||||
is implemented by struct type D
|
|
||||||
is implemented by interface type FG
|
|
||||||
|
|
||||||
-------- @implements FG --------
|
|
||||||
interface type FG
|
|
||||||
is implemented by pointer type *D
|
|
||||||
implements F
|
|
||||||
|
|
||||||
-------- @implements slice --------
|
|
||||||
slice type []int implements only interface{}
|
|
||||||
|
|
||||||
-------- @implements C --------
|
|
||||||
pointer type *C
|
|
||||||
implements F
|
|
||||||
|
|
||||||
-------- @implements starC --------
|
|
||||||
pointer type *C
|
|
||||||
implements F
|
|
||||||
|
|
||||||
-------- @implements D --------
|
|
||||||
struct type D
|
|
||||||
implements F
|
|
||||||
pointer type *D
|
|
||||||
implements FG
|
|
||||||
|
|
||||||
-------- @implements starD --------
|
|
||||||
pointer type *D
|
|
||||||
implements F
|
|
||||||
implements FG
|
|
||||||
|
|
||||||
-------- @implements sorter --------
|
|
||||||
slice type sorter
|
|
||||||
implements lib.Sorter
|
|
||||||
|
|
||||||
-------- @implements I --------
|
|
||||||
interface type I
|
|
||||||
is implemented by basic type lib.Type
|
|
||||||
is implemented by interface type main.I
|
|
||||||
implements main.I
|
|
||||||
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"hash/fnv" // @describe ref-pkg-import2 "fnv"
|
|
||||||
"lib" // @describe ref-pkg-import "lib"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Tests that import another package. (To make the tests run quickly,
|
|
||||||
// we avoid using imports in all the other tests. Remember, each
|
|
||||||
// query causes parsing and typechecking of the whole program.)
|
|
||||||
//
|
|
||||||
// See go.tools/oracle/oracle_test.go for explanation.
|
|
||||||
// See imports.golden for expected query results.
|
|
||||||
|
|
||||||
var a int
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
const c = lib.Const // @describe ref-const "Const"
|
|
||||||
lib.Func() // @describe ref-func "Func"
|
|
||||||
lib.Var++ // @describe ref-var "Var"
|
|
||||||
var t lib.Type // @describe ref-type "Type"
|
|
||||||
p := t.Method(&a) // @describe ref-method "Method"
|
|
||||||
|
|
||||||
print(*p + 1) // @pointsto p "p "
|
|
||||||
|
|
||||||
var _ lib.Type // @describe ref-pkg "lib"
|
|
||||||
|
|
||||||
fnv.New32()
|
|
||||||
}
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
-------- @describe ref-pkg-import2 --------
|
|
||||||
import of package "hash/fnv"
|
|
||||||
func New32 func() hash.Hash32
|
|
||||||
func New32a func() hash.Hash32
|
|
||||||
func New64 func() hash.Hash64
|
|
||||||
func New64a func() hash.Hash64
|
|
||||||
|
|
||||||
-------- @describe ref-pkg-import --------
|
|
||||||
import of package "lib"
|
|
||||||
const Const untyped int = 3
|
|
||||||
func Func func()
|
|
||||||
type Sorter interface{...}
|
|
||||||
method (Sorter) Len() int
|
|
||||||
method (Sorter) Less(i int, j int) bool
|
|
||||||
method (Sorter) Swap(i int, j int)
|
|
||||||
type Type int
|
|
||||||
method (Type) Method(x *int) *int
|
|
||||||
var Var int
|
|
||||||
|
|
||||||
-------- @describe ref-const --------
|
|
||||||
reference to const lib.Const untyped int
|
|
||||||
defined here
|
|
||||||
|
|
||||||
-------- @describe ref-func --------
|
|
||||||
reference to func lib.Func()
|
|
||||||
defined here
|
|
||||||
|
|
||||||
-------- @describe ref-var --------
|
|
||||||
reference to var lib.Var int
|
|
||||||
defined here
|
|
||||||
|
|
||||||
-------- @describe ref-type --------
|
|
||||||
reference to type lib.Type (size 8, align 8)
|
|
||||||
defined as int
|
|
||||||
Method set:
|
|
||||||
method (lib.Type) Method(x *int) *int
|
|
||||||
|
|
||||||
-------- @describe ref-method --------
|
|
||||||
reference to method func (lib.Type).Method(x *int) *int
|
|
||||||
defined here
|
|
||||||
|
|
||||||
-------- @pointsto p --------
|
|
||||||
this *int may point to these objects:
|
|
||||||
main.a
|
|
||||||
|
|
||||||
-------- @describe ref-pkg --------
|
|
||||||
reference to package "lib"
|
|
||||||
const Const untyped int = 3
|
|
||||||
func Func func()
|
|
||||||
type Sorter interface{...}
|
|
||||||
method (Sorter) Len() int
|
|
||||||
method (Sorter) Less(i int, j int) bool
|
|
||||||
method (Sorter) Swap(i int, j int)
|
|
||||||
type Type int
|
|
||||||
method (Type) Method(x *int) *int
|
|
||||||
var Var int
|
|
||||||
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
package lib
|
|
||||||
|
|
||||||
type Type int
|
|
||||||
|
|
||||||
func (Type) Method(x *int) *int {
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
|
|
||||||
func Func() {
|
|
||||||
}
|
|
||||||
|
|
||||||
const Const = 3
|
|
||||||
|
|
||||||
var Var = 0
|
|
||||||
|
|
||||||
type Sorter interface {
|
|
||||||
Len() int
|
|
||||||
Less(i, j int) bool
|
|
||||||
Swap(i, j int)
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
func g(x int) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func f() {
|
|
||||||
x := 1
|
|
||||||
g(x) // "g(x)" is the selection for multiple queries
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
f()
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
// Tests of channel 'peers' query, -format=json.
|
|
||||||
// See go.tools/oracle/oracle_test.go for explanation.
|
|
||||||
// See peers-json.golden for expected query results.
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
chA := make(chan *int)
|
|
||||||
<-chA
|
|
||||||
select {
|
|
||||||
case <-chA: // @peers peer-recv-chA "<-"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
-------- @peers peer-recv-chA --------
|
|
||||||
{
|
|
||||||
"mode": "peers",
|
|
||||||
"peers": {
|
|
||||||
"pos": "testdata/src/peers-json/main.go:11:7",
|
|
||||||
"type": "chan *int",
|
|
||||||
"allocs": [
|
|
||||||
"testdata/src/peers-json/main.go:8:13"
|
|
||||||
],
|
|
||||||
"receives": [
|
|
||||||
"testdata/src/peers-json/main.go:9:2",
|
|
||||||
"testdata/src/peers-json/main.go:11:7"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
// Tests of channel 'peers' query.
|
|
||||||
// See go.tools/oracle/oracle_test.go for explanation.
|
|
||||||
// See peers.golden for expected query results.
|
|
||||||
|
|
||||||
var a2 int
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
chA := make(chan *int)
|
|
||||||
a1 := 1
|
|
||||||
chA <- &a1
|
|
||||||
|
|
||||||
chA2 := make(chan *int, 2)
|
|
||||||
if a2 == 0 {
|
|
||||||
chA = chA2
|
|
||||||
}
|
|
||||||
|
|
||||||
chB := make(chan *int)
|
|
||||||
b := 3
|
|
||||||
chB <- &b
|
|
||||||
|
|
||||||
<-chA // @pointsto pointsto-chA "chA"
|
|
||||||
<-chA2 // @pointsto pointsto-chA2 "chA2"
|
|
||||||
<-chB // @pointsto pointsto-chB "chB"
|
|
||||||
|
|
||||||
select {
|
|
||||||
case rA := <-chA: // @peers peer-recv-chA "<-"
|
|
||||||
_ = rA // @pointsto pointsto-rA "rA"
|
|
||||||
case rB := <-chB: // @peers peer-recv-chB "<-"
|
|
||||||
_ = rB // @pointsto pointsto-rB "rB"
|
|
||||||
|
|
||||||
case <-chA: // @peers peer-recv-chA' "<-"
|
|
||||||
|
|
||||||
case chA2 <- &a2: // @peers peer-send-chA' "<-"
|
|
||||||
}
|
|
||||||
|
|
||||||
for _ = range chA {
|
|
||||||
}
|
|
||||||
|
|
||||||
close(chA) // @peers peer-close-chA "chA"
|
|
||||||
|
|
||||||
chC := make(chan *int)
|
|
||||||
(close)(chC) // @peers peer-close-chC "chC"
|
|
||||||
|
|
||||||
close := func(ch chan *int) chan *int {
|
|
||||||
return ch
|
|
||||||
}
|
|
||||||
|
|
||||||
close(chC) <- &b // @peers peer-send-chC "chC"
|
|
||||||
<-close(chC) // @peers peer-recv-chC "chC"
|
|
||||||
}
|
|
||||||
|
|
@ -1,100 +0,0 @@
|
||||||
-------- @pointsto pointsto-chA --------
|
|
||||||
this chan *int may point to these objects:
|
|
||||||
makechan
|
|
||||||
makechan
|
|
||||||
|
|
||||||
-------- @pointsto pointsto-chA2 --------
|
|
||||||
this chan *int may point to these objects:
|
|
||||||
makechan
|
|
||||||
|
|
||||||
-------- @pointsto pointsto-chB --------
|
|
||||||
this chan *int may point to these objects:
|
|
||||||
makechan
|
|
||||||
|
|
||||||
-------- @peers peer-recv-chA --------
|
|
||||||
This channel of type chan *int may be:
|
|
||||||
allocated here
|
|
||||||
allocated here
|
|
||||||
sent to, here
|
|
||||||
sent to, here
|
|
||||||
received from, here
|
|
||||||
received from, here
|
|
||||||
received from, here
|
|
||||||
received from, here
|
|
||||||
received from, here
|
|
||||||
closed, here
|
|
||||||
|
|
||||||
-------- @pointsto pointsto-rA --------
|
|
||||||
this *int may point to these objects:
|
|
||||||
main.a2
|
|
||||||
a1
|
|
||||||
|
|
||||||
-------- @peers peer-recv-chB --------
|
|
||||||
This channel of type chan *int may be:
|
|
||||||
allocated here
|
|
||||||
sent to, here
|
|
||||||
received from, here
|
|
||||||
received from, here
|
|
||||||
|
|
||||||
-------- @pointsto pointsto-rB --------
|
|
||||||
this *int may point to these objects:
|
|
||||||
b
|
|
||||||
|
|
||||||
-------- @peers peer-recv-chA' --------
|
|
||||||
This channel of type chan *int may be:
|
|
||||||
allocated here
|
|
||||||
allocated here
|
|
||||||
sent to, here
|
|
||||||
sent to, here
|
|
||||||
received from, here
|
|
||||||
received from, here
|
|
||||||
received from, here
|
|
||||||
received from, here
|
|
||||||
received from, here
|
|
||||||
closed, here
|
|
||||||
|
|
||||||
-------- @peers peer-send-chA' --------
|
|
||||||
This channel of type chan *int may be:
|
|
||||||
allocated here
|
|
||||||
sent to, here
|
|
||||||
received from, here
|
|
||||||
received from, here
|
|
||||||
received from, here
|
|
||||||
received from, here
|
|
||||||
received from, here
|
|
||||||
closed, here
|
|
||||||
|
|
||||||
-------- @peers peer-close-chA --------
|
|
||||||
This channel of type chan *int may be:
|
|
||||||
allocated here
|
|
||||||
allocated here
|
|
||||||
sent to, here
|
|
||||||
sent to, here
|
|
||||||
received from, here
|
|
||||||
received from, here
|
|
||||||
received from, here
|
|
||||||
received from, here
|
|
||||||
received from, here
|
|
||||||
closed, here
|
|
||||||
|
|
||||||
-------- @peers peer-close-chC --------
|
|
||||||
This channel of type chan *int may be:
|
|
||||||
allocated here
|
|
||||||
sent to, here
|
|
||||||
received from, here
|
|
||||||
closed, here
|
|
||||||
|
|
||||||
-------- @peers peer-send-chC --------
|
|
||||||
This channel of type chan *int may be:
|
|
||||||
allocated here
|
|
||||||
sent to, here
|
|
||||||
received from, here
|
|
||||||
closed, here
|
|
||||||
|
|
||||||
-------- @peers peer-recv-chC --------
|
|
||||||
This channel of type chan *int may be:
|
|
||||||
allocated here
|
|
||||||
sent to, here
|
|
||||||
received from, here
|
|
||||||
closed, here
|
|
||||||
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
// Tests of 'pointsto' queries, -format=json.
|
|
||||||
// See go.tools/oracle/oracle_test.go for explanation.
|
|
||||||
// See pointsto-json.golden for expected query results.
|
|
||||||
|
|
||||||
func main() { //
|
|
||||||
var s struct{ x [3]int }
|
|
||||||
p := &s.x[0] // @pointsto val-p "p"
|
|
||||||
_ = p
|
|
||||||
|
|
||||||
var i I = C(0)
|
|
||||||
if i == nil {
|
|
||||||
i = new(D)
|
|
||||||
}
|
|
||||||
print(i) // @pointsto val-i "\\bi\\b"
|
|
||||||
}
|
|
||||||
|
|
||||||
type I interface {
|
|
||||||
f()
|
|
||||||
}
|
|
||||||
|
|
||||||
type C int
|
|
||||||
type D struct{}
|
|
||||||
|
|
||||||
func (c C) f() {}
|
|
||||||
func (d *D) f() {}
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
-------- @pointsto val-p --------
|
|
||||||
{
|
|
||||||
"mode": "pointsto",
|
|
||||||
"pointsto": [
|
|
||||||
{
|
|
||||||
"type": "*int",
|
|
||||||
"labels": [
|
|
||||||
{
|
|
||||||
"pos": "testdata/src/pointsto-json/main.go:8:6",
|
|
||||||
"desc": "s.x[*]"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
-------- @pointsto val-i --------
|
|
||||||
{
|
|
||||||
"mode": "pointsto",
|
|
||||||
"pointsto": [
|
|
||||||
{
|
|
||||||
"type": "*D",
|
|
||||||
"namepos": "testdata/src/pointsto-json/main.go:24:6",
|
|
||||||
"labels": [
|
|
||||||
{
|
|
||||||
"pos": "testdata/src/pointsto-json/main.go:14:10",
|
|
||||||
"desc": "new"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "C",
|
|
||||||
"namepos": "testdata/src/pointsto-json/main.go:23:6"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
// Tests of 'pointsto' query.
|
|
||||||
// See go.tools/oracle/oracle_test.go for explanation.
|
|
||||||
// See pointsto.golden for expected query results.
|
|
||||||
|
|
||||||
const pi = 3.141 // @pointsto const "pi"
|
|
||||||
|
|
||||||
var global = new(string) // NB: ssa.Global is indirect, i.e. **string
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
livecode()
|
|
||||||
|
|
||||||
// func objects
|
|
||||||
_ = main // @pointsto func-ref-main "main"
|
|
||||||
_ = (*C).f // @pointsto func-ref-*C.f "..C..f"
|
|
||||||
_ = D.f // @pointsto func-ref-D.f "D.f"
|
|
||||||
_ = I.f // @pointsto func-ref-I.f "I.f"
|
|
||||||
var d D
|
|
||||||
var i I
|
|
||||||
_ = d.f // @pointsto func-ref-d.f "d.f"
|
|
||||||
_ = i.f // @pointsto func-ref-i.f "i.f"
|
|
||||||
|
|
||||||
// var objects
|
|
||||||
anon := func() {
|
|
||||||
_ = d.f // @pointsto ref-lexical-d.f "d.f"
|
|
||||||
}
|
|
||||||
_ = anon // @pointsto ref-anon "anon"
|
|
||||||
_ = global // @pointsto ref-global "global"
|
|
||||||
|
|
||||||
// SSA affords some local flow sensitivity.
|
|
||||||
var a, b int
|
|
||||||
var x = &a // @pointsto var-def-x-1 "x"
|
|
||||||
_ = x // @pointsto var-ref-x-1 "x"
|
|
||||||
x = &b // @pointsto var-def-x-2 "x"
|
|
||||||
_ = x // @pointsto var-ref-x-2 "x"
|
|
||||||
|
|
||||||
i = new(C) // @pointsto var-ref-i-C "i"
|
|
||||||
if i != nil {
|
|
||||||
i = D{} // @pointsto var-ref-i-D "i"
|
|
||||||
}
|
|
||||||
print(i) // @pointsto var-ref-i "\\bi\\b"
|
|
||||||
|
|
||||||
m := map[string]*int{"a": &a}
|
|
||||||
mapval, _ := m["a"] // @pointsto map-lookup,ok "m..a.."
|
|
||||||
_ = mapval // @pointsto mapval "mapval"
|
|
||||||
_ = m // @pointsto m "m"
|
|
||||||
|
|
||||||
if false {
|
|
||||||
panic(3) // @pointsto builtin-panic "panic"
|
|
||||||
}
|
|
||||||
|
|
||||||
// NB: s.f is addressable per (*ssa.Program).VarValue,
|
|
||||||
// but our query concerns the object, not its address.
|
|
||||||
s := struct{ f interface{} }{f: make(chan bool)}
|
|
||||||
print(s.f) // @pointsto var-ref-s-f "s.f"
|
|
||||||
}
|
|
||||||
|
|
||||||
func livecode() {} // @pointsto func-live "livecode"
|
|
||||||
|
|
||||||
func deadcode() { // @pointsto func-dead "deadcode"
|
|
||||||
// Pointer analysis can't run on dead code.
|
|
||||||
var b = new(int) // @pointsto b "b"
|
|
||||||
_ = b
|
|
||||||
}
|
|
||||||
|
|
||||||
type I interface {
|
|
||||||
f()
|
|
||||||
}
|
|
||||||
|
|
||||||
type C int
|
|
||||||
type D struct{}
|
|
||||||
|
|
||||||
func (c *C) f() {}
|
|
||||||
func (d D) f() {}
|
|
||||||
|
|
@ -1,96 +0,0 @@
|
||||||
-------- @pointsto const --------
|
|
||||||
|
|
||||||
Error: pointer analysis wants an expression of reference type; got untyped float
|
|
||||||
-------- @pointsto func-ref-main --------
|
|
||||||
this func() may point to these objects:
|
|
||||||
main.main
|
|
||||||
|
|
||||||
-------- @pointsto func-ref-*C.f --------
|
|
||||||
this func() may point to these objects:
|
|
||||||
(*main.C).f
|
|
||||||
|
|
||||||
-------- @pointsto func-ref-D.f --------
|
|
||||||
this func() may point to these objects:
|
|
||||||
(main.D).f
|
|
||||||
|
|
||||||
-------- @pointsto func-ref-I.f --------
|
|
||||||
|
|
||||||
Error: func (main.I).f() is an interface method
|
|
||||||
-------- @pointsto func-ref-d.f --------
|
|
||||||
this func() may point to these objects:
|
|
||||||
(main.D).f
|
|
||||||
|
|
||||||
-------- @pointsto func-ref-i.f --------
|
|
||||||
|
|
||||||
Error: func (main.I).f() is an interface method
|
|
||||||
-------- @pointsto ref-lexical-d.f --------
|
|
||||||
this func() may point to these objects:
|
|
||||||
(main.D).f
|
|
||||||
|
|
||||||
-------- @pointsto ref-anon --------
|
|
||||||
this func() may point to these objects:
|
|
||||||
main.main$1
|
|
||||||
|
|
||||||
-------- @pointsto ref-global --------
|
|
||||||
this *string may point to these objects:
|
|
||||||
new
|
|
||||||
|
|
||||||
-------- @pointsto var-def-x-1 --------
|
|
||||||
this *int may point to these objects:
|
|
||||||
a
|
|
||||||
|
|
||||||
-------- @pointsto var-ref-x-1 --------
|
|
||||||
this *int may point to these objects:
|
|
||||||
a
|
|
||||||
|
|
||||||
-------- @pointsto var-def-x-2 --------
|
|
||||||
this *int may point to these objects:
|
|
||||||
b
|
|
||||||
|
|
||||||
-------- @pointsto var-ref-x-2 --------
|
|
||||||
this *int may point to these objects:
|
|
||||||
b
|
|
||||||
|
|
||||||
-------- @pointsto var-ref-i-C --------
|
|
||||||
this I may contain these dynamic types:
|
|
||||||
*C, may point to:
|
|
||||||
new
|
|
||||||
|
|
||||||
-------- @pointsto var-ref-i-D --------
|
|
||||||
this I may contain these dynamic types:
|
|
||||||
D
|
|
||||||
|
|
||||||
-------- @pointsto var-ref-i --------
|
|
||||||
this I may contain these dynamic types:
|
|
||||||
*C, may point to:
|
|
||||||
new
|
|
||||||
D
|
|
||||||
|
|
||||||
-------- @pointsto map-lookup,ok --------
|
|
||||||
|
|
||||||
Error: pointer analysis wants an expression of reference type; got (*int, bool)
|
|
||||||
-------- @pointsto mapval --------
|
|
||||||
this *int may point to these objects:
|
|
||||||
a
|
|
||||||
|
|
||||||
-------- @pointsto m --------
|
|
||||||
this map[string]*int may point to these objects:
|
|
||||||
makemap
|
|
||||||
|
|
||||||
-------- @pointsto builtin-panic --------
|
|
||||||
|
|
||||||
Error: pointer analysis wants an expression of reference type; got ()
|
|
||||||
-------- @pointsto var-ref-s-f --------
|
|
||||||
this interface{} may contain these dynamic types:
|
|
||||||
chan bool, may point to:
|
|
||||||
makechan
|
|
||||||
|
|
||||||
-------- @pointsto func-live --------
|
|
||||||
|
|
||||||
Error: pointer analysis did not find expression (dead code?)
|
|
||||||
-------- @pointsto func-dead --------
|
|
||||||
|
|
||||||
Error: pointer analysis did not find expression (dead code?)
|
|
||||||
-------- @pointsto b --------
|
|
||||||
|
|
||||||
Error: pointer analysis did not find expression (dead code?)
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
// Tests of 'referrers' query.
|
|
||||||
// See go.tools/oracle/oracle_test.go for explanation.
|
|
||||||
// See referrers.golden for expected query results.
|
|
||||||
|
|
||||||
import "lib"
|
|
||||||
|
|
||||||
type s struct {
|
|
||||||
f int
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
var v lib.Type = lib.Const // @referrers ref-package "lib"
|
|
||||||
_ = v.Method // @referrers ref-method "Method"
|
|
||||||
_ = v.Method
|
|
||||||
v++ //@referrers ref-local "v"
|
|
||||||
v++
|
|
||||||
|
|
||||||
_ = s{}.f // @referrers ref-field "f"
|
|
||||||
|
|
||||||
var s2 s
|
|
||||||
s2.f = 1
|
|
||||||
}
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
-------- @referrers ref-package --------
|
|
||||||
{
|
|
||||||
"mode": "referrers",
|
|
||||||
"referrers": {
|
|
||||||
"pos": "testdata/src/referrers-json/main.go:14:8",
|
|
||||||
"objpos": "testdata/src/referrers-json/main.go:7:8",
|
|
||||||
"desc": "package lib",
|
|
||||||
"refs": [
|
|
||||||
"testdata/src/referrers-json/main.go:14:8",
|
|
||||||
"testdata/src/referrers-json/main.go:14:19"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-------- @referrers ref-method --------
|
|
||||||
{
|
|
||||||
"mode": "referrers",
|
|
||||||
"referrers": {
|
|
||||||
"pos": "testdata/src/referrers-json/main.go:15:8",
|
|
||||||
"objpos": "testdata/src/lib/lib.go:5:13",
|
|
||||||
"desc": "func (lib.Type).Method(x *int) *int",
|
|
||||||
"refs": [
|
|
||||||
"testdata/src/imports/main.go:22:9",
|
|
||||||
"testdata/src/referrers-json/main.go:15:8",
|
|
||||||
"testdata/src/referrers-json/main.go:16:8",
|
|
||||||
"testdata/src/referrers/ext_test.go:10:17",
|
|
||||||
"testdata/src/referrers/int_test.go:7:17",
|
|
||||||
"testdata/src/referrers/main.go:17:8",
|
|
||||||
"testdata/src/referrers/main.go:18:8"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-------- @referrers ref-local --------
|
|
||||||
{
|
|
||||||
"mode": "referrers",
|
|
||||||
"referrers": {
|
|
||||||
"pos": "testdata/src/referrers-json/main.go:17:2",
|
|
||||||
"objpos": "testdata/src/referrers-json/main.go:14:6",
|
|
||||||
"desc": "var v lib.Type",
|
|
||||||
"refs": [
|
|
||||||
"testdata/src/referrers-json/main.go:15:6",
|
|
||||||
"testdata/src/referrers-json/main.go:16:6",
|
|
||||||
"testdata/src/referrers-json/main.go:17:2",
|
|
||||||
"testdata/src/referrers-json/main.go:18:2"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-------- @referrers ref-field --------
|
|
||||||
{
|
|
||||||
"mode": "referrers",
|
|
||||||
"referrers": {
|
|
||||||
"pos": "testdata/src/referrers-json/main.go:20:10",
|
|
||||||
"objpos": "testdata/src/referrers-json/main.go:10:2",
|
|
||||||
"desc": "field f int",
|
|
||||||
"refs": [
|
|
||||||
"testdata/src/referrers-json/main.go:20:10",
|
|
||||||
"testdata/src/referrers-json/main.go:23:5"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
package main_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"lib"
|
|
||||||
renamed "referrers" // package has name "main", path "referrers", local name "renamed"
|
|
||||||
)
|
|
||||||
|
|
||||||
func _() {
|
|
||||||
// This reference should be found by the ref-method query.
|
|
||||||
_ = (lib.Type).Method // ref from external test package
|
|
||||||
var _ renamed.T
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import "lib"
|
|
||||||
|
|
||||||
func _() {
|
|
||||||
// This reference should be found by the ref-method query.
|
|
||||||
_ = (lib.Type).Method // ref from internal test package
|
|
||||||
}
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
package main // @referrers package-decl "main"
|
|
||||||
|
|
||||||
// Tests of 'referrers' query.
|
|
||||||
// See go.tools/oracle/oracle_test.go for explanation.
|
|
||||||
// See referrers.golden for expected query results.
|
|
||||||
|
|
||||||
import "lib"
|
|
||||||
|
|
||||||
type s struct { // @referrers type " s "
|
|
||||||
f int
|
|
||||||
}
|
|
||||||
|
|
||||||
type T int
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
var v lib.Type = lib.Const // @referrers ref-package "lib"
|
|
||||||
_ = v.Method // @referrers ref-method "Method"
|
|
||||||
_ = v.Method
|
|
||||||
v++ //@referrers ref-local "v"
|
|
||||||
v++
|
|
||||||
|
|
||||||
_ = s{}.f // @referrers ref-field "f"
|
|
||||||
|
|
||||||
var s2 s
|
|
||||||
s2.f = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test //line directives:
|
|
||||||
|
|
||||||
type U int // @referrers ref-type-U "U"
|
|
||||||
|
|
||||||
//line nosuchfile.y:123
|
|
||||||
var u1 U
|
|
||||||
var u2 U
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
-------- @referrers package-decl --------
|
|
||||||
1 references to package main ("referrers")
|
|
||||||
var _ renamed.T
|
|
||||||
|
|
||||||
-------- @referrers type --------
|
|
||||||
2 references to type s struct{f int}
|
|
||||||
_ = s{}.f // @referrers ref-field "f"
|
|
||||||
var s2 s
|
|
||||||
|
|
||||||
-------- @referrers ref-package --------
|
|
||||||
4 references to package lib
|
|
||||||
_ = (lib.Type).Method // ref from external test package
|
|
||||||
_ = (lib.Type).Method // ref from internal test package
|
|
||||||
var v lib.Type = lib.Const // @referrers ref-package "lib"
|
|
||||||
var v lib.Type = lib.Const // @referrers ref-package "lib"
|
|
||||||
|
|
||||||
-------- @referrers ref-method --------
|
|
||||||
7 references to func (lib.Type).Method(x *int) *int
|
|
||||||
p := t.Method(&a) // @describe ref-method "Method"
|
|
||||||
_ = v.Method // @referrers ref-method "Method"
|
|
||||||
_ = v.Method
|
|
||||||
_ = (lib.Type).Method // ref from external test package
|
|
||||||
_ = (lib.Type).Method // ref from internal test package
|
|
||||||
_ = v.Method // @referrers ref-method "Method"
|
|
||||||
_ = v.Method
|
|
||||||
|
|
||||||
-------- @referrers ref-local --------
|
|
||||||
4 references to var v lib.Type
|
|
||||||
_ = v.Method // @referrers ref-method "Method"
|
|
||||||
_ = v.Method
|
|
||||||
v++ //@referrers ref-local "v"
|
|
||||||
v++
|
|
||||||
|
|
||||||
-------- @referrers ref-field --------
|
|
||||||
2 references to field f int
|
|
||||||
_ = s{}.f // @referrers ref-field "f"
|
|
||||||
s2.f = 1
|
|
||||||
|
|
||||||
-------- @referrers ref-type-U --------
|
|
||||||
2 references to type U int
|
|
||||||
open testdata/src/referrers/nosuchfile.y: no such file or directory (+ 1 more refs in this file)
|
|
||||||
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
// This is a test of 'pointsto', but we split it into a separate file
|
|
||||||
// so that pointsto.go doesn't have to import "reflect" each time.
|
|
||||||
|
|
||||||
import "reflect"
|
|
||||||
|
|
||||||
var a int
|
|
||||||
var b bool
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
m := make(map[*int]*bool)
|
|
||||||
m[&a] = &b
|
|
||||||
|
|
||||||
mrv := reflect.ValueOf(m)
|
|
||||||
if a > 0 {
|
|
||||||
mrv = reflect.ValueOf(&b)
|
|
||||||
}
|
|
||||||
if a > 0 {
|
|
||||||
mrv = reflect.ValueOf(&a)
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = mrv // @pointsto mrv "mrv"
|
|
||||||
p1 := mrv.Interface() // @pointsto p1 "p1"
|
|
||||||
p2 := mrv.MapKeys() // @pointsto p2 "p2"
|
|
||||||
p3 := p2[0] // @pointsto p3 "p3"
|
|
||||||
p4 := reflect.TypeOf(p1) // @pointsto p4 "p4"
|
|
||||||
|
|
||||||
_, _, _, _ = p1, p2, p3, p4
|
|
||||||
}
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
-------- @pointsto mrv --------
|
|
||||||
this reflect.Value may contain these dynamic types:
|
|
||||||
*bool, may point to:
|
|
||||||
main.b
|
|
||||||
*int, may point to:
|
|
||||||
main.a
|
|
||||||
map[*int]*bool, may point to:
|
|
||||||
makemap
|
|
||||||
|
|
||||||
-------- @pointsto p1 --------
|
|
||||||
this interface{} may contain these dynamic types:
|
|
||||||
*bool, may point to:
|
|
||||||
main.b
|
|
||||||
*int, may point to:
|
|
||||||
main.a
|
|
||||||
map[*int]*bool, may point to:
|
|
||||||
makemap
|
|
||||||
|
|
||||||
-------- @pointsto p2 --------
|
|
||||||
this []reflect.Value may point to these objects:
|
|
||||||
<alloc in (reflect.Value).MapKeys>
|
|
||||||
|
|
||||||
-------- @pointsto p3 --------
|
|
||||||
this reflect.Value may contain these dynamic types:
|
|
||||||
*int, may point to:
|
|
||||||
main.a
|
|
||||||
|
|
||||||
-------- @pointsto p4 --------
|
|
||||||
this reflect.Type may contain these dynamic types:
|
|
||||||
*reflect.rtype, may point to:
|
|
||||||
*bool
|
|
||||||
*int
|
|
||||||
map[*int]*bool
|
|
||||||
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
// Tests of 'what' queries, -format=json.
|
|
||||||
// See go.tools/oracle/oracle_test.go for explanation.
|
|
||||||
// See what-json.golden for expected query results.
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
f() // @what call "f"
|
|
||||||
}
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
-------- @what call --------
|
|
||||||
{
|
|
||||||
"mode": "what",
|
|
||||||
"what": {
|
|
||||||
"enclosing": [
|
|
||||||
{
|
|
||||||
"desc": "identifier",
|
|
||||||
"start": 179,
|
|
||||||
"end": 180
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"desc": "function call",
|
|
||||||
"start": 179,
|
|
||||||
"end": 182
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"desc": "expression statement",
|
|
||||||
"start": 179,
|
|
||||||
"end": 182
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"desc": "block",
|
|
||||||
"start": 176,
|
|
||||||
"end": 202
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"desc": "function declaration",
|
|
||||||
"start": 164,
|
|
||||||
"end": 202
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"desc": "source file",
|
|
||||||
"start": 0,
|
|
||||||
"end": 202
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"modes": [
|
|
||||||
"callees",
|
|
||||||
"callers",
|
|
||||||
"callstack",
|
|
||||||
"definition",
|
|
||||||
"describe",
|
|
||||||
"freevars",
|
|
||||||
"implements",
|
|
||||||
"pointsto",
|
|
||||||
"referrers"
|
|
||||||
],
|
|
||||||
"srcdir": "testdata/src",
|
|
||||||
"importpath": "what-json"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
package main // @what pkgdecl "main"
|
|
||||||
|
|
||||||
// Tests of 'what' queries.
|
|
||||||
// See go.tools/oracle/oracle_test.go for explanation.
|
|
||||||
// See what.golden for expected query results.
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
f() // @what call "f"
|
|
||||||
var ch chan int // @what var "var"
|
|
||||||
<-ch // @what recv "ch"
|
|
||||||
}
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
-------- @what pkgdecl --------
|
|
||||||
identifier
|
|
||||||
source file
|
|
||||||
modes: [definition describe freevars implements pointsto referrers]
|
|
||||||
srcdir: testdata/src
|
|
||||||
import path: what
|
|
||||||
|
|
||||||
-------- @what call --------
|
|
||||||
identifier
|
|
||||||
function call
|
|
||||||
expression statement
|
|
||||||
block
|
|
||||||
function declaration
|
|
||||||
source file
|
|
||||||
modes: [callees callers callstack definition describe freevars implements pointsto referrers]
|
|
||||||
srcdir: testdata/src
|
|
||||||
import path: what
|
|
||||||
|
|
||||||
-------- @what var --------
|
|
||||||
variable declaration
|
|
||||||
variable declaration statement
|
|
||||||
block
|
|
||||||
function declaration
|
|
||||||
source file
|
|
||||||
modes: [callers callstack describe freevars pointsto]
|
|
||||||
srcdir: testdata/src
|
|
||||||
import path: what
|
|
||||||
|
|
||||||
-------- @what recv --------
|
|
||||||
identifier
|
|
||||||
unary <- operation
|
|
||||||
expression statement
|
|
||||||
block
|
|
||||||
function declaration
|
|
||||||
source file
|
|
||||||
modes: [callers callstack definition describe freevars implements peers pointsto referrers]
|
|
||||||
srcdir: testdata/src
|
|
||||||
import path: what
|
|
||||||
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
type errType string
|
|
||||||
|
|
||||||
const constErr errType = "blah"
|
|
||||||
|
|
||||||
func (et errType) Error() string {
|
|
||||||
return string(et)
|
|
||||||
}
|
|
||||||
|
|
||||||
var errVar error = errType("foo")
|
|
||||||
|
|
||||||
func genErr(i int) error {
|
|
||||||
switch i {
|
|
||||||
case 0:
|
|
||||||
return constErr
|
|
||||||
case 1:
|
|
||||||
return errVar
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
err := genErr(0) // @whicherrs localerrs "err"
|
|
||||||
_ = err
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
-------- @whicherrs localerrs --------
|
|
||||||
this error may point to these globals:
|
|
||||||
errVar
|
|
||||||
this error may contain these constants:
|
|
||||||
constErr
|
|
||||||
this error may contain these dynamic types:
|
|
||||||
errType
|
|
||||||
|
|
||||||
210
oracle/what.go
210
oracle/what.go
|
|
@ -1,210 +0,0 @@
|
||||||
// Copyright 2013 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package oracle
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"go/ast"
|
|
||||||
"go/build"
|
|
||||||
"go/token"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/tools/go/ast/astutil"
|
|
||||||
"golang.org/x/tools/oracle/serial"
|
|
||||||
)
|
|
||||||
|
|
||||||
// what reports all the information about the query selection that can be
|
|
||||||
// obtained from parsing only its containing source file.
|
|
||||||
// It is intended to be a very low-latency query callable from GUI
|
|
||||||
// tools, e.g. to populate a menu of options of slower queries about
|
|
||||||
// the selected location.
|
|
||||||
//
|
|
||||||
func what(q *Query) error {
|
|
||||||
qpos, err := fastQueryPos(q.Pos)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
q.Fset = qpos.fset
|
|
||||||
|
|
||||||
// (ignore errors)
|
|
||||||
srcdir, importPath, _ := guessImportPath(q.Fset.File(qpos.start).Name(), q.Build)
|
|
||||||
|
|
||||||
// Determine which query modes are applicable to the selection.
|
|
||||||
enable := map[string]bool{
|
|
||||||
"describe": true, // any syntax; always enabled
|
|
||||||
}
|
|
||||||
|
|
||||||
if qpos.end > qpos.start {
|
|
||||||
enable["freevars"] = true // nonempty selection?
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, n := range qpos.path {
|
|
||||||
switch n := n.(type) {
|
|
||||||
case *ast.Ident:
|
|
||||||
enable["definition"] = true
|
|
||||||
enable["referrers"] = true
|
|
||||||
enable["implements"] = true
|
|
||||||
case *ast.CallExpr:
|
|
||||||
enable["callees"] = true
|
|
||||||
case *ast.FuncDecl:
|
|
||||||
enable["callers"] = true
|
|
||||||
enable["callstack"] = true
|
|
||||||
case *ast.SendStmt:
|
|
||||||
enable["peers"] = true
|
|
||||||
case *ast.UnaryExpr:
|
|
||||||
if n.Op == token.ARROW {
|
|
||||||
enable["peers"] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// For implements, we approximate findInterestingNode.
|
|
||||||
if _, ok := enable["implements"]; !ok {
|
|
||||||
switch n.(type) {
|
|
||||||
case *ast.ArrayType,
|
|
||||||
*ast.StructType,
|
|
||||||
*ast.FuncType,
|
|
||||||
*ast.InterfaceType,
|
|
||||||
*ast.MapType,
|
|
||||||
*ast.ChanType:
|
|
||||||
enable["implements"] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// For pointsto, we approximate findInterestingNode.
|
|
||||||
if _, ok := enable["pointsto"]; !ok {
|
|
||||||
switch n.(type) {
|
|
||||||
case ast.Stmt,
|
|
||||||
*ast.ArrayType,
|
|
||||||
*ast.StructType,
|
|
||||||
*ast.FuncType,
|
|
||||||
*ast.InterfaceType,
|
|
||||||
*ast.MapType,
|
|
||||||
*ast.ChanType:
|
|
||||||
enable["pointsto"] = false // not an expr
|
|
||||||
|
|
||||||
case ast.Expr, ast.Decl, *ast.ValueSpec:
|
|
||||||
enable["pointsto"] = true // an expr, maybe
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Comment, Field, KeyValueExpr, etc: ascend.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we don't have an exact selection, disable modes that need one.
|
|
||||||
if !qpos.exact {
|
|
||||||
enable["callees"] = false
|
|
||||||
enable["pointsto"] = false
|
|
||||||
enable["whicherrs"] = false
|
|
||||||
enable["describe"] = false
|
|
||||||
}
|
|
||||||
|
|
||||||
var modes []string
|
|
||||||
for mode := range enable {
|
|
||||||
modes = append(modes, mode)
|
|
||||||
}
|
|
||||||
sort.Strings(modes)
|
|
||||||
|
|
||||||
q.result = &whatResult{
|
|
||||||
path: qpos.path,
|
|
||||||
srcdir: srcdir,
|
|
||||||
importPath: importPath,
|
|
||||||
modes: modes,
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// guessImportPath finds the package containing filename, and returns
|
|
||||||
// its source directory (an element of $GOPATH) and its import path
|
|
||||||
// relative to it.
|
|
||||||
//
|
|
||||||
// TODO(adonovan): what about _test.go files that are not part of the
|
|
||||||
// package?
|
|
||||||
//
|
|
||||||
func guessImportPath(filename string, buildContext *build.Context) (srcdir, importPath string, err error) {
|
|
||||||
absFile, err := filepath.Abs(filename)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("can't form absolute path of %s", filename)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
absFileDir := segments(filepath.Dir(absFile))
|
|
||||||
|
|
||||||
// Find the innermost directory in $GOPATH that encloses filename.
|
|
||||||
minD := 1024
|
|
||||||
for _, gopathDir := range buildContext.SrcDirs() {
|
|
||||||
absDir, err := filepath.Abs(gopathDir)
|
|
||||||
if err != nil {
|
|
||||||
continue // e.g. non-existent dir on $GOPATH
|
|
||||||
}
|
|
||||||
d := prefixLen(segments(absDir), absFileDir)
|
|
||||||
// If there are multiple matches,
|
|
||||||
// prefer the innermost enclosing directory
|
|
||||||
// (smallest d).
|
|
||||||
if d >= 0 && d < minD {
|
|
||||||
minD = d
|
|
||||||
srcdir = gopathDir
|
|
||||||
importPath = strings.Join(absFileDir[len(absFileDir)-minD:], string(os.PathSeparator))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if srcdir == "" {
|
|
||||||
err = fmt.Errorf("directory %s is not beneath any of these GOROOT/GOPATH directories: %s",
|
|
||||||
filepath.Dir(absFile), strings.Join(buildContext.SrcDirs(), ", "))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func segments(path string) []string {
|
|
||||||
return strings.Split(path, string(os.PathSeparator))
|
|
||||||
}
|
|
||||||
|
|
||||||
// prefixLen returns the length of the remainder of y if x is a prefix
|
|
||||||
// of y, a negative number otherwise.
|
|
||||||
func prefixLen(x, y []string) int {
|
|
||||||
d := len(y) - len(x)
|
|
||||||
if d >= 0 {
|
|
||||||
for i := range x {
|
|
||||||
if y[i] != x[i] {
|
|
||||||
return -1 // not a prefix
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
type whatResult struct {
|
|
||||||
path []ast.Node
|
|
||||||
modes []string
|
|
||||||
srcdir string
|
|
||||||
importPath string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *whatResult) display(printf printfFunc) {
|
|
||||||
for _, n := range r.path {
|
|
||||||
printf(n, "%s", astutil.NodeDescription(n))
|
|
||||||
}
|
|
||||||
printf(nil, "modes: %s", r.modes)
|
|
||||||
printf(nil, "srcdir: %s", r.srcdir)
|
|
||||||
printf(nil, "import path: %s", r.importPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *whatResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
|
||||||
var enclosing []serial.SyntaxNode
|
|
||||||
for _, n := range r.path {
|
|
||||||
enclosing = append(enclosing, serial.SyntaxNode{
|
|
||||||
Description: astutil.NodeDescription(n),
|
|
||||||
Start: fset.Position(n.Pos()).Offset,
|
|
||||||
End: fset.Position(n.End()).Offset,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
res.What = &serial.What{
|
|
||||||
Modes: r.modes,
|
|
||||||
SrcDir: r.srcdir,
|
|
||||||
ImportPath: r.importPath,
|
|
||||||
Enclosing: enclosing,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,328 +0,0 @@
|
||||||
// Copyright 2014 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build go1.5
|
|
||||||
|
|
||||||
package oracle
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"go/ast"
|
|
||||||
"go/token"
|
|
||||||
"go/types"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"golang.org/x/tools/go/ast/astutil"
|
|
||||||
"golang.org/x/tools/go/loader"
|
|
||||||
"golang.org/x/tools/go/ssa"
|
|
||||||
"golang.org/x/tools/go/ssa/ssautil"
|
|
||||||
"golang.org/x/tools/oracle/serial"
|
|
||||||
)
|
|
||||||
|
|
||||||
var builtinErrorType = types.Universe.Lookup("error").Type()
|
|
||||||
|
|
||||||
// whicherrs takes an position to an error and tries to find all types, constants
|
|
||||||
// and global value which a given error can point to and which can be checked from the
|
|
||||||
// scope where the error lives.
|
|
||||||
// In short, it returns a list of things that can be checked against in order to handle
|
|
||||||
// an error properly.
|
|
||||||
//
|
|
||||||
// TODO(dmorsing): figure out if fields in errors like *os.PathError.Err
|
|
||||||
// can be queried recursively somehow.
|
|
||||||
func whicherrs(q *Query) error {
|
|
||||||
lconf := loader.Config{Build: q.Build}
|
|
||||||
|
|
||||||
if err := setPTAScope(&lconf, q.Scope); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load/parse/type-check the program.
|
|
||||||
lprog, err := lconf.Load()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
q.Fset = lprog.Fset
|
|
||||||
|
|
||||||
qpos, err := parseQueryPos(lprog, q.Pos, true) // needs exact pos
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug)
|
|
||||||
|
|
||||||
ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
path, action := findInterestingNode(qpos.info, qpos.path)
|
|
||||||
if action != actionExpr {
|
|
||||||
return fmt.Errorf("whicherrs wants an expression; got %s",
|
|
||||||
astutil.NodeDescription(qpos.path[0]))
|
|
||||||
}
|
|
||||||
var expr ast.Expr
|
|
||||||
var obj types.Object
|
|
||||||
switch n := path[0].(type) {
|
|
||||||
case *ast.ValueSpec:
|
|
||||||
// ambiguous ValueSpec containing multiple names
|
|
||||||
return fmt.Errorf("multiple value specification")
|
|
||||||
case *ast.Ident:
|
|
||||||
obj = qpos.info.ObjectOf(n)
|
|
||||||
expr = n
|
|
||||||
case ast.Expr:
|
|
||||||
expr = n
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unexpected AST for expr: %T", n)
|
|
||||||
}
|
|
||||||
|
|
||||||
typ := qpos.info.TypeOf(expr)
|
|
||||||
if !types.Identical(typ, builtinErrorType) {
|
|
||||||
return fmt.Errorf("selection is not an expression of type 'error'")
|
|
||||||
}
|
|
||||||
// Determine the ssa.Value for the expression.
|
|
||||||
var value ssa.Value
|
|
||||||
if obj != nil {
|
|
||||||
// def/ref of func/var object
|
|
||||||
value, _, err = ssaValueForIdent(prog, qpos.info, obj, path)
|
|
||||||
} else {
|
|
||||||
value, _, err = ssaValueForExpr(prog, qpos.info, path)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err // e.g. trivially dead code
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defer SSA construction till after errors are reported.
|
|
||||||
prog.Build()
|
|
||||||
|
|
||||||
globals := findVisibleErrs(prog, qpos)
|
|
||||||
constants := findVisibleConsts(prog, qpos)
|
|
||||||
|
|
||||||
res := &whicherrsResult{
|
|
||||||
qpos: qpos,
|
|
||||||
errpos: expr.Pos(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(adonovan): the following code is heavily duplicated
|
|
||||||
// w.r.t. "pointsto". Refactor?
|
|
||||||
|
|
||||||
// Find the instruction which initialized the
|
|
||||||
// global error. If more than one instruction has stored to the global
|
|
||||||
// remove the global from the set of values that we want to query.
|
|
||||||
allFuncs := ssautil.AllFunctions(prog)
|
|
||||||
for fn := range allFuncs {
|
|
||||||
for _, b := range fn.Blocks {
|
|
||||||
for _, instr := range b.Instrs {
|
|
||||||
store, ok := instr.(*ssa.Store)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
gval, ok := store.Addr.(*ssa.Global)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
gbl, ok := globals[gval]
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// we already found a store to this global
|
|
||||||
// The normal error define is just one store in the init
|
|
||||||
// so we just remove this global from the set we want to query
|
|
||||||
if gbl != nil {
|
|
||||||
delete(globals, gval)
|
|
||||||
}
|
|
||||||
globals[gval] = store.Val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ptaConfig.AddQuery(value)
|
|
||||||
for _, v := range globals {
|
|
||||||
ptaConfig.AddQuery(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
ptares := ptrAnalysis(ptaConfig)
|
|
||||||
valueptr := ptares.Queries[value]
|
|
||||||
for g, v := range globals {
|
|
||||||
ptr, ok := ptares.Queries[v]
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !ptr.MayAlias(valueptr) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
res.globals = append(res.globals, g)
|
|
||||||
}
|
|
||||||
pts := valueptr.PointsTo()
|
|
||||||
dedup := make(map[*ssa.NamedConst]bool)
|
|
||||||
for _, label := range pts.Labels() {
|
|
||||||
// These values are either MakeInterfaces or reflect
|
|
||||||
// generated interfaces. For the purposes of this
|
|
||||||
// analysis, we don't care about reflect generated ones
|
|
||||||
makeiface, ok := label.Value().(*ssa.MakeInterface)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
constval, ok := makeiface.X.(*ssa.Const)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
c := constants[*constval]
|
|
||||||
if c != nil && !dedup[c] {
|
|
||||||
dedup[c] = true
|
|
||||||
res.consts = append(res.consts, c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
concs := pts.DynamicTypes()
|
|
||||||
concs.Iterate(func(conc types.Type, _ interface{}) {
|
|
||||||
// go/types is a bit annoying here.
|
|
||||||
// We want to find all the types that we can
|
|
||||||
// typeswitch or assert to. This means finding out
|
|
||||||
// if the type pointed to can be seen by us.
|
|
||||||
//
|
|
||||||
// For the purposes of this analysis, the type is always
|
|
||||||
// either a Named type or a pointer to one.
|
|
||||||
// There are cases where error can be implemented
|
|
||||||
// by unnamed types, but in that case, we can't assert to
|
|
||||||
// it, so we don't care about it for this analysis.
|
|
||||||
var name *types.TypeName
|
|
||||||
switch t := conc.(type) {
|
|
||||||
case *types.Pointer:
|
|
||||||
named, ok := t.Elem().(*types.Named)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
name = named.Obj()
|
|
||||||
case *types.Named:
|
|
||||||
name = t.Obj()
|
|
||||||
default:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !isAccessibleFrom(name, qpos.info.Pkg) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
res.types = append(res.types, &errorType{conc, name})
|
|
||||||
})
|
|
||||||
sort.Sort(membersByPosAndString(res.globals))
|
|
||||||
sort.Sort(membersByPosAndString(res.consts))
|
|
||||||
sort.Sort(sorterrorType(res.types))
|
|
||||||
|
|
||||||
q.result = res
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// findVisibleErrs returns a mapping from each package-level variable of type "error" to nil.
|
|
||||||
func findVisibleErrs(prog *ssa.Program, qpos *queryPos) map[*ssa.Global]ssa.Value {
|
|
||||||
globals := make(map[*ssa.Global]ssa.Value)
|
|
||||||
for _, pkg := range prog.AllPackages() {
|
|
||||||
for _, mem := range pkg.Members {
|
|
||||||
gbl, ok := mem.(*ssa.Global)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
gbltype := gbl.Type()
|
|
||||||
// globals are always pointers
|
|
||||||
if !types.Identical(deref(gbltype), builtinErrorType) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !isAccessibleFrom(gbl.Object(), qpos.info.Pkg) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
globals[gbl] = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return globals
|
|
||||||
}
|
|
||||||
|
|
||||||
// findVisibleConsts returns a mapping from each package-level constant assignable to type "error", to nil.
|
|
||||||
func findVisibleConsts(prog *ssa.Program, qpos *queryPos) map[ssa.Const]*ssa.NamedConst {
|
|
||||||
constants := make(map[ssa.Const]*ssa.NamedConst)
|
|
||||||
for _, pkg := range prog.AllPackages() {
|
|
||||||
for _, mem := range pkg.Members {
|
|
||||||
obj, ok := mem.(*ssa.NamedConst)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
consttype := obj.Type()
|
|
||||||
if !types.AssignableTo(consttype, builtinErrorType) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !isAccessibleFrom(obj.Object(), qpos.info.Pkg) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
constants[*obj.Value] = obj
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return constants
|
|
||||||
}
|
|
||||||
|
|
||||||
type membersByPosAndString []ssa.Member
|
|
||||||
|
|
||||||
func (a membersByPosAndString) Len() int { return len(a) }
|
|
||||||
func (a membersByPosAndString) Less(i, j int) bool {
|
|
||||||
cmp := a[i].Pos() - a[j].Pos()
|
|
||||||
return cmp < 0 || cmp == 0 && a[i].String() < a[j].String()
|
|
||||||
}
|
|
||||||
func (a membersByPosAndString) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
||||||
|
|
||||||
type sorterrorType []*errorType
|
|
||||||
|
|
||||||
func (a sorterrorType) Len() int { return len(a) }
|
|
||||||
func (a sorterrorType) Less(i, j int) bool {
|
|
||||||
cmp := a[i].obj.Pos() - a[j].obj.Pos()
|
|
||||||
return cmp < 0 || cmp == 0 && a[i].typ.String() < a[j].typ.String()
|
|
||||||
}
|
|
||||||
func (a sorterrorType) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
||||||
|
|
||||||
type errorType struct {
|
|
||||||
typ types.Type // concrete type N or *N that implements error
|
|
||||||
obj *types.TypeName // the named type N
|
|
||||||
}
|
|
||||||
|
|
||||||
type whicherrsResult struct {
|
|
||||||
qpos *queryPos
|
|
||||||
errpos token.Pos
|
|
||||||
globals []ssa.Member
|
|
||||||
consts []ssa.Member
|
|
||||||
types []*errorType
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *whicherrsResult) display(printf printfFunc) {
|
|
||||||
if len(r.globals) > 0 {
|
|
||||||
printf(r.qpos, "this error may point to these globals:")
|
|
||||||
for _, g := range r.globals {
|
|
||||||
printf(g.Pos(), "\t%s", g.RelString(r.qpos.info.Pkg))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(r.consts) > 0 {
|
|
||||||
printf(r.qpos, "this error may contain these constants:")
|
|
||||||
for _, c := range r.consts {
|
|
||||||
printf(c.Pos(), "\t%s", c.RelString(r.qpos.info.Pkg))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(r.types) > 0 {
|
|
||||||
printf(r.qpos, "this error may contain these dynamic types:")
|
|
||||||
for _, t := range r.types {
|
|
||||||
printf(t.obj.Pos(), "\t%s", r.qpos.typeString(t.typ))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *whicherrsResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
|
||||||
we := &serial.WhichErrs{}
|
|
||||||
we.ErrPos = fset.Position(r.errpos).String()
|
|
||||||
for _, g := range r.globals {
|
|
||||||
we.Globals = append(we.Globals, fset.Position(g.Pos()).String())
|
|
||||||
}
|
|
||||||
for _, c := range r.consts {
|
|
||||||
we.Constants = append(we.Constants, fset.Position(c.Pos()).String())
|
|
||||||
}
|
|
||||||
for _, t := range r.types {
|
|
||||||
var et serial.WhichErrsType
|
|
||||||
et.Type = r.qpos.typeString(t.typ)
|
|
||||||
et.Position = fset.Position(t.obj.Pos()).String()
|
|
||||||
we.Types = append(we.Types, et)
|
|
||||||
}
|
|
||||||
res.WhichErrs = we
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue