1 | package main |
---|---|
2 | |
3 | import ( |
4 | "errors" |
5 | "fmt" |
6 | "go/build" |
7 | "go/types" |
8 | "io" |
9 | "log" |
10 | "net/http" |
11 | "os" |
12 | "path/filepath" |
13 | "strings" |
14 | |
15 | "golang.org/x/tools/go/callgraph" |
16 | "golang.org/x/tools/go/callgraph/cha" |
17 | "golang.org/x/tools/go/callgraph/rta" |
18 | "golang.org/x/tools/go/callgraph/static" |
19 | |
20 | "golang.org/x/tools/go/packages" |
21 | "golang.org/x/tools/go/pointer" |
22 | "golang.org/x/tools/go/ssa" |
23 | "golang.org/x/tools/go/ssa/ssautil" |
24 | ) |
25 | |
26 | type CallGraphType string |
27 | |
28 | const ( |
29 | CallGraphTypeStatic = "static" |
30 | CallGraphTypeCha = "cha" |
31 | CallGraphTypeRta = "rta" |
32 | CallGraphTypePointer CallGraphType = "pointer" |
33 | ) |
34 | |
35 | //==[ type def/func: analysis ]=============================================== |
36 | type renderOpts struct { |
37 | cacheDir string |
38 | focus string |
39 | group []string |
40 | ignore []string |
41 | include []string |
42 | limit []string |
43 | nointer bool |
44 | refresh bool |
45 | nostd bool |
46 | algo CallGraphType |
47 | } |
48 | |
49 | // mainPackages returns the main packages to analyze. |
50 | // Each resulting package is named "main" and has a main function. |
51 | func mainPackages(pkgs []*ssa.Package) ([]*ssa.Package, error) { |
52 | var mains []*ssa.Package |
53 | for _, p := range pkgs { |
54 | if p != nil && p.Pkg.Name() == "main" && p.Func("main") != nil { |
55 | mains = append(mains, p) |
56 | } |
57 | } |
58 | if len(mains) == 0 { |
59 | return nil, fmt.Errorf("no main packages") |
60 | } |
61 | return mains, nil |
62 | } |
63 | |
64 | //==[ type def/func: analysis ]=============================================== |
65 | type analysis struct { |
66 | opts *renderOpts |
67 | prog *ssa.Program |
68 | pkgs []*ssa.Package |
69 | mainPkg *ssa.Package |
70 | callgraph *callgraph.Graph |
71 | } |
72 | |
73 | var Analysis *analysis |
74 | |
75 | func (a *analysis) DoAnalysis( |
76 | algo CallGraphType, |
77 | dir string, |
78 | tests bool, |
79 | args []string, |
80 | ) error { |
81 | cfg := &packages.Config{ |
82 | Mode: packages.LoadAllSyntax, |
83 | Tests: tests, |
84 | Dir: dir, |
85 | BuildFlags: build.Default.BuildTags, |
86 | } |
87 | |
88 | initial, err := packages.Load(cfg, args...) |
89 | if err != nil { |
90 | return err |
91 | } |
92 | |
93 | if packages.PrintErrors(initial) > 0 { |
94 | return fmt.Errorf("packages contain errors") |
95 | } |
96 | |
97 | // Create and build SSA-form program representation. |
98 | prog, pkgs := ssautil.AllPackages(initial, 0) |
99 | prog.Build() |
100 | |
101 | var graph *callgraph.Graph |
102 | var mainPkg *ssa.Package |
103 | |
104 | switch algo { |
105 | case CallGraphTypeStatic: |
106 | graph = static.CallGraph(prog) |
107 | case CallGraphTypeCha: |
108 | graph = cha.CallGraph(prog) |
109 | case CallGraphTypeRta: |
110 | mains, err := mainPackages(prog.AllPackages()) |
111 | if err != nil { |
112 | return err |
113 | } |
114 | var roots []*ssa.Function |
115 | mainPkg = mains[0] |
116 | for _, main := range mains { |
117 | roots = append(roots, main.Func("main")) |
118 | } |
119 | graph = rta.Analyze(roots, true).CallGraph |
120 | case CallGraphTypePointer: |
121 | mains, err := mainPackages(prog.AllPackages()) |
122 | if err != nil { |
123 | return err |
124 | } |
125 | mainPkg = mains[0] |
126 | config := &pointer.Config{ |
127 | Mains: mains, |
128 | BuildCallGraph: true, |
129 | } |
130 | ptares, err := pointer.Analyze(config) |
131 | if err != nil { |
132 | return err |
133 | } |
134 | graph = ptares.CallGraph |
135 | default: |
136 | return fmt.Errorf("invalid call graph type: %s", a.opts.algo) |
137 | } |
138 | |
139 | //cg.DeleteSyntheticNodes() |
140 | |
141 | a.prog = prog |
142 | a.pkgs = pkgs |
143 | a.mainPkg = mainPkg |
144 | a.callgraph = graph |
145 | return nil |
146 | } |
147 | |
148 | func (a *analysis) OptsSetup() { |
149 | a.opts = &renderOpts{ |
150 | cacheDir: *cacheDir, |
151 | focus: *focusFlag, |
152 | group: []string{*groupFlag}, |
153 | ignore: []string{*ignoreFlag}, |
154 | include: []string{*includeFlag}, |
155 | limit: []string{*limitFlag}, |
156 | nointer: *nointerFlag, |
157 | nostd: *nostdFlag, |
158 | } |
159 | } |
160 | |
161 | func (a *analysis) ProcessListArgs() (e error) { |
162 | var groupBy []string |
163 | var ignorePaths []string |
164 | var includePaths []string |
165 | var limitPaths []string |
166 | |
167 | for _, g := range strings.Split(a.opts.group[0], ",") { |
168 | g := strings.TrimSpace(g) |
169 | if g == "" { |
170 | continue |
171 | } |
172 | if g != "pkg" && g != "type" { |
173 | e = errors.New("invalid group option") |
174 | return |
175 | } |
176 | groupBy = append(groupBy, g) |
177 | } |
178 | |
179 | for _, p := range strings.Split(a.opts.ignore[0], ",") { |
180 | p = strings.TrimSpace(p) |
181 | if p != "" { |
182 | ignorePaths = append(ignorePaths, p) |
183 | } |
184 | } |
185 | |
186 | for _, p := range strings.Split(a.opts.include[0], ",") { |
187 | p = strings.TrimSpace(p) |
188 | if p != "" { |
189 | includePaths = append(includePaths, p) |
190 | } |
191 | } |
192 | |
193 | for _, p := range strings.Split(a.opts.limit[0], ",") { |
194 | p = strings.TrimSpace(p) |
195 | if p != "" { |
196 | limitPaths = append(limitPaths, p) |
197 | } |
198 | } |
199 | |
200 | a.opts.group = groupBy |
201 | a.opts.ignore = ignorePaths |
202 | a.opts.include = includePaths |
203 | a.opts.limit = limitPaths |
204 | |
205 | return |
206 | } |
207 | |
208 | func (a *analysis) OverrideByHTTP(r *http.Request) { |
209 | if f := r.FormValue("f"); f == "all" { |
210 | a.opts.focus = "" |
211 | } else if f != "" { |
212 | a.opts.focus = f |
213 | } |
214 | if std := r.FormValue("std"); std != "" { |
215 | a.opts.nostd = false |
216 | } |
217 | if inter := r.FormValue("nointer"); inter != "" { |
218 | a.opts.nointer = true |
219 | } |
220 | if refresh := r.FormValue("refresh"); refresh != "" { |
221 | a.opts.refresh = true |
222 | } |
223 | if g := r.FormValue("group"); g != "" { |
224 | a.opts.group[0] = g |
225 | } |
226 | if l := r.FormValue("limit"); l != "" { |
227 | a.opts.limit[0] = l |
228 | } |
229 | if ign := r.FormValue("ignore"); ign != "" { |
230 | a.opts.ignore[0] = ign |
231 | } |
232 | if inc := r.FormValue("include"); inc != "" { |
233 | a.opts.include[0] = inc |
234 | } |
235 | return |
236 | } |
237 | |
238 | // basically do printOutput() with previously checking |
239 | // focus option and respective package |
240 | func (a *analysis) Render() ([]byte, error) { |
241 | var ( |
242 | err error |
243 | ssaPkg *ssa.Package |
244 | focusPkg *types.Package |
245 | ) |
246 | |
247 | if a.opts.focus != "" { |
248 | if ssaPkg = a.prog.ImportedPackage(a.opts.focus); ssaPkg == nil { |
249 | if strings.Contains(a.opts.focus, "/") { |
250 | return nil, fmt.Errorf("focus failed: %v", err) |
251 | } |
252 | // try to find package by name |
253 | var foundPaths []string |
254 | for _, p := range a.pkgs { |
255 | if p.Pkg.Name() == a.opts.focus { |
256 | foundPaths = append(foundPaths, p.Pkg.Path()) |
257 | } |
258 | } |
259 | if len(foundPaths) == 0 { |
260 | return nil, fmt.Errorf("focus failed, could not find package: %v", a.opts.focus) |
261 | } else if len(foundPaths) > 1 { |
262 | for _, p := range foundPaths { |
263 | fmt.Fprintf(os.Stderr, " - %s\n", p) |
264 | } |
265 | return nil, fmt.Errorf("focus failed, found multiple packages with name: %v", a.opts.focus) |
266 | } |
267 | // found single package |
268 | if ssaPkg = a.prog.ImportedPackage(foundPaths[0]); ssaPkg == nil { |
269 | return nil, fmt.Errorf("focus failed: %v", err) |
270 | } |
271 | } |
272 | focusPkg = ssaPkg.Pkg |
273 | logf("focusing: %v", focusPkg.Path()) |
274 | } |
275 | |
276 | dot, err := printOutput( |
277 | a.prog, |
278 | a.mainPkg, |
279 | a.callgraph, |
280 | focusPkg, |
281 | a.opts.limit, |
282 | a.opts.ignore, |
283 | a.opts.include, |
284 | a.opts.group, |
285 | a.opts.nostd, |
286 | a.opts.nointer, |
287 | ) |
288 | if err != nil { |
289 | return nil, fmt.Errorf("processing failed: %v", err) |
290 | } |
291 | |
292 | return dot, nil |
293 | } |
294 | |
295 | func (a *analysis) FindCachedImg() string { |
296 | if a.opts.cacheDir == "" || a.opts.refresh { |
297 | return "" |
298 | } |
299 | |
300 | focus := a.opts.focus |
301 | if focus == "" { |
302 | focus = "all" |
303 | } |
304 | focusFilePath := focus + "." + *outputFormat |
305 | absFilePath := filepath.Join(a.opts.cacheDir, focusFilePath) |
306 | |
307 | if exists, err := pathExists(absFilePath); err != nil || !exists { |
308 | log.Println("not cached img:", absFilePath) |
309 | return "" |
310 | } |
311 | |
312 | log.Println("hit cached img") |
313 | return absFilePath |
314 | } |
315 | |
316 | func (a *analysis) CacheImg(img string) error { |
317 | if a.opts.cacheDir == "" || img == "" { |
318 | return nil |
319 | } |
320 | |
321 | focus := a.opts.focus |
322 | if focus == "" { |
323 | focus = "all" |
324 | } |
325 | absCacheDirPrefix := filepath.Join(a.opts.cacheDir, focus) |
326 | absCacheDirPath := strings.TrimRightFunc(absCacheDirPrefix, func(r rune) bool { |
327 | return r != '\\' && r != '/' |
328 | }) |
329 | err := os.MkdirAll(absCacheDirPath, os.ModePerm) |
330 | if err != nil { |
331 | return err |
332 | } |
333 | |
334 | absFilePath := absCacheDirPrefix + "." + *outputFormat |
335 | _, err = copyFile(img, absFilePath) |
336 | if err != nil { |
337 | return err |
338 | } |
339 | |
340 | return nil |
341 | } |
342 | |
343 | func pathExists(path string) (bool, error) { |
344 | _, err := os.Stat(path) |
345 | if err == nil { |
346 | return true, nil |
347 | } |
348 | if os.IsNotExist(err) { |
349 | return false, nil |
350 | } |
351 | return false, err |
352 | } |
353 | |
354 | func copyFile(src, dst string) (int64, error) { |
355 | sourceFileStat, err := os.Stat(src) |
356 | |
357 | if err != nil { |
358 | return 0, err |
359 | } |
360 | |
361 | if !sourceFileStat.Mode().IsRegular() { |
362 | return 0, fmt.Errorf("%s is not a regular file", src) |
363 | } |
364 | |
365 | source, err := os.Open(src) |
366 | if err != nil { |
367 | return 0, err |
368 | } |
369 | defer source.Close() |
370 | |
371 | destination, err := os.Create(dst) |
372 | if err != nil { |
373 | return 0, err |
374 | } |
375 | defer destination.Close() |
376 | nBytes, err := io.Copy(destination, source) |
377 | return nBytes, err |
378 | } |
379 |
Members