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