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  }