go.tools/oracle: change -mode argument into subcommand.

e.g. "oracle callgraph <package>"

Also: simplified error handling.
Eliminated oracle.errorf because it prepends "file:line:col: "
to the error message so the main function can't safely prepend "Error: ".
The position wasn't interesting though: it was just -pos, more or less.

R=crawshaw, dominik.honnef, r
CC=golang-dev
https://golang.org/cl/13864044
This commit is contained in:
Alan Donovan 2013-09-25 14:34:39 -04:00
parent 39779f52c3
commit 3a4c0462d8
13 changed files with 65 additions and 70 deletions

View File

@ -24,7 +24,7 @@ trap "rm -f $log" EXIT
go get code.google.com/p/go.tools/cmd/oracle || die "'go get' failed" go get code.google.com/p/go.tools/cmd/oracle || die "'go get' failed"
mv -f $GOPATH/bin/oracle $GOROOT/bin/ mv -f $GOPATH/bin/oracle $GOROOT/bin/
$GOROOT/bin/oracle >$log 2>&1 || true # (prints usage and exits 1) $GOROOT/bin/oracle >$log 2>&1 || true # (prints usage and exits 1)
grep -q "Usage: oracle" $log || die "$GOROOT/bin/oracle not installed" grep -q "Run.*help" $log || die "$GOROOT/bin/oracle not installed"
# Run Emacs, set the scope to the oracle tool itself, # Run Emacs, set the scope to the oracle tool itself,

View File

