blob: 2b78742970a4e76c15bd099e07c0cca3d1727bb8 [file]
// 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))
}