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