Left: | ||
Right: |
OLD | NEW |
---|---|
(Empty) | |
1 /* | |
2 The sockdrawer command is an analysis and visualization tool to help | |
3 you reorganize a complex Go package into several simpler ones. | |
4 | |
5 OVERVIEW | |
rsc
2014/12/12 19:53:23
Godoc will recognize plain 'Overview' as a headlin
adonovan
2014/12/12 20:13:00
Oh yeah, I discovered that recently. Fixed.
| |
6 | |
7 sockdrawer operates on three kinds of graphs at different levels of | |
8 abstraction. The lowest level is the NODE GRAPH. A node is a | |
9 package-level declaration of a named entity (func, var, const or type). | |
10 | |
11 An entire constant declaration is treated as a single node, even if it | |
12 contains multiple "specs" each defining multiple names, since constants | |
13 so grouped are typically closely related; an important special case is | |
14 an enumerated set data type. Also, we treat each "spec" or a var or | |
15 type declaration as a single node. | |
16 | |
17 func f() // a func node | |
18 const ( a, b = 0, 1; c = 0 ) // a single const node | |
19 var ( | |
20 a, b = 0, 1 // a single var node | |
21 c = 0 // another var node | |
22 ) | |
23 type ( x int; y int ) // a single type node | |
24 | |
25 Each reference to a package-level entity E forms an edge in the node | |
26 graph, from the node in which it appears to the node E. For example: | |
27 | |
28 var x int | |
29 var y = x // edge y -> x | |
30 func f() int { return y } // edge f -> y | |
31 | |
32 Each method declaration depends on its receiver named type; in addition | |
33 we add an edge from each receiver type to its methods: | |
34 | |
35 type T int // edge T -> T.f | |
36 func (T) f() // edge T.f -> T | |
37 | |
38 to ensure that a type and its methods stay together. | |
39 | |
40 The node graph is highly cyclic, and obviously all nodes in a cycle must | |
41 belong to the same package for the package import graph to remain | |
42 acyclic. | |
43 | |
44 So, we compute the second graph, the SCNODE GRAPH. In essence, the | |
45 scnode graph is the graph of strongly connected components (SCCs) of the | |
46 (ordinary) node graph. By construction, the scnode graph is acyclic. | |
47 | |
48 We optionally perform an optimization at this point, which is to fuse | |
49 single-predecessor scnodes with their sole predecessor, as this tends to | |
50 reduce clutter in big graphs. This means that the scnodes are no longer | |
51 true SCCs; however, the scnode graph remains acyclic. | |
52 | |
53 We define a valid PARTITION P of the scnode graph as a mapping from | |
54 scnodes to CLUSTERS such that the projection of the scnode graph using | |
55 mapping P is an acyclic graph. This third graph is the CLUSTER GRAPH. | |
56 | |
57 Every partition represents a valid refactoring of the original package | |
58 into hypothetical subpackages, each cluster being a subpackage. Two | |
59 partitions define the extreme ends of a spectrum: the MINIMAL partition | |
60 maps every scnode to a single cluster; it represents the status quo, a | |
61 monolithic package. The MAXIMAL partition maps each scnode to a unique | |
62 cluster; this breaks the package up into an impractically large number | |
63 of small fragments. The ideal partition lies somewhere in between. | |
64 | |
65 | |
66 CLUSTER FILE | |
67 | |
68 The --clusters=<file> argument specifies a "clusters file" that | |
69 constrains the partition algorithm. The file consists of a number of | |
70 stanzas, each naming a cluster ("foo") and assigning a set of initial | |
71 nodes ({x, y, z}) to it: | |
72 | |
73 = foo | |
74 x | |
75 y # this is a comment | |
76 z | |
77 | |
78 Order of stanzas is important: clusters must be be declared bottom to | |
79 top. After each stanza, all nodes transitively reachable (via the node | |
80 graph) from that cluster are assigned to that cluster, if they have not | |
81 yet been assigned to some other cluster. Thus we need only mention the | |
82 root nodes of the cluster, not all its internal nodes. A warning is | |
83 reported if a node mentioned in a stanza already belongs to a previously | |
84 defined cluster. | |
85 | |
86 There is an implicit cluster, "residue", that holds all remaining nodes | |
87 after the cluster defined by the file have been processed. Initially, | |
88 when the cluster file is empty, the residue cluster contains the entire | |
89 package. (It is logically at the top.) The task for the user is to | |
90 iteratively define new clusters until the residue becomes empty. | |
91 | |
92 | |
93 VISUALIZATION | |
94 | |
95 When sockdrawer is run, it analyzes the source package, builds the node | |
96 graph and the scgraph, loads the cluster file, computes the clusters for | |
97 every node, and then emits SVG renderings of the three levels of graphs, | |
98 with nodes color codes as follows: | |
99 | |
100 green = cluster (candidate subpackage) | |
101 pink = scnode (strong component of size > 1) | |
102 blue = node (func/type/var/const) | |
103 | |
104 The graphs of all clusters, a DAG, has green nodes; clicking one takes | |
105 you to the graph over scnodes for a that cluster, also a DAG. Each pink | |
106 node in this graph represents a cyclical bunch of the node graph, | |
107 collapsed together for ease of viewing. Each blue node represents a | |
108 singleton SCC, a single named entity; singular SCCs are replaced by | |
109 their sole element for simplicity. | |
110 | |
111 Clicking a pink (plural) scnode shows the cyclical portion of the node | |
112 graph that it represents. (If the fusion optimization was enabled, it | |
113 may not be fully cyclic.) All of its nodes are blue. | |
114 | |
115 Clicking a blue node shows the definition of that node in godoc. | |
116 (The godoc server's base URL is specified by the --godoc flag.) | |
117 | |
118 | |
119 WORKFLOW | |
120 | |
121 Initially, all nodes belong to the "residue" cluster. (GraphViz graph | |
122 rendering can be slow for the first several iterations.) The sockdrawer | |
123 | |
124 The user's task when decomposing a package into clusters is to identify | |
125 the lowest-hanging fruit (so to speak) in the residue cluster---a | |
126 coherent group of related scnodes at the bottom of the graph---and to | |
127 "snip off" a bunch at the "stem" by appending a new stanza to the | |
128 cluster file, listing the roots of that bunch in the stanza, and | |
129 re-running the tool. Nodes may be added to an existing stanza if | |
130 appropriate, but if they are added to a cluster that is "too low", this | |
131 may create conflicts; keep an eye out for warnings. | |
132 | |
133 This process continues iteratively until the residue has become empty | |
134 and the sets of clusters are satisfactory. | |
135 | |
136 The tool prints the assignments of nodes to clusters: the "shopping | |
137 list" for the refactoring work. Clusters should split off into | |
138 subpackages in dependency order, lowest first. | |
139 | |
140 | |
141 CAVEATS | |
142 | |
143 The analysis chooses a single configuration, such as linux/amd64. | |
144 Declarations for other configurations (e.g. windows/arm) will be absent | |
145 from the node graph. | |
146 | |
147 There may be some excessively large SCCs in the node graph that reflect | |
148 a circularity in the design. For the purposes of analysis, you can | |
149 break them arbitrarily by commenting out some code, though more thought | |
150 will be required for a principled fix (e.g. dependency injection). | |
151 | |
152 | |
153 TODO | |
154 | |
155 - Make pretty and stable names for anonymous nodes such as: | |
156 func init() {...} | |
157 var _ int = ... | |
158 Currently their names are very sensitive to lexical perturbations. | |
159 - Infer more constraints from co-located declarations. Most of the stuff | |
160 in the runtime's residue could be disposed of this way. | |
161 - Analyze the package's *_test.go files too. If they define an external | |
162 test package, we'll have to deal with two packages at once. | |
163 - Write tests. | |
164 | |
165 */ | |
166 package main | |
167 | |
168 import ( | |
169 "bufio" | |
170 "bytes" | |
171 "flag" | |
172 "fmt" | |
173 "go/ast" | |
174 "go/token" | |
175 "os" | |
176 "reflect" | |
177 "sort" | |
178 "strings" | |
179 "unicode" | |
180 "unicode/utf8" | |
181 | |
182 "golang.org/x/tools/go/loader" | |
183 "golang.org/x/tools/go/types" | |
184 ) | |
185 | |
186 var ( | |
187 outdir = flag.String("outdir", "out", "output directory") | |
188 | |
189 collapseMethods = flag.Bool("collapse_methods", true, | |
190 "treat all references to methods as references to the receiver t ype") | |
191 | |
192 fuse = flag.Bool("fuse", false, | |
193 "fuse each single-predecessor SCC with its sole predecessor") | |
194 | |
195 godoc = flag.String("godoc", "http://localhost:4999", | |
196 "base URL for godoc server") | |
197 | |
198 clusterFile = flag.String("clusters", "", "File containing cluster annot ations") | |
199 ) | |
200 | |
201 const Usage = "Usage: sockdrawer [-clusters=file] [-fuse] [-godoc URL] package" | |
202 | |
203 func main() { | |
204 flag.Parse() | |
205 args := flag.Args() | |
206 if err := doMain(args); err != nil { | |
207 fmt.Fprintf(os.Stderr, "sockdrawer: %s\n", err) | |
208 os.Exit(1) | |
209 } | |
210 } | |
211 | |
212 func doMain(args []string) error { | |
213 conf := loader.Config{ | |
214 SourceImports: true, | |
215 } | |
216 | |
217 if len(args) == 0 { | |
218 fmt.Fprintln(os.Stderr, Usage) | |
219 return nil | |
220 } | |
221 | |
222 // Use the initial packages from the command line. | |
223 // TODO(adonovan): support *_test.go files too. | |
224 _, err := conf.FromArgs(args, false /*FIXME*/) | |
225 if err != nil { | |
226 return err | |
227 } | |
228 | |
229 // Typecheck only the necessary function bodies. | |
230 // TODO(adonovan): opt: type-check only the bodies of functions | |
231 // with the initial packages. | |
232 conf.TypeCheckFuncBodies = func(p string) bool { return true } | |
233 | |
234 // Load, parse and type-check the whole program. | |
235 iprog, err := conf.Load() | |
236 if err != nil { | |
237 return err | |
238 } | |
239 | |
240 clusters := make(map[string]*cluster) | |
241 | |
242 o := organizer{ | |
243 fset: conf.Fset, | |
244 } | |
245 for _, info := range iprog.InitialPackages() { | |
246 o.doPackage(info) | |
247 } | |
248 | |
249 // Load groups from the cluster file, if any. | |
250 if *clusterFile != "" { | |
251 byName := make(map[string]*node) | |
252 for _, n := range o.nodes { | |
253 byName[n.name()] = n | |
254 } | |
255 | |
256 f, err := os.Open(*clusterFile) | |
257 if err != nil { | |
258 return err | |
259 } | |
260 in := bufio.NewScanner(f) | |
261 var linenum int | |
262 var c *cluster | |
263 for in.Scan() { | |
264 linenum++ | |
265 line := strings.TrimSpace(in.Text()) | |
266 if i := strings.IndexByte(line, '#'); i >= 0 { | |
267 line = strings.TrimSpace(line[:i]) // strip comm ents | |
268 } | |
269 if line == "" { | |
270 continue // skip blanks | |
271 } | |
272 if strings.HasPrefix(line, "= ") { | |
273 if c != nil { | |
274 c.finish() | |
275 } | |
276 | |
277 c = &cluster{ | |
278 id: len(clusters), | |
279 name: line[2:], | |
280 nodes: make(map[*node]bool), | |
281 } | |
282 if prev := clusters[c.name]; prev != nil { | |
283 fmt.Fprintf(os.Stderr, "%s:%d: duplicate cluster name: %s\n", | |
284 *clusterFile, linenum, c.name) | |
285 continue | |
286 } | |
287 clusters[c.name] = c | |
288 fmt.Printf("\n# cluster %s\n", c.name) | |
289 continue | |
290 } | |
291 if c == nil { | |
292 fmt.Fprintf(os.Stderr, "%s:%d: node before '= cl uster' marker\n", | |
293 *clusterFile, linenum) | |
294 continue | |
295 } | |
296 | |
297 n := byName[line] | |
298 if n == nil { | |
299 fmt.Fprintf(os.Stderr, "%s:%d: can't find node % q\n", | |
300 *clusterFile, linenum, line) | |
301 } else if n.cluster != nil { | |
302 fmt.Fprintf(os.Stderr, "%s:%d: node %q appears i n clusters %q and %q\n", | |
303 *clusterFile, linenum, line, n.cluster.n ame, c.name) | |
304 } else { | |
305 n.cluster = c | |
306 fmt.Printf("\t%s\n", n) | |
307 c.nodes[n] = true | |
308 } | |
309 } | |
310 if c != nil { | |
311 c.finish() | |
312 } | |
313 | |
314 // The final cluster, residue, includes all other nodes. | |
315 c = &cluster{ | |
316 id: len(clusters), | |
317 name: "residue", | |
318 nodes: make(map[*node]bool), | |
319 } | |
320 fmt.Printf("\n# cluster %s\n", c.name) | |
321 for _, n := range o.nodes { | |
322 if n.cluster == nil { | |
323 n.cluster = c | |
324 fmt.Printf("\t%-50s\n", n) | |
325 c.nodes[n] = true | |
326 } | |
327 } | |
328 c.finish() | |
329 if len(c.nodes) > 0 { | |
330 clusters[c.name] = c | |
331 } | |
332 | |
333 f.Close() | |
334 if err := in.Err(); err != nil { | |
335 return err | |
336 } | |
337 } | |
338 | |
339 scgraph := o.makeSCGraph() | |
340 | |
341 // dump the partition to stdout | |
342 for _, n := range o.nodes { | |
343 fmt.Printf("n%d\t%-20s s%d c%d (%s)\t\n", | |
344 n.id, n.String(), n.scc.id, n.cluster.id, n.cluster.name ) | |
345 } | |
346 | |
347 return renderGraphs(clusters, scgraph) | |
348 } | |
349 | |
350 type organizer struct { | |
351 fset *token.FileSet | |
352 nodes []*node | |
353 clusters map[string]*cluster | |
354 } | |
355 | |
356 // A node represents a package-level declaration. | |
357 // An entire const declaration is a single node. | |
358 // An entire var or type "spec" is a single node. | |
359 // | |
360 // Examples: | |
361 // func f() // FuncDecl node | |
362 // func init() {...} // FuncDecl node (no types.Object) | |
363 // type ( | |
364 // T int // TypeSpec node | |
365 // U int // TypeSpec node | |
366 // ) | |
367 // type T int // TypeSpec node | |
368 // const ( a, b = 0, 1; c = 0 ) // GenDecl(CONST) node (multiple objects ) | |
369 // var x = f() // ValueSpec(var) node | |
370 // var x, y = f() // ValueSpec(var) node (multiple objects ) | |
371 // var _ T = C(0) // ValueSpec(var) node (no object) | |
372 // | |
373 type node struct { | |
374 o *organizer | |
375 info *loader.PackageInfo | |
376 id int | |
377 syntax ast.Node // *ast.{FuncDecl,ValueSpec(VAR),TypeSpec,Ge nDecl(CONST)} | |
378 objects []types.Object // declared objects in lexical order; blanks omitted | |
379 succs, preds map[*node]bool // node graph adjacency sets | |
380 scc *scnode // SCC to which this node belongs | |
381 cluster *cluster // cluster to which this node belongs | |
382 } | |
383 | |
384 func (n *node) name() string { | |
385 // Only the first object of a group (e.g. const decl, or | |
386 // type+methods) is used for the node label. | |
387 // This is deterministic. | |
388 if len(n.objects) > 0 { | |
389 return n.objects[0].Name() | |
390 } else { | |
391 var kind string | |
392 switch n.syntax.(type) { | |
393 case *ast.FuncDecl: | |
394 // e.g. func init() | |
395 kind = "func" | |
396 case *ast.ValueSpec: | |
397 // e.g. var _ int | |
398 kind = "var" | |
399 default: | |
400 // can't happen? | |
401 kind = reflect.TypeOf(n.syntax).String() | |
402 } | |
403 // We give it a stable(-ish) name so we can mention it | |
404 // in annotations files. (The name will change if the | |
405 // source is edited though.) Make the numbering tighter. | |
406 return fmt.Sprintf("%s$%d", kind, n.id) | |
407 } | |
408 } | |
409 | |
410 func (n *node) String() string { | |
411 var buf bytes.Buffer | |
412 buf.WriteString(n.name()) | |
413 if nobj := len(n.objects); nobj > 1 { | |
414 fmt.Fprintf(&buf, " + %d", nobj-1) | |
415 } | |
416 return buf.String() | |
417 } | |
418 | |
419 func (n *node) godocURL() string { | |
420 posn := n.o.fset.Position(n.syntax.Pos()) | |
421 i := strings.Index(posn.Filename, "/src/") // hack! | |
422 | |
423 selLen := 1 | |
424 switch syntax := n.syntax.(type) { | |
425 case *ast.FuncDecl: | |
426 selLen = len("func") | |
427 case *ast.TypeSpec: | |
428 selLen = len("type") | |
429 case *ast.GenDecl: | |
430 selLen = len("const") | |
431 case *ast.ValueSpec: | |
432 // For "var (...; x, y = ...)", select "x, y". | |
433 selLen = int(syntax.Names[len(syntax.Names)-1].End() - syntax.Na mes[0].Pos()) | |
434 } | |
435 return fmt.Sprintf("%s/%s?s=%d:%d#L%d", *godoc, | |
436 posn.Filename[i+1:], posn.Offset, posn.Offset+selLen, posn.Line) | |
437 } | |
438 | |
439 func (n *node) exportedness() int { | |
440 for _, obj := range n.objects { | |
441 if obj.Exported() { | |
442 return 1 | |
443 } | |
444 } | |
445 return 0 | |
446 } | |
447 | |
448 func addEdge(from, to *node) { | |
449 if from == to { | |
450 return // skip self-edges | |
451 } | |
452 from.succs[to] = true | |
453 to.preds[from] = true | |
454 } | |
455 | |
456 type cluster struct { | |
457 id int | |
458 name string | |
459 nodes map[*node]bool | |
460 } | |
461 | |
462 func (c *cluster) finish() { | |
463 // annotateReachable applies n's annotation to all unannotated | |
464 // nodes reachable from it. | |
465 var annotateReachable func(n *node) | |
466 annotateReachable = func(n *node) { | |
467 for s := range n.succs { | |
468 if s.cluster == nil { | |
469 s.cluster = n.cluster | |
470 n.cluster.nodes[s] = true | |
471 fmt.Printf("\t%-50s (indirect)\n", s) | |
472 annotateReachable(s) | |
473 } | |
474 } | |
475 } | |
476 | |
477 // Add synthetic edges around the ring of nodes in the cluster. | |
478 // The cycle will ensure all appear in the same cluster. | |
479 // | |
480 // We must do this step on the node graph to avoid making the | |
481 // scgraph cyclic (but this does mean that we can't blindly | |
482 // choose cluster by picking one name from an SCC node, due to | |
483 // the sole-predecessor-fusion step). | |
484 var first, prev *node | |
485 for n := range c.nodes { | |
486 if first == nil { | |
487 first = n | |
488 } | |
489 if prev != nil { | |
490 annotateReachable(n) | |
491 } | |
492 prev = n | |
493 } | |
494 if prev != nil { | |
495 annotateReachable(first) | |
496 } | |
497 } | |
498 | |
499 func (o *organizer) newNode(info *loader.PackageInfo, syntax ast.Node) *node { | |
500 n := &node{ | |
501 o: o, | |
502 info: info, | |
503 id: len(o.nodes), | |
504 syntax: syntax, | |
505 succs: make(map[*node]bool), | |
506 preds: make(map[*node]bool), | |
507 } | |
508 o.nodes = append(o.nodes, n) | |
509 return n | |
510 } | |
511 | |
512 func (o *organizer) doPackage(info *loader.PackageInfo) { | |
513 fmt.Fprintf(os.Stderr, "\n\n\n==== %s ====\n\n\n", info.Pkg.Path()) | |
514 | |
515 nodesByObj := make(map[types.Object]*node) // keys are package-level obj ects | |
516 | |
517 // -- Pass 1: Defs ---------------------------------------------------- | |
518 | |
519 // Node creation (and hence ids) are deterministic. | |
520 | |
521 // findDefs visits the AST, associating every object defined at | |
522 // package-level (and every struct field or interface method | |
523 // defined at any level) with node for its enclosing top-level | |
524 // syntax tree. | |
525 findDefs := func(n *node) { | |
526 ast.Inspect(n.syntax, func(syntax ast.Node) bool { | |
527 if id, ok := syntax.(*ast.Ident); ok { | |
528 // Definition of package-level object, | |
529 // or struct field or interface method? | |
530 if obj := info.Info.Defs[id]; obj != nil { | |
531 if obj.Pkg().Scope().Lookup(obj.Name()) == obj { | |
532 // ok: package-level object | |
533 n.objects = append(n.objects, ob j) | |
534 } else if v, ok := obj.(*types.Var); ok && v.IsField() { | |
535 // ok: struct field | |
536 } else if _, ok := obj.(*types.Func); ok { | |
537 // ok: concrete method or interf ace method | |
538 } else { | |
539 return true // ignore | |
540 } | |
541 nodesByObj[obj] = n | |
542 } | |
543 } | |
544 return true | |
545 }) | |
546 } | |
547 | |
548 var methods []*ast.FuncDecl | |
549 for _, f := range info.Files { | |
550 for _, decl := range f.Decls { | |
551 switch decl := decl.(type) { | |
552 case *ast.GenDecl: | |
553 switch decl.Tok { | |
554 case token.CONST: | |
555 // treat decl as one node | |
556 findDefs(o.newNode(info, decl)) | |
557 | |
558 case token.VAR, token.TYPE: | |
559 // each spec gets its own node | |
560 for _, spec := range decl.Specs { | |
561 findDefs(o.newNode(info, spec)) | |
562 } | |
563 } | |
564 | |
565 case *ast.FuncDecl: | |
566 if decl.Recv != nil && *collapseMethods { | |
567 methods = append(methods, decl) | |
568 continue // collapsed method get no node | |
569 } | |
570 findDefs(o.newNode(info, decl)) | |
571 } | |
572 } | |
573 } | |
574 | |
575 // -- Pass 2: Refs ---------------------------------------------------- | |
576 | |
577 findRefs := func(n *node, syntax ast.Node) { | |
578 ast.Inspect(syntax, func(syntax ast.Node) bool { | |
579 switch syntax := syntax.(type) { | |
580 case *ast.SelectorExpr: | |
581 // Selection of struct field or interface method of | |
582 // package-level named type? | |
583 if sel, ok := info.Info.Selections[syntax]; ok & & sel.Obj().Pkg() == info.Pkg { | |
584 //recv := sel.Recv() | |
585 //fmt.Printf("%s: %v\n", o.fset.Position (syntax.Pos()), recv) | |
586 // obj = recvTypeName(recv) | |
587 // TODO(adonovan): don't we need to do s omething here? | |
588 // Test. | |
589 } | |
590 | |
591 case *ast.Ident: | |
592 // Reference to package-level object? | |
593 if obj, ok := info.Info.Uses[syntax]; ok { | |
594 // If --collapse_methods, treat referenc es to methods | |
595 // as references to their receiver type name. | |
596 if recv := methodRecv(obj); recv != nil && *collapseMethods && !isInterface(recv) { | |
597 obj = recvTypeName(recv) | |
598 } | |
599 | |
600 // Need to do the same for struct fields too! | |
601 | |
602 // Only same-package references matter f or now. | |
603 // TODO(adonovan): handle external test packages. | |
604 if n2, ok := nodesByObj[obj]; ok { | |
605 // reference to package-level ob ject (or method) | |
606 addEdge(n, n2) | |
607 } | |
608 } | |
609 | |
610 } | |
611 return true | |
612 }) | |
613 } | |
614 | |
615 // Concrete method declarations (collapsed with the receiver). | |
616 for _, decl := range methods { | |
617 method := info.Info.Defs[decl.Name] | |
618 recv := recvTypeName(methodRecv(method)) | |
619 // Collapsed method: treat references from this method as | |
620 // references from its named receiver type. | |
621 findRefs(nodesByObj[recv], decl) | |
622 } | |
623 | |
624 // All other declarations. | |
625 for _, n := range o.nodes { | |
626 findRefs(n, n.syntax) | |
627 | |
628 if decl, ok := n.syntax.(*ast.FuncDecl); ok && decl.Recv != nil { | |
629 recv := recvTypeName(methodRecv(info.Info.Defs[decl.Name ])) | |
630 | |
631 // Add edges from each named receiver type to its method s, | |
632 // so they are coupled together. | |
633 addEdge(nodesByObj[recv], n) | |
634 } | |
635 } | |
636 | |
637 fmt.Fprintf(os.Stderr, "\t%d nodes\n", len(o.nodes)) | |
638 } | |
639 | |
640 // -- SCCs ------------------------------------------------------------- | |
rsc
2014/12/12 19:53:23
Banners like this are discouraged. http://golang.o
adonovan
2014/12/12 20:13:00
Reduced to 4 dashes either side. :)
| |
641 | |
642 // An scnode is a node in the scnode graph. | |
643 // It is (approximately; see -fuse) an SCC of the node graph. | |
644 type scnode struct { | |
645 id int // unique id | |
646 nodes map[*node]bool // elements of this SCC | |
647 succs, preds map[*scnode]bool // scnode graph adjacency sets | |
648 cluster *cluster // the (sole) cluster to which this SCC be longs | |
649 } | |
650 | |
651 const maxLines = 8 // maximum number of lines in a label | |
652 | |
653 func (s *scnode) String() string { | |
654 var buf bytes.Buffer | |
655 // Order nodes by exportedness and in-degree. | |
656 order := make([]*node, 0, len(s.nodes)) | |
657 for n := range s.nodes { | |
658 order = append(order, n) | |
659 } | |
660 sort.Sort(byExportednessAndInDegree(order)) | |
661 for i, n := range order { | |
662 if i > 0 { | |
663 buf.WriteByte('\n') | |
664 } | |
665 if i == maxLines-1 && len(order) > maxLines { | |
666 fmt.Fprintf(&buf, "+ %d more", len(order)-i) | |
667 break | |
668 } | |
669 buf.WriteString(n.String()) | |
670 } | |
671 return buf.String() | |
672 } | |
673 | |
674 type byExportednessAndInDegree []*node | |
675 | |
676 func (b byExportednessAndInDegree) Len() int { return len(b) } | |
677 func (b byExportednessAndInDegree) Less(i, j int) bool { | |
678 if r := b[i].exportedness() - b[j].exportedness(); r != 0 { | |
679 return r > 0 | |
680 } | |
681 if r := len(b[i].preds) - len(b[j].preds); r != 0 { | |
682 return r > 0 | |
683 } | |
684 return false | |
685 } | |
686 func (b byExportednessAndInDegree) Swap(i, j int) { b[i], b[j] = b[j], b[i] } | |
687 | |
688 func (o *organizer) makeSCGraph() map[*scnode]bool { | |
689 // Kosaraju's algorithm---Tarjan is overkill here. | |
690 | |
691 // Forward pass. | |
692 S := make([]*node, 0, len(o.nodes)) // postorder stack | |
693 seen := make(map[*node]bool) | |
694 var visit func(n *node) | |
695 visit = func(n *node) { | |
696 if !seen[n] { | |
697 seen[n] = true | |
698 for s := range n.succs { | |
699 visit(s) | |
700 } | |
701 S = append(S, n) | |
702 } | |
703 } | |
704 | |
705 for _, n := range o.nodes { | |
706 visit(n) | |
707 } | |
708 | |
709 // Reverse pass. | |
710 var current *scnode | |
711 seen = make(map[*node]bool) | |
712 var rvisit func(d *node) | |
713 rvisit = func(d *node) { | |
714 if !seen[d] { | |
715 seen[d] = true | |
716 current.nodes[d] = true | |
717 d.scc = current | |
718 for p := range d.preds { | |
719 rvisit(p) | |
720 } | |
721 } | |
722 } | |
723 scnodes := make(map[*scnode]bool) | |
724 for len(S) > 0 { | |
725 top := S[len(S)-1] | |
726 S = S[:len(S)-1] // pop | |
727 if !seen[top] { | |
728 current = &scnode{ | |
729 id: len(scnodes), | |
730 cluster: top.cluster, | |
731 nodes: make(map[*node]bool), | |
732 succs: make(map[*scnode]bool), | |
733 preds: make(map[*scnode]bool), | |
734 } | |
735 rvisit(top) | |
736 scnodes[current] = true | |
737 } | |
738 } | |
739 | |
740 // Build the strong-component DAG by | |
741 // projecting the edges of the node graph, | |
742 // discarding self-edges. | |
743 for s := range scnodes { | |
744 for n := range s.nodes { | |
745 for pred := range n.preds { | |
746 if s != pred.scc { | |
747 s.preds[pred.scc] = true | |
748 } | |
749 } | |
750 for succ := range n.succs { | |
751 if s != succ.scc { | |
752 s.succs[succ.scc] = true | |
753 } | |
754 } | |
755 } | |
756 } | |
757 | |
758 fmt.Fprintf(os.Stderr, "\t%d SCCs\n", len(scnodes)) | |
759 | |
760 if *fuse { | |
761 // Now fold each single-predecessor scnode into that predecessor . | |
762 // Iterate until a fixed point is reached. | |
763 // | |
764 // Example: a -> b -> c | |
765 // b -> d | |
766 // Becomes: ab -> c | |
767 // ab -> d | |
768 // Then: abcd | |
769 // | |
770 // Since the loop conserves predecessor count for all | |
771 // non-deleted scnodes, the algorithm is order-invariant. | |
772 for { | |
773 var changed bool | |
774 for b := range scnodes { | |
775 if b == nil || len(b.preds) != 1 { | |
776 continue | |
777 } | |
778 var a *scnode | |
779 for a = range b.preds { | |
780 } | |
781 // a is sole predecessor of b | |
782 if a.cluster != b.cluster { | |
783 // don't fuse SCCs belonging to differen t clusters! | |
784 continue | |
785 } | |
786 | |
787 changed = true | |
788 | |
789 b.preds = nil | |
790 delete(a.succs, b) | |
791 | |
792 // a gets all b's nodes | |
793 for n := range b.nodes { | |
794 a.nodes[n] = true | |
795 n.scc = a | |
796 } | |
797 b.nodes = nil | |
798 | |
799 // a gets all b's succs | |
800 for c := range b.succs { | |
801 a.succs[c] = true | |
802 c.preds[a] = true | |
803 delete(c.preds, b) | |
804 } | |
805 b.succs = nil | |
806 | |
807 delete(scnodes, b) | |
808 } | |
809 if !changed { | |
810 break | |
811 } | |
812 } | |
813 | |
814 fmt.Fprintf(os.Stderr, "\t%d SCCs (excluding single-predecessor ones)\n", | |
815 len(scnodes)) | |
816 } | |
817 | |
818 return scnodes | |
819 } | |
820 | |
821 // -- util ------------------------------------------------------------- | |
822 | |
823 func recvTypeName(T types.Type) *types.TypeName { | |
rsc
2014/12/12 19:53:23
It is very unusual in Go to see capitalized local
adonovan
2014/12/12 20:13:00
Agreed, but T seems to have become the default nam
rsc
2014/12/12 21:00:04
I'm happy for you both but it's not idiomatic Go a
| |
824 if ptr, ok := T.(*types.Pointer); ok { | |
825 T = ptr.Elem() | |
826 } | |
827 return T.(*types.Named).Obj() | |
828 } | |
829 | |
830 // methodRecv returns the receiver type of obj, | |
831 // if it's a method, or nil otherwise. | |
832 // TODO(adonovan): move this to go/types. It gets re-invented a lot. | |
833 func methodRecv(obj types.Object) types.Type { | |
834 if obj, ok := obj.(*types.Func); ok { | |
835 recv := obj.Type().(*types.Signature).Recv() | |
836 if recv != nil { | |
837 return recv.Type() | |
838 } | |
839 } | |
840 return nil | |
841 } | |
842 | |
843 // isInterface reports whether T's underlying type is an interface. | |
844 func isInterface(T types.Type) bool { | |
845 _, ok := T.Underlying().(*types.Interface) | |
846 return ok | |
847 } | |
848 | |
849 // from go/ast | |
850 func IsExported(name string) bool { | |
851 ch, _ := utf8.DecodeRuneInString(name) | |
852 return unicode.IsUpper(ch) | |
853 } | |
OLD | NEW |