1050 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			1050 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package vrp
 | |
| 
 | |
| // TODO(dh) widening and narrowing have a lot of code in common. Make
 | |
| // it reusable.
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"go/constant"
 | |
| 	"go/token"
 | |
| 	"go/types"
 | |
| 	"math/big"
 | |
| 	"sort"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/golangci/go-tools/ssa"
 | |
| )
 | |
| 
 | |
| type Future interface {
 | |
| 	Constraint
 | |
| 	Futures() []ssa.Value
 | |
| 	Resolve()
 | |
| 	IsKnown() bool
 | |
| 	MarkUnresolved()
 | |
| 	MarkResolved()
 | |
| 	IsResolved() bool
 | |
| }
 | |
| 
 | |
| type Range interface {
 | |
| 	Union(other Range) Range
 | |
| 	IsKnown() bool
 | |
| }
 | |
| 
 | |
| type Constraint interface {
 | |
| 	Y() ssa.Value
 | |
| 	isConstraint()
 | |
| 	String() string
 | |
| 	Eval(*Graph) Range
 | |
| 	Operands() []ssa.Value
 | |
| }
 | |
| 
 | |
| type aConstraint struct {
 | |
| 	y ssa.Value
 | |
| }
 | |
| 
 | |
| func NewConstraint(y ssa.Value) aConstraint {
 | |
| 	return aConstraint{y}
 | |
| }
 | |
| 
 | |
| func (aConstraint) isConstraint()  {}
 | |
| func (c aConstraint) Y() ssa.Value { return c.y }
 | |
| 
 | |
| type PhiConstraint struct {
 | |
| 	aConstraint
 | |
| 	Vars []ssa.Value
 | |
| }
 | |
| 
 | |
