274 lines
8.6 KiB
Go
274 lines
8.6 KiB
Go
// Copyright 2016 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 cfg constructs a simple control-flow graph (CFG) of the
|
||
// statements and expressions within a single function.
|
||
//
|
||
// Use cfg.New to construct the CFG for a function body.
|
||
//
|
||
// The blocks of the CFG contain all the function's non-control
|
||
// statements. The CFG does not contain control statements such as If,
|
||
// Switch, Select, and Branch, but does contain their subexpressions;
|
||
// also, each block records the control statement (Block.Stmt) that
|
||
// gave rise to it and its relationship (Block.Kind) to that statement.
|
||
//
|
||
// For example, this source code:
|
||
//
|
||
// if x := f(); x != nil {
|
||
// T()
|
||
// } else {
|
||
// F()
|
||
// }
|
||
//
|
||
// produces this CFG:
|
||
//
|
||
// 1: x := f() Body
|
||
// x != nil
|
||
// succs: 2, 3
|
||
// 2: T() IfThen
|
||
// succs: 4
|
||
// 3: F() IfElse
|
||
// succs: 4
|
||
// 4: IfDone
|
||
//
|
||
// The CFG does contain Return statements; even implicit returns are
|
||
// materialized (at the position of the function's closing brace).
|
||
//
|
||
// The CFG does not record conditions associated with conditional branch
|
||
// edges, nor the short-circuit semantics of the && and || operators,
|
||
// nor abnormal control flow caused by panic. If you need this
|
||
// information, use golang.org/x/tools/go/ssa instead.
|
||
package cfg
|
||
|
||
import (
|
||
"bytes"
|
||
"fmt"
|
||
"go/ast"
|
||
"go/format"
|
||
"go/token"
|
||
)
|
||
|
||
// A CFG represents the control-flow graph of a single function.
|
||
//
|
||
// The entry point is Blocks[0]; there may be multiple return blocks.
|
||
type CFG struct {
|
||
Blocks []*Block // block[0] is entry; order otherwise undefined
|
||
noreturn bool // function body lacks a reachable return statement
|
||
}
|
||
|
||
// NoReturn reports whether the function has no reachable return.
|
||
func (cfg *CFG) NoReturn() bool { return cfg.noreturn }
|
||
|
||
// A Block represents a basic block: a list of statements and
|
||
// expressions that are always evaluated sequentially.
|
||
//
|
||
// A block may have 0-2 successors: zero for a return block or a block
|
||
// that calls a function such as panic that never returns; one for a
|
||
// normal (jump) block; and two for a conditional (if) block.
|
||
//
|
||
// In a conditional block, the last entry in Nodes is the condition and always
|
||
// an [ast.Expr], Succs[0] is the successor if the condition is true, and
|
||
// Succs[1] is the successor if the condition is false.
|
||
type Block struct {
|
||
Nodes []ast.Node // statements, expressions, and ValueSpecs
|
||
Succs []*Block // successor nodes in the graph
|
||
Index int32 // index within CFG.Blocks
|
||
Live bool // block is reachable from entry
|
||
returns bool // block contains return or defer (which may recover and return)
|
||
Kind BlockKind // block kind
|
||
Stmt ast.Stmt // statement that gave rise to this block (see BlockKind for details)
|
||
|
||
succs2 [2]*Block // underlying array for Succs
|
||
}
|
||
|
||
// A BlockKind identifies the purpose of a block.
|
||
// It also determines the possible types of its Stmt field.
|
||
type BlockKind uint8
|
||
|
||
const (
|
||
KindInvalid BlockKind = iota // Stmt=nil
|
||
|
||
KindUnreachable // unreachable block after {Branch,Return}Stmt / no-return call ExprStmt
|
||
KindBody // function body BlockStmt
|
||
KindForBody // body of ForStmt
|
||
KindForDone // block after ForStmt
|
||
KindForLoop // head of ForStmt
|
||
KindForPost // post condition of ForStmt
|
||
KindIfDone // block after IfStmt
|
||
KindIfElse // else block of IfStmt
|
||
KindIfThen // then block of IfStmt
|
||
KindLabel // labeled block of BranchStmt (Stmt may be nil for dangling label)
|
||
KindRangeBody // body of RangeStmt
|
||
KindRangeDone // block after RangeStmt
|
||
KindRangeLoop // head of RangeStmt
|
||
KindSelectCaseBody // body of SelectStmt
|
||
KindSelectDone // block after SelectStmt
|
||
KindSelectAfterCase // block after a CommClause
|
||
KindSwitchCaseBody // body of CaseClause
|
||
KindSwitchDone // block after {Type.}SwitchStmt
|
||
KindSwitchNextCase // secondary expression of a multi-expression CaseClause
|
||
)
|
||
|
||
func (kind BlockKind) String() string {
|
||
return [...]string{
|
||
KindInvalid: "Invalid",
|
||
KindUnreachable: "Unreachable",
|
||
KindBody: "Body",
|
||
KindForBody: "ForBody",
|
||
KindForDone: "ForDone",
|
||
KindForLoop: "ForLoop",
|
||
KindForPost: "ForPost",
|
||
KindIfDone: "IfDone",
|
||
KindIfElse: "IfElse",
|
||
KindIfThen: "IfThen",
|
||
KindLabel: "Label",
|
||
KindRangeBody: "RangeBody",
|
||
KindRangeDone: "RangeDone",
|
||
KindRangeLoop: "RangeLoop",
|
||
KindSelectCaseBody: "SelectCaseBody",
|
||
KindSelectDone: "SelectDone",
|
||
KindSelectAfterCase: "SelectAfterCase",
|
||
KindSwitchCaseBody: "SwitchCaseBody",
|
||
KindSwitchDone: "SwitchDone",
|
||
KindSwitchNextCase: "SwitchNextCase",
|
||
}[kind]
|
||
}
|
||
|
||
// New returns a new control-flow graph for the specified function body,
|
||
// which must be non-nil.
|
||
//
|
||
// The CFG builder calls mayReturn to determine whether a given function
|
||
// call may return. For example, calls to panic, os.Exit, and log.Fatal
|
||
// do not return, so the builder can remove infeasible graph edges
|
||
// following such calls. The builder calls mayReturn only for a
|
||
// CallExpr beneath an ExprStmt.
|
||
func New(body *ast.BlockStmt, mayReturn func(*ast.CallExpr) bool) *CFG {
|
||
b := builder{
|
||
mayReturn: mayReturn,
|
||
}
|
||
b.current = b.newBlock(KindBody, body)
|
||
b.stmt(body)
|
||
|
||
// Compute liveness (reachability from entry point),
|
||
// breadth-first, marking Block.Live flags.
|
||
q := make([]*Block, 0, len(b.blocks))
|
||
q = append(q, b.blocks[0]) // entry point
|
||
for len(q) > 0 {
|
||
b := q[len(q)-1]
|
||
q = q[:len(q)-1]
|
||
|
||
if !b.Live {
|
||
b.Live = true
|
||
q = append(q, b.Succs...)
|
||
}
|
||
}
|
||
|
||
// Does control fall off the end of the function's body?
|
||
// Make implicit return explicit.
|
||
if b.current != nil && b.current.Live {
|
||
b.current.returns = true
|
||
b.add(&ast.ReturnStmt{
|
||
Return: body.End() - 1,
|
||
})
|
||
}
|
||
|
||
// Is any return (or defer+recover) block reachable?
|
||
noreturn := true
|
||
for _, bl := range b.blocks {
|
||
if bl.Live && bl.returns {
|
||
noreturn = false
|
||
break
|
||
}
|
||
}
|
||
|
||
return &CFG{Blocks: b.blocks, noreturn: noreturn}
|
||
}
|
||
|
||
func (b *Block) String() string {
|
||
return fmt.Sprintf("block %d (%s)", b.Index, b.comment(nil))
|
||
}
|
||
|
||
func (b *Block) comment(fset *token.FileSet) string {
|
||
s := b.Kind.String()
|
||
if fset != nil && b.Stmt != nil {
|
||
s = fmt.Sprintf("%s@L%d", s, fset.Position(b.Stmt.Pos()).Line)
|
||
}
|
||
return s
|
||
}
|
||
|
||
// Return returns the return statement at the end of this block if present, nil
|
||
// otherwise.
|
||
//
|
||
// When control falls off the end of the function, the ReturnStmt is synthetic
|
||
// and its [ast.Node.End] position may be beyond the end of the file.
|
||
//
|
||
// A function that contains no return statement (explicit or implied)
|
||
// may yet return normally, and may even return a nonzero value. For example:
|
||
//
|
||
// func() (res any) {
|
||
// defer func() { res = recover() }()
|
||
// panic(123)
|
||
// }
|
||
func (b *Block) Return() (ret *ast.ReturnStmt) {
|
||
if len(b.Nodes) > 0 {
|
||
ret, _ = b.Nodes[len(b.Nodes)-1].(*ast.ReturnStmt)
|
||
}
|
||
return
|
||
}
|
||
|
||
// Format formats the control-flow graph for ease of debugging.
|
||
func (g *CFG) Format(fset *token.FileSet) string {
|
||
var buf bytes.Buffer
|
||
for _, b := range g.Blocks {
|
||
fmt.Fprintf(&buf, ".%d: # %s\n", b.Index, b.comment(fset))
|
||
for _, n := range b.Nodes {
|
||
fmt.Fprintf(&buf, "\t%s\n", formatNode(fset, n))
|
||
}
|
||
if len(b.Succs) > 0 {
|
||
fmt.Fprintf(&buf, "\tsuccs:")
|
||
for _, succ := range b.Succs {
|
||
fmt.Fprintf(&buf, " %d", succ.Index)
|
||
}
|
||
buf.WriteByte('\n')
|
||
}
|
||
buf.WriteByte('\n')
|
||
}
|
||
return buf.String()
|
||
}
|
||
|
||
// Dot returns the control-flow graph in the [Dot graph description language].
|
||
// Use a command such as 'dot -Tsvg' to render it in a form viewable in a browser.
|
||
// This method is provided as a debugging aid; the details of the
|
||
// output are unspecified and may change.
|
||
//
|
||
// [Dot graph description language]: https://en.wikipedia.org/wiki/DOT_(graph_description_language)
|
||
func (g *CFG) Dot(fset *token.FileSet) string {
|
||
var buf bytes.Buffer
|
||
buf.WriteString("digraph CFG {\n")
|
||
buf.WriteString(" node [shape=box];\n")
|
||
for _, b := range g.Blocks {
|
||
// node label
|
||
var text bytes.Buffer
|
||
text.WriteString(b.comment(fset))
|
||
for _, n := range b.Nodes {
|
||
fmt.Fprintf(&text, "\n%s", formatNode(fset, n))
|
||
}
|
||
|
||
// node and edges
|
||
fmt.Fprintf(&buf, " n%d [label=%q];\n", b.Index, &text)
|
||
for _, succ := range b.Succs {
|
||
fmt.Fprintf(&buf, " n%d -> n%d;\n", b.Index, succ.Index)
|
||
}
|
||
}
|
||
buf.WriteString("}\n")
|
||
return buf.String()
|
||
}
|
||
|
||
func formatNode(fset *token.FileSet, n ast.Node) string {
|
||
var buf bytes.Buffer
|
||
format.Node(&buf, fset, n)
|
||
// Indent secondary lines by a tab.
|
||
return string(bytes.Replace(buf.Bytes(), []byte("\n"), []byte("\n\t"), -1))
|
||
}
|