github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/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/build" 11 "go/importer" 12 "go/parser" 13 "go/token" 14 "go/types" 15 "os" 16 "path/filepath" 17 "reflect" 18 "sort" 19 "strings" 20 "testing" 21 22 "github.com/powerman/golang-tools/go/loader" 23 "github.com/powerman/golang-tools/go/ssa" 24 "github.com/powerman/golang-tools/go/ssa/ssautil" 25 "github.com/powerman/golang-tools/internal/typeparams" 26 ) 27 28 func isEmpty(f *ssa.Function) bool { return f.Blocks == nil } 29 30 // Tests that programs partially loaded from gc object files contain 31 // functions with no code for the external portions, but are otherwise ok. 32 func TestBuildPackage(t *testing.T) { 33 input := ` 34 package main 35 36 import ( 37 "bytes" 38 "io" 39 "testing" 40 ) 41 42 func main() { 43 var t testing.T 44 t.Parallel() // static call to external declared method 45 t.Fail() // static call to promoted external declared method 46 testing.Short() // static call to external package-level function 47 48 var w io.Writer = new(bytes.Buffer) 49 w.Write(nil) // interface invoke of external declared method 50 } 51 ` 52 53 // Parse the file. 54 fset := token.NewFileSet() 55 f, err := parser.ParseFile(fset, "input.go", input, 0) 56 if err != nil { 57 t.Error(err) 58 return 59 } 60 61 // Build an SSA program from the parsed file. 62 // Load its dependencies from gc binary export data. 63 mainPkg, _, err := ssautil.BuildPackage(&types.Config{Importer: importer.Default()}, fset, 64 types.NewPackage("main", ""), []*ast.File{f}, ssa.SanityCheckFunctions) 65 if err != nil { 66 t.Error(err) 67 return 68 } 69 70 // The main package, its direct and indirect dependencies are loaded. 71 deps := []string{ 72 // directly imported dependencies: 73 "bytes", "io", "testing", 74 // indirect dependencies mentioned by 75 // the direct imports' export data 76 "sync", "unicode", "time", 77 } 78 79 prog := mainPkg.Prog 80 all := prog.AllPackages() 81 if len(all) <= len(deps) { 82 t.Errorf("unexpected set of loaded packages: %q", all) 83 } 84 for _, path := range deps { 85 pkg := prog.ImportedPackage(path) 86 if pkg == nil { 87 t.Errorf("package not loaded: %q", path) 88 continue 89 } 90 91 // External packages should have no function bodies (except for wrappers). 92 isExt := pkg != mainPkg 93 94 // init() 95 if isExt && !isEmpty(pkg.Func("init")) { 96 t.Errorf("external package %s has non-empty init", pkg) 97 } else if !isExt && isEmpty(pkg.Func("init")) { 98 t.Errorf("main package %s has empty init", pkg) 99 } 100 101 for _, mem := range pkg.Members { 102 switch mem := mem.(type) { 103 case *ssa.Function: 104 // Functions at package level. 105 if isExt && !isEmpty(mem) { 106 t.Errorf("external function %s is non-empty", mem) 107 } else if !isExt && isEmpty(mem) { 108 t.Errorf("function %s is empty", mem) 109 } 110 111 case *ssa.Type: 112 // Methods of named types T. 113 // (In this test, all exported methods belong to *T not T.) 114 if !isExt { 115 t.Fatalf("unexpected name type in main package: %s", mem) 116 } 117 mset := prog.MethodSets.MethodSet(types.NewPointer(mem.Type())) 118 for i, n := 0, mset.Len(); i < n; i++ { 119 m := prog.MethodValue(mset.At(i)) 120 // For external types, only synthetic wrappers have code. 121 expExt := !strings.Contains(m.Synthetic, "wrapper") 122 if expExt && !isEmpty(m) { 123 t.Errorf("external method %s is non-empty: %s", 124 m, m.Synthetic) 125 } else if !expExt && isEmpty(m) { 126 t.Errorf("method function %s is empty: %s", 127 m, m.Synthetic) 128 } 129 } 130 } 131 } 132 } 133 134 expectedCallee := []string{ 135 "(*testing.T).Parallel", 136 "(*testing.common).Fail", 137 "testing.Short", 138 "N/A", 139 } 140 callNum := 0 141 for _, b := range mainPkg.Func("main").Blocks { 142 for _, instr := range b.Instrs { 143 switch instr := instr.(type) { 144 case ssa.CallInstruction: 145 call := instr.Common() 146 if want := expectedCallee[callNum]; want != "N/A" { 147 got := call.StaticCallee().String() 148 if want != got { 149 t.Errorf("call #%d from main.main: got callee %s, want %s", 150 callNum, got, want) 151 } 152 } 153 callNum++ 154 } 155 } 156 } 157 if callNum != 4 { 158 t.Errorf("in main.main: got %d calls, want %d", callNum, 4) 159 } 160 } 161 162 // TestRuntimeTypes tests that (*Program).RuntimeTypes() includes all necessary types. 163 func TestRuntimeTypes(t *testing.T) { 164 tests := []struct { 165 input string 166 want []string 167 }{ 168 // An exported package-level type is needed. 169 {`package A; type T struct{}; func (T) f() {}`, 170 []string{"*p.T", "p.T"}, 171 }, 172 // An unexported package-level type is not needed. 173 {`package B; type t struct{}; func (t) f() {}`, 174 nil, 175 }, 176 // Subcomponents of type of exported package-level var are needed. 177 {`package C; import "bytes"; var V struct {*bytes.Buffer}`, 178 []string{"*bytes.Buffer", "*struct{*bytes.Buffer}", "struct{*bytes.Buffer}"}, 179 }, 180 // Subcomponents of type of unexported package-level var are not needed. 181 {`package D; import "bytes"; var v struct {*bytes.Buffer}`, 182 nil, 183 }, 184 // Subcomponents of type of exported package-level function are needed. 185 {`package E; import "bytes"; func F(struct {*bytes.Buffer}) {}`, 186 []string{"*bytes.Buffer", "struct{*bytes.Buffer}"}, 187 }, 188 // Subcomponents of type of unexported package-level function are not needed. 189 {`package F; import "bytes"; func f(struct {*bytes.Buffer}) {}`, 190 nil, 191 }, 192 // Subcomponents of type of exported method of uninstantiated unexported type are not needed. 193 {`package G; import "bytes"; type x struct{}; func (x) G(struct {*bytes.Buffer}) {}; var v x`, 194 nil, 195 }, 196 // ...unless used by MakeInterface. 197 {`package G2; import "bytes"; type x struct{}; func (x) G(struct {*bytes.Buffer}) {}; var v interface{} = x{}`, 198 []string{"*bytes.Buffer", "*p.x", "p.x", "struct{*bytes.Buffer}"}, 199 }, 200 // Subcomponents of type of unexported method are not needed. 201 {`package I; import "bytes"; type X struct{}; func (X) G(struct {*bytes.Buffer}) {}`, 202 []string{"*bytes.Buffer", "*p.X", "p.X", "struct{*bytes.Buffer}"}, 203 }, 204 // Local types aren't needed. 205 {`package J; import "bytes"; func f() { type T struct {*bytes.Buffer}; var t T; _ = t }`, 206 nil, 207 }, 208 // ...unless used by MakeInterface. 209 {`package K; import "bytes"; func f() { type T struct {*bytes.Buffer}; _ = interface{}(T{}) }`, 210 []string{"*bytes.Buffer", "*p.T", "p.T"}, 211 }, 212 // Types used as operand of MakeInterface are needed. 213 {`package L; import "bytes"; func f() { _ = interface{}(struct{*bytes.Buffer}{}) }`, 214 []string{"*bytes.Buffer", "struct{*bytes.Buffer}"}, 215 }, 216 // MakeInterface is optimized away when storing to a blank. 217 {`package M; import "bytes"; var _ interface{} = struct{*bytes.Buffer}{}`, 218 nil, 219 }, 220 } 221 for _, test := range tests { 222 // Parse the file. 223 fset := token.NewFileSet() 224 f, err := parser.ParseFile(fset, "input.go", test.input, 0) 225 if err != nil { 226 t.Errorf("test %q: %s", test.input[:15], err) 227 continue 228 } 229 230 // Create a single-file main package. 231 // Load dependencies from gc binary export data. 232 ssapkg, _, err := ssautil.BuildPackage(&types.Config{Importer: importer.Default()}, fset, 233 types.NewPackage("p", ""), []*ast.File{f}, ssa.SanityCheckFunctions) 234 if err != nil { 235 t.Errorf("test %q: %s", test.input[:15], err) 236 continue 237 } 238 239 var typstrs []string 240 for _, T := range ssapkg.Prog.RuntimeTypes() { 241 typstrs = append(typstrs, T.String()) 242 } 243 sort.Strings(typstrs) 244 245 if !reflect.DeepEqual(typstrs, test.want) { 246 t.Errorf("test 'package %s': got %q, want %q", 247 f.Name.Name, typstrs, test.want) 248 } 249 } 250 } 251 252 // TestInit tests that synthesized init functions are correctly formed. 253 // Bare init functions omit calls to dependent init functions and the use of 254 // an init guard. They are useful in cases where the client uses a different 255 // calling convention for init functions, or cases where it is easier for a 256 // client to analyze bare init functions. Both of these aspects are used by 257 // the llgo compiler for simpler integration with gccgo's runtime library, 258 // and to simplify the analysis whereby it deduces which stores to globals 259 // can be lowered to global initializers. 260 func TestInit(t *testing.T) { 261 tests := []struct { 262 mode ssa.BuilderMode 263 input, want string 264 }{ 265 {0, `package A; import _ "errors"; var i int = 42`, 266 `# Name: A.init 267 # Package: A 268 # Synthetic: package initializer 269 func init(): 270 0: entry P:0 S:2 271 t0 = *init$guard bool 272 if t0 goto 2 else 1 273 1: init.start P:1 S:1 274 *init$guard = true:bool 275 t1 = errors.init() () 276 *i = 42:int 277 jump 2 278 2: init.done P:2 S:0 279 return 280 281 `}, 282 {ssa.BareInits, `package B; import _ "errors"; var i int = 42`, 283 `# Name: B.init 284 # Package: B 285 # Synthetic: package initializer 286 func init(): 287 0: entry P:0 S:0 288 *i = 42:int 289 return 290 291 `}, 292 } 293 for _, test := range tests { 294 // Create a single-file main package. 295 var conf loader.Config 296 f, err := conf.ParseFile("<input>", test.input) 297 if err != nil { 298 t.Errorf("test %q: %s", test.input[:15], err) 299 continue 300 } 301 conf.CreateFromFiles(f.Name.Name, f) 302 303 lprog, err := conf.Load() 304 if err != nil { 305 t.Errorf("test 'package %s': Load: %s", f.Name.Name, err) 306 continue 307 } 308 prog := ssautil.CreateProgram(lprog, test.mode) 309 mainPkg := prog.Package(lprog.Created[0].Pkg) 310 prog.Build() 311 initFunc := mainPkg.Func("init") 312 if initFunc == nil { 313 t.Errorf("test 'package %s': no init function", f.Name.Name) 314 continue 315 } 316 317 var initbuf bytes.Buffer 318 _, err = initFunc.WriteTo(&initbuf) 319 if err != nil { 320 t.Errorf("test 'package %s': WriteTo: %s", f.Name.Name, err) 321 continue 322 } 323 324 if initbuf.String() != test.want { 325 t.Errorf("test 'package %s': got %s, want %s", f.Name.Name, initbuf.String(), test.want) 326 } 327 } 328 } 329 330 // TestSyntheticFuncs checks that the expected synthetic functions are 331 // created, reachable, and not duplicated. 332 func TestSyntheticFuncs(t *testing.T) { 333 const input = `package P 334 type T int 335 func (T) f() int 336 func (*T) g() int 337 var ( 338 // thunks 339 a = T.f 340 b = T.f 341 c = (struct{T}).f 342 d = (struct{T}).f 343 e = (*T).g 344 f = (*T).g 345 g = (struct{*T}).g 346 h = (struct{*T}).g 347 348 // bounds 349 i = T(0).f 350 j = T(0).f 351 k = new(T).g 352 l = new(T).g 353 354 // wrappers 355 m interface{} = struct{T}{} 356 n interface{} = struct{T}{} 357 o interface{} = struct{*T}{} 358 p interface{} = struct{*T}{} 359 q interface{} = new(struct{T}) 360 r interface{} = new(struct{T}) 361 s interface{} = new(struct{*T}) 362 t interface{} = new(struct{*T}) 363 ) 364 ` 365 // Parse 366 var conf loader.Config 367 f, err := conf.ParseFile("<input>", input) 368 if err != nil { 369 t.Fatalf("parse: %v", err) 370 } 371 conf.CreateFromFiles(f.Name.Name, f) 372 373 // Load 374 lprog, err := conf.Load() 375 if err != nil { 376 t.Fatalf("Load: %v", err) 377 } 378 379 // Create and build SSA 380 prog := ssautil.CreateProgram(lprog, 0) 381 prog.Build() 382 383 // Enumerate reachable synthetic functions 384 want := map[string]string{ 385 "(*P.T).g$bound": "bound method wrapper for func (*P.T).g() int", 386 "(P.T).f$bound": "bound method wrapper for func (P.T).f() int", 387 388 "(*P.T).g$thunk": "thunk for func (*P.T).g() int", 389 "(P.T).f$thunk": "thunk for func (P.T).f() int", 390 "(struct{*P.T}).g$thunk": "thunk for func (*P.T).g() int", 391 "(struct{P.T}).f$thunk": "thunk for func (P.T).f() int", 392 393 "(*P.T).f": "wrapper for func (P.T).f() int", 394 "(*struct{*P.T}).f": "wrapper for func (P.T).f() int", 395 "(*struct{*P.T}).g": "wrapper for func (*P.T).g() int", 396 "(*struct{P.T}).f": "wrapper for func (P.T).f() int", 397 "(*struct{P.T}).g": "wrapper for func (*P.T).g() int", 398 "(struct{*P.T}).f": "wrapper for func (P.T).f() int", 399 "(struct{*P.T}).g": "wrapper for func (*P.T).g() int", 400 "(struct{P.T}).f": "wrapper for func (P.T).f() int", 401 402 "P.init": "package initializer", 403 } 404 for fn := range ssautil.AllFunctions(prog) { 405 if fn.Synthetic == "" { 406 continue 407 } 408 name := fn.String() 409 wantDescr, ok := want[name] 410 if !ok { 411 t.Errorf("got unexpected/duplicate func: %q: %q", name, fn.Synthetic) 412 continue 413 } 414 delete(want, name) 415 416 if wantDescr != fn.Synthetic { 417 t.Errorf("(%s).Synthetic = %q, want %q", name, fn.Synthetic, wantDescr) 418 } 419 } 420 for fn, descr := range want { 421 t.Errorf("want func: %q: %q", fn, descr) 422 } 423 } 424 425 // TestPhiElimination ensures that dead phis, including those that 426 // participate in a cycle, are properly eliminated. 427 func TestPhiElimination(t *testing.T) { 428 const input = ` 429 package p 430 431 func f() error 432 433 func g(slice []int) { 434 for { 435 for range slice { 436 // e should not be lifted to a dead φ-node. 437 e := f() 438 h(e) 439 } 440 } 441 } 442 443 func h(error) 444 ` 445 // The SSA code for this function should look something like this: 446 // 0: 447 // jump 1 448 // 1: 449 // t0 = len(slice) 450 // jump 2 451 // 2: 452 // t1 = phi [1: -1:int, 3: t2] 453 // t2 = t1 + 1:int 454 // t3 = t2 < t0 455 // if t3 goto 3 else 1 456 // 3: 457 // t4 = f() 458 // t5 = h(t4) 459 // jump 2 460 // 461 // But earlier versions of the SSA construction algorithm would 462 // additionally generate this cycle of dead phis: 463 // 464 // 1: 465 // t7 = phi [0: nil:error, 2: t8] #e 466 // ... 467 // 2: 468 // t8 = phi [1: t7, 3: t4] #e 469 // ... 470 471 // Parse 472 var conf loader.Config 473 f, err := conf.ParseFile("<input>", input) 474 if err != nil { 475 t.Fatalf("parse: %v", err) 476 } 477 conf.CreateFromFiles("p", f) 478 479 // Load 480 lprog, err := conf.Load() 481 if err != nil { 482 t.Fatalf("Load: %v", err) 483 } 484 485 // Create and build SSA 486 prog := ssautil.CreateProgram(lprog, 0) 487 p := prog.Package(lprog.Package("p").Pkg) 488 p.Build() 489 g := p.Func("g") 490 491 phis := 0 492 for _, b := range g.Blocks { 493 for _, instr := range b.Instrs { 494 if _, ok := instr.(*ssa.Phi); ok { 495 phis++ 496 } 497 } 498 } 499 if phis != 1 { 500 g.WriteTo(os.Stderr) 501 t.Errorf("expected a single Phi (for the range index), got %d", phis) 502 } 503 } 504 505 // TestGenericDecls ensures that *unused* generic types, methods and functions 506 // signatures can be built. 507 // 508 // TODO(taking): Add calls from non-generic functions to instantiations of generic functions. 509 // TODO(taking): Add globals with types that are instantiations of generic functions. 510 func TestGenericDecls(t *testing.T) { 511 if !typeparams.Enabled { 512 t.Skip("TestGenericDecls only works with type parameters enabled.") 513 } 514 const input = ` 515 package p 516 517 import "unsafe" 518 519 type Pointer[T any] struct { 520 v unsafe.Pointer 521 } 522 523 func (x *Pointer[T]) Load() *T { 524 return (*T)(LoadPointer(&x.v)) 525 } 526 527 func Load[T any](x *Pointer[T]) *T { 528 return x.Load() 529 } 530 531 func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer) 532 ` 533 // The SSA members for this package should look something like this: 534 // func LoadPointer func(addr *unsafe.Pointer) (val unsafe.Pointer) 535 // type Pointer struct{v unsafe.Pointer} 536 // method (*Pointer[T any]) Load() *T 537 // func init func() 538 // var init$guard bool 539 540 // Parse 541 var conf loader.Config 542 f, err := conf.ParseFile("<input>", input) 543 if err != nil { 544 t.Fatalf("parse: %v", err) 545 } 546 conf.CreateFromFiles("p", f) 547 548 // Load 549 lprog, err := conf.Load() 550 if err != nil { 551 t.Fatalf("Load: %v", err) 552 } 553 554 // Create and build SSA 555 prog := ssautil.CreateProgram(lprog, 0) 556 p := prog.Package(lprog.Package("p").Pkg) 557 p.Build() 558 559 if load := p.Func("Load"); typeparams.ForSignature(load.Signature).Len() != 1 { 560 t.Errorf("expected a single type param T for Load got %q", load.Signature) 561 } 562 if ptr := p.Type("Pointer"); typeparams.ForNamed(ptr.Type().(*types.Named)).Len() != 1 { 563 t.Errorf("expected a single type param T for Pointer got %q", ptr.Type()) 564 } 565 } 566 567 // TestTypeparamTest builds SSA over compilable examples in $GOROOT/test/typeparam/*.go. 568 569 func TestTypeparamTest(t *testing.T) { 570 if !typeparams.Enabled { 571 return 572 } 573 574 // Tests use a fake goroot to stub out standard libraries with delcarations in 575 // testdata/src. Decreases runtime from ~80s to ~1s. 576 577 dir := filepath.Join(build.Default.GOROOT, "test", "typeparam") 578 579 // Collect all of the .go files in 580 list, err := os.ReadDir(dir) 581 if err != nil { 582 t.Fatal(err) 583 } 584 585 for _, entry := range list { 586 if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".go") { 587 continue // Consider standalone go files. 588 } 589 input := filepath.Join(dir, entry.Name()) 590 t.Run(entry.Name(), func(t *testing.T) { 591 src, err := os.ReadFile(input) 592 if err != nil { 593 t.Fatal(err) 594 } 595 // Only build test files that can be compiled, or compiled and run. 596 if !bytes.HasPrefix(src, []byte("// run")) && !bytes.HasPrefix(src, []byte("// compile")) { 597 t.Skipf("not detected as a run test") 598 } 599 600 t.Logf("Input: %s\n", input) 601 602 ctx := build.Default // copy 603 ctx.GOROOT = "testdata" // fake goroot. Makes tests ~1s. tests take ~80s. 604 605 reportErr := func(err error) { 606 t.Error(err) 607 } 608 conf := loader.Config{Build: &ctx, TypeChecker: types.Config{Error: reportErr}} 609 if _, err := conf.FromArgs([]string{input}, true); err != nil { 610 t.Fatalf("FromArgs(%s) failed: %s", input, err) 611 } 612 613 iprog, err := conf.Load() 614 if iprog != nil { 615 for _, pkg := range iprog.Created { 616 for i, e := range pkg.Errors { 617 t.Errorf("Loading pkg %s error[%d]=%s", pkg, i, e) 618 } 619 } 620 } 621 if err != nil { 622 t.Fatalf("conf.Load(%s) failed: %s", input, err) 623 } 624 625 prog := ssautil.CreateProgram(iprog, ssa.SanityCheckFunctions) 626 prog.Build() 627 }) 628 } 629 }