513 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			513 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // 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.newUnreachableBlock("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.newUnreachableBlock("unreachable.return")
 | |
| 
 | |
| 	case *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; 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.newUnreachableBlock("unreachable.branch")
 | |
| 
 | |
| 	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) 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) newUnreachableBlock(comment string) *Block {
 | |
| 	block := b.newBlock(comment)
 | |
| 	block.unreachable = true
 | |
| 	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
 | |
| }
 | 
