github.com/jhump/golang-x-tools@v0.0.0-20220218190644-4958d6d39439/go/callgraph/vta/propagation_test.go (about) 1 // Copyright 2021 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package vta 6 7 import ( 8 "go/token" 9 "go/types" 10 "reflect" 11 "sort" 12 "strings" 13 "testing" 14 "unsafe" 15 16 "github.com/jhump/golang-x-tools/go/ssa" 17 18 "github.com/jhump/golang-x-tools/go/types/typeutil" 19 ) 20 21 // val is a test data structure for creating ssa.Value 22 // outside of the ssa package. Needed for manual creation 23 // of vta graph nodes in testing. 24 type val struct { 25 name string 26 typ types.Type 27 } 28 29 func (v val) String() string { 30 return v.name 31 } 32 33 func (v val) Name() string { 34 return v.name 35 } 36 37 func (v val) Type() types.Type { 38 return v.typ 39 } 40 41 func (v val) Parent() *ssa.Function { 42 return nil 43 } 44 45 func (v val) Referrers() *[]ssa.Instruction { 46 return nil 47 } 48 49 func (v val) Pos() token.Pos { 50 return token.NoPos 51 } 52 53 // newLocal creates a new local node with ssa.Value 54 // named `name` and type `t`. 55 func newLocal(name string, t types.Type) local { 56 return local{val: val{name: name, typ: t}} 57 } 58 59 // newNamedType creates a bogus type named `name`. 60 func newNamedType(name string) *types.Named { 61 return types.NewNamed(types.NewTypeName(token.NoPos, nil, name, nil), nil, nil) 62 } 63 64 // sccString is a utility for stringifying `nodeToScc`. Every 65 // scc is represented as a string where string representation 66 // of scc nodes are sorted and concatenated using `;`. 67 func sccString(nodeToScc map[node]int) []string { 68 sccs := make(map[int][]node) 69 for n, id := range nodeToScc { 70 sccs[id] = append(sccs[id], n) 71 } 72 73 var sccsStr []string 74 for _, scc := range sccs { 75 var nodesStr []string 76 for _, node := range scc { 77 nodesStr = append(nodesStr, node.String()) 78 } 79 sort.Strings(nodesStr) 80 sccsStr = append(sccsStr, strings.Join(nodesStr, ";")) 81 } 82 return sccsStr 83 } 84 85 // nodeToTypeString is testing utility for stringifying results 86 // of type propagation: propTypeMap `pMap` is converted to a map 87 // from node strings to a string consisting of type stringifications 88 // concatenated with `;`. We stringify reachable type information 89 // that also has an accompanying function by the function name. 90 func nodeToTypeString(pMap propTypeMap) map[string]string { 91 // Convert propType to a string. If propType has 92 // an attached function, return the function name. 93 // Otherwise, return the type name. 94 propTypeString := func(p propType) string { 95 if p.f != nil { 96 return p.f.Name() 97 } 98 return p.typ.String() 99 } 100 101 nodeToTypeStr := make(map[string]string) 102 for node := range pMap.nodeToScc { 103 var propStrings []string 104 for _, prop := range pMap.propTypes(node) { 105 propStrings = append(propStrings, propTypeString(prop)) 106 } 107 sort.Strings(propStrings) 108 nodeToTypeStr[node.String()] = strings.Join(propStrings, ";") 109 } 110 111 return nodeToTypeStr 112 } 113 114 // sccEqual compares two sets of SCC stringifications. 115 func sccEqual(sccs1 []string, sccs2 []string) bool { 116 if len(sccs1) != len(sccs2) { 117 return false 118 } 119 sort.Strings(sccs1) 120 sort.Strings(sccs2) 121 return reflect.DeepEqual(sccs1, sccs2) 122 } 123 124 // isRevTopSorted checks if sccs of `g` are sorted in reverse 125 // topological order: 126 // for every edge x -> y in g, nodeToScc[x] > nodeToScc[y] 127 func isRevTopSorted(g vtaGraph, nodeToScc map[node]int) bool { 128 for n, succs := range g { 129 for s := range succs { 130 if nodeToScc[n] < nodeToScc[s] { 131 return false 132 } 133 } 134 } 135 return true 136 } 137 138 // setName sets name of the function `f` to `name` 139 // using reflection since setting the name otherwise 140 // is only possible within the ssa package. 141 func setName(f *ssa.Function, name string) { 142 fi := reflect.ValueOf(f).Elem().FieldByName("name") 143 fi = reflect.NewAt(fi.Type(), unsafe.Pointer(fi.UnsafeAddr())).Elem() 144 fi.SetString(name) 145 } 146 147 // testSuite produces a named set of graphs as follows, where 148 // parentheses contain node types and F nodes stand for function 149 // nodes whose content is function named F: 150 // 151 // no-cycles: 152 // t0 (A) -> t1 (B) -> t2 (C) 153 // 154 // trivial-cycle: 155 // <-------- <-------- 156 // | | | | 157 // t0 (A) -> t1 (B) -> 158 // 159 // circle-cycle: 160 // t0 (A) -> t1 (A) -> t2 (B) 161 // | | 162 // <-------------------- 163 // 164 // fully-connected: 165 // t0 (A) <-> t1 (B) 166 // \ / 167 // t2(C) 168 // 169 // subsumed-scc: 170 // t0 (A) -> t1 (B) -> t2(B) -> t3 (A) 171 // | | | | 172 // | <--------- | 173 // <----------------------------- 174 // 175 // more-realistic: 176 // <-------- 177 // | | 178 // t0 (A) --> 179 // ----------> 180 // | | 181 // t1 (A) -> t2 (B) -> F1 -> F2 -> F3 -> F4 182 // | | | | 183 // <------- <------------ 184 func testSuite() map[string]vtaGraph { 185 a := newNamedType("A") 186 b := newNamedType("B") 187 c := newNamedType("C") 188 sig := types.NewSignature(nil, types.NewTuple(), types.NewTuple(), false) 189 190 f1 := &ssa.Function{Signature: sig} 191 setName(f1, "F1") 192 f2 := &ssa.Function{Signature: sig} 193 setName(f2, "F2") 194 f3 := &ssa.Function{Signature: sig} 195 setName(f3, "F3") 196 f4 := &ssa.Function{Signature: sig} 197 setName(f4, "F4") 198 199 graphs := make(map[string]vtaGraph) 200 graphs["no-cycles"] = map[node]map[node]bool{ 201 newLocal("t0", a): {newLocal("t1", b): true}, 202 newLocal("t1", b): {newLocal("t2", c): true}, 203 } 204 205 graphs["trivial-cycle"] = map[node]map[node]bool{ 206 newLocal("t0", a): {newLocal("t0", a): true}, 207 newLocal("t1", b): {newLocal("t1", b): true}, 208 } 209 210 graphs["circle-cycle"] = map[node]map[node]bool{ 211 newLocal("t0", a): {newLocal("t1", a): true}, 212 newLocal("t1", a): {newLocal("t2", b): true}, 213 newLocal("t2", b): {newLocal("t0", a): true}, 214 } 215 216 graphs["fully-connected"] = map[node]map[node]bool{ 217 newLocal("t0", a): {newLocal("t1", b): true, newLocal("t2", c): true}, 218 newLocal("t1", b): {newLocal("t0", a): true, newLocal("t2", c): true}, 219 newLocal("t2", c): {newLocal("t0", a): true, newLocal("t1", b): true}, 220 } 221 222 graphs["subsumed-scc"] = map[node]map[node]bool{ 223 newLocal("t0", a): {newLocal("t1", b): true}, 224 newLocal("t1", b): {newLocal("t2", b): true}, 225 newLocal("t2", b): {newLocal("t1", b): true, newLocal("t3", a): true}, 226 newLocal("t3", a): {newLocal("t0", a): true}, 227 } 228 229 graphs["more-realistic"] = map[node]map[node]bool{ 230 newLocal("t0", a): {newLocal("t0", a): true}, 231 newLocal("t1", a): {newLocal("t2", b): true}, 232 newLocal("t2", b): {newLocal("t1", a): true, function{f1}: true}, 233 function{f1}: {function{f2}: true, function{f3}: true}, 234 function{f2}: {function{f3}: true}, 235 function{f3}: {function{f1}: true, function{f4}: true}, 236 } 237 238 return graphs 239 } 240 241 func TestSCC(t *testing.T) { 242 suite := testSuite() 243 for _, test := range []struct { 244 name string 245 graph vtaGraph 246 want []string 247 }{ 248 // No cycles results in three separate SCCs: {t0} {t1} {t2} 249 {name: "no-cycles", graph: suite["no-cycles"], want: []string{"Local(t0)", "Local(t1)", "Local(t2)"}}, 250 // The two trivial self-loop cycles results in: {t0} {t1} 251 {name: "trivial-cycle", graph: suite["trivial-cycle"], want: []string{"Local(t0)", "Local(t1)"}}, 252 // The circle cycle produce a single SCC: {t0, t1, t2} 253 {name: "circle-cycle", graph: suite["circle-cycle"], want: []string{"Local(t0);Local(t1);Local(t2)"}}, 254 // Similar holds for fully connected SCC: {t0, t1, t2} 255 {name: "fully-connected", graph: suite["fully-connected"], want: []string{"Local(t0);Local(t1);Local(t2)"}}, 256 // Subsumed SCC also has a single SCC: {t0, t1, t2, t3} 257 {name: "subsumed-scc", graph: suite["subsumed-scc"], want: []string{"Local(t0);Local(t1);Local(t2);Local(t3)"}}, 258 // The more realistic example has the following SCCs: {t0} {t1, t2} {F1, F2, F3} {F4} 259 {name: "more-realistic", graph: suite["more-realistic"], want: []string{"Local(t0)", "Local(t1);Local(t2)", "Function(F1);Function(F2);Function(F3)", "Function(F4)"}}, 260 } { 261 sccs, _ := scc(test.graph) 262 if got := sccString(sccs); !sccEqual(test.want, got) { 263 t.Errorf("want %v for graph %v; got %v", test.want, test.name, got) 264 } 265 if !isRevTopSorted(test.graph, sccs) { 266 t.Errorf("%v not topologically sorted", test.name) 267 } 268 } 269 } 270 271 func TestPropagation(t *testing.T) { 272 suite := testSuite() 273 var canon typeutil.Map 274 for _, test := range []struct { 275 name string 276 graph vtaGraph 277 want map[string]string 278 }{ 279 // No cycles graph pushes type information forward. 280 {name: "no-cycles", graph: suite["no-cycles"], 281 want: map[string]string{ 282 "Local(t0)": "A", 283 "Local(t1)": "A;B", 284 "Local(t2)": "A;B;C", 285 }, 286 }, 287 // No interesting type flow in trivial cycle graph. 288 {name: "trivial-cycle", graph: suite["trivial-cycle"], 289 want: map[string]string{ 290 "Local(t0)": "A", 291 "Local(t1)": "B", 292 }, 293 }, 294 // Circle cycle makes type A and B get propagated everywhere. 295 {name: "circle-cycle", graph: suite["circle-cycle"], 296 want: map[string]string{ 297 "Local(t0)": "A;B", 298 "Local(t1)": "A;B", 299 "Local(t2)": "A;B", 300 }, 301 }, 302 // Similarly for fully connected graph. 303 {name: "fully-connected", graph: suite["fully-connected"], 304 want: map[string]string{ 305 "Local(t0)": "A;B;C", 306 "Local(t1)": "A;B;C", 307 "Local(t2)": "A;B;C", 308 }, 309 }, 310 // The outer loop of subsumed-scc pushes A an B through the graph. 311 {name: "subsumed-scc", graph: suite["subsumed-scc"], 312 want: map[string]string{ 313 "Local(t0)": "A;B", 314 "Local(t1)": "A;B", 315 "Local(t2)": "A;B", 316 "Local(t3)": "A;B", 317 }, 318 }, 319 // More realistic graph has a more fine grained flow. 320 {name: "more-realistic", graph: suite["more-realistic"], 321 want: map[string]string{ 322 "Local(t0)": "A", 323 "Local(t1)": "A;B", 324 "Local(t2)": "A;B", 325 "Function(F1)": "A;B;F1;F2;F3", 326 "Function(F2)": "A;B;F1;F2;F3", 327 "Function(F3)": "A;B;F1;F2;F3", 328 "Function(F4)": "A;B;F1;F2;F3;F4", 329 }, 330 }, 331 } { 332 if got := nodeToTypeString(propagate(test.graph, &canon)); !reflect.DeepEqual(got, test.want) { 333 t.Errorf("want %v for graph %v; got %v", test.want, test.name, got) 334 } 335 } 336 }