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