--- /dev/null
+// 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 {
+ cfg *CFG
+ mayReturn func(*ast.CallExpr) bool
+ current *Block
+ lblocks map[*ast.Object]*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.DeferStmt,
+ *ast.EmptyStmt,
+ *ast.AssignStmt:
+ // No effect on control flow.
+ b.add(s)
+
+ 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("unreachable.call")
+ }
+
+ 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)
+ b.jump(label._goto)
+ b.current = label._goto
+ _s = s.Stmt
+ goto start // effectively: tailcall stmt(g, s.Stmt, label)
+
+ case *ast.ReturnStmt:
+ b.add(s)
+ b.current = b.newBlock("unreachable.return")
+
+ 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("if.then")
+ done := b.newBlock("if.done")
+ _else := done
+ if s.Else != nil {
+ _else = b.newBlock("if.else")
+ }
+ 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); 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); 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)._goto
+ }
+ }
+ if block == nil {
+ block = b.newBlock("undefined.branch")
+ }
+ b.jump(block)
+ b.current = b.newBlock("unreachable.branch")
+}
+
+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("switch.done")
+ 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("switch.body") // first case only
+ }
+
+ // Preallocate body block for the next case.
+ fallthru = done
+ if i+1 < ncases {
+ fallthru = b.newBlock("switch.body")
+ }
+
+ 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("switch.next")
+ 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("typeswitch.done")
+ 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("typeswitch.body")
+ var next *Block
+ for _, casetype := range cc.List {
+ next = b.newBlock("typeswitch.next")
+ // 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("select.done")
+ 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("select.body")
+ next := b.newBlock("select.next")
+ 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("for.body")
+ done := b.newBlock("for.done") // target of 'break'
+ loop := body // target of back-edge
+ if s.Cond != nil {
+ loop = b.newBlock("for.loop")
+ }
+ cont := loop // target of 'continue'
+ if s.Post != nil {
+ cont = b.newBlock("for.post")
+ }
+ 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("range.loop")
+ b.jump(loop)
+ b.current = loop
+
+ body := b.newBlock("range.body")
+ done := b.newBlock("range.done")
+ 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) *lblock {
+ lb := b.lblocks[label.Obj]
+ if lb == nil {
+ lb = &lblock{_goto: b.newBlock(label.Name)}
+ if b.lblocks == nil {
+ b.lblocks = make(map[*ast.Object]*lblock)
+ }
+ b.lblocks[label.Obj] = lb
+ }
+ 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(comment string) *Block {
+ g := b.cfg
+ block := &Block{
+ Index: int32(len(g.Blocks)),
+ comment: comment,
+ }
+ block.Succs = block.succs2[:0]
+ g.Blocks = append(g.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
+}