@ -6,9 +6,7 @@
// http://golang.org/s/oracle-design // http://golang.org/s/oracle-design
// http://golang.org/s/oracle-user-manual // http://golang.org/s/oracle-user-manual
// //
// Run with -help for usage information. // Run with -help flag or help subcommand for usage information.
//
// TODO(adonovan): perhaps -mode should be an args[1] verb, e.g. 'oracle callgraph ...'
// //
package main package main
@ -33,9 +31,6 @@ var posFlag = flag.String("pos", "",
"Filename and byte offset or extent of a syntax element about which to query, "+ "Filename and byte offset or extent of a syntax element about which to query, "+
"e.g. foo.go:#123,#456, bar.go:#123.") "e.g. foo.go:#123,#456, bar.go:#123.")
var modeFlag = flag.String("mode", "",
"Mode of query to perform: e.g. callers, describe, etc.")
var ptalogFlag = flag.String("ptalog", "", var ptalogFlag = flag.String("ptalog", "",
"Location of the points-to analysis log file, or empty to disable logging.") "Location of the points-to analysis log file, or empty to disable logging.")
@ -47,7 +42,7 @@ var reflectFlag = flag.Bool("reflect", true, "Analyze reflection soundly (slow).
const useHelp = "Run 'oracle -help' for more information.\n" const useHelp = "Run 'oracle -help' for more information.\n"
const helpMessage = `Go source code oracle. const helpMessage = `Go source code oracle.
Usage: oracle [<flag> ...] <args> ... Usage: oracle [<flag> ...] <mode> <args> ...
The -format flag controls the output format: The -format flag controls the output format:
plain an editor-friendly format in which every line of output plain an editor-friendly format in which every line of output
@ -57,7 +52,8 @@ The -format flag controls the output format:
The -pos flag is required in all modes except 'callgraph'. The -pos flag is required in all modes except 'callgraph'.
The -mode flag determines the query to perform: The mode argument determines the query to perform:
callees show possible targets of selected function call callees show possible targets of selected function call
callers show possible callers of selected function callers show possible callers of selected function
callgraph show complete callgraph of program callgraph show complete callgraph of program
@ -73,11 +69,11 @@ The user manual is available here: http://golang.org/s/oracle-user-manual
Examples: Examples:
Describe the syntax at offset 530 in this file (an import spec): Describe the syntax at offset 530 in this file (an import spec):
% oracle -mode=describe -pos=src/code.google.com/p/go.tools/cmd/oracle/main.go:#530 \ % oracle -pos=src/code.google.com/p/go.tools/cmd/oracle/main.go:#530 describe \
code.google.com/p/go.tools/cmd/oracle code.google.com/p/go.tools/cmd/oracle
Print the callgraph of the trivial web-server in JSON format: Print the callgraph of the trivial web-server in JSON format:
% oracle -mode=callgraph -format=json src/pkg/net/http/triv.go % oracle -format=json src/pkg/net/http/triv.go callgraph
` + importer.InitialPackagesUsage ` + importer.InitialPackagesUsage
var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file") var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
@ -94,6 +90,12 @@ func init() {
} }
} }
func printHelp() {
fmt.Println(helpMessage)
fmt.Println("Flags:")
flag.PrintDefaults()
}
func main() { func main() {
// Don't print full help unless -help was requested. // Don't print full help unless -help was requested.
// Just gently remind users that it's there. // Just gently remind users that it's there.
@ -102,13 +104,23 @@ func main() {
if err := flag.CommandLine.Parse(os.Args[1:]); err != nil { if err := flag.CommandLine.Parse(os.Args[1:]); err != nil {
// (err has already been printed) // (err has already been printed)
if err == flag.ErrHelp { if err == flag.ErrHelp {
fmt.Println(helpMessage) printHelp()
fmt.Println("Flags:")
flag.PrintDefaults()
} }
os.Exit(2) os.Exit(2)
} }
args := flag.Args() args := flag.Args()
if len(args) == 0 || args[0] == "" {
fmt.Fprint(os.Stderr, "Error: a mode argument is required.\n"+useHelp)
os.Exit(2)
}
mode := args[0]
args = args[1:]
if mode == "help" {
printHelp()
os.Exit(2)
}
if len(args) == 0 { if len(args) == 0 {
fmt.Fprint(os.Stderr, "Error: no package arguments.\n"+useHelp) fmt.Fprint(os.Stderr, "Error: no package arguments.\n"+useHelp)
@ -145,20 +157,14 @@ func main() {
case "json", "plain", "xml": case "json", "plain", "xml":
// ok // ok
default: default:
fmt.Fprintf(os.Stderr, "Error: illegal -format value: %q\n"+useHelp, *formatFlag) fmt.Fprintf(os.Stderr, "Error: illegal -format value: %q.\n"+useHelp, *formatFlag)
os.Exit(2)
}
// -mode flag
if *modeFlag == "" {
fmt.Fprintf(os.Stderr, "Error: a query -mode is required.\n"+useHelp)
os.Exit(2) os.Exit(2)
} }
// Ask the oracle. // Ask the oracle.
res, err := oracle.Query(args, *modeFlag, *posFlag, ptalog, &build.Default, *reflectFlag) res, err := oracle.Query(args, mode, *posFlag, ptalog, &build.Default, *reflectFlag)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "%s\n"+useHelp, err) fmt.Fprintf(os.Stderr, "Error: %s.\n", err)
os.Exit(1) os.Exit(1)
} }
@ -167,7 +173,7 @@ func main() {
case "json": case "json":
b, err := json.MarshalIndent(res.Serial(), "", "\t") b, err := json.MarshalIndent(res.Serial(), "", "\t")
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "JSON error: %s\n", err) fmt.Fprintf(os.Stderr, "JSON error: %s.\n", err)
os.Exit(1) os.Exit(1)
} }
os.Stdout.Write(b) os.Stdout.Write(b)
@ -175,7 +181,7 @@ func main() {
case "xml": case "xml":
b, err := xml.MarshalIndent(res.Serial(), "", "\t") b, err := xml.MarshalIndent(res.Serial(), "", "\t")
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "XML error: %s\n", err) fmt.Fprintf(os.Stderr, "XML error: %s.\n", err)
os.Exit(1) os.Exit(1)
} }
os.Stdout.Write(b) os.Stdout.Write(b)

