226 lines
6.3 KiB
Go
226 lines
6.3 KiB
Go
// 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.
|
|
|
|
// guru: a tool for answering questions about Go source code.
|
|
//
|
|
// http://golang.org/s/using-guru
|
|
//
|
|
// Run with -help flag or help subcommand for usage information.
|
|
//
|
|
package main // import "golang.org/x/tools/cmd/guru"
|
|
|
|
import (
|
|
"bufio"
|
|
"flag"
|
|
"fmt"
|
|
"go/build"
|
|
"go/token"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"runtime/pprof"
|
|
"strings"
|
|
"sync"
|
|
|
|
"golang.org/x/tools/go/buildutil"
|
|
)
|
|
|
|
// flags
|
|
var (
|
|
modifiedFlag = flag.Bool("modified", false, "read archive of modified files from standard input")
|
|
scopeFlag = flag.String("scope", "", "comma-separated list of `packages` the analysis should be limited to")
|
|
ptalogFlag = flag.String("ptalog", "", "write points-to analysis log to `file`")
|
|
jsonFlag = flag.Bool("json", false, "emit output in JSON format")
|
|
reflectFlag = flag.Bool("reflect", false, "analyze reflection soundly (slow)")
|
|
cpuprofileFlag = flag.String("cpuprofile", "", "write CPU profile to `file`")
|
|
)
|
|
|
|
func init() {
|
|
flag.Var((*buildutil.TagsFlag)(&build.Default.BuildTags), "tags", buildutil.TagsFlagDoc)
|
|
|
|
// gccgo does not provide a GOROOT with standard library sources.
|
|
// If we have one in the environment, force gc mode.
|
|
if build.Default.Compiler == "gccgo" {
|
|
if _, err := os.Stat(filepath.Join(runtime.GOROOT(), "src", "runtime", "runtime.go")); err == nil {
|
|
build.Default.Compiler = "gc"
|
|
}
|
|
}
|
|
}
|
|
|
|
const useHelp = "Run 'guru -help' for more information.\n"
|
|
|
|
const helpMessage = `Go source code guru.
|
|
Usage: guru [flags] <mode> <position>
|
|
|
|
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 the selected pointer may point to
|
|
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 position argument specifies the filename and byte offset (or range)
|
|
of the syntax element to query. For example:
|
|
|
|
foo.go:#123,#128
|
|
bar.go:#123
|
|
|
|
The -json flag causes guru to emit output in JSON format;
|
|
golang.org/x/tools/cmd/guru/serial defines its schema.
|
|
Otherwise, the output is in an editor-friendly format in which
|
|
every line has the form "pos: text", where pos is "-" if unknown.
|
|
|
|
The -modified flag causes guru to read an archive from standard input.
|
|
Files in this archive will be used in preference to those in
|
|
the file system. In this way, a text editor may supply guru
|
|
with the contents of its unsaved buffers. Each archive entry
|
|
consists of the file name, a newline, the decimal file size,
|
|
another newline, and the contents of the file.
|
|
|
|
The -scope flag restricts analysis to the specified packages.
|
|
Its value is a comma-separated list of patterns of these forms:
|
|
golang.org/x/tools/cmd/guru # a single package
|
|
golang.org/x/tools/... # all packages beneath dir
|
|
... # the entire workspace.
|
|
A pattern preceded by '-' is negative, so the scope
|
|
encoding/...,-encoding/xml
|
|
matches all encoding packages except encoding/xml.
|
|
|
|
User manual: http://golang.org/s/using-guru
|
|
|
|
Example: describe syntax at offset 530 in this file (an import spec):
|
|
|
|
$ guru describe src/golang.org/x/tools/cmd/guru/main.go:#530
|
|
`
|
|
|
|
func printHelp() {
|
|
fmt.Fprintln(os.Stderr, helpMessage)
|
|
fmt.Fprintln(os.Stderr, "Flags:")
|
|
flag.PrintDefaults()
|
|
}
|
|
|
|
func main() {
|
|
log.SetPrefix("guru: ")
|
|
log.SetFlags(0)
|
|
|
|
// 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) != 2 {
|
|
flag.Usage()
|
|
os.Exit(2)
|
|
}
|
|
mode, posn := args[0], 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 *cpuprofileFlag != "" {
|
|
f, err := os.Create(*cpuprofileFlag)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
pprof.StartCPUProfile(f)
|
|
defer pprof.StopCPUProfile()
|
|
}
|
|
|
|
ctxt := &build.Default
|
|
|
|
// If there were modified files,
|
|
// read them from the standard input and
|
|
// overlay them on the build context.
|
|
if *modifiedFlag {
|
|
modified, err := buildutil.ParseOverlayArchive(os.Stdin)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
// All I/O done by guru needs to consult the modified map.
|
|
// The ReadFile done by referrers does,
|
|
// but the loader's cgo preprocessing currently does not.
|
|
|
|
if len(modified) > 0 {
|
|
ctxt = buildutil.OverlayContext(ctxt, modified)
|
|
}
|
|
}
|
|
|
|
var outputMu sync.Mutex
|
|
output := func(fset *token.FileSet, qr QueryResult) {
|
|
outputMu.Lock()
|
|
defer outputMu.Unlock()
|
|
if *jsonFlag {
|
|
// JSON output
|
|
fmt.Printf("%s\n", qr.JSON(fset))
|
|
} else {
|
|
// plain output
|
|
printf := func(pos interface{}, format string, args ...interface{}) {
|
|
fprintf(os.Stdout, fset, pos, format, args...)
|
|
}
|
|
qr.PrintPlain(printf)
|
|
}
|
|
}
|
|
|
|
// Avoid corner case of split("").
|
|
var scope []string
|
|
if *scopeFlag != "" {
|
|
scope = strings.Split(*scopeFlag, ",")
|
|
}
|
|
|
|
// Ask the guru.
|
|
query := Query{
|
|
Pos: posn,
|
|
Build: ctxt,
|
|
Scope: scope,
|
|
PTALog: ptalog,
|
|
Reflection: *reflectFlag,
|
|
Output: output,
|
|
}
|
|
|
|
if err := Run(mode, &query); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|