Significantly improve CPU and memory usage when not using SSA-powered linters. Improve readability of go/packages errors. Improve debugging capabilities and write doc about debugging.
		
			
				
	
	
		
			511 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			511 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.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
 | 
						|
}
 |