View File

@ -111,9 +111,7 @@ result."
(setq buffer-read-only nil) (setq buffer-read-only nil)
(erase-buffer) (erase-buffer)
(insert "Go Oracle\n") (insert "Go Oracle\n")
(let ((args (append (list go-oracle-command nil t nil (let ((args (append (list go-oracle-command nil t nil posflag mode)
posflag
(format "-mode=%s" mode))
(split-string go-oracle-scope " " t)))) (split-string go-oracle-scope " " t))))
;; Log the command to *Messages*, for debugging. ;; Log the command to *Messages*, for debugging.
(message "Command: %s:" args) (message "Command: %s:" args)

View File

@ -68,14 +68,14 @@ func! s:RunOracle(mode, selected) range abort
if a:selected != -1 if a:selected != -1
let pos1 = s:getpos(line("'<"), col("'<")) let pos1 = s:getpos(line("'<"), col("'<"))
let pos2 = s:getpos(line("'>"), col("'>")) let pos2 = s:getpos(line("'>"), col("'>"))
let cmd = printf('%s -mode=%s -pos=%s:#%d,#%d %s', let cmd = printf('%s -pos=%s:#%d,#%d %s %s',
\ s:go_oracle, a:mode, \ s:go_oracle,
\ shellescape(fname), pos1, pos2, shellescape(sname)) \ shellescape(fname), pos1, pos2, a:mode, shellescape(sname))
else else
let pos = s:getpos(line('.'), col('.')) let pos = s:getpos(line('.'), col('.'))
let cmd = printf('%s -mode=%s -pos=%s:#%d %s', let cmd = printf('%s -pos=%s:#%d %s %s',
\ s:go_oracle, a:mode, \ s:go_oracle,
\ shellescape(fname), pos, shellescape(sname)) \ shellescape(fname), pos, a:mode, shellescape(sname))
endif endif
call s:qflist(system(cmd)) call s:qflist(system(cmd))
endfun endfun

View File

@ -5,6 +5,7 @@
package oracle package oracle
import ( import (
"fmt"
"go/ast" "go/ast"
"go/token" "go/token"
"sort" "sort"
@ -29,7 +30,7 @@ func callees(o *Oracle, qpos *QueryPos) (queryResult, error) {
} }
} }
if call == nil { if call == nil {
return nil, o.errorf(qpos.path[0], "there is no function call here") return nil, fmt.Errorf("there is no function call here")
} }
// TODO(adonovan): issue an error if the call is "too far // TODO(adonovan): issue an error if the call is "too far
// away" from the current selection, as this most likely is // away" from the current selection, as this most likely is
@ -37,13 +38,13 @@ func callees(o *Oracle, qpos *QueryPos) (queryResult, error) {
// Reject type conversions. // Reject type conversions.
if qpos.info.IsType(call.Fun) { if qpos.info.IsType(call.Fun) {
return nil, o.errorf(call, "this is a type conversion, not a function call") return nil, fmt.Errorf("this is a type conversion, not a function call")
} }
// Reject calls to built-ins. // Reject calls to built-ins.
if id, ok := unparen(call.Fun).(*ast.Ident); ok { if id, ok := unparen(call.Fun).(*ast.Ident); ok {
if b, ok := qpos.info.ObjectOf(id).(*types.Builtin); ok { if b, ok := qpos.info.ObjectOf(id).(*types.Builtin); ok {
return nil, o.errorf(call, "this is a call to the built-in '%s' operator", b.Name()) return nil, fmt.Errorf("this is a call to the built-in '%s' operator", b.Name())
} }
} }
@ -74,7 +75,7 @@ func callees(o *Oracle, qpos *QueryPos) (queryResult, error) {
ptrAnalysis(o) ptrAnalysis(o)
if arbitrarySite == nil { if arbitrarySite == nil {
return nil, o.errorf(call.Lparen, "this call site is unreachable in this analysis") return nil, fmt.Errorf("this call site is unreachable in this analysis")
} }
// Compute union of callees across all contexts. // Compute union of callees across all contexts.

View File

@ -5,6 +5,7 @@
package oracle package oracle
import ( import (
"fmt"
"go/token" "go/token"
"code.google.com/p/go.tools/oracle/serial" "code.google.com/p/go.tools/oracle/serial"
@ -20,17 +21,17 @@ import (
func callers(o *Oracle, qpos *QueryPos) (queryResult, error) { func callers(o *Oracle, qpos *QueryPos) (queryResult, error) {
pkg := o.prog.Package(qpos.info.Pkg) pkg := o.prog.Package(qpos.info.Pkg)
if pkg == nil { if pkg == nil {
return nil, o.errorf(qpos.path[0], "no SSA package") return nil, fmt.Errorf("no SSA package")
} }
if !ssa.HasEnclosingFunction(pkg, qpos.path) { if !ssa.HasEnclosingFunction(pkg, qpos.path) {
return nil, o.errorf(qpos.path[0], "this position is not inside a function") return nil, fmt.Errorf("this position is not inside a function")
} }
buildSSA(o) buildSSA(o)
target := ssa.EnclosingFunction(pkg, qpos.path) target := ssa.EnclosingFunction(pkg, qpos.path)
if target == nil { if target == nil {
return nil, o.errorf(qpos.path[0], "no SSA function built for this location (dead code?)") return nil, fmt.Errorf("no SSA function built for this location (dead code?)")
} }
// Run the pointer analysis, recording each // Run the pointer analysis, recording each

View File

@ -5,6 +5,7 @@
package oracle package oracle
import ( import (
"fmt"
"go/token" "go/token"
"code.google.com/p/go.tools/oracle/serial" "code.google.com/p/go.tools/oracle/serial"
@ -25,19 +26,18 @@ import (
func callstack(o *Oracle, qpos *QueryPos) (queryResult, error) { func callstack(o *Oracle, qpos *QueryPos) (queryResult, error) {
pkg := o.prog.Package(qpos.info.Pkg) pkg := o.prog.Package(qpos.info.Pkg)
if pkg == nil { if pkg == nil {
return nil, o.errorf(qpos.path[0], "no SSA package") return nil, fmt.Errorf("no SSA package")
} }
if !ssa.HasEnclosingFunction(pkg, qpos.path) { if !ssa.HasEnclosingFunction(pkg, qpos.path) {
return nil, o.errorf(qpos.path[0], "this position is not inside a function") return nil, fmt.Errorf("this position is not inside a function")
} }
buildSSA(o) buildSSA(o)
target := ssa.EnclosingFunction(pkg, qpos.path) target := ssa.EnclosingFunction(pkg, qpos.path)
if target == nil { if target == nil {
return nil, o.errorf(qpos.path[0], return nil, fmt.Errorf("no SSA function built for this location (dead code?)")
"no SSA function built for this location (dead code?)")
} }
// Run the pointer analysis and build the complete call graph. // Run the pointer analysis and build the complete call graph.

View File

@ -351,7 +351,7 @@ func describeValue(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeValueRe
switch n := path[0].(type) { switch n := path[0].(type) {
case *ast.ValueSpec: case *ast.ValueSpec:
// ambiguous ValueSpec containing multiple names // ambiguous ValueSpec containing multiple names
return nil, o.errorf(n, "multiple value specification") return nil, fmt.Errorf("multiple value specification")
case *ast.Ident: case *ast.Ident:
obj = qpos.info.ObjectOf(n) obj = qpos.info.ObjectOf(n)
expr = n expr = n
@ -359,7 +359,7 @@ func describeValue(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeValueRe
expr = n expr = n
default: default:
// Is this reachable? // Is this reachable?
return nil, o.errorf(n, "unexpected AST for expr: %T", n) return nil, fmt.Errorf("unexpected AST for expr: %T", n)
} }
typ := qpos.info.TypeOf(expr) typ := qpos.info.TypeOf(expr)
@ -649,7 +649,7 @@ func describeType(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeTypeResu
default: default:
// Unreachable? // Unreachable?
return nil, o.errorf(n, "unexpected AST for type: %T", n) return nil, fmt.Errorf("unexpected AST for type: %T", n)
} }
return &describeTypeResult{ return &describeTypeResult{
@ -737,7 +737,7 @@ func describePackage(o *Oracle, qpos *QueryPos, path []ast.Node) (*describePacka
default: default:
// Unreachable? // Unreachable?
return nil, o.errorf(n, "unexpected AST for package: %T", n) return nil, fmt.Errorf("unexpected AST for package: %T", n)
} }
var members []*describeMember var members []*describeMember

View File

@ -52,7 +52,7 @@ func freevars(o *Oracle, qpos *QueryPos) (queryResult, error) {
obj := qpos.info.ObjectOf(n) obj := qpos.info.ObjectOf(n)
if obj == nil { if obj == nil {
return nil // TODO(adonovan): fix: this fails for *types.Label. return nil // TODO(adonovan): fix: this fails for *types.Label.
panic(o.errorf(n, "no types.Object for ast.Ident")) panic("no types.Object for ast.Ident")
} }
if _, ok := obj.(*types.PkgName); ok { if _, ok := obj.(*types.PkgName); ok {
return nil // imported package return nil // imported package

View File

@ -18,7 +18,6 @@ package oracle
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"go/ast" "go/ast"
"go/build" "go/build"
@ -257,7 +256,7 @@ func newOracle(imp *importer.Importer, args []string, ptalog io.Writer, needs in
// Create SSA packages. // Create SSA packages.
if err := o.prog.CreatePackages(imp); err != nil { if err := o.prog.CreatePackages(imp); err != nil {
return nil, o.errorf(nil, "%s", err) return nil, err
} }
// Initial packages (specified on command line) // Initial packages (specified on command line)
@ -270,7 +269,7 @@ func newOracle(imp *importer.Importer, args []string, ptalog io.Writer, needs in
// should build a single synthetic testmain package, // should build a single synthetic testmain package,
// not synthetic main functions to many packages. // not synthetic main functions to many packages.
if initialPkg.CreateTestMainFunction() == nil { if initialPkg.CreateTestMainFunction() == nil {
return nil, o.errorf(nil, "analysis scope has no main() entry points") return nil, fmt.Errorf("analysis scope has no main() entry points")
} }
} }
o.config.Mains = append(o.config.Mains, initialPkg) o.config.Mains = append(o.config.Mains, initialPkg)
@ -324,7 +323,7 @@ func ParseQueryPos(imp *importer.Importer, pos string, needExact bool) (*QueryPo
} }
info, path, exact := imp.PathEnclosingInterval(start, end) info, path, exact := imp.PathEnclosingInterval(start, end)
if path == nil { if path == nil {
return nil, errors.New("no syntax here") return nil, fmt.Errorf("no syntax here")
} }
if needExact && !exact { if needExact && !exact {
return nil, fmt.Errorf("ambiguous selection within %s", importer.NodeDescription(path[0])) return nil, fmt.Errorf("ambiguous selection within %s", importer.NodeDescription(path[0]))
@ -537,13 +536,6 @@ func (o *Oracle) fprintf(w io.Writer, pos interface{}, format string, args ...in
io.WriteString(w, "\n") io.WriteString(w, "\n")
} }
// errorf is like fprintf, but returns a formatted error string.
func (o *Oracle) errorf(pos interface{}, format string, args ...interface{}) error {
var buf bytes.Buffer
o.fprintf(&buf, pos, format, args...)
return errors.New(buf.String())
}
// printNode returns the pretty-printed syntax of n. // printNode returns the pretty-printed syntax of n.
func (o *Oracle) printNode(n ast.Node) string { func (o *Oracle) printNode(n ast.Node) string {
var buf bytes.Buffer var buf bytes.Buffer

View File

@ -5,6 +5,7 @@
package oracle package oracle
import ( import (
"fmt"
"go/ast" "go/ast"
"go/token" "go/token"
"sort" "sort"
@ -25,7 +26,7 @@ import (
func peers(o *Oracle, qpos *QueryPos) (queryResult, error) { func peers(o *Oracle, qpos *QueryPos) (queryResult, error) {
arrowPos := findArrow(qpos) arrowPos := findArrow(qpos)
if arrowPos == token.NoPos { if arrowPos == token.NoPos {
return nil, o.errorf(qpos.path[0], "there is no send/receive here") return nil, fmt.Errorf("there is no send/receive here")
} }
buildSSA(o) buildSSA(o)
@ -49,7 +50,7 @@ func peers(o *Oracle, qpos *QueryPos) (queryResult, error) {
} }
} }
if queryOp.ch == nil { if queryOp.ch == nil {
return nil, o.errorf(arrowPos, "ssa.Instruction for send/receive not found") return nil, fmt.Errorf("ssa.Instruction for send/receive not found")
} }
// Discard operations of wrong channel element type. // Discard operations of wrong channel element type.

View File

@ -5,6 +5,7 @@
package oracle package oracle
import ( import (
"fmt"
"go/ast" "go/ast"
"go/token" "go/token"
"sort" "sort"
@ -19,13 +20,13 @@ import (
func referrers(o *Oracle, qpos *QueryPos) (queryResult, error) { func referrers(o *Oracle, qpos *QueryPos) (queryResult, error) {
id, _ := qpos.path[0].(*ast.Ident) id, _ := qpos.path[0].(*ast.Ident)
if id == nil { if id == nil {
return nil, o.errorf(qpos, "no identifier here") return nil, fmt.Errorf("no identifier here")
} }
obj := qpos.info.ObjectOf(id) obj := qpos.info.ObjectOf(id)
if obj == nil { if obj == nil {
// Happens for y in "switch y := x.(type)", but I think that's all. // Happens for y in "switch y := x.(type)", but I think that's all.
return nil, o.errorf(qpos, "no object for identifier") return nil, fmt.Errorf("no object for identifier")
} }
// Iterate over all go/types' resolver facts for the entire program. // Iterate over all go/types' resolver facts for the entire program.

View File

@ -68,22 +68,18 @@ value may point to these labels:
-------- @callees callees-err-no-call -------- -------- @callees callees-err-no-call --------
Error: there is no function call here Error: there is no function call here
-------- @callees callees-err-builtin -------- -------- @callees callees-err-builtin --------
Error: this is a call to the built-in 'print' operator Error: this is a call to the built-in 'print' operator
-------- @callees callees-err-conversion -------- -------- @callees callees-err-conversion --------
Error: this is a type conversion, not a function call Error: this is a type conversion, not a function call
-------- @callees callees-err-bad-selection -------- -------- @callees callees-err-bad-selection --------
Error: ambiguous selection within function call (or conversion) Error: ambiguous selection within function call (or conversion)
-------- @callees callees-err-deadcode1 -------- -------- @callees callees-err-deadcode1 --------
Error: this call site is unreachable in this analysis Error: this call site is unreachable in this analysis
-------- @callees callees-err-nil-func -------- -------- @callees callees-err-nil-func --------
dynamic function call on nil value dynamic function call on nil value
@ -93,7 +89,6 @@ dynamic method call on nil value
-------- @callees callees-err-deadcode2 -------- -------- @callees callees-err-deadcode2 --------
Error: this call site is unreachable in this analysis Error: this call site is unreachable in this analysis
-------- @callstack callstack-err-deadcode -------- -------- @callstack callstack-err-deadcode --------
main.deadcode is unreachable in this analysis scope main.deadcode is unreachable in this analysis scope