github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/go/pointer/pointer_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  // No testdata on Android.
     6  
     7  //go:build !android
     8  // +build !android
     9  
    10  package pointer_test
    11  
    12  // This test uses 'expectation' comments embedded within testdata/*.go
    13  // files to specify the expected pointer analysis behaviour.
    14  // See below for grammar.
    15  
    16  import (
    17  	"bytes"
    18  	"errors"
    19  	"fmt"
    20  	"go/token"
    21  	"go/types"
    22  	"io/ioutil"
    23  	"os"
    24  	"path/filepath"
    25  	"regexp"
    26  	"strconv"
    27  	"strings"
    28  	"testing"
    29  	"unsafe"
    30  
    31  	"golang.org/x/tools/go/callgraph"
    32  	"golang.org/x/tools/go/packages"
    33  	"golang.org/x/tools/go/pointer"
    34  	"golang.org/x/tools/go/ssa"
    35  	"golang.org/x/tools/go/ssa/ssautil"
    36  	"golang.org/x/tools/go/types/typeutil"
    37  	"golang.org/x/tools/internal/typeparams"
    38  )
    39  
    40  var inputs = []string{
    41  	"testdata/a_test.go",
    42  	"testdata/another.go",
    43  	"testdata/arrayreflect.go",
    44  	"testdata/arrays.go",
    45  	"testdata/channels.go",
    46  	"testdata/chanreflect.go",
    47  	"testdata/context.go",
    48  	"testdata/conv.go",
    49  	"testdata/extended.go",
    50  	"testdata/finalizer.go",
    51  	"testdata/flow.go",
    52  	"testdata/fmtexcerpt.go",
    53  	"testdata/func.go",
    54  	"testdata/funcreflect.go",
    55  	"testdata/hello.go", // NB: causes spurious failure of HVN cross-check
    56  	"testdata/interfaces.go",
    57  	"testdata/issue9002.go",
    58  	"testdata/mapreflect.go",
    59  	"testdata/maps.go",
    60  	"testdata/panic.go",
    61  	"testdata/recur.go",
    62  	"testdata/reflect.go",
    63  	"testdata/rtti.go",
    64  	"testdata/structreflect.go",
    65  	"testdata/structs.go",
    66  	// "testdata/timer.go", // TODO(adonovan): fix broken assumptions about runtime timers
    67  }
    68  
    69  var raceEnabled = false
    70  
    71  // Expectation grammar:
    72  //
    73  // @calls f -> g
    74  //
    75  //	A 'calls' expectation asserts that edge (f, g) appears in the
    76  //	callgraph.  f and g are notated as per Function.String(), which
    77  //	may contain spaces (e.g. promoted method in anon struct).
    78  //
    79  // @pointsto a | b | c
    80  //
    81  //		A 'pointsto' expectation asserts that the points-to set of its
    82  //		operand contains exactly the set of labels {a,b,c} notated as per
    83  //		labelString.
    84  //
    85  //		A 'pointsto' expectation must appear on the same line as a
    86  //		print(x) statement; the expectation's operand is x.
    87  //
    88  //		If one of the strings is "...", the expectation asserts that the
    89  //		points-to set at least the other labels.
    90  //
    91  //		We use '|' because label names may contain spaces, e.g.  methods
    92  //		of anonymous structs.
    93  //
    94  //	 Assertions within generic functions are treated as a union of all
    95  //	 of the instantiations.
    96  //
    97  //		From a theoretical perspective, concrete types in interfaces are
    98  //		labels too, but they are represented differently and so have a
    99  //		different expectation, @types, below.
   100  //
   101  // @types t | u | v
   102  //
   103  //		A 'types' expectation asserts that the set of possible dynamic
   104  //		types of its interface operand is exactly {t,u,v}, notated per
   105  //		go/types.Type.String(). In other words, it asserts that the type
   106  //		component of the interface may point to that set of concrete type
   107  //		literals.  It also works for reflect.Value, though the types
   108  //		needn't be concrete in that case.
   109  //
   110  //		A 'types' expectation must appear on the same line as a
   111  //		print(x) statement; the expectation's operand is x.
   112  //
   113  //		If one of the strings is "...", the expectation asserts that the
   114  //		interface's type may point to at least the other types.
   115  //
   116  //		We use '|' because type names may contain spaces.
   117  //
   118  //	 Assertions within generic functions are treated as a union of all
   119  //	 of the instantiations.
   120  //
   121  // @warning "regexp"
   122  //
   123  //	A 'warning' expectation asserts that the analysis issues a
   124  //	warning that matches the regular expression within the string
   125  //	literal.
   126  //
   127  // @line id
   128  //
   129  //	A line directive associates the name "id" with the current
   130  //	file:line.  The string form of labels will use this id instead of
   131  //	a file:line, making @pointsto expectations more robust against
   132  //	perturbations in the source file.
   133  //	(NB, anon functions still include line numbers.)
   134  type expectation struct {
   135  	kind     string // "pointsto" | "pointstoquery" | "types" | "calls" | "warning"
   136  	filepath string
   137  	linenum  int // source line number, 1-based
   138  	args     []string
   139  	query    string             // extended query
   140  	extended []*pointer.Pointer // extended query pointer [per instantiation]
   141  	types    []types.Type       // for types
   142  }
   143  
   144  func (e *expectation) String() string {
   145  	return fmt.Sprintf("@%s[%s]", e.kind, strings.Join(e.args, " | "))
   146  }
   147  
   148  func (e *expectation) errorf(format string, args ...interface{}) {
   149  	fmt.Printf("%s:%d: ", e.filepath, e.linenum)
   150  	fmt.Printf(format, args...)
   151  	fmt.Println()
   152  }
   153  
   154  func (e *expectation) needsProbe() bool {
   155  	return e.kind == "pointsto" || e.kind == "pointstoquery" || e.kind == "types"
   156  }
   157  
   158  // Find probes (call to print(x)) of same source file/line as expectation.
   159  //
   160  // May match multiple calls for different instantiations.
   161  func findProbes(prog *ssa.Program, probes map[*ssa.CallCommon]bool, e *expectation) []*ssa.CallCommon {
   162  	var calls []*ssa.CallCommon
   163  	for call := range probes {
   164  		pos := prog.Fset.Position(call.Pos())
   165  		if pos.Line == e.linenum && pos.Filename == e.filepath {
   166  			// TODO(adonovan): send this to test log (display only on failure).
   167  			// fmt.Printf("%s:%d: info: found probe for %s: %s\n",
   168  			// 	e.filepath, e.linenum, e, p.arg0) // debugging
   169  			calls = append(calls, call)
   170  		}
   171  	}
   172  	return calls
   173  }
   174  
   175  // Find points to sets of probes (call to print(x)).
   176  func probesPointTo(calls []*ssa.CallCommon, queries map[ssa.Value]pointer.Pointer) []pointer.PointsToSet {
   177  	ptss := make([]pointer.PointsToSet, len(calls))
   178  	for i, call := range calls {
   179  		ptss[i] = queries[call.Args[0]].PointsTo()
   180  	}
   181  	return ptss
   182  }
   183  
   184  // Find the types of the probes (call to print(x)).
   185  // Returns an error if type of the probe cannot point.
   186  func probesPointToTypes(calls []*ssa.CallCommon) ([]types.Type, error) {
   187  	tProbes := make([]types.Type, len(calls))
   188  	for i, call := range calls {
   189  		tProbes[i] = call.Args[0].Type()
   190  		if !pointer.CanPoint(tProbes[i]) {
   191  			return nil, fmt.Errorf("expectation on non-pointerlike operand: %s", tProbes[i])
   192  		}
   193  	}
   194  	return tProbes, nil
   195  }
   196  
   197  func doOneInput(t *testing.T, input, fpath string) bool {
   198  	cfg := &packages.Config{
   199  		Mode:  packages.LoadAllSyntax,
   200  		Tests: true,
   201  	}
   202  	pkgs, err := packages.Load(cfg, fpath)
   203  	if err != nil {
   204  		fmt.Println(err)
   205  		return false
   206  	}
   207  	if packages.PrintErrors(pkgs) > 0 {
   208  		fmt.Println("loaded packages have errors")
   209  		return false
   210  	}
   211  
   212  	// SSA creation + building.
   213  	mode := ssa.SanityCheckFunctions | ssa.InstantiateGenerics
   214  	prog, ssaPkgs := ssautil.AllPackages(pkgs, mode)
   215  	prog.Build()
   216  
   217  	// main underlying packages.Package.
   218  	mainPpkg := pkgs[0]
   219  	mainpkg := ssaPkgs[0]
   220  	ptrmain := mainpkg // main package for the pointer analysis
   221  	if mainpkg.Func("main") == nil {
   222  		// For test programs without main, such as testdata/a_test.go,
   223  		// the package with the original code is "main [main.test]" and
   224  		// the package with the main is "main.test".
   225  		for i, pkg := range pkgs {
   226  			if pkg.ID == mainPpkg.ID+".test" {
   227  				ptrmain = ssaPkgs[i]
   228  			} else if pkg.ID == fmt.Sprintf("%s [%s.test]", mainPpkg.ID, mainPpkg.ID) {
   229  				mainpkg = ssaPkgs[i]
   230  			}
   231  		}
   232  	}
   233  
   234  	// files in mainPpkg.
   235  	mainFiles := make(map[*token.File]bool)
   236  	for _, syn := range mainPpkg.Syntax {
   237  		mainFiles[prog.Fset.File(syn.Pos())] = true
   238  	}
   239  
   240  	// Find all calls to the built-in print(x).  Analytically,
   241  	// print is a no-op, but it's a convenient hook for testing
   242  	// the PTS of an expression, so our tests use it.
   243  	// Exclude generic bodies as these should be dead code for pointer.
   244  	// Instance of generics are included.
   245  	probes := make(map[*ssa.CallCommon]bool)
   246  	for fn := range ssautil.AllFunctions(prog) {
   247  		if isGenericBody(fn) {
   248  			continue // skip generic bodies
   249  		}
   250  		// TODO(taking): Switch to a more principled check like fn.declaredPackage() == mainPkg if Origin is exported.
   251  		if fn.Pkg == mainpkg || (fn.Pkg == nil && mainFiles[prog.Fset.File(fn.Pos())]) {
   252  			for _, b := range fn.Blocks {
   253  				for _, instr := range b.Instrs {
   254  					if instr, ok := instr.(ssa.CallInstruction); ok {
   255  						call := instr.Common()
   256  						if b, ok := call.Value.(*ssa.Builtin); ok && b.Name() == "print" && len(call.Args) == 1 {
   257  							probes[instr.Common()] = true
   258  						}
   259  					}
   260  				}
   261  			}
   262  		}
   263  	}
   264  
   265  	ok := true
   266  
   267  	lineMapping := make(map[string]string) // maps "file:line" to @line tag
   268  
   269  	// Parse expectations in this input.
   270  	var exps []*expectation
   271  	re := regexp.MustCompile("// *@([a-z]*) *(.*)$")
   272  	lines := strings.Split(input, "\n")
   273  	for linenum, line := range lines {
   274  		linenum++ // make it 1-based
   275  		if matches := re.FindAllStringSubmatch(line, -1); matches != nil {
   276  			match := matches[0]
   277  			kind, rest := match[1], match[2]
   278  			e := &expectation{kind: kind, filepath: fpath, linenum: linenum}
   279  
   280  			if kind == "line" {
   281  				if rest == "" {
   282  					ok = false
   283  					e.errorf("@%s expectation requires identifier", kind)
   284  				} else {
   285  					lineMapping[fmt.Sprintf("%s:%d", fpath, linenum)] = rest
   286  				}
   287  				continue
   288  			}
   289  
   290  			if e.needsProbe() && !strings.Contains(line, "print(") {
   291  				ok = false
   292  				e.errorf("@%s expectation must follow call to print(x)", kind)
   293  				continue
   294  			}
   295  
   296  			switch kind {
   297  			case "pointsto":
   298  				e.args = split(rest, "|")
   299  
   300  			case "pointstoquery":
   301  				args := strings.SplitN(rest, " ", 2)
   302  				e.query = args[0]
   303  				e.args = split(args[1], "|")
   304  			case "types":
   305  				for _, typstr := range split(rest, "|") {
   306  					var t types.Type = types.Typ[types.Invalid] // means "..."
   307  					if typstr != "..." {
   308  						tv, err := types.Eval(prog.Fset, mainpkg.Pkg, mainPpkg.Syntax[0].Pos(), typstr)
   309  						if err != nil {
   310  							ok = false
   311  							// Don't print err since its location is bad.
   312  							e.errorf("'%s' is not a valid type: %s", typstr, err)
   313  							continue
   314  						}
   315  						t = tv.Type
   316  					}
   317  					e.types = append(e.types, t)
   318  				}
   319  
   320  			case "calls":
   321  				e.args = split(rest, "->")
   322  				// TODO(adonovan): eagerly reject the
   323  				// expectation if fn doesn't denote
   324  				// existing function, rather than fail
   325  				// the expectation after analysis.
   326  				if len(e.args) != 2 {
   327  					ok = false
   328  					e.errorf("@calls expectation wants 'caller -> callee' arguments")
   329  					continue
   330  				}
   331  
   332  			case "warning":
   333  				lit, err := strconv.Unquote(strings.TrimSpace(rest))
   334  				if err != nil {
   335  					ok = false
   336  					e.errorf("couldn't parse @warning operand: %s", err.Error())
   337  					continue
   338  				}
   339  				e.args = append(e.args, lit)
   340  
   341  			default:
   342  				ok = false
   343  				e.errorf("unknown expectation kind: %s", e)
   344  				continue
   345  			}
   346  			exps = append(exps, e)
   347  		}
   348  	}
   349  
   350  	var log bytes.Buffer
   351  	fmt.Fprintf(&log, "Input: %s\n", fpath)
   352  
   353  	// Run the analysis.
   354  	config := &pointer.Config{
   355  		Reflection:     true,
   356  		BuildCallGraph: true,
   357  		Mains:          []*ssa.Package{ptrmain},
   358  		Log:            &log,
   359  	}
   360  	for probe := range probes {
   361  		v := probe.Args[0]
   362  		pos := prog.Fset.Position(probe.Pos())
   363  		for _, e := range exps {
   364  			if e.linenum == pos.Line && e.filepath == pos.Filename && e.kind == "pointstoquery" {
   365  				extended, err := config.AddExtendedQuery(v, e.query)
   366  				if err != nil {
   367  					panic(err)
   368  				}
   369  				e.extended = append(e.extended, extended)
   370  			}
   371  		}
   372  		if pointer.CanPoint(v.Type()) {
   373  			config.AddQuery(v)
   374  		}
   375  	}
   376  
   377  	// Print the log is there was an error or a panic.
   378  	complete := false
   379  	defer func() {
   380  		if !complete || !ok {
   381  			log.WriteTo(os.Stderr)
   382  		}
   383  	}()
   384  
   385  	result, err := pointer.Analyze(config)
   386  	if err != nil {
   387  		panic(err) // internal error in pointer analysis
   388  	}
   389  
   390  	// Check the expectations.
   391  	for _, e := range exps {
   392  		var tProbes []types.Type
   393  		var calls []*ssa.CallCommon
   394  		var ptss []pointer.PointsToSet
   395  		if e.needsProbe() {
   396  			calls = findProbes(prog, probes, e)
   397  			if len(calls) == 0 {
   398  				ok = false
   399  				e.errorf("unreachable print() statement has expectation %s", e)
   400  				continue
   401  			}
   402  			if e.extended == nil {
   403  				ptss = probesPointTo(calls, result.Queries)
   404  			} else {
   405  				ptss = make([]pointer.PointsToSet, len(e.extended))
   406  				for i, p := range e.extended {
   407  					ptss[i] = p.PointsTo()
   408  				}
   409  			}
   410  
   411  			var err error
   412  			tProbes, err = probesPointToTypes(calls)
   413  			if err != nil {
   414  				ok = false
   415  				e.errorf(err.Error())
   416  				continue
   417  			}
   418  		}
   419  
   420  		switch e.kind {
   421  		case "pointsto", "pointstoquery":
   422  			if !checkPointsToExpectation(e, ptss, lineMapping, prog) {
   423  				ok = false
   424  			}
   425  
   426  		case "types":
   427  			if !checkTypesExpectation(e, ptss, tProbes) {
   428  				ok = false
   429  			}
   430  
   431  		case "calls":
   432  			if !checkCallsExpectation(prog, e, result.CallGraph) {
   433  				ok = false
   434  			}
   435  
   436  		case "warning":
   437  			if !checkWarningExpectation(prog, e, result.Warnings) {
   438  				ok = false
   439  			}
   440  		}
   441  	}
   442  
   443  	complete = true
   444  
   445  	// ok = false // debugging: uncomment to always see log
   446  
   447  	return ok
   448  }
   449  
   450  func labelString(l *pointer.Label, lineMapping map[string]string, prog *ssa.Program) string {
   451  	// Functions and Globals need no pos suffix,
   452  	// nor do allocations in intrinsic operations
   453  	// (for which we'll print the function name).
   454  	switch l.Value().(type) {
   455  	case nil, *ssa.Function, *ssa.Global:
   456  		return l.String()
   457  	}
   458  
   459  	str := l.String()
   460  	if pos := l.Pos(); pos != token.NoPos {
   461  		// Append the position, using a @line tag instead of a line number, if defined.
   462  		posn := prog.Fset.Position(pos)
   463  		s := fmt.Sprintf("%s:%d", posn.Filename, posn.Line)
   464  		if tag, ok := lineMapping[s]; ok {
   465  			return fmt.Sprintf("%s@%s:%d", str, tag, posn.Column)
   466  		}
   467  		str = fmt.Sprintf("%s@%s", str, posn)
   468  	}
   469  	return str
   470  }
   471  
   472  func checkPointsToExpectation(e *expectation, ptss []pointer.PointsToSet, lineMapping map[string]string, prog *ssa.Program) bool {
   473  	expected := make(map[string]int)
   474  	surplus := make(map[string]int)
   475  	exact := true
   476  	for _, g := range e.args {
   477  		if g == "..." {
   478  			exact = false
   479  			continue
   480  		}
   481  		expected[g]++
   482  	}
   483  	// Find the set of labels that the probe's
   484  	// argument (x in print(x)) may point to.
   485  	for _, pts := range ptss { // treat ptss as union of points-to sets.
   486  		for _, label := range pts.Labels() {
   487  			name := labelString(label, lineMapping, prog)
   488  			if expected[name] > 0 {
   489  				expected[name]--
   490  			} else if exact {
   491  				surplus[name]++
   492  			}
   493  		}
   494  	}
   495  	// Report multiset difference:
   496  	ok := true
   497  	for _, count := range expected {
   498  		if count > 0 {
   499  			ok = false
   500  			e.errorf("value does not alias these expected labels: %s", join(expected))
   501  			break
   502  		}
   503  	}
   504  	for _, count := range surplus {
   505  		if count > 0 {
   506  			ok = false
   507  			e.errorf("value may additionally alias these labels: %s", join(surplus))
   508  			break
   509  		}
   510  	}
   511  	return ok
   512  }
   513  
   514  func checkTypesExpectation(e *expectation, ptss []pointer.PointsToSet, typs []types.Type) bool {
   515  	var expected typeutil.Map
   516  	var surplus typeutil.Map
   517  	exact := true
   518  	for _, g := range e.types {
   519  		if g == types.Typ[types.Invalid] {
   520  			exact = false
   521  			continue
   522  		}
   523  		expected.Set(g, struct{}{})
   524  	}
   525  
   526  	if len(typs) != len(ptss) {
   527  		e.errorf("@types expectation internal error differing number of types(%d) and points to sets (%d)", len(typs), len(ptss))
   528  		return false
   529  	}
   530  
   531  	// Find the set of types that the probe's
   532  	// argument (x in print(x)) may contain.
   533  	for i := range ptss {
   534  		var Ts []types.Type
   535  		if pointer.CanHaveDynamicTypes(typs[i]) {
   536  			Ts = ptss[i].DynamicTypes().Keys()
   537  		} else {
   538  			Ts = append(Ts, typs[i]) // static type
   539  		}
   540  		for _, T := range Ts {
   541  			if expected.At(T) != nil {
   542  				expected.Delete(T)
   543  			} else if exact {
   544  				surplus.Set(T, struct{}{})
   545  			}
   546  		}
   547  	}
   548  	// Report set difference:
   549  	ok := true
   550  	if expected.Len() > 0 {
   551  		ok = false
   552  		e.errorf("interface cannot contain these types: %s", expected.KeysString())
   553  	}
   554  	if surplus.Len() > 0 {
   555  		ok = false
   556  		e.errorf("interface may additionally contain these types: %s", surplus.KeysString())
   557  	}
   558  	return ok
   559  }
   560  
   561  var errOK = errors.New("OK")
   562  
   563  func checkCallsExpectation(prog *ssa.Program, e *expectation, cg *callgraph.Graph) bool {
   564  	found := make(map[string]int)
   565  	err := callgraph.GraphVisitEdges(cg, func(edge *callgraph.Edge) error {
   566  		// Name-based matching is inefficient but it allows us to
   567  		// match functions whose names that would not appear in an
   568  		// index ("<root>") or which are not unique ("func@1.2").
   569  		if edge.Caller.Func.String() == e.args[0] {
   570  			calleeStr := edge.Callee.Func.String()
   571  			if calleeStr == e.args[1] {
   572  				return errOK // expectation satisfied; stop the search
   573  			}
   574  			found[calleeStr]++
   575  		}
   576  		return nil
   577  	})
   578  	if err == errOK {
   579  		return true
   580  	}
   581  	if len(found) == 0 {
   582  		e.errorf("didn't find any calls from %s", e.args[0])
   583  	}
   584  	e.errorf("found no call from %s to %s, but only to %s",
   585  		e.args[0], e.args[1], join(found))
   586  	return false
   587  }
   588  
   589  func checkWarningExpectation(prog *ssa.Program, e *expectation, warnings []pointer.Warning) bool {
   590  	// TODO(adonovan): check the position part of the warning too?
   591  	re, err := regexp.Compile(e.args[0])
   592  	if err != nil {
   593  		e.errorf("invalid regular expression in @warning expectation: %s", err.Error())
   594  		return false
   595  	}
   596  
   597  	if len(warnings) == 0 {
   598  		e.errorf("@warning %q expectation, but no warnings", e.args[0])
   599  		return false
   600  	}
   601  
   602  	for _, w := range warnings {
   603  		if re.MatchString(w.Message) {
   604  			return true
   605  		}
   606  	}
   607  
   608  	e.errorf("@warning %q expectation not satisfied; found these warnings though:", e.args[0])
   609  	for _, w := range warnings {
   610  		fmt.Printf("%s: warning: %s\n", prog.Fset.Position(w.Pos), w.Message)
   611  	}
   612  	return false
   613  }
   614  
   615  func TestInput(t *testing.T) {
   616  	if testing.Short() {
   617  		t.Skip("skipping in short mode; this test requires tons of memory; https://golang.org/issue/14113")
   618  	}
   619  
   620  	wd, err := os.Getwd()
   621  	if err != nil {
   622  		t.Errorf("os.Getwd: %s", err)
   623  		return
   624  	}
   625  
   626  	// 'go test' does a chdir so that relative paths in
   627  	// diagnostics no longer make sense relative to the invoking
   628  	// shell's cwd.  We print a special marker so that Emacs can
   629  	// make sense of them.
   630  	fmt.Fprintf(os.Stderr, "Entering directory `%s'\n", wd)
   631  
   632  	for _, filename := range inputs {
   633  		filename := filename
   634  		t.Run(filename, func(t *testing.T) {
   635  			if filename == "testdata/a_test.go" {
   636  				// For some reason this particular file is way more expensive than the others.
   637  				if unsafe.Sizeof(unsafe.Pointer(nil)) <= 4 {
   638  					t.Skip("skipping memory-intensive test on platform with small address space; https://golang.org/issue/14113")
   639  				}
   640  				if raceEnabled {
   641  					t.Skip("skipping memory-intensive test under race detector; https://golang.org/issue/14113")
   642  				}
   643  			} else {
   644  				t.Parallel()
   645  			}
   646  
   647  			content, err := ioutil.ReadFile(filename)
   648  			if err != nil {
   649  				t.Fatalf("couldn't read file '%s': %s", filename, err)
   650  			}
   651  
   652  			fpath, err := filepath.Abs(filename)
   653  			if err != nil {
   654  				t.Fatalf("couldn't get absolute path for '%s': %s", filename, err)
   655  			}
   656  
   657  			if !doOneInput(t, string(content), fpath) {
   658  				t.Fail()
   659  			}
   660  		})
   661  	}
   662  }
   663  
   664  // isGenericBody returns true if fn is the body of a generic function.
   665  func isGenericBody(fn *ssa.Function) bool {
   666  	sig := fn.Signature
   667  	if typeparams.ForSignature(sig).Len() > 0 || typeparams.RecvTypeParams(sig).Len() > 0 {
   668  		return fn.Synthetic == ""
   669  	}
   670  	return false
   671  }
   672  
   673  // join joins the elements of multiset with " | "s.
   674  func join(set map[string]int) string {
   675  	var buf bytes.Buffer
   676  	sep := ""
   677  	for name, count := range set {
   678  		for i := 0; i < count; i++ {
   679  			buf.WriteString(sep)
   680  			sep = " | "
   681  			buf.WriteString(name)
   682  		}
   683  	}
   684  	return buf.String()
   685  }
   686  
   687  // split returns the list of sep-delimited non-empty strings in s.
   688  func split(s, sep string) (r []string) {
   689  	for _, elem := range strings.Split(s, sep) {
   690  		elem = strings.TrimSpace(elem)
   691  		if elem != "" {
   692  			r = append(r, elem)
   693  		}
   694  	}
   695  	return
   696  }
   697  
   698  func TestTypeParam(t *testing.T) {
   699  	if !typeparams.Enabled {
   700  		t.Skip("TestTypeParamInput requires type parameters")
   701  	}
   702  	// Based on TestInput. Keep this up to date with that.
   703  	filename := "testdata/typeparams.go"
   704  
   705  	if testing.Short() {
   706  		t.Skip("skipping in short mode; this test requires tons of memory; https://golang.org/issue/14113")
   707  	}
   708  
   709  	wd, err := os.Getwd()
   710  	if err != nil {
   711  		t.Fatalf("os.Getwd: %s", err)
   712  	}
   713  	fmt.Fprintf(os.Stderr, "Entering directory `%s'\n", wd)
   714  
   715  	content, err := ioutil.ReadFile(filename)
   716  	if err != nil {
   717  		t.Fatalf("couldn't read file '%s': %s", filename, err)
   718  	}
   719  	fpath, err := filepath.Abs(filename)
   720  	if err != nil {
   721  		t.Errorf("couldn't get absolute path for '%s': %s", filename, err)
   722  	}
   723  
   724  	if !doOneInput(t, string(content), fpath) {
   725  		t.Fail()
   726  	}
   727  }