github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/go/callgraph/vta/graph_test.go (about)

     1  // Copyright 2021 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 vta
     6  
     7  import (
     8  	"fmt"
     9  	"go/types"
    10  	"reflect"
    11  	"sort"
    12  	"strings"
    13  	"testing"
    14  
    15  	"github.com/powerman/golang-tools/go/callgraph/cha"
    16  	"github.com/powerman/golang-tools/go/ssa/ssautil"
    17  )
    18  
    19  func TestNodeInterface(t *testing.T) {
    20  	// Since ssa package does not allow explicit creation of ssa
    21  	// values, we use the values from the program testdata/src/simple.go:
    22  	//   - basic type int
    23  	//   - struct X with two int fields a and b
    24  	//   - global variable "gl"
    25  	//   - "main" function and its
    26  	//   - first register instruction t0 := *gl
    27  	prog, _, err := testProg("testdata/src/simple.go")
    28  	if err != nil {
    29  		t.Fatalf("couldn't load testdata/src/simple.go program: %v", err)
    30  	}
    31  
    32  	pkg := prog.AllPackages()[0]
    33  	main := pkg.Func("main")
    34  	reg := firstRegInstr(main) // t0 := *gl
    35  	X := pkg.Type("X").Type()
    36  	gl := pkg.Var("gl")
    37  	glPtrType, ok := gl.Type().(*types.Pointer)
    38  	if !ok {
    39  		t.Fatalf("could not cast gl variable to pointer type")
    40  	}
    41  	bint := glPtrType.Elem()
    42  
    43  	pint := types.NewPointer(bint)
    44  	i := types.NewInterface(nil, nil)
    45  
    46  	voidFunc := main.Signature.Underlying()
    47  
    48  	for _, test := range []struct {
    49  		n node
    50  		s string
    51  		t types.Type
    52  	}{
    53  		{constant{typ: bint}, "Constant(int)", bint},
    54  		{pointer{typ: pint}, "Pointer(*int)", pint},
    55  		{mapKey{typ: bint}, "MapKey(int)", bint},
    56  		{mapValue{typ: pint}, "MapValue(*int)", pint},
    57  		{sliceElem{typ: bint}, "Slice([]int)", bint},
    58  		{channelElem{typ: pint}, "Channel(chan *int)", pint},
    59  		{field{StructType: X, index: 0}, "Field(testdata.X:a)", bint},
    60  		{field{StructType: X, index: 1}, "Field(testdata.X:b)", bint},
    61  		{global{val: gl}, "Global(gl)", gl.Type()},
    62  		{local{val: reg}, "Local(t0)", bint},
    63  		{indexedLocal{val: reg, typ: X, index: 0}, "Local(t0[0])", X},
    64  		{function{f: main}, "Function(main)", voidFunc},
    65  		{nestedPtrInterface{typ: i}, "PtrInterface(interface{})", i},
    66  		{nestedPtrFunction{typ: voidFunc}, "PtrFunction(func())", voidFunc},
    67  		{panicArg{}, "Panic", nil},
    68  		{recoverReturn{}, "Recover", nil},
    69  	} {
    70  		if test.s != test.n.String() {
    71  			t.Errorf("want %s; got %s", test.s, test.n.String())
    72  		}
    73  		if test.t != test.n.Type() {
    74  			t.Errorf("want %s; got %s", test.t, test.n.Type())
    75  		}
    76  	}
    77  }
    78  
    79  func TestVtaGraph(t *testing.T) {
    80  	// Get the basic type int from a real program.
    81  	prog, _, err := testProg("testdata/src/simple.go")
    82  	if err != nil {
    83  		t.Fatalf("couldn't load testdata/src/simple.go program: %v", err)
    84  	}
    85  
    86  	glPtrType, ok := prog.AllPackages()[0].Var("gl").Type().(*types.Pointer)
    87  	if !ok {
    88  		t.Fatalf("could not cast gl variable to pointer type")
    89  	}
    90  	bint := glPtrType.Elem()
    91  
    92  	n1 := constant{typ: bint}
    93  	n2 := pointer{typ: types.NewPointer(bint)}
    94  	n3 := mapKey{typ: types.NewMap(bint, bint)}
    95  	n4 := mapValue{typ: types.NewMap(bint, bint)}
    96  
    97  	// Create graph
    98  	//   n1   n2
    99  	//    \  / /
   100  	//     n3 /
   101  	//     | /
   102  	//     n4
   103  	g := make(vtaGraph)
   104  	g.addEdge(n1, n3)
   105  	g.addEdge(n2, n3)
   106  	g.addEdge(n3, n4)
   107  	g.addEdge(n2, n4)
   108  	// for checking duplicates
   109  	g.addEdge(n1, n3)
   110  
   111  	want := vtaGraph{
   112  		n1: map[node]bool{n3: true},
   113  		n2: map[node]bool{n3: true, n4: true},
   114  		n3: map[node]bool{n4: true},
   115  	}
   116  
   117  	if !reflect.DeepEqual(want, g) {
   118  		t.Errorf("want %v; got %v", want, g)
   119  	}
   120  
   121  	for _, test := range []struct {
   122  		n node
   123  		l int
   124  	}{
   125  		{n1, 1},
   126  		{n2, 2},
   127  		{n3, 1},
   128  		{n4, 0},
   129  	} {
   130  		if sl := len(g.successors(test.n)); sl != test.l {
   131  			t.Errorf("want %d successors; got %d", test.l, sl)
   132  		}
   133  	}
   134  }
   135  
   136  // vtaGraphStr stringifies vtaGraph into a list of strings
   137  // where each string represents an edge set of the format
   138  // node -> succ_1, ..., succ_n. succ_1, ..., succ_n are
   139  // sorted in alphabetical order.
   140  func vtaGraphStr(g vtaGraph) []string {
   141  	var vgs []string
   142  	for n, succ := range g {
   143  		var succStr []string
   144  		for s := range succ {
   145  			succStr = append(succStr, s.String())
   146  		}
   147  		sort.Strings(succStr)
   148  		entry := fmt.Sprintf("%v -> %v", n.String(), strings.Join(succStr, ", "))
   149  		vgs = append(vgs, entry)
   150  	}
   151  	return vgs
   152  }
   153  
   154  // subGraph checks if a graph `g1` is a subgraph of graph `g2`.
   155  // Assumes that each element in `g1` and `g2` is an edge set
   156  // for a particular node in a fixed yet arbitrary format.
   157  func subGraph(g1, g2 []string) bool {
   158  	m := make(map[string]bool)
   159  	for _, s := range g2 {
   160  		m[s] = true
   161  	}
   162  
   163  	for _, s := range g1 {
   164  		if _, ok := m[s]; !ok {
   165  			return false
   166  		}
   167  	}
   168  	return true
   169  }
   170  
   171  func TestVTAGraphConstruction(t *testing.T) {
   172  	for _, file := range []string{
   173  		"testdata/src/store.go",
   174  		"testdata/src/phi.go",
   175  		"testdata/src/type_conversions.go",
   176  		"testdata/src/type_assertions.go",
   177  		"testdata/src/fields.go",
   178  		"testdata/src/node_uniqueness.go",
   179  		"testdata/src/store_load_alias.go",
   180  		"testdata/src/phi_alias.go",
   181  		"testdata/src/channels.go",
   182  		"testdata/src/select.go",
   183  		"testdata/src/stores_arrays.go",
   184  		"testdata/src/maps.go",
   185  		"testdata/src/ranges.go",
   186  		"testdata/src/closures.go",
   187  		"testdata/src/function_alias.go",
   188  		"testdata/src/static_calls.go",
   189  		"testdata/src/dynamic_calls.go",
   190  		"testdata/src/returns.go",
   191  		"testdata/src/panic.go",
   192  	} {
   193  		t.Run(file, func(t *testing.T) {
   194  			prog, want, err := testProg(file)
   195  			if err != nil {
   196  				t.Fatalf("couldn't load test file '%s': %s", file, err)
   197  			}
   198  			if len(want) == 0 {
   199  				t.Fatalf("couldn't find want in `%s`", file)
   200  			}
   201  
   202  			g, _ := typePropGraph(ssautil.AllFunctions(prog), cha.CallGraph(prog))
   203  			if gs := vtaGraphStr(g); !subGraph(want, gs) {
   204  				t.Errorf("`%s`: want superset of %v;\n got %v", file, want, gs)
   205  			}
   206  		})
   207  	}
   208  }