| func NewPhiConstraint(vars []ssa.Value, y ssa.Value) Constraint {
 | |
| 	uniqm := map[ssa.Value]struct{}{}
 | |
| 	for _, v := range vars {
 | |
| 		uniqm[v] = struct{}{}
 | |
| 	}
 | |
| 	var uniq []ssa.Value
 | |
| 	for v := range uniqm {
 | |
| 		uniq = append(uniq, v)
 | |
| 	}
 | |
| 	return &PhiConstraint{
 | |
| 		aConstraint: NewConstraint(y),
 | |
| 		Vars:        uniq,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (c *PhiConstraint) Operands() []ssa.Value {
 | |
| 	return c.Vars
 | |
| }
 | |
| 
 | |
| func (c *PhiConstraint) Eval(g *Graph) Range {
 | |
| 	i := Range(nil)
 | |
| 	for _, v := range c.Vars {
 | |
| 		i = g.Range(v).Union(i)
 | |
| 	}
 | |
| 	return i
 | |
| }
 | |
| 
 | |
| func (c *PhiConstraint) String() string {
 | |
| 	names := make([]string, len(c.Vars))
 | |
| 	for i, v := range c.Vars {
 | |
| 		names[i] = v.Name()
 | |
| 	}
 | |
| 	return fmt.Sprintf("%s = φ(%s)", c.Y().Name(), strings.Join(names, ", "))
 | |
| }
 | |
| 
 | |
| func isSupportedType(typ types.Type) bool {
 | |
| 	switch typ := typ.Underlying().(type) {
 | |
| 	case *types.Basic:
 | |
| 		switch typ.Kind() {
 | |
| 		case types.String, types.UntypedString:
 | |
| 			return true
 | |
| 		default:
 | |
| 			if (typ.Info() & types.IsInteger) == 0 {
 | |
| 				return false
 | |
| 			}
 | |
| 		}
 | |
| 	case *types.Chan:
 | |
| 		return true
 | |
| 	case *types.Slice:
 | |
| 		return true
 | |
| 	default:
 | |
| 		return false
 | |
| 	}
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| func ConstantToZ(c constant.Value) Z {
 | |
| 	s := constant.ToInt(c).ExactString()
 | |
| 	n := &big.Int{}
 | |
| 	n.SetString(s, 10)
 | |
| 	return NewBigZ(n)
 | |
| }
 | |
| 
 | |
| func sigmaInteger(g *Graph, ins *ssa.Sigma, cond *ssa.BinOp, ops []*ssa.Value) Constraint {
 | |
| 	op := cond.Op
 | |
| 	if !ins.Branch {
 | |
| 		op = (invertToken(op))
 | |
| 	}
 | |
| 
 | |
| 	switch op {
 | |
| 	case token.EQL, token.GTR, token.GEQ, token.LSS, token.LEQ:
 | |
| 	default:
 | |
| 		return nil
 | |
| 	}
 | |
| 	var a, b ssa.Value
 | |
| 	if (*ops[0]) == ins.X {
 | |
| 		a = *ops[0]
 | |
| 		b = *ops[1]
 | |
| 	} else {
 | |
| 		a = *ops[1]
 | |
| 		b = *ops[0]
 | |
| 		op = flipToken(op)
 | |
| 	}
 | |
| 	return NewIntIntersectionConstraint(a, b, op, g.ranges, ins)
 | |
| }
 | |
| 
 | |
| func sigmaString(g *Graph, ins *ssa.Sigma, cond *ssa.BinOp, ops []*ssa.Value) Constraint {
 | |
| 	op := cond.Op
 | |
| 	if !ins.Branch {
 | |
| 		op = (invertToken(op))
 | |
| 	}
 | |
| 
 | |
| 	switch op {
 | |
| 	case token.EQL, token.GTR, token.GEQ, token.LSS, token.LEQ:
 | |
| 	default:
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	if ((*ops[0]).Type().Underlying().(*types.Basic).Info() & types.IsString) == 0 {
 | |
| 		var a, b ssa.Value
 | |
| 		call, ok := (*ops[0]).(*ssa.Call)
 | |
| 		if ok && call.Common().Args[0] == ins.X {
 | |
| 			a = *ops[0]
 | |
| 			b = *ops[1]
 | |
| 		} else {
 | |
| 			a = *ops[1]
 | |
| 			b = *ops[0]
 | |
| 			op = flipToken(op)
 | |
| 		}
 | |
| 		return NewStringIntersectionConstraint(a, b, op, g.ranges, ins)
 | |
| 	}
 | |
| 	var a, b ssa.Value
 | |
| 	if (*ops[0]) == ins.X {
 | |
| 		a = *ops[0]
 | |
| 		b = *ops[1]
 | |
| 	} else {
 | |
| 		a = *ops[1]
 | |
| 		b = *ops[0]
 | |
| 		op = flipToken(op)
 | |
| 	}
 | |
| 	return NewStringIntersectionConstraint(a, b, op, g.ranges, ins)
 | |
| }
 | |
| 
 | |
| func sigmaSlice(g *Graph, ins *ssa.Sigma, cond *ssa.BinOp, ops []*ssa.Value) Constraint {
 | |
| 	// TODO(dh) sigmaSlice and sigmaString are a lot alike. Can they
 | |
| 	// be merged?
 | |
| 	//
 | |
| 	// XXX support futures
 | |
| 
 | |
| 	op := cond.Op
 | |
| 	if !ins.Branch {
 | |
| 		op = (invertToken(op))
 | |
| 	}
 | |
| 
 | |
| 	k, ok := (*ops[1]).(*ssa.Const)
 | |
| 	// XXX investigate in what cases this wouldn't be a Const
 | |
| 	//
 | |
| 	// XXX what if left and right are swapped?
 | |
| 	if !ok {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	call, ok := (*ops[0]).(*ssa.Call)
 | |
| 	if !ok {
 | |
| 		return nil
 | |
| 	}
 | |
| 	builtin, ok := call.Common().Value.(*ssa.Builtin)
 | |
| 	if !ok {
 | |
| 		return nil
 | |
| 	}
 | |
| 	if builtin.Name() != "len" {
 | |
| 		return nil
 | |
| 	}
 | |
| 	callops := call.Operands(nil)
 | |
| 
 | |
| 	v := ConstantToZ(k.Value)
 | |
| 	c := NewSliceIntersectionConstraint(*callops[1], IntInterval{}, ins).(*SliceIntersectionConstraint)
 | |
| 	switch op {
 | |
| 	case token.EQL:
 | |
| 		c.I = NewIntInterval(v, v)
 | |
| 	case token.GTR, token.GEQ:
 | |
| 		off := int64(0)
 | |
| 		if cond.Op == token.GTR {
 | |
| 			off = 1
 | |
| 		}
 | |
| 		c.I = NewIntInterval(
 | |
| 			v.Add(NewZ(off)),
 | |
| 			PInfinity,
 | |
| 		)
 | |
| 	case token.LSS, token.LEQ:
 | |
| 		off := int64(0)
 | |
| 		if cond.Op == token.LSS {
 | |
| 			off = -1
 | |
| 		}
 | |
| 		c.I = NewIntInterval(
 | |
| 			NInfinity,
 | |
| 			v.Add(NewZ(off)),
 | |
| 		)
 | |
| 	default:
 | |
| 		return nil
 | |
| 	}
 | |
| 	return c
 | |
| }
 | |
| 
 | |
| func BuildGraph(f *ssa.Function) *Graph {
 | |
| 	g := &Graph{
 | |
| 		Vertices: map[interface{}]*Vertex{},
 | |
| 		ranges:   Ranges{},
 | |
| 	}
 | |
| 
 | |
| 	var cs []Constraint
 | |
| 
 | |
| 	ops := make([]*ssa.Value, 16)
 | |
| 	seen := map[ssa.Value]bool{}
 | |
| 	for _, block := range f.Blocks {
 | |
| 		for _, ins := range block.Instrs {
 | |
| 			ops = ins.Operands(ops[:0])
 | |
| 			for _, op := range ops {
 | |
| 				if c, ok := (*op).(*ssa.Const); ok {
 | |
| 					if seen[c] {
 | |
| 						continue
 | |
| 					}
 | |
| 					seen[c] = true
 | |
| 					if c.Value == nil {
 | |
| 						switch c.Type().Underlying().(type) {
 | |
| 						case *types.Slice:
 | |
| 							cs = append(cs, NewSliceIntervalConstraint(NewIntInterval(NewZ(0), NewZ(0)), c))
 | |
| 						}
 | |
| 						continue
 | |
| 					}
 | |
| 					switch c.Value.Kind() {
 | |
| 					case constant.Int:
 | |
| 						v := ConstantToZ(c.Value)
 | |
| 						cs = append(cs, NewIntIntervalConstraint(NewIntInterval(v, v), c))
 | |
| 					case constant.String:
 | |
| 						s := constant.StringVal(c.Value)
 | |
| 						n := NewZ(int64(len(s)))
 | |
| 						cs = append(cs, NewStringIntervalConstraint(NewIntInterval(n, n), c))
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	for _, block := range f.Blocks {
 | |
| 		for _, ins := range block.Instrs {
 | |
| 			switch ins := ins.(type) {
 | |
| 			case *ssa.Convert:
 | |
| 				switch v := ins.Type().Underlying().(type) {
 | |
| 				case *types.Basic:
 | |
| 					if (v.Info() & types.IsInteger) == 0 {
 | |
| 						continue
 | |
| 					}
 | |
| 					cs = append(cs, NewIntConversionConstraint(ins.X, ins))
 | |
| 				}
 | |
| 			case *ssa.Call:
 | |
| 				if static := ins.Common().StaticCallee(); static != nil {
 | |
| 					if fn, ok := static.Object().(*types.Func); ok {
 | |
| 						switch fn.FullName() {
 | |
| 						case "bytes.Index", "bytes.IndexAny", "bytes.IndexByte",
 | |
| 							"bytes.IndexFunc", "bytes.IndexRune", "bytes.LastIndex",
 | |
| 							"bytes.LastIndexAny", "bytes.LastIndexByte", "bytes.LastIndexFunc",
 | |
| 							"strings.Index", "strings.IndexAny", "strings.IndexByte",
 | |
| 							"strings.IndexFunc", "strings.IndexRune", "strings.LastIndex",
 | |
| 							"strings.LastIndexAny", "strings.LastIndexByte", "strings.LastIndexFunc":
 | |
| 							// TODO(dh): instead of limiting by +∞,
 | |
| 							// limit by the upper bound of the passed
 | |
| 							// string
 | |
| 							cs = append(cs, NewIntIntervalConstraint(NewIntInterval(NewZ(-1), PInfinity), ins))
 | |
| 						case "bytes.Title", "bytes.ToLower", "bytes.ToTitle", "bytes.ToUpper",
 | |
| 							"strings.Title", "strings.ToLower", "strings.ToTitle", "strings.ToUpper":
 | |
| 							cs = append(cs, NewCopyConstraint(ins.Common().Args[0], ins))
 | |
| 						case "bytes.ToLowerSpecial", "bytes.ToTitleSpecial", "bytes.ToUpperSpecial",
 | |
| 							"strings.ToLowerSpecial", "strings.ToTitleSpecial", "strings.ToUpperSpecial":
 | |
| 							cs = append(cs, NewCopyConstraint(ins.Common().Args[1], ins))
 | |
| 						case "bytes.Compare", "strings.Compare":
 | |
| 							cs = append(cs, NewIntIntervalConstraint(NewIntInterval(NewZ(-1), NewZ(1)), ins))
 | |
| 						case "bytes.Count", "strings.Count":
 | |
| 							// TODO(dh): instead of limiting by +∞,
 | |
| 							// limit by the upper bound of the passed
 | |
| 							// string.
 | |
| 							cs = append(cs, NewIntIntervalConstraint(NewIntInterval(NewZ(0), PInfinity), ins))
 | |
| 						case "bytes.Map", "bytes.TrimFunc", "bytes.TrimLeft", "bytes.TrimLeftFunc",
 | |
| 							"bytes.TrimRight", "bytes.TrimRightFunc", "bytes.TrimSpace",
 | |
| 							"strings.Map", "strings.TrimFunc", "strings.TrimLeft", "strings.TrimLeftFunc",
 | |
| 							"strings.TrimRight", "strings.TrimRightFunc", "strings.TrimSpace":
 | |
| 							// TODO(dh): lower = 0, upper = upper of passed string
 | |
| 						case "bytes.TrimPrefix", "bytes.TrimSuffix",
 | |
| 							"strings.TrimPrefix", "strings.TrimSuffix":
 | |
| 							// TODO(dh) range between "unmodified" and len(cutset) removed
 | |
| 						case "(*bytes.Buffer).Cap", "(*bytes.Buffer).Len", "(*bytes.Reader).Len", "(*bytes.Reader).Size":
 | |
| 							cs = append(cs, NewIntIntervalConstraint(NewIntInterval(NewZ(0), PInfinity), ins))
 | |
| 						}
 | |
| 					}
 | |
| 				}
 | |
| 				builtin, ok := ins.Common().Value.(*ssa.Builtin)
 | |
| 				ops := ins.Operands(nil)
 | |
| 				if !ok {
 | |
| 					continue
 | |
| 				}
 | |
| 				switch builtin.Name() {
 | |
| 				case "len":
 | |
| 					switch op1 := (*ops[1]).Type().Underlying().(type) {
 | |
| 					case *types.Basic:
 | |
| 						if op1.Kind() == types.String || op1.Kind() == types.UntypedString {
 | |
| 							cs = append(cs, NewStringLengthConstraint(*ops[1], ins))
 | |
| 						}
 | |
| 					case *types.Slice:
 | |
| 						cs = append(cs, NewSliceLengthConstraint(*ops[1], ins))
 | |
| 					}
 | |
| 
 | |
| 				case "append":
 | |
| 					cs = append(cs, NewSliceAppendConstraint(ins.Common().Args[0], ins.Common().Args[1], ins))
 | |
| 				}
 | |
| 			case *ssa.BinOp:
 | |
| 				ops := ins.Operands(nil)
 | |
| 				basic, ok := (*ops[0]).Type().Underlying().(*types.Basic)
 | |
| 				if !ok {
 | |
| 					continue
 | |
| 				}
 | |
| 				switch basic.Kind() {
 | |
| 				case types.Int, types.Int8, types.Int16, types.Int32, types.Int64,
 | |
| 					types.Uint, types.Uint8, types.Uint16, types.Uint32, types.Uint64, types.UntypedInt:
 | |
| 					fns := map[token.Token]func(ssa.Value, ssa.Value, ssa.Value) Constraint{
 | |
| 						token.ADD: NewIntAddConstraint,
 | |
| 						token.SUB: NewIntSubConstraint,
 | |
| 						token.MUL: NewIntMulConstraint,
 | |
| 						// XXX support QUO, REM, SHL, SHR
 | |
| 					}
 | |
| 					fn, ok := fns[ins.Op]
 | |
| 					if ok {
 | |
| 						cs = append(cs, fn(*ops[0], *ops[1], ins))
 | |
| 					}
 | |
| 				case types.String, types.UntypedString:
 | |
| 					if ins.Op == token.ADD {
 | |
| 						cs = append(cs, NewStringConcatConstraint(*ops[0], *ops[1], ins))
 | |
| 					}
 | |
| 				}
 | |
| 			case *ssa.Slice:
 | |
| 				typ := ins.X.Type().Underlying()
 | |
| 				switch typ := typ.(type) {
 | |
| 				case *types.Basic:
 | |
| 					cs = append(cs, NewStringSliceConstraint(ins.X, ins.Low, ins.High, ins))
 | |
| 				case *types.Slice:
 | |
| 					cs = append(cs, NewSliceSliceConstraint(ins.X, ins.Low, ins.High, ins))
 | |
| 				case *types.Array:
 | |
| 					cs = append(cs, NewArraySliceConstraint(ins.X, ins.Low, ins.High, ins))
 | |
| 				case *types.Pointer:
 | |
| 					if _, ok := typ.Elem().(*types.Array); !ok {
 | |
| 						continue
 | |
| 					}
 | |
| 					cs = append(cs, NewArraySliceConstraint(ins.X, ins.Low, ins.High, ins))
 | |
| 				}
 | |
| 			case *ssa.Phi:
 | |
| 				if !isSupportedType(ins.Type()) {
 | |
| 					continue
 | |
| 				}
 | |
| 				ops := ins.Operands(nil)
 | |
| 				dops := make([]ssa.Value, len(ops))
 | |
| 				for i, op := range ops {
 | |
| 					dops[i] = *op
 | |
| 				}
 | |
| 				cs = append(cs, NewPhiConstraint(dops, ins))
 | |
| 			case *ssa.Sigma:
 | |
| 				pred := ins.Block().Preds[0]
 | |
| 				instrs := pred.Instrs
 | |
| 				cond, ok := instrs[len(instrs)-1].(*ssa.If).Cond.(*ssa.BinOp)
 | |
| 				ops := cond.Operands(nil)
 | |
| 				if !ok {
 | |
| 					continue
 | |
| 				}
 | |
| 				switch typ := ins.Type().Underlying().(type) {
 | |
| 				case *types.Basic:
 | |
| 					var c Constraint
 | |
| 					switch typ.Kind() {
 | |
| 					case types.Int, types.Int8, types.Int16, types.Int32, types.Int64,
 | |
| 						types.Uint, types.Uint8, types.Uint16, types.Uint32, types.Uint64, types.UntypedInt:
 | |
| 						c = sigmaInteger(g, ins, cond, ops)
 | |
| 					case types.String, types.UntypedString:
 | |
| 						c = sigmaString(g, ins, cond, ops)
 | |
| 					}
 | |
| 					if c != nil {
 | |
| 						cs = append(cs, c)
 | |
| 					}
 | |
| 				case *types.Slice:
 | |
| 					c := sigmaSlice(g, ins, cond, ops)
 | |
| 					if c != nil {
 | |
| 						cs = append(cs, c)
 | |
| 					}
 | |
| 				default:
 | |
| 					//log.Printf("unsupported sigma type %T", typ) // XXX
 | |
| 				}
 | |
| 			case *ssa.MakeChan:
 | |
| 				cs = append(cs, NewMakeChannelConstraint(ins.Size, ins))
 | |
| 			case *ssa.MakeSlice:
 | |
| 				cs = append(cs, NewMakeSliceConstraint(ins.Len, ins))
 | |
| 			case *ssa.ChangeType:
 | |
| 				switch ins.X.Type().Underlying().(type) {
 | |
| 				case *types.Chan:
 | |
| 					cs = append(cs, NewChannelChangeTypeConstraint(ins.X, ins))
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for _, c := range cs {
 | |
| 		if c == nil {
 | |
| 			panic("nil constraint")
 | |
| 		}
 | |
| 		// If V is used in constraint C, then we create an edge V->C
 | |
| 		for _, op := range c.Operands() {
 | |
| 			g.AddEdge(op, c, false)
 | |
| 		}
 | |
| 		if c, ok := c.(Future); ok {
 | |
| 			for _, op := range c.Futures() {
 | |
| 				g.AddEdge(op, c, true)
 | |
| 			}
 | |
| 		}
 | |
| 		// If constraint C defines variable V, then we create an edge
 | |
| 		// C->V
 | |
| 		g.AddEdge(c, c.Y(), false)
 | |
| 	}
 | |
| 
 | |
| 	g.FindSCCs()
 | |
| 	g.sccEdges = make([][]Edge, len(g.SCCs))
 | |
| 	g.futures = make([][]Future, len(g.SCCs))
 | |
| 	for _, e := range g.Edges {
 | |
| 		g.sccEdges[e.From.SCC] = append(g.sccEdges[e.From.SCC], e)
 | |
| 		if !e.control {
 | |
| 			continue
 | |
| 		}
 | |
| 		if c, ok := e.To.Value.(Future); ok {
 | |
| 			g.futures[e.From.SCC] = append(g.futures[e.From.SCC], c)
 | |
| 		}
 | |
| 	}
 | |
| 	return g
 | |
| }
 | |
| 
 | |
| func (g *Graph) Solve() Ranges {
 | |
| 	var consts []Z
 | |
| 	off := NewZ(1)
 | |
| 	for _, n := range g.Vertices {
 | |
| 		if c, ok := n.Value.(*ssa.Const); ok {
 | |
| 			basic, ok := c.Type().Underlying().(*types.Basic)
 | |
| 			if !ok {
 | |
| 				continue
 | |
| 			}
 | |
| 			if (basic.Info() & types.IsInteger) != 0 {
 | |
| 				z := ConstantToZ(c.Value)
 | |
| 				consts = append(consts, z)
 | |
| 				consts = append(consts, z.Add(off))
 | |
| 				consts = append(consts, z.Sub(off))
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 	}
 | |
| 	sort.Sort(Zs(consts))
 | |
| 
 | |
| 	for scc, vertices := range g.SCCs {
 | |
| 		n := 0
 | |
| 		n = len(vertices)
 | |
| 		if n == 1 {
 | |
| 			g.resolveFutures(scc)
 | |
| 			v := vertices[0]
 | |
| 			if v, ok := v.Value.(ssa.Value); ok {
 | |
| 				switch typ := v.Type().Underlying().(type) {
 | |
| 				case *types.Basic:
 | |
| 					switch typ.Kind() {
 | |
| 					case types.String, types.UntypedString:
 | |
| 						if !g.Range(v).(StringInterval).IsKnown() {
 | |
| 							g.SetRange(v, StringInterval{NewIntInterval(NewZ(0), PInfinity)})
 | |
| 						}
 | |
| 					default:
 | |
| 						if !g.Range(v).(IntInterval).IsKnown() {
 | |
| 							g.SetRange(v, InfinityFor(v))
 | |
| 						}
 | |
| 					}
 | |
| 				case *types.Chan:
 | |
| 					if !g.Range(v).(ChannelInterval).IsKnown() {
 | |
| 						g.SetRange(v, ChannelInterval{NewIntInterval(NewZ(0), PInfinity)})
 | |
| 					}
 | |
| 				case *types.Slice:
 | |
| 					if !g.Range(v).(SliceInterval).IsKnown() {
 | |
| 						g.SetRange(v, SliceInterval{NewIntInterval(NewZ(0), PInfinity)})
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 			if c, ok := v.Value.(Constraint); ok {
 | |
| 				g.SetRange(c.Y(), c.Eval(g))
 | |
| 			}
 | |
| 		} else {
 | |
| 			uses := g.uses(scc)
 | |
| 			entries := g.entries(scc)
 | |
| 			for len(entries) > 0 {
 | |
| 				v := entries[len(entries)-1]
 | |
| 				entries = entries[:len(entries)-1]
 | |
| 				for _, use := range uses[v] {
 | |
| 					if g.widen(use, consts) {
 | |
| 						entries = append(entries, use.Y())
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			g.resolveFutures(scc)
 | |
| 
 | |
| 			// XXX this seems to be necessary, but shouldn't be.
 | |
| 			// removing it leads to nil pointer derefs; investigate
 | |
| 			// where we're not setting values correctly.
 | |
| 			for _, n := range vertices {
 | |
| 				if v, ok := n.Value.(ssa.Value); ok {
 | |
| 					i, ok := g.Range(v).(IntInterval)
 | |
| 					if !ok {
 | |
| 						continue
 | |
| 					}
 | |
| 					if !i.IsKnown() {
 | |
| 						g.SetRange(v, InfinityFor(v))
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			actives := g.actives(scc)
 | |
| 			for len(actives) > 0 {
 | |
| 				v := actives[len(actives)-1]
 | |
| 				actives = actives[:len(actives)-1]
 | |
| 				for _, use := range uses[v] {
 | |
| 					if g.narrow(use) {
 | |
| 						actives = append(actives, use.Y())
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		// propagate scc
 | |
| 		for _, edge := range g.sccEdges[scc] {
 | |
| 			if edge.control {
 | |
| 				continue
 | |
| 			}
 | |
| 			if edge.From.SCC == edge.To.SCC {
 | |
| 				continue
 | |
| 			}
 | |
| 			if c, ok := edge.To.Value.(Constraint); ok {
 | |
| 				g.SetRange(c.Y(), c.Eval(g))
 | |
| 			}
 | |
| 			if c, ok := edge.To.Value.(Future); ok {
 | |
| 				if !c.IsKnown() {
 | |
| 					c.MarkUnresolved()
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for v, r := range g.ranges {
 | |
| 		i, ok := r.(IntInterval)
 | |
| 		if !ok {
 | |
| 			continue
 | |
| 		}
 | |
| 		if (v.Type().Underlying().(*types.Basic).Info() & types.IsUnsigned) == 0 {
 | |
| 			if i.Upper != PInfinity {
 | |
| 				s := &types.StdSizes{
 | |
| 					// XXX is it okay to assume the largest word size, or do we
 | |
| 					// need to be platform specific?
 | |
| 					WordSize: 8,
 | |
| 					MaxAlign: 1,
 | |
| 				}
 | |
| 				bits := (s.Sizeof(v.Type()) * 8) - 1
 | |
| 				n := big.NewInt(1)
 | |
| 				n = n.Lsh(n, uint(bits))
 | |
| 				upper, lower := &big.Int{}, &big.Int{}
 | |
| 				upper.Sub(n, big.NewInt(1))
 | |
| 				lower.Neg(n)
 | |
| 
 | |
| 				if i.Upper.Cmp(NewBigZ(upper)) == 1 {
 | |
| 					i = NewIntInterval(NInfinity, PInfinity)
 | |
| 				} else if i.Lower.Cmp(NewBigZ(lower)) == -1 {
 | |
| 					i = NewIntInterval(NInfinity, PInfinity)
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		g.ranges[v] = i
 | |
| 	}
 | |
| 
 | |
| 	return g.ranges
 | |
| }
 | |
| 
 | |
| func VertexString(v *Vertex) string {
 | |
| 	switch v := v.Value.(type) {
 | |
| 	case Constraint:
 | |
| 		return v.String()
 | |
| 	case ssa.Value:
 | |
| 		return v.Name()
 | |
| 	case nil:
 | |
| 		return "BUG: nil vertex value"
 | |
| 	default:
 | |
| 		panic(fmt.Sprintf("unexpected type %T", v))
 | |
| 	}
 | |
| }
 | |
| 
 | |
| type Vertex struct {
 | |
| 	Value   interface{} // one of Constraint or ssa.Value
 | |
| 	SCC     int
 | |
| 	index   int
 | |
| 	lowlink int
 | |
| 	stack   bool
 | |
| 
 | |
| 	Succs []Edge
 | |
| }
 | |
| 
 | |
| type Ranges map[ssa.Value]Range
 | |
| 
 | |
| func (r Ranges) Get(x ssa.Value) Range {
 | |
| 	if x == nil {
 | |
| 		return nil
 | |
| 	}
 | |
| 	i, ok := r[x]
 | |
| 	if !ok {
 | |
| 		switch x := x.Type().Underlying().(type) {
 | |
| 		case *types.Basic:
 | |
| 			switch x.Kind() {
 | |
| 			case types.String, types.UntypedString:
 | |
| 				return StringInterval{}
 | |
| 			default:
 | |
| 				return IntInterval{}
 | |
| 			}
 | |
| 		case *types.Chan:
 | |
| 			return ChannelInterval{}
 | |
| 		case *types.Slice:
 | |
| 			return SliceInterval{}
 | |
| 		}
 | |
| 	}
 | |
| 	return i
 | |
| }
 | |
| 
 | |
| type Graph struct {
 | |
| 	Vertices map[interface{}]*Vertex
 | |
| 	Edges    []Edge
 | |
| 	SCCs     [][]*Vertex
 | |
| 	ranges   Ranges
 | |
| 
 | |
| 	// map SCCs to futures
 | |
| 	futures [][]Future
 | |
| 	// map SCCs to edges
 | |
| 	sccEdges [][]Edge
 | |
| }
 | |
| 
 | |
| func (g Graph) Graphviz() string {
 | |
| 	var lines []string
 | |
| 	lines = append(lines, "digraph{")
 | |
| 	ids := map[interface{}]int{}
 | |
| 	i := 1
 | |
| 	for _, v := range g.Vertices {
 | |
| 		ids[v] = i
 | |
| 		shape := "box"
 | |
| 		if _, ok := v.Value.(ssa.Value); ok {
 | |
| 			shape = "oval"
 | |
| 		}
 | |
| 		lines = append(lines, fmt.Sprintf(`n%d [shape="%s", label=%q, colorscheme=spectral11, style="filled", fillcolor="%d"]`,
 | |
| 			i, shape, VertexString(v), (v.SCC%11)+1))
 | |
| 		i++
 | |
| 	}
 | |
| 	for _, e := range g.Edges {
 | |
| 		style := "solid"
 | |
| 		if e.control {
 | |
| 			style = "dashed"
 | |
| 		}
 | |
| 		lines = append(lines, fmt.Sprintf(`n%d -> n%d [style="%s"]`, ids[e.From], ids[e.To], style))
 | |
| 	}
 | |
| 	lines = append(lines, "}")
 | |
| 	return strings.Join(lines, "\n")
 | |
| }
 | |
| 
 | |
| func (g *Graph) SetRange(x ssa.Value, r Range) {
 | |
| 	g.ranges[x] = r
 | |
| }
 | |
| 
 | |
| func (g *Graph) Range(x ssa.Value) Range {
 | |
| 	return g.ranges.Get(x)
 | |
| }
 | |
| 
 | |
| func (g *Graph) widen(c Constraint, consts []Z) bool {
 | |
| 	setRange := func(i Range) {
 | |
| 		g.SetRange(c.Y(), i)
 | |
| 	}
 | |
| 	widenIntInterval := func(oi, ni IntInterval) (IntInterval, bool) {
 | |
| 		if !ni.IsKnown() {
 | |
| 			return oi, false
 | |
| 		}
 | |
| 		nlc := NInfinity
 | |
| 		nuc := PInfinity
 | |
| 		for _, co := range consts {
 | |
| 			if co.Cmp(ni.Lower) <= 0 {
 | |
| 				nlc = co
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 		for _, co := range consts {
 | |
| 			if co.Cmp(ni.Upper) >= 0 {
 | |
| 				nuc = co
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if !oi.IsKnown() {
 | |
| 			return ni, true
 | |
| 		}
 | |
| 		if ni.Lower.Cmp(oi.Lower) == -1 && ni.Upper.Cmp(oi.Upper) == 1 {
 | |
| 			return NewIntInterval(nlc, nuc), true
 | |
| 		}
 | |
| 		if ni.Lower.Cmp(oi.Lower) == -1 {
 | |
| 			return NewIntInterval(nlc, oi.Upper), true
 | |
| 		}
 | |
| 		if ni.Upper.Cmp(oi.Upper) == 1 {
 | |
| 			return NewIntInterval(oi.Lower, nuc), true
 | |
| 		}
 | |
| 		return oi, false
 | |
| 	}
 | |
| 	switch oi := g.Range(c.Y()).(type) {
 | |
| 	case IntInterval:
 | |
| 		ni := c.Eval(g).(IntInterval)
 | |
| 		si, changed := widenIntInterval(oi, ni)
 | |
| 		if changed {
 | |
| 			setRange(si)
 | |
| 			return true
 | |
| 		}
 | |
| 		return false
 | |
| 	case StringInterval:
 | |
| 		ni := c.Eval(g).(StringInterval)
 | |
| 		si, changed := widenIntInterval(oi.Length, ni.Length)
 | |
| 		if changed {
 | |
| 			setRange(StringInterval{si})
 | |
| 			return true
 | |
| 		}
 | |
| 		return false
 | |
| 	case SliceInterval:
 | |
| 		ni := c.Eval(g).(SliceInterval)
 | |
| 		si, changed := widenIntInterval(oi.Length, ni.Length)
 | |
| 		if changed {
 | |
| 			setRange(SliceInterval{si})
 | |
| 			return true
 | |
| 		}
 | |
| 		return false
 | |
| 	default:
 | |
| 		return false
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (g *Graph) narrow(c Constraint) bool {
 | |
| 	narrowIntInterval := func(oi, ni IntInterval) (IntInterval, bool) {
 | |
| 		oLower := oi.Lower
 | |
| 		oUpper := oi.Upper
 | |
| 		nLower := ni.Lower
 | |
| 		nUpper := ni.Upper
 | |
| 
 | |
| 		if oLower == NInfinity && nLower != NInfinity {
 | |
| 			return NewIntInterval(nLower, oUpper), true
 | |
| 		}
 | |
| 		if oUpper == PInfinity && nUpper != PInfinity {
 | |
| 			return NewIntInterval(oLower, nUpper), true
 | |
| 		}
 | |
| 		if oLower.Cmp(nLower) == 1 {
 | |
| 			return NewIntInterval(nLower, oUpper), true
 | |
| 		}
 | |
| 		if oUpper.Cmp(nUpper) == -1 {
 | |
| 			return NewIntInterval(oLower, nUpper), true
 | |
| 		}
 | |
| 		return oi, false
 | |
| 	}
 | |
| 	switch oi := g.Range(c.Y()).(type) {
 | |
| 	case IntInterval:
 | |
| 		ni := c.Eval(g).(IntInterval)
 | |
| 		si, changed := narrowIntInterval(oi, ni)
 | |
| 		if changed {
 | |
| 			g.SetRange(c.Y(), si)
 | |
| 			return true
 | |
| 		}
 | |
| 		return false
 | |
| 	case StringInterval:
 | |
| 		ni := c.Eval(g).(StringInterval)
 | |
| 		si, changed := narrowIntInterval(oi.Length, ni.Length)
 | |
| 		if changed {
 | |
| 			g.SetRange(c.Y(), StringInterval{si})
 | |
| 			return true
 | |
| 		}
 | |
| 		return false
 | |
| 	case SliceInterval:
 | |
| 		ni := c.Eval(g).(SliceInterval)
 | |
| 		si, changed := narrowIntInterval(oi.Length, ni.Length)
 | |
| 		if changed {
 | |
| 			g.SetRange(c.Y(), SliceInterval{si})
 | |
| 			return true
 | |
| 		}
 | |
| 		return false
 | |
| 	default:
 | |
| 		return false
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (g *Graph) resolveFutures(scc int) {
 | |
| 	for _, c := range g.futures[scc] {
 | |
| 		c.Resolve()
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (g *Graph) entries(scc int) []ssa.Value {
 | |
| 	var entries []ssa.Value
 | |
| 	for _, n := range g.Vertices {
 | |
| 		if n.SCC != scc {
 | |
| 			continue
 | |
| 		}
 | |
| 		if v, ok := n.Value.(ssa.Value); ok {
 | |
| 			// XXX avoid quadratic runtime
 | |
| 			//
 | |
| 			// XXX I cannot think of any code where the future and its
 | |
| 			// variables aren't in the same SCC, in which case this
 | |
| 			// code isn't very useful (the variables won't be resolved
 | |
| 			// yet). Before we have a cross-SCC example, however, we
 | |
| 			// can't really verify that this code is working
 | |
| 			// correctly, or indeed doing anything useful.
 | |
| 			for _, on := range g.Vertices {
 | |
| 				if c, ok := on.Value.(Future); ok {
 | |
| 					if c.Y() == v {
 | |
| 						if !c.IsResolved() {
 | |
| 							g.SetRange(c.Y(), c.Eval(g))
 | |
| 							c.MarkResolved()
 | |
| 						}
 | |
| 						break
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 			if g.Range(v).IsKnown() {
 | |
| 				entries = append(entries, v)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return entries
 | |
| }
 | |
| 
 | |
| func (g *Graph) uses(scc int) map[ssa.Value][]Constraint {
 | |
| 	m := map[ssa.Value][]Constraint{}
 | |
| 	for _, e := range g.sccEdges[scc] {
 | |
| 		if e.control {
 | |
| 			continue
 | |
| 		}
 | |
| 		if v, ok := e.From.Value.(ssa.Value); ok {
 | |
| 			c := e.To.Value.(Constraint)
 | |
| 			sink := c.Y()
 | |
| 			if g.Vertices[sink].SCC == scc {
 | |
| 				m[v] = append(m[v], c)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return m
 | |
| }
 | |
| 
 | |
| func (g *Graph) actives(scc int) []ssa.Value {
 | |
| 	var actives []ssa.Value
 | |
| 	for _, n := range g.Vertices {
 | |
| 		if n.SCC != scc {
 | |
| 			continue
 | |
| 		}
 | |
| 		if v, ok := n.Value.(ssa.Value); ok {
 | |
| 			if _, ok := v.(*ssa.Const); !ok {
 | |
| 				actives = append(actives, v)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return actives
 | |
| }
 | |
| 
 | |
| func (g *Graph) AddEdge(from, to interface{}, ctrl bool) {
 | |
| 	vf, ok := g.Vertices[from]
 | |
| 	if !ok {
 | |
| 		vf = &Vertex{Value: from}
 | |
| 		g.Vertices[from] = vf
 | |
| 	}
 | |
| 	vt, ok := g.Vertices[to]
 | |
| 	if !ok {
 | |
| 		vt = &Vertex{Value: to}
 | |
| 		g.Vertices[to] = vt
 | |
| 	}
 | |
| 	e := Edge{From: vf, To: vt, control: ctrl}
 | |
| 	g.Edges = append(g.Edges, e)
 | |
| 	vf.Succs = append(vf.Succs, e)
 | |
| }
 | |
| 
 | |
| type Edge struct {
 | |
| 	From, To *Vertex
 | |
| 	control  bool
 | |
| }
 | |
| 
 | |
| func (e Edge) String() string {
 | |
| 	return fmt.Sprintf("%s -> %s", VertexString(e.From), VertexString(e.To))
 | |
| }
 | |
| 
 | |
| func (g *Graph) FindSCCs() {
 | |
| 	// use Tarjan to find the SCCs
 | |
| 
 | |
| 	index := 1
 | |
| 	var s []*Vertex
 | |
| 
 | |
| 	scc := 0
 | |
| 	var strongconnect func(v *Vertex)
 | |
| 	strongconnect = func(v *Vertex) {
 | |
| 		// set the depth index for v to the smallest unused index
 | |
| 		v.index = index
 | |
| 		v.lowlink = index
 | |
| 		index++
 | |
| 		s = append(s, v)
 | |
| 		v.stack = true
 | |
| 
 | |
| 		for _, e := range v.Succs {
 | |
| 			w := e.To
 | |
| 			if w.index == 0 {
 | |
| 				// successor w has not yet been visited; recurse on it
 | |
| 				strongconnect(w)
 | |
| 				if w.lowlink < v.lowlink {
 | |
| 					v.lowlink = w.lowlink
 | |
| 				}
 | |
| 			} else if w.stack {
 | |
| 				// successor w is in stack s and hence in the current scc
 | |
| 				if w.index < v.lowlink {
 | |
| 					v.lowlink = w.index
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if v.lowlink == v.index {
 | |
| 			for {
 | |
| 				w := s[len(s)-1]
 | |
| 				s = s[:len(s)-1]
 | |
| 				w.stack = false
 | |
| 				w.SCC = scc
 | |
| 				if w == v {
 | |
| 					break
 | |
| 				}
 | |
| 			}
 | |
| 			scc++
 | |
| 		}
 | |
| 	}
 | |
| 	for _, v := range g.Vertices {
 | |
| 		if v.index == 0 {
 | |
| 			strongconnect(v)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	g.SCCs = make([][]*Vertex, scc)
 | |
| 	for _, n := range g.Vertices {
 | |
| 		n.SCC = scc - n.SCC - 1
 | |
| 		g.SCCs[n.SCC] = append(g.SCCs[n.SCC], n)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func invertToken(tok token.Token) token.Token {
 | |
| 	switch tok {
 | |
| 	case token.LSS:
 | |
| 		return token.GEQ
 | |
| 	case token.GTR:
 | |
| 		return token.LEQ
 | |
| 	case token.EQL:
 | |
| 		return token.NEQ
 | |
| 	case token.NEQ:
 | |
| 		return token.EQL
 | |
| 	case token.GEQ:
 | |
| 		return token.LSS
 | |
| 	case token.LEQ:
 | |
| 		return token.GTR
 | |
| 	default:
 | |
| 		panic(fmt.Sprintf("unsupported token %s", tok))
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func flipToken(tok token.Token) token.Token {
 | |
| 	switch tok {
 | |
| 	case token.LSS:
 | |
| 		return token.GTR
 | |
| 	case token.GTR:
 | |
| 		return token.LSS
 | |
| 	case token.EQL:
 | |
| 		return token.EQL
 | |
| 	case token.NEQ:
 | |
| 		return token.NEQ
 | |
| 	case token.GEQ:
 | |
| 		return token.LEQ
 | |
| 	case token.LEQ:
 | |
| 		return token.GEQ
 | |
| 	default:
 | |
| 		panic(fmt.Sprintf("unsupported token %s", tok))
 | |
| 	}
 | |
| }
 | |
| 
 | |
| type CopyConstraint struct {
 | |
| 	aConstraint
 | |
| 	X ssa.Value
 | |
| }
 | |
| 
 | |
| func (c *CopyConstraint) String() string {
 | |
| 	return fmt.Sprintf("%s = copy(%s)", c.Y().Name(), c.X.Name())
 | |
| }
 | |
| 
 | |
| func (c *CopyConstraint) Eval(g *Graph) Range {
 | |
| 	return g.Range(c.X)
 | |
| }
 | |
| 
 | |
| func (c *CopyConstraint) Operands() []ssa.Value {
 | |
| 	return []ssa.Value{c.X}
 | |
| }
 | |
| 
 | |
| func NewCopyConstraint(x, y ssa.Value) Constraint {
 | |
| 	return &CopyConstraint{
 | |
| 		aConstraint: aConstraint{
 | |
| 			y: y,
 | |
| 		},
 | |
| 		X: x,
 | |
| 	}
 | |
| }
 | 
