| 1 | // Copyright 2013 The Go Authors. All rights reserved. |
|---|---|
| 2 | // Use of this source code is governed by a BSD-style |
| 3 | // license that can be found in the LICENSE file. |
| 4 | |
| 5 | // guru: a tool for answering questions about Go source code. |
| 6 | // |
| 7 | // http://golang.org/s/using-guru |
| 8 | // |
| 9 | // Run with -help flag or help subcommand for usage information. |
| 10 | package main // import "golang.org/x/tools/cmd/guru" |
| 11 | |
| 12 | import ( |
| 13 | "bufio" |
| 14 | "flag" |
| 15 | "fmt" |
| 16 | "go/build" |
| 17 | "go/token" |
| 18 | "io" |
| 19 | "log" |
| 20 | "os" |
| 21 | "path/filepath" |
| 22 | "runtime" |
| 23 | "runtime/pprof" |
| 24 | "strings" |
| 25 | "sync" |
| 26 | |
| 27 | "golang.org/x/tools/go/buildutil" |
| 28 | ) |
| 29 | |
| 30 | // flags |
| 31 | var ( |
| 32 | modifiedFlag = flag.Bool("modified", false, "read archive of modified files from standard input") |
| 33 | scopeFlag = flag.String("scope", "", "comma-separated list of `packages` the analysis should be limited to") |
| 34 | ptalogFlag = flag.String("ptalog", "", "write points-to analysis log to `file`") |
| 35 | jsonFlag = flag.Bool("json", false, "emit output in JSON format") |
| 36 | reflectFlag = flag.Bool("reflect", false, "analyze reflection soundly (slow)") |
| 37 | cpuprofileFlag = flag.String("cpuprofile", "", "write CPU profile to `file`") |
| 38 | ) |
| 39 | |
| 40 | func init() { |
| 41 | flag.Var((*buildutil.TagsFlag)(&build.Default.BuildTags), "tags", buildutil.TagsFlagDoc) |
| 42 | |
| 43 | // gccgo does not provide a GOROOT with standard library sources. |
| 44 | // If we have one in the environment, force gc mode. |
| 45 | if build.Default.Compiler == "gccgo" { |
| 46 | if _, err := os.Stat(filepath.Join(runtime.GOROOT(), "src", "runtime", "runtime.go")); err == nil { |
| 47 | build.Default.Compiler = "gc" |
| 48 | } |
| 49 | } |
| 50 | } |
| 51 | |
| 52 | const useHelp = "Run 'guru -help' for more information.\n" |
| 53 | |
| 54 | const helpMessage = `Go source code guru. |
| 55 | Usage: guru [flags] <mode> <position> |
| 56 | |
| 57 | The mode argument determines the query to perform: |
| 58 | |
| 59 | callees show possible targets of selected function call |
| 60 | callers show possible callers of selected function |
| 61 | callstack show path from callgraph root to selected function |
| 62 | definition show declaration of selected identifier |
| 63 | describe describe selected syntax: definition, methods, etc |
| 64 | freevars show free variables of selection |
| 65 | implements show 'implements' relation for selected type or method |
| 66 | peers show send/receive corresponding to selected channel op |
| 67 | pointsto show variables the selected pointer may point to |
| 68 | referrers show all refs to entity denoted by selected identifier |
| 69 | what show basic information about the selected syntax node |
| 70 | whicherrs show possible values of the selected error variable |
| 71 | |
| 72 | The position argument specifies the filename and byte offset (or range) |
| 73 | of the syntax element to query. For example: |
| 74 | |
| 75 | foo.go:#123,#128 |
| 76 | bar.go:#123 |
| 77 | |
| 78 | The -json flag causes guru to emit output in JSON format; |
| 79 | golang.org/x/tools/cmd/guru/serial defines its schema. |
| 80 | Otherwise, the output is in an editor-friendly format in which |
| 81 | every line has the form "pos: text", where pos is "-" if unknown. |
| 82 | |
| 83 | The -modified flag causes guru to read an archive from standard input. |
| 84 | Files in this archive will be used in preference to those in |
| 85 | the file system. In this way, a text editor may supply guru |
| 86 | with the contents of its unsaved buffers. Each archive entry |
| 87 | consists of the file name, a newline, the decimal file size, |
| 88 | another newline, and the contents of the file. |
| 89 | |
| 90 | The -scope flag restricts analysis to the specified packages. |
| 91 | Its value is a comma-separated list of patterns of these forms: |
| 92 | golang.org/x/tools/cmd/guru # a single package |
| 93 | golang.org/x/tools/... # all packages beneath dir |
| 94 | ... # the entire workspace. |
| 95 | A pattern preceded by '-' is negative, so the scope |
| 96 | encoding/...,-encoding/xml |
| 97 | matches all encoding packages except encoding/xml. |
| 98 | |
| 99 | User manual: http://golang.org/s/using-guru |
| 100 | |
| 101 | Example: describe syntax at offset 530 in this file (an import spec): |
| 102 | |
| 103 | $ guru describe src/golang.org/x/tools/cmd/guru/main.go:#530 |
| 104 | ` |
| 105 | |
| 106 | func printHelp() { |
| 107 | fmt.Fprint(os.Stderr, helpMessage) |
| 108 | fmt.Fprintln(os.Stderr, "\nFlags:") |
| 109 | flag.PrintDefaults() |
| 110 | } |
| 111 | |
| 112 | func main() { |
| 113 | log.SetPrefix("guru: ") |
| 114 | log.SetFlags(0) |
| 115 | |
| 116 | // Don't print full help unless -help was requested. |
| 117 | // Just gently remind users that it's there. |
| 118 | flag.Usage = func() { fmt.Fprint(os.Stderr, useHelp) } |
| 119 | flag.CommandLine.Init(os.Args[0], flag.ContinueOnError) // hack |
| 120 | if err := flag.CommandLine.Parse(os.Args[1:]); err != nil { |
| 121 | // (err has already been printed) |
| 122 | if err == flag.ErrHelp { |
| 123 | printHelp() |
| 124 | } |
| 125 | os.Exit(2) |
| 126 | } |
| 127 | |
| 128 | args := flag.Args() |
| 129 | if len(args) != 2 { |
| 130 | flag.Usage() |
| 131 | os.Exit(2) |
| 132 | } |
| 133 | mode, posn := args[0], args[1] |
| 134 | |
| 135 | if mode == "help" { |
| 136 | printHelp() |
| 137 | os.Exit(2) |
| 138 | } |
| 139 | |
| 140 | // Set up points-to analysis log file. |
| 141 | var ptalog io.Writer |
| 142 | if *ptalogFlag != "" { |
| 143 | if f, err := os.Create(*ptalogFlag); err != nil { |
| 144 | log.Fatalf("Failed to create PTA log file: %s", err) |
| 145 | } else { |
| 146 | buf := bufio.NewWriter(f) |
| 147 | ptalog = buf |
| 148 | defer func() { |
| 149 | if err := buf.Flush(); err != nil { |
| 150 | log.Printf("flush: %s", err) |
| 151 | } |
| 152 | if err := f.Close(); err != nil { |
| 153 | log.Printf("close: %s", err) |
| 154 | } |
| 155 | }() |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | // Profiling support. |
| 160 | if *cpuprofileFlag != "" { |
| 161 | f, err := os.Create(*cpuprofileFlag) |
| 162 | if err != nil { |
| 163 | log.Fatal(err) |
| 164 | } |
| 165 | pprof.StartCPUProfile(f) |
| 166 | defer pprof.StopCPUProfile() |
| 167 | } |
| 168 | |
| 169 | ctxt := &build.Default |
| 170 | |
| 171 | // If there were modified files, |
| 172 | // read them from the standard input and |
| 173 | // overlay them on the build context. |
| 174 | if *modifiedFlag { |
| 175 | modified, err := buildutil.ParseOverlayArchive(os.Stdin) |
| 176 | if err != nil { |
| 177 | log.Fatal(err) |
| 178 | } |
| 179 | |
| 180 | // All I/O done by guru needs to consult the modified map. |
| 181 | // The ReadFile done by referrers does, |
| 182 | // but the loader's cgo preprocessing currently does not. |
| 183 | |
| 184 | if len(modified) > 0 { |
| 185 | ctxt = buildutil.OverlayContext(ctxt, modified) |
| 186 | } |
| 187 | } |
| 188 | |
| 189 | var outputMu sync.Mutex |
| 190 | output := func(fset *token.FileSet, qr QueryResult) { |
| 191 | outputMu.Lock() |
| 192 | defer outputMu.Unlock() |
| 193 | if *jsonFlag { |
| 194 | // JSON output |
| 195 | fmt.Printf("%s\n", qr.JSON(fset)) |
| 196 | } else { |
| 197 | // plain output |
| 198 | printf := func(pos interface{}, format string, args ...interface{}) { |
| 199 | fprintf(os.Stdout, fset, pos, format, args...) |
| 200 | } |
| 201 | qr.PrintPlain(printf) |
| 202 | } |
| 203 | } |
| 204 | |
| 205 | // Avoid corner case of split(""). |
| 206 | var scope []string |
| 207 | if *scopeFlag != "" { |
| 208 | scope = strings.Split(*scopeFlag, ",") |
| 209 | } |
| 210 | |
| 211 | // Ask the guru. |
| 212 | query := Query{ |
| 213 | Pos: posn, |
| 214 | Build: ctxt, |
| 215 | Scope: scope, |
| 216 | PTALog: ptalog, |
| 217 | Reflection: *reflectFlag, |
| 218 | Output: output, |
| 219 | } |
| 220 | |
| 221 | if err := Run(mode, &query); err != nil { |
| 222 | log.Fatal(err) |
| 223 | } |
| 224 | } |
| 225 |
Members