+++ /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
-}