github.com/jd-ly/tools@v0.5.7/go/ssa/builder_test.go (about) 1 // Copyright 2013 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 ssa_test 6 7 import ( 8 "bytes" 9 "go/ast" 10 "go/importer" 11 "go/parser" 12 "go/token" 13 "go/types" 14 "os" 15 "reflect" 16 "sort" 17 "strings" 18 "testing" 19 20 "github.com/jd-ly/tools/go/loader" 21 "github.com/jd-ly/tools/go/ssa" 22 "github.com/jd-ly/tools/go/ssa/ssautil" 23 ) 24 25 func isEmpty(f *ssa.Function) bool { return f.Blocks == nil } 26 27 // Tests that programs partially loaded from gc object files contain 28 // functions with no code for the external portions, but are otherwise ok. 29 func TestBuildPackage(t *testing.T) { 30 input := ` 31 package main 32 33 import ( 34 "bytes" 35 "io" 36 "testing" 37 ) 38 39 func main() { 40 var t testing.T 41 t.Parallel() // static call to external declared method 42 t.Fail() // static call to promoted external declared method 43 testing.Short() // static call to external package-level function 44 45 var w io.Writer = new(bytes.Buffer) 46 w.Write(nil) // interface invoke of external declared method 47 } 48 ` 49 50 // Parse the file. 51 fset := token.NewFileSet() 52 f, err := parser.ParseFile(fset, "input.go", input, 0) 53 if err != nil { 54 t.Error(err) 55 return 56 } 57 58 // Build an SSA program from the parsed file. 59 // Load its dependencies from gc binary export data. 60 mainPkg, _, err := ssautil.BuildPackage(&types.Config{Importer: importer.Default()}, fset, 61 types.NewPackage("main", ""), []*ast.File{f}, ssa.SanityCheckFunctions) 62 if err != nil { 63 t.Error(err) 64 return 65 } 66 67 // The main package, its direct and indirect dependencies are loaded. 68 deps := []string{ 69 // directly imported dependencies: 70 "bytes", "io", "testing", 71 // indirect dependencies mentioned by 72 // the direct imports' export data 73 "sync", "unicode", "time", 74 } 75 76 prog := mainPkg.Prog 77 all := prog.AllPackages() 78 if len(all) <= len(deps) { 79 t.Errorf("unexpected set of loaded packages: %q", all) 80 } 81 for _, path := range deps { 82 pkg := prog.ImportedPackage(path) 83 if pkg == nil { 84 t.Errorf("package not loaded: %q", path) 85 continue 86 } 87 88 // External packages should have no function bodies (except for wrappers). 89 isExt := pkg != mainPkg 90 91 // init() 92 if isExt && !isEmpty(pkg.Func("init")) { 93 t.Errorf("external package %s has non-empty init", pkg) 94 } else if !isExt && isEmpty(pkg.Func("init")) { 95 t.Errorf("main package %s has empty init", pkg) 96 } 97 98 for _, mem := range pkg.Members { 99 switch mem := mem.(type) { 100 case *ssa.Function: 101 // Functions at package level. 102 if isExt && !isEmpty(mem) { 103 t.Errorf("external function %s is non-empty", mem) 104 } else if !isExt && isEmpty(mem) { 105 t.Errorf("function %s is empty", mem) 106 } 107 108 case *ssa.Type: 109 // Methods of named types T. 110 // (In this test, all exported methods belong to *T not T.) 111 if !isExt { 112 t.Fatalf("unexpected name type in main package: %s", mem) 113 } 114 mset := prog.MethodSets.MethodSet(types.NewPointer(mem.Type())) 115 for i, n := 0, mset.Len(); i < n; i++ { 116 m := prog.MethodValue(mset.At(i)) 117 // For external types, only synthetic wrappers have code. 118 expExt := !strings.Contains(m.Synthetic, "wrapper") 119 if expExt && !isEmpty(m) { 120 t.Errorf("external method %s is non-empty: %s", 121 m, m.Synthetic) 122 } else if !expExt && isEmpty(m) { 123 t.Errorf("method function %s is empty: %s", 124 m, m.Synthetic) 125 } 126 } 127 } 128 } 129 } 130 131 expectedCallee := []string{ 132 "(*testing.T).Parallel", 133 "(*testing.common).Fail", 134 "testing.Short", 135 "N/A", 136 } 137 callNum := 0 138 for _, b := range mainPkg.Func("main").Blocks { 139 for _, instr := range b.Instrs { 140 switch instr := instr.(type) { 141 case ssa.CallInstruction: 142 call := instr.Common() 143 if want := expectedCallee[callNum]; want != "N/A" { 144 got := call.StaticCallee().String() 145 if want != got { 146 t.Errorf("call #%d from main.main: got callee %s, want %s", 147 callNum, got, want) 148 } 149 } 150 callNum++ 151 } 152 } 153 } 154 if callNum != 4 { 155 t.Errorf("in main.main: got %d calls, want %d", callNum, 4) 156 } 157 } 158 159 // TestRuntimeTypes tests that (*Program).RuntimeTypes() includes all necessary types. 160 func TestRuntimeTypes(t *testing.T) { 161 tests := []struct { 162 input string 163 want []string 164 }{ 165 // An exported package-level type is needed. 166 {`package A; type T struct{}; func (T) f() {}`, 167 []string{"*p.T", "p.T"}, 168 }, 169 // An unexported package-level type is not needed. 170 {`package B; type t struct{}; func (t) f() {}`, 171 nil, 172 }, 173 // Subcomponents of type of exported package-level var are needed. 174 {`package C; import "bytes"; var V struct {*bytes.Buffer}`, 175 []string{"*bytes.Buffer", "*struct{*bytes.Buffer}", "struct{*bytes.Buffer}"}, 176 }, 177 // Subcomponents of type of unexported package-level var are not needed. 178 {`package D; import "bytes"; var v struct {*bytes.Buffer}`, 179 nil, 180 }, 181 // Subcomponents of type of exported package-level function are needed. 182 {`package E; import "bytes"; func F(struct {*bytes.Buffer}) {}`, 183 []string{"*bytes.Buffer", "struct{*bytes.Buffer}"}, 184 }, 185 // Subcomponents of type of unexported package-level function are not needed. 186 {`package F; import "bytes"; func f(struct {*bytes.Buffer}) {}`, 187 nil, 188 }, 189 // Subcomponents of type of exported method of uninstantiated unexported type are not needed. 190 {`package G; import "bytes"; type x struct{}; func (x) G(struct {*bytes.Buffer}) {}; var v x`, 191 nil, 192 }, 193 // ...unless used by MakeInterface. 194 {`package G2; import "bytes"; type x struct{}; func (x) G(struct {*bytes.Buffer}) {}; var v interface{} = x{}`, 195 []string{"*bytes.Buffer", "*p.x", "p.x", "struct{*bytes.Buffer}"}, 196 }, 197 // Subcomponents of type of unexported method are not needed. 198 {`package I; import "bytes"; type X struct{}; func (X) G(struct {*bytes.Buffer}) {}`, 199 []string{"*bytes.Buffer", "*p.X", "p.X", "struct{*bytes.Buffer}"}, 200 }, 201 // Local types aren't needed. 202 {`package J; import "bytes"; func f() { type T struct {*bytes.Buffer}; var t T; _ = t }`, 203 nil, 204 }, 205 // ...unless used by MakeInterface. 206 {`package K; import "bytes"; func f() { type T struct {*bytes.Buffer}; _ = interface{}(T{}) }`, 207 []string{"*bytes.Buffer", "*p.T", "p.T"}, 208 }, 209 // Types used as operand of MakeInterface are needed. 210 {`package L; import "bytes"; func f() { _ = interface{}(struct{*bytes.Buffer}{}) }`, 211 []string{"*bytes.Buffer", "struct{*bytes.Buffer}"}, 212 }, 213 // MakeInterface is optimized away when storing to a blank. 214 {`package M; import "bytes"; var _ interface{} = struct{*bytes.Buffer}{}`, 215 nil, 216 }, 217 } 218 for _, test := range tests { 219 // Parse the file. 220 fset := token.NewFileSet() 221 f, err := parser.ParseFile(fset, "input.go", test.input, 0) 222 if err != nil { 223 t.Errorf("test %q: %s", test.input[:15], err) 224 continue 225 } 226 227 // Create a single-file main package. 228 // Load dependencies from gc binary export data. 229 ssapkg, _, err := ssautil.BuildPackage(&types.Config{Importer: importer.Default()}, fset, 230 types.NewPackage("p", ""), []*ast.File{f}, ssa.SanityCheckFunctions) 231 if err != nil { 232 t.Errorf("test %q: %s", test.input[:15], err) 233 continue 234 } 235 236 var typstrs []string 237 for _, T := range ssapkg.Prog.RuntimeTypes() { 238 typstrs = append(typstrs, T.String()) 239 } 240 sort.Strings(typstrs) 241 242 if !reflect.DeepEqual(typstrs, test.want) { 243 t.Errorf("test 'package %s': got %q, want %q", 244 f.Name.Name, typstrs, test.want) 245 } 246 } 247 } 248 249 // TestInit tests that synthesized init functions are correctly formed. 250 // Bare init functions omit calls to dependent init functions and the use of 251 // an init guard. They are useful in cases where the client uses a different 252 // calling convention for init functions, or cases where it is easier for a 253 // client to analyze bare init functions. Both of these aspects are used by 254 // the llgo compiler for simpler integration with gccgo's runtime library, 255 // and to simplify the analysis whereby it deduces which stores to globals 256 // can be lowered to global initializers. 257 func TestInit(t *testing.T) { 258 tests := []struct { 259 mode ssa.BuilderMode 260 input, want string 261 }{ 262 {0, `package A; import _ "errors"; var i int = 42`, 263 `# Name: A.init 264 # Package: A 265 # Synthetic: package initializer 266 func init(): 267 0: entry P:0 S:2 268 t0 = *init$guard bool 269 if t0 goto 2 else 1 270 1: init.start P:1 S:1 271 *init$guard = true:bool 272 t1 = errors.init() () 273 *i = 42:int 274 jump 2 275 2: init.done P:2 S:0 276 return 277 278 `}, 279 {ssa.BareInits, `package B; import _ "errors"; var i int = 42`, 280 `# Name: B.init 281 # Package: B 282 # Synthetic: package initializer 283 func init(): 284 0: entry P:0 S:0 285 *i = 42:int 286 return 287 288 `}, 289 } 290 for _, test := range tests { 291 // Create a single-file main package. 292 var conf loader.Config 293 f, err := conf.ParseFile("<input>", test.input) 294 if err != nil { 295 t.Errorf("test %q: %s", test.input[:15], err) 296 continue 297 } 298 conf.CreateFromFiles(f.Name.Name, f) 299 300 lprog, err := conf.Load() 301 if err != nil { 302 t.Errorf("test 'package %s': Load: %s", f.Name.Name, err) 303 continue 304 } 305 prog := ssautil.CreateProgram(lprog, test.mode) 306 mainPkg := prog.Package(lprog.Created[0].Pkg) 307 prog.Build() 308 initFunc := mainPkg.Func("init") 309 if initFunc == nil { 310 t.Errorf("test 'package %s': no init function", f.Name.Name) 311 continue 312 } 313 314 var initbuf bytes.Buffer 315 _, err = initFunc.WriteTo(&initbuf) 316 if err != nil { 317 t.Errorf("test 'package %s': WriteTo: %s", f.Name.Name, err) 318 continue 319 } 320 321 if initbuf.String() != test.want { 322 t.Errorf("test 'package %s': got %s, want %s", f.Name.Name, initbuf.String(), test.want) 323 } 324 } 325 } 326 327 // TestSyntheticFuncs checks that the expected synthetic functions are 328 // created, reachable, and not duplicated. 329 func TestSyntheticFuncs(t *testing.T) { 330 const input = `package P 331 type T int 332 func (T) f() int 333 func (*T) g() int 334 var ( 335 // thunks 336 a = T.f 337 b = T.f 338 c = (struct{T}).f 339 d = (struct{T}).f 340 e = (*T).g 341 f = (*T).g 342 g = (struct{*T}).g 343 h = (struct{*T}).g 344 345 // bounds 346 i = T(0).f 347 j = T(0).f 348 k = new(T).g 349 l = new(T).g 350 351 // wrappers 352 m interface{} = struct{T}{} 353 n interface{} = struct{T}{} 354 o interface{} = struct{*T}{} 355 p interface{} = struct{*T}{} 356 q interface{} = new(struct{T}) 357 r interface{} = new(struct{T}) 358 s interface{} = new(struct{*T}) 359 t interface{} = new(struct{*T}) 360 ) 361 ` 362 // Parse 363 var conf loader.Config 364 f, err := conf.ParseFile("<input>", input) 365 if err != nil { 366 t.Fatalf("parse: %v", err) 367 } 368 conf.CreateFromFiles(f.Name.Name, f) 369 370 // Load 371 lprog, err := conf.Load() 372 if err != nil { 373 t.Fatalf("Load: %v", err) 374 } 375 376 // Create and build SSA 377 prog := ssautil.CreateProgram(lprog, 0) 378 prog.Build() 379 380 // Enumerate reachable synthetic functions 381 want := map[string]string{ 382 "(*P.T).g$bound": "bound method wrapper for func (*P.T).g() int", 383 "(P.T).f$bound": "bound method wrapper for func (P.T).f() int", 384 385 "(*P.T).g$thunk": "thunk for func (*P.T).g() int", 386 "(P.T).f$thunk": "thunk for func (P.T).f() int", 387 "(struct{*P.T}).g$thunk": "thunk for func (*P.T).g() int", 388 "(struct{P.T}).f$thunk": "thunk for func (P.T).f() int", 389 390 "(*P.T).f": "wrapper for func (P.T).f() int", 391 "(*struct{*P.T}).f": "wrapper for func (P.T).f() int", 392 "(*struct{*P.T}).g": "wrapper for func (*P.T).g() int", 393 "(*struct{P.T}).f": "wrapper for func (P.T).f() int", 394 "(*struct{P.T}).g": "wrapper for func (*P.T).g() int", 395 "(struct{*P.T}).f": "wrapper for func (P.T).f() int", 396 "(struct{*P.T}).g": "wrapper for func (*P.T).g() int", 397 "(struct{P.T}).f": "wrapper for func (P.T).f() int", 398 399 "P.init": "package initializer", 400 } 401 for fn := range ssautil.AllFunctions(prog) { 402 if fn.Synthetic == "" { 403 continue 404 } 405 name := fn.String() 406 wantDescr, ok := want[name] 407 if !ok { 408 t.Errorf("got unexpected/duplicate func: %q: %q", name, fn.Synthetic) 409 continue 410 } 411 delete(want, name) 412 413 if wantDescr != fn.Synthetic { 414 t.Errorf("(%s).Synthetic = %q, want %q", name, fn.Synthetic, wantDescr) 415 } 416 } 417 for fn, descr := range want { 418 t.Errorf("want func: %q: %q", fn, descr) 419 } 420 } 421 422 // TestPhiElimination ensures that dead phis, including those that 423 // participate in a cycle, are properly eliminated. 424 func TestPhiElimination(t *testing.T) { 425 const input = ` 426 package p 427 428 func f() error 429 430 func g(slice []int) { 431 for { 432 for range slice { 433 // e should not be lifted to a dead φ-node. 434 e := f() 435 h(e) 436 } 437 } 438 } 439 440 func h(error) 441 ` 442 // The SSA code for this function should look something like this: 443 // 0: 444 // jump 1 445 // 1: 446 // t0 = len(slice) 447 // jump 2 448 // 2: 449 // t1 = phi [1: -1:int, 3: t2] 450 // t2 = t1 + 1:int 451 // t3 = t2 < t0 452 // if t3 goto 3 else 1 453 // 3: 454 // t4 = f() 455 // t5 = h(t4) 456 // jump 2 457 // 458 // But earlier versions of the SSA construction algorithm would 459 // additionally generate this cycle of dead phis: 460 // 461 // 1: 462 // t7 = phi [0: nil:error, 2: t8] #e 463 // ... 464 // 2: 465 // t8 = phi [1: t7, 3: t4] #e 466 // ... 467 468 // Parse 469 var conf loader.Config 470 f, err := conf.ParseFile("<input>", input) 471 if err != nil { 472 t.Fatalf("parse: %v", err) 473 } 474 conf.CreateFromFiles("p", f) 475 476 // Load 477 lprog, err := conf.Load() 478 if err != nil { 479 t.Fatalf("Load: %v", err) 480 } 481 482 // Create and build SSA 483 prog := ssautil.CreateProgram(lprog, 0) 484 p := prog.Package(lprog.Package("p").Pkg) 485 p.Build() 486 g := p.Func("g") 487 488 phis := 0 489 for _, b := range g.Blocks { 490 for _, instr := range b.Instrs { 491 if _, ok := instr.(*ssa.Phi); ok { 492 phis++ 493 } 494 } 495 } 496 if phis != 1 { 497 g.WriteTo(os.Stderr) 498 t.Errorf("expected a single Phi (for the range index), got %d", phis) 499 } 500 }