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