| // Copyright 2026 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. |
| |
| package excfg |
| |
| import ( |
| "bytes" |
| "fmt" |
| "go/ast" |
| "go/format" |
| "strings" |
| |
| "golang.org/x/tools/go/ast/astutil" |
| internalastutil "golang.org/x/tools/internal/astutil" |
| ) |
| |
| // String formats the control-flow graph for ease of debugging. |
| func (cfg *CFG) String() string { |
| var buf strings.Builder |
| for _, b := range cfg.Blocks { |
| fmt.Fprintf(&buf, "%s: %s, CFG block .%d\n", b, b.Kind, b.CFGBlock) |
| fmt.Fprintf(&buf, "\t%s\n", formatBlockNode(cfg, b)) |
| if len(b.Succs) > 0 { |
| fmt.Fprintf(&buf, "\tsuccs:") |
| for _, succ := range b.Succs { |
| fmt.Fprintf(&buf, " %s", succ) |
| } |
| buf.WriteByte('\n') |
| } |
| buf.WriteByte('\n') |
| } |
| return buf.String() |
| } |
| |
| func (b *Block) String() string { |
| return fmt.Sprintf("B%d", b.Index) |
| } |
| |
| func formatBlockNode(cfg *CFG, b *Block) string { |
| // Replace sub-expressions with their block names. |
| subs := cfg.Subexprs(b) |
| subMap := make(map[ast.Node]*Block) |
| for _, sub := range subs { |
| subMap[sub.Node] = sub |
| } |
| |
| // We can't necessarily modify the AST in place, so we defensively clone it. |
| n := internalastutil.CloneNode(b.Node) |
| // The tricky part is that subMap is indexed by pre, but we need to modify |
| // post. To solve this, we number the nodes in a first pass over the |
| // pre-clone, and then use the same numbering to modify the post-clone. |
| apply := func(n ast.Node, f func(i int, c *astutil.Cursor) bool) { |
| i := 0 |
| astutil.Apply(n, func(c *astutil.Cursor) bool { |
| visit := f(i, c) |
| i++ |
| return visit |
| }, nil) |
| } |
| // First pass: number nodes and collect numbered rewrites. |
| type rewrite struct { |
| i int |
| b *Block |
| } |
| var rewrites []rewrite |
| apply(b.Node, func(i int, c *astutil.Cursor) bool { |
| node := c.Node() |
| if sub, ok := subMap[node]; ok { |
| rewrites = append(rewrites, rewrite{i, sub}) |
| return false |
| } |
| return true |
| }) |
| // Second pass: apply numbered rewrites. |
| apply(n, func(i int, c *astutil.Cursor) bool { |
| if len(rewrites) > 0 && rewrites[0].i == i { |
| id := &ast.Ident{Name: rewrites[0].b.String()} |
| c.Replace(id) |
| rewrites = rewrites[1:] |
| return false |
| } |
| return true |
| }) |
| |
| // Format. |
| var buf bytes.Buffer |
| format.Node(&buf, cfg.Fset, n) |
| return string(bytes.Replace(buf.Bytes(), []byte("\n"), []byte("\n\t"), -1)) |
| } |