| // 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 { |
| fset *token.FileSet |
| Blocks []*Block // block[0] is entry; order otherwise undefined |
| } |
| |
| // 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. |
| 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 |
| 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, |
| cfg: new(CFG), |
| } |
| b.current = b.newBlock(KindBody, body) |
| b.stmt(body) |
| |
| // Compute liveness (reachability from entry point), breadth-first. |
| q := make([]*Block, 0, len(b.cfg.Blocks)) |
| q = append(q, b.cfg.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.add(&ast.ReturnStmt{ |
| Return: body.End() - 1, |
| }) |
| } |
| |
| return b.cfg |
| } |
| |
| 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. |
| 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)) |
| } |