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