Add staticcheck tool
This commit is contained in:
519
vendor/golang.org/x/tools/go/cfg/builder.go
generated
vendored
Normal file
519
vendor/golang.org/x/tools/go/cfg/builder.go
generated
vendored
Normal file
@@ -0,0 +1,519 @@
|
||||
// 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
|
||||
|
||||
// This file implements the CFG construction pass.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
)
|
||||
|
||||
type builder struct {
|
||||
blocks []*Block
|
||||
mayReturn func(*ast.CallExpr) bool
|
||||
current *Block
|
||||
lblocks map[string]*lblock // labeled blocks
|
||||
targets *targets // linked stack of branch targets
|
||||
}
|
||||
|
||||
func (b *builder) stmt(_s ast.Stmt) {
|
||||
// The label of the current statement. If non-nil, its _goto
|
||||
// target is always set; its _break and _continue are set only
|
||||
// within the body of switch/typeswitch/select/for/range.
|
||||
// It is effectively an additional default-nil parameter of stmt().
|
||||
var label *lblock
|
||||
start:
|
||||
switch s := _s.(type) {
|
||||
case *ast.BadStmt,
|
||||
*ast.SendStmt,
|
||||
*ast.IncDecStmt,
|
||||
*ast.GoStmt,
|
||||
*ast.EmptyStmt,
|
||||
*ast.AssignStmt:
|
||||
// No effect on control flow.
|
||||
b.add(s)
|
||||
|
||||
case *ast.DeferStmt:
|
||||
b.add(s)
|
||||
// Assume conservatively that this behaves like:
|
||||
// defer func() { recover() }
|
||||
// so any subsequent panic may act like a return.
|
||||
b.current.returns = true
|
||||
|
||||
case *ast.ExprStmt:
|
||||
b.add(s)
|
||||
if call, ok := s.X.(*ast.CallExpr); ok && !b.mayReturn(call) {
|
||||
// Calls to panic, os.Exit, etc, never return.
|
||||
b.current = b.newBlock(KindUnreachable, s)
|
||||
}
|
||||
|
||||
case *ast.DeclStmt:
|
||||
// Treat each var ValueSpec as a separate statement.
|
||||
d := s.Decl.(*ast.GenDecl)
|
||||
if d.Tok == token.VAR {
|
||||
for _, spec := range d.Specs {
|
||||
if spec, ok := spec.(*ast.ValueSpec); ok {
|
||||
b.add(spec)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case *ast.LabeledStmt:
|
||||
label = b.labeledBlock(s.Label, s)
|
||||
b.jump(label._goto)
|
||||
b.current = label._goto
|
||||
_s = s.Stmt
|
||||
goto start // effectively: tailcall stmt(g, s.Stmt, label)
|
||||
|
||||
case *ast.ReturnStmt:
|
||||
b.current.returns = true
|
||||
b.add(s)
|
||||
b.current = b.newBlock(KindUnreachable, s)
|
||||
|
||||
case *ast.BranchStmt:
|
||||
b.branchStmt(s)
|
||||
|
||||
case *ast.BlockStmt:
|
||||
b.stmtList(s.List)
|
||||
|
||||
case *ast.IfStmt:
|
||||
if s.Init != nil {
|
||||
b.stmt(s.Init)
|
||||
}
|
||||
then := b.newBlock(KindIfThen, s)
|
||||
done := b.newBlock(KindIfDone, s)
|
||||
_else := done
|
||||
if s.Else != nil {
|
||||
_else = b.newBlock(KindIfElse, s)
|
||||
}
|
||||
b.add(s.Cond)
|
||||
b.ifelse(then, _else)
|
||||
b.current = then
|
||||
b.stmt(s.Body)
|
||||
b.jump(done)
|
||||
|
||||
if s.Else != nil {
|
||||
b.current = _else
|
||||
b.stmt(s.Else)
|
||||
b.jump(done)
|
||||
}
|
||||
|
||||
b.current = done
|
||||
|
||||
case *ast.SwitchStmt:
|
||||
b.switchStmt(s, label)
|
||||
|
||||
case *ast.TypeSwitchStmt:
|
||||
b.typeSwitchStmt(s, label)
|
||||
|
||||
case *ast.SelectStmt:
|
||||
b.selectStmt(s, label)
|
||||
|
||||
case *ast.ForStmt:
|
||||
b.forStmt(s, label)
|
||||
|
||||
case *ast.RangeStmt:
|
||||
b.rangeStmt(s, label)
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected statement kind: %T", s))
|
||||
}
|
||||
}
|
||||
|
||||
func (b *builder) stmtList(list []ast.Stmt) {
|
||||
for _, s := range list {
|
||||
b.stmt(s)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *builder) branchStmt(s *ast.BranchStmt) {
|
||||
var block *Block
|
||||
switch s.Tok {
|
||||
case token.BREAK:
|
||||
if s.Label != nil {
|
||||
if lb := b.labeledBlock(s.Label, nil); lb != nil {
|
||||
block = lb._break
|
||||
}
|
||||
} else {
|
||||
for t := b.targets; t != nil && block == nil; t = t.tail {
|
||||
block = t._break
|
||||
}
|
||||
}
|
||||
|
||||
case token.CONTINUE:
|
||||
if s.Label != nil {
|
||||
if lb := b.labeledBlock(s.Label, nil); lb != nil {
|
||||
block = lb._continue
|
||||
}
|
||||
} else {
|
||||
for t := b.targets; t != nil && block == nil; t = t.tail {
|
||||
block = t._continue
|
||||
}
|
||||
}
|
||||
|
||||
case token.FALLTHROUGH:
|
||||
for t := b.targets; t != nil && block == nil; t = t.tail {
|
||||
block = t._fallthrough
|
||||
}
|
||||
|
||||
case token.GOTO:
|
||||
if s.Label != nil {
|
||||
block = b.labeledBlock(s.Label, nil)._goto
|
||||
}
|
||||
}
|
||||
if block == nil { // ill-typed (e.g. undefined label)
|
||||
block = b.newBlock(KindUnreachable, s)
|
||||
}
|
||||
b.jump(block)
|
||||
b.current = b.newBlock(KindUnreachable, s)
|
||||
}
|
||||
|
||||
func (b *builder) switchStmt(s *ast.SwitchStmt, label *lblock) {
|
||||
if s.Init != nil {
|
||||
b.stmt(s.Init)
|
||||
}
|
||||
if s.Tag != nil {
|
||||
b.add(s.Tag)
|
||||
}
|
||||
done := b.newBlock(KindSwitchDone, s)
|
||||
if label != nil {
|
||||
label._break = done
|
||||
}
|
||||
// We pull the default case (if present) down to the end.
|
||||
// But each fallthrough label must point to the next
|
||||
// body block in source order, so we preallocate a
|
||||
// body block (fallthru) for the next case.
|
||||
// Unfortunately this makes for a confusing block order.
|
||||
var defaultBody *[]ast.Stmt
|
||||
var defaultFallthrough *Block
|
||||
var fallthru, defaultBlock *Block
|
||||
ncases := len(s.Body.List)
|
||||
for i, clause := range s.Body.List {
|
||||
body := fallthru
|
||||
if body == nil {
|
||||
body = b.newBlock(KindSwitchCaseBody, clause) // first case only
|
||||
}
|
||||
|
||||
// Preallocate body block for the next case.
|
||||
fallthru = done
|
||||
if i+1 < ncases {
|
||||
fallthru = b.newBlock(KindSwitchCaseBody, s.Body.List[i+1])
|
||||
}
|
||||
|
||||
cc := clause.(*ast.CaseClause)
|
||||
if cc.List == nil {
|
||||
// Default case.
|
||||
defaultBody = &cc.Body
|
||||
defaultFallthrough = fallthru
|
||||
defaultBlock = body
|
||||
continue
|
||||
}
|
||||
|
||||
var nextCond *Block
|
||||
for _, cond := range cc.List {
|
||||
nextCond = b.newBlock(KindSwitchNextCase, cc)
|
||||
b.add(cond) // one half of the tag==cond condition
|
||||
b.ifelse(body, nextCond)
|
||||
b.current = nextCond
|
||||
}
|
||||
b.current = body
|
||||
b.targets = &targets{
|
||||
tail: b.targets,
|
||||
_break: done,
|
||||
_fallthrough: fallthru,
|
||||
}
|
||||
b.stmtList(cc.Body)
|
||||
b.targets = b.targets.tail
|
||||
b.jump(done)
|
||||
b.current = nextCond
|
||||
}
|
||||
if defaultBlock != nil {
|
||||
b.jump(defaultBlock)
|
||||
b.current = defaultBlock
|
||||
b.targets = &targets{
|
||||
tail: b.targets,
|
||||
_break: done,
|
||||
_fallthrough: defaultFallthrough,
|
||||
}
|
||||
b.stmtList(*defaultBody)
|
||||
b.targets = b.targets.tail
|
||||
}
|
||||
b.jump(done)
|
||||
b.current = done
|
||||
}
|
||||
|
||||
func (b *builder) typeSwitchStmt(s *ast.TypeSwitchStmt, label *lblock) {
|
||||
if s.Init != nil {
|
||||
b.stmt(s.Init)
|
||||
}
|
||||
if s.Assign != nil {
|
||||
b.add(s.Assign)
|
||||
}
|
||||
|
||||
done := b.newBlock(KindSwitchDone, s)
|
||||
if label != nil {
|
||||
label._break = done
|
||||
}
|
||||
var default_ *ast.CaseClause
|
||||
for _, clause := range s.Body.List {
|
||||
cc := clause.(*ast.CaseClause)
|
||||
if cc.List == nil {
|
||||
default_ = cc
|
||||
continue
|
||||
}
|
||||
body := b.newBlock(KindSwitchCaseBody, cc)
|
||||
var next *Block
|
||||
for _, casetype := range cc.List {
|
||||
next = b.newBlock(KindSwitchNextCase, cc)
|
||||
// casetype is a type, so don't call b.add(casetype).
|
||||
// This block logically contains a type assertion,
|
||||
// x.(casetype), but it's unclear how to represent x.
|
||||
_ = casetype
|
||||
b.ifelse(body, next)
|
||||
b.current = next
|
||||
}
|
||||
b.current = body
|
||||
b.typeCaseBody(cc, done)
|
||||
b.current = next
|
||||
}
|
||||
if default_ != nil {
|
||||
b.typeCaseBody(default_, done)
|
||||
} else {
|
||||
b.jump(done)
|
||||
}
|
||||
b.current = done
|
||||
}
|
||||
|
||||
func (b *builder) typeCaseBody(cc *ast.CaseClause, done *Block) {
|
||||
b.targets = &targets{
|
||||
tail: b.targets,
|
||||
_break: done,
|
||||
}
|
||||
b.stmtList(cc.Body)
|
||||
b.targets = b.targets.tail
|
||||
b.jump(done)
|
||||
}
|
||||
|
||||
func (b *builder) selectStmt(s *ast.SelectStmt, label *lblock) {
|
||||
// First evaluate channel expressions.
|
||||
// TODO(adonovan): fix: evaluate only channel exprs here.
|
||||
for _, clause := range s.Body.List {
|
||||
if comm := clause.(*ast.CommClause).Comm; comm != nil {
|
||||
b.stmt(comm)
|
||||
}
|
||||
}
|
||||
|
||||
done := b.newBlock(KindSelectDone, s)
|
||||
if label != nil {
|
||||
label._break = done
|
||||
}
|
||||
|
||||
var defaultBody *[]ast.Stmt
|
||||
for _, cc := range s.Body.List {
|
||||
clause := cc.(*ast.CommClause)
|
||||
if clause.Comm == nil {
|
||||
defaultBody = &clause.Body
|
||||
continue
|
||||
}
|
||||
body := b.newBlock(KindSelectCaseBody, clause)
|
||||
next := b.newBlock(KindSelectAfterCase, clause)
|
||||
b.ifelse(body, next)
|
||||
b.current = body
|
||||
b.targets = &targets{
|
||||
tail: b.targets,
|
||||
_break: done,
|
||||
}
|
||||
switch comm := clause.Comm.(type) {
|
||||
case *ast.ExprStmt: // <-ch
|
||||
// nop
|
||||
case *ast.AssignStmt: // x := <-states[state].Chan
|
||||
b.add(comm.Lhs[0])
|
||||
}
|
||||
b.stmtList(clause.Body)
|
||||
b.targets = b.targets.tail
|
||||
b.jump(done)
|
||||
b.current = next
|
||||
}
|
||||
if defaultBody != nil {
|
||||
b.targets = &targets{
|
||||
tail: b.targets,
|
||||
_break: done,
|
||||
}
|
||||
b.stmtList(*defaultBody)
|
||||
b.targets = b.targets.tail
|
||||
b.jump(done)
|
||||
}
|
||||
b.current = done
|
||||
}
|
||||
|
||||
func (b *builder) forStmt(s *ast.ForStmt, label *lblock) {
|
||||
// ...init...
|
||||
// jump loop
|
||||
// loop:
|
||||
// if cond goto body else done
|
||||
// body:
|
||||
// ...body...
|
||||
// jump post
|
||||
// post: (target of continue)
|
||||
// ...post...
|
||||
// jump loop
|
||||
// done: (target of break)
|
||||
if s.Init != nil {
|
||||
b.stmt(s.Init)
|
||||
}
|
||||
body := b.newBlock(KindForBody, s)
|
||||
done := b.newBlock(KindForDone, s) // target of 'break'
|
||||
loop := body // target of back-edge
|
||||
if s.Cond != nil {
|
||||
loop = b.newBlock(KindForLoop, s)
|
||||
}
|
||||
cont := loop // target of 'continue'
|
||||
if s.Post != nil {
|
||||
cont = b.newBlock(KindForPost, s)
|
||||
}
|
||||
if label != nil {
|
||||
label._break = done
|
||||
label._continue = cont
|
||||
}
|
||||
b.jump(loop)
|
||||
b.current = loop
|
||||
if loop != body {
|
||||
b.add(s.Cond)
|
||||
b.ifelse(body, done)
|
||||
b.current = body
|
||||
}
|
||||
b.targets = &targets{
|
||||
tail: b.targets,
|
||||
_break: done,
|
||||
_continue: cont,
|
||||
}
|
||||
b.stmt(s.Body)
|
||||
b.targets = b.targets.tail
|
||||
b.jump(cont)
|
||||
|
||||
if s.Post != nil {
|
||||
b.current = cont
|
||||
b.stmt(s.Post)
|
||||
b.jump(loop) // back-edge
|
||||
}
|
||||
b.current = done
|
||||
}
|
||||
|
||||
func (b *builder) rangeStmt(s *ast.RangeStmt, label *lblock) {
|
||||
b.add(s.X)
|
||||
|
||||
if s.Key != nil {
|
||||
b.add(s.Key)
|
||||
}
|
||||
if s.Value != nil {
|
||||
b.add(s.Value)
|
||||
}
|
||||
|
||||
// ...
|
||||
// loop: (target of continue)
|
||||
// if ... goto body else done
|
||||
// body:
|
||||
// ...
|
||||
// jump loop
|
||||
// done: (target of break)
|
||||
|
||||
loop := b.newBlock(KindRangeLoop, s)
|
||||
b.jump(loop)
|
||||
b.current = loop
|
||||
|
||||
body := b.newBlock(KindRangeBody, s)
|
||||
done := b.newBlock(KindRangeDone, s)
|
||||
b.ifelse(body, done)
|
||||
b.current = body
|
||||
|
||||
if label != nil {
|
||||
label._break = done
|
||||
label._continue = loop
|
||||
}
|
||||
b.targets = &targets{
|
||||
tail: b.targets,
|
||||
_break: done,
|
||||
_continue: loop,
|
||||
}
|
||||
b.stmt(s.Body)
|
||||
b.targets = b.targets.tail
|
||||
b.jump(loop) // back-edge
|
||||
b.current = done
|
||||
}
|
||||
|
||||
// -------- helpers --------
|
||||
|
||||
// Destinations associated with unlabeled for/switch/select stmts.
|
||||
// We push/pop one of these as we enter/leave each construct and for
|
||||
// each BranchStmt we scan for the innermost target of the right type.
|
||||
type targets struct {
|
||||
tail *targets // rest of stack
|
||||
_break *Block
|
||||
_continue *Block
|
||||
_fallthrough *Block
|
||||
}
|
||||
|
||||
// Destinations associated with a labeled block.
|
||||
// We populate these as labels are encountered in forward gotos or
|
||||
// labeled statements.
|
||||
type lblock struct {
|
||||
_goto *Block
|
||||
_break *Block
|
||||
_continue *Block
|
||||
}
|
||||
|
||||
// labeledBlock returns the branch target associated with the
|
||||
// specified label, creating it if needed.
|
||||
func (b *builder) labeledBlock(label *ast.Ident, stmt *ast.LabeledStmt) *lblock {
|
||||
lb := b.lblocks[label.Name]
|
||||
if lb == nil {
|
||||
lb = &lblock{_goto: b.newBlock(KindLabel, nil)}
|
||||
if b.lblocks == nil {
|
||||
b.lblocks = make(map[string]*lblock)
|
||||
}
|
||||
b.lblocks[label.Name] = lb
|
||||
}
|
||||
// Fill in the label later (in case of forward goto).
|
||||
// Stmt may be set already if labels are duplicated (ill-typed).
|
||||
if stmt != nil && lb._goto.Stmt == nil {
|
||||
lb._goto.Stmt = stmt
|
||||
}
|
||||
return lb
|
||||
}
|
||||
|
||||
// newBlock appends a new unconnected basic block to b.cfg's block
|
||||
// slice and returns it.
|
||||
// It does not automatically become the current block.
|
||||
// comment is an optional string for more readable debugging output.
|
||||
func (b *builder) newBlock(kind BlockKind, stmt ast.Stmt) *Block {
|
||||
block := &Block{
|
||||
Index: int32(len(b.blocks)),
|
||||
Kind: kind,
|
||||
Stmt: stmt,
|
||||
}
|
||||
block.Succs = block.succs2[:0]
|
||||
b.blocks = append(b.blocks, block)
|
||||
return block
|
||||
}
|
||||
|
||||
func (b *builder) add(n ast.Node) {
|
||||
b.current.Nodes = append(b.current.Nodes, n)
|
||||
}
|
||||
|
||||
// jump adds an edge from the current block to the target block,
|
||||
// and sets b.current to nil.
|
||||
func (b *builder) jump(target *Block) {
|
||||
b.current.Succs = append(b.current.Succs, target)
|
||||
b.current = nil
|
||||
}
|
||||
|
||||
// ifelse emits edges from the current block to the t and f blocks,
|
||||
// and sets b.current to nil.
|
||||
func (b *builder) ifelse(t, f *Block) {
|
||||
b.current.Succs = append(b.current.Succs, t, f)
|
||||
b.current = nil
|
||||
}
|
||||
273
vendor/golang.org/x/tools/go/cfg/cfg.go
generated
vendored
Normal file
273
vendor/golang.org/x/tools/go/cfg/cfg.go
generated
vendored
Normal file
@@ -0,0 +1,273 @@
|
||||
// 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))
|
||||
}
|
||||
Reference in New Issue
Block a user