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