1057 lines
24 KiB
Go
1057 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"
|
|
|
|
"honnef.co/go/tools/lint"
|
|
"honnef.co/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 lint.FuncName(fn) {
|
|
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
|
|
|
|
// Don't get stuck widening for an absurd amount of time due
|
|
// to an excess number of constants, as may be present in
|
|
// table-based scanners.
|
|
if len(consts) < 1000 {
|
|
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,
|
|
}
|
|
}
|