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