github.com/cilium/ebpf@v0.10.0/btf/types_test.go (about)

     1  package btf
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"testing"
     7  
     8  	qt "github.com/frankban/quicktest"
     9  	"github.com/google/go-cmp/cmp"
    10  )
    11  
    12  func TestSizeof(t *testing.T) {
    13  	testcases := []struct {
    14  		size int
    15  		typ  Type
    16  	}{
    17  		{0, (*Void)(nil)},
    18  		{1, &Int{Size: 1}},
    19  		{8, &Enum{Size: 8}},
    20  		{0, &Array{Type: &Pointer{Target: (*Void)(nil)}, Nelems: 0}},
    21  		{12, &Array{Type: &Enum{Size: 4}, Nelems: 3}},
    22  	}
    23  
    24  	for _, tc := range testcases {
    25  		name := fmt.Sprint(tc.typ)
    26  		t.Run(name, func(t *testing.T) {
    27  			have, err := Sizeof(tc.typ)
    28  			if err != nil {
    29  				t.Fatal("Can't calculate size:", err)
    30  			}
    31  			if have != tc.size {
    32  				t.Errorf("Expected size %d, got %d", tc.size, have)
    33  			}
    34  		})
    35  	}
    36  }
    37  
    38  func TestCopy(t *testing.T) {
    39  	_ = Copy((*Void)(nil), nil)
    40  
    41  	in := &Int{Size: 4}
    42  	out := Copy(in, nil)
    43  
    44  	in.Size = 8
    45  	if size := out.(*Int).Size; size != 4 {
    46  		t.Error("Copy doesn't make a copy, expected size 4, got", size)
    47  	}
    48  
    49  	t.Run("cyclical", func(t *testing.T) {
    50  		_ = Copy(newCyclicalType(2), nil)
    51  	})
    52  
    53  	t.Run("identity", func(t *testing.T) {
    54  		u16 := &Int{Size: 2}
    55  
    56  		out := Copy(&Struct{
    57  			Members: []Member{
    58  				{Name: "a", Type: u16},
    59  				{Name: "b", Type: u16},
    60  			},
    61  		}, nil)
    62  
    63  		outStruct := out.(*Struct)
    64  		qt.Assert(t, outStruct.Members[0].Type, qt.Equals, outStruct.Members[1].Type)
    65  	})
    66  }
    67  
    68  func BenchmarkCopy(b *testing.B) {
    69  	typ := newCyclicalType(10)
    70  
    71  	b.ReportAllocs()
    72  	b.ResetTimer()
    73  
    74  	for i := 0; i < b.N; i++ {
    75  		Copy(typ, nil)
    76  	}
    77  }
    78  
    79  // The following are valid Types.
    80  //
    81  // There currently is no better way to document which
    82  // types implement an interface.
    83  func ExampleType_validTypes() {
    84  	var _ Type = &Void{}
    85  	var _ Type = &Int{}
    86  	var _ Type = &Pointer{}
    87  	var _ Type = &Array{}
    88  	var _ Type = &Struct{}
    89  	var _ Type = &Union{}
    90  	var _ Type = &Enum{}
    91  	var _ Type = &Fwd{}
    92  	var _ Type = &Typedef{}
    93  	var _ Type = &Volatile{}
    94  	var _ Type = &Const{}
    95  	var _ Type = &Restrict{}
    96  	var _ Type = &Func{}
    97  	var _ Type = &FuncProto{}
    98  	var _ Type = &Var{}
    99  	var _ Type = &Datasec{}
   100  	var _ Type = &Float{}
   101  }
   102  
   103  func TestType(t *testing.T) {
   104  	types := []func() Type{
   105  		func() Type { return &Void{} },
   106  		func() Type { return &Int{Size: 2} },
   107  		func() Type { return &Pointer{Target: &Void{}} },
   108  		func() Type { return &Array{Type: &Int{}} },
   109  		func() Type {
   110  			return &Struct{
   111  				Members: []Member{{Type: &Void{}}},
   112  			}
   113  		},
   114  		func() Type {
   115  			return &Union{
   116  				Members: []Member{{Type: &Void{}}},
   117  			}
   118  		},
   119  		func() Type { return &Enum{} },
   120  		func() Type { return &Fwd{Name: "thunk"} },
   121  		func() Type { return &Typedef{Type: &Void{}} },
   122  		func() Type { return &Volatile{Type: &Void{}} },
   123  		func() Type { return &Const{Type: &Void{}} },
   124  		func() Type { return &Restrict{Type: &Void{}} },
   125  		func() Type { return &Func{Name: "foo", Type: &Void{}} },
   126  		func() Type {
   127  			return &FuncProto{
   128  				Params: []FuncParam{{Name: "bar", Type: &Void{}}},
   129  				Return: &Void{},
   130  			}
   131  		},
   132  		func() Type { return &Var{Type: &Void{}} },
   133  		func() Type {
   134  			return &Datasec{
   135  				Vars: []VarSecinfo{{Type: &Void{}}},
   136  			}
   137  		},
   138  		func() Type { return &Float{} },
   139  		func() Type { return &declTag{Type: &Void{}} },
   140  		func() Type { return &typeTag{Type: &Void{}} },
   141  		func() Type { return &cycle{&Void{}} },
   142  	}
   143  
   144  	compareTypes := cmp.Comparer(func(a, b *Type) bool {
   145  		return a == b
   146  	})
   147  
   148  	for _, fn := range types {
   149  		typ := fn()
   150  		t.Run(fmt.Sprintf("%T", typ), func(t *testing.T) {
   151  			t.Logf("%v", typ)
   152  
   153  			if typ == typ.copy() {
   154  				t.Error("Copy doesn't copy")
   155  			}
   156  
   157  			var a []*Type
   158  			walkType(typ, func(t *Type) { a = append(a, t) })
   159  
   160  			if _, ok := typ.(*cycle); !ok {
   161  				if n := countChildren(t, reflect.TypeOf(typ)); len(a) < n {
   162  					t.Errorf("walkType visited %d children, expected at least %d", len(a), n)
   163  				}
   164  			}
   165  
   166  			var b []*Type
   167  			walkType(typ, func(t *Type) { b = append(b, t) })
   168  
   169  			if diff := cmp.Diff(a, b, compareTypes); diff != "" {
   170  				t.Errorf("Walk mismatch (-want +got):\n%s", diff)
   171  			}
   172  		})
   173  	}
   174  }
   175  
   176  func countChildren(t *testing.T, typ reflect.Type) int {
   177  	if typ.Kind() != reflect.Pointer {
   178  		t.Fatal("Expected pointer, got", typ.Kind())
   179  	}
   180  
   181  	typ = typ.Elem()
   182  	if typ.Kind() != reflect.Struct {
   183  		t.Fatal("Expected struct, got", typ.Kind())
   184  	}
   185  
   186  	var n int
   187  	for i := 0; i < typ.NumField(); i++ {
   188  		if typ.Field(i).Type == reflect.TypeOf((*Type)(nil)).Elem() {
   189  			n++
   190  		}
   191  	}
   192  
   193  	return n
   194  }
   195  
   196  type testFormattableType struct {
   197  	name  string
   198  	extra []interface{}
   199  }
   200  
   201  var _ formattableType = (*testFormattableType)(nil)
   202  
   203  func (tft *testFormattableType) TypeName() string { return tft.name }
   204  func (tft *testFormattableType) Format(fs fmt.State, verb rune) {
   205  	formatType(fs, verb, tft, tft.extra...)
   206  }
   207  
   208  func TestFormatType(t *testing.T) {
   209  	t1 := &testFormattableType{"", []interface{}{"extra"}}
   210  	t1Addr := fmt.Sprintf("%#p", t1)
   211  	goType := reflect.TypeOf(t1).Elem().Name()
   212  
   213  	t2 := &testFormattableType{"foo", []interface{}{t1}}
   214  
   215  	t3 := &testFormattableType{extra: []interface{}{""}}
   216  
   217  	tests := []struct {
   218  		t        formattableType
   219  		fmt      string
   220  		contains []string
   221  		omits    []string
   222  	}{
   223  		// %s doesn't contain address or extra.
   224  		{t1, "%s", []string{goType}, []string{t1Addr, "extra"}},
   225  		// %+s doesn't contain extra.
   226  		{t1, "%+s", []string{goType, t1Addr}, []string{"extra"}},
   227  		// %v does contain extra.
   228  		{t1, "%v", []string{goType, "extra"}, []string{t1Addr}},
   229  		// %+v does contain address.
   230  		{t1, "%+v", []string{goType, "extra", t1Addr}, nil},
   231  		// %v doesn't print nested types' extra.
   232  		{t2, "%v", []string{goType, t2.name}, []string{"extra"}},
   233  		// %1v does print nested types' extra.
   234  		{t2, "%1v", []string{goType, t2.name, "extra"}, nil},
   235  		// empty strings in extra don't emit anything.
   236  		{t3, "%v", []string{"[]"}, nil},
   237  	}
   238  
   239  	for _, test := range tests {
   240  		t.Run(test.fmt, func(t *testing.T) {
   241  			str := fmt.Sprintf(test.fmt, test.t)
   242  			t.Log(str)
   243  
   244  			for _, want := range test.contains {
   245  				qt.Assert(t, str, qt.Contains, want)
   246  			}
   247  
   248  			for _, notWant := range test.omits {
   249  				qt.Assert(t, str, qt.Not(qt.Contains), notWant)
   250  			}
   251  		})
   252  	}
   253  }
   254  
   255  func newCyclicalType(n int) Type {
   256  	ptr := &Pointer{}
   257  	prev := Type(ptr)
   258  	for i := 0; i < n; i++ {
   259  		switch i % 5 {
   260  		case 0:
   261  			prev = &Struct{
   262  				Members: []Member{
   263  					{Type: prev},
   264  				},
   265  			}
   266  
   267  		case 1:
   268  			prev = &Const{Type: prev}
   269  		case 2:
   270  			prev = &Volatile{Type: prev}
   271  		case 3:
   272  			prev = &Typedef{Type: prev}
   273  		case 4:
   274  			prev = &Array{Type: prev, Index: &Int{Size: 1}}
   275  		}
   276  	}
   277  	ptr.Target = prev
   278  	return ptr
   279  }
   280  
   281  func TestUnderlyingType(t *testing.T) {
   282  	wrappers := []struct {
   283  		name string
   284  		fn   func(Type) Type
   285  	}{
   286  		{"const", func(t Type) Type { return &Const{Type: t} }},
   287  		{"volatile", func(t Type) Type { return &Volatile{Type: t} }},
   288  		{"restrict", func(t Type) Type { return &Restrict{Type: t} }},
   289  		{"typedef", func(t Type) Type { return &Typedef{Type: t} }},
   290  		{"type tag", func(t Type) Type { return &typeTag{Type: t} }},
   291  	}
   292  
   293  	for _, test := range wrappers {
   294  		t.Run(test.name+" cycle", func(t *testing.T) {
   295  			root := &Volatile{}
   296  			root.Type = test.fn(root)
   297  
   298  			got, ok := UnderlyingType(root).(*cycle)
   299  			qt.Assert(t, ok, qt.IsTrue)
   300  			qt.Assert(t, got.root, qt.Equals, root)
   301  		})
   302  	}
   303  
   304  	for _, test := range wrappers {
   305  		t.Run(test.name, func(t *testing.T) {
   306  			want := &Int{}
   307  			got := UnderlyingType(test.fn(want))
   308  			qt.Assert(t, got, qt.Equals, want)
   309  		})
   310  	}
   311  }
   312  
   313  func TestInflateLegacyBitfield(t *testing.T) {
   314  	const offset = 3
   315  	const size = 5
   316  
   317  	var rawInt rawType
   318  	rawInt.SetKind(kindInt)
   319  	rawInt.SetSize(4)
   320  	var data btfInt
   321  	data.SetOffset(offset)
   322  	data.SetBits(size)
   323  	rawInt.data = &data
   324  
   325  	var beforeInt rawType
   326  	beforeInt.SetKind(kindStruct)
   327  	beforeInt.SetVlen(1)
   328  	beforeInt.data = []btfMember{{Type: 2}}
   329  
   330  	afterInt := beforeInt
   331  	afterInt.data = []btfMember{{Type: 1}}
   332  
   333  	emptyStrings := newStringTable("")
   334  
   335  	for _, test := range []struct {
   336  		name string
   337  		raw  []rawType
   338  	}{
   339  		{"struct before int", []rawType{beforeInt, rawInt}},
   340  		{"struct after int", []rawType{rawInt, afterInt}},
   341  	} {
   342  		t.Run(test.name, func(t *testing.T) {
   343  			types, err := inflateRawTypes(test.raw, nil, emptyStrings)
   344  			if err != nil {
   345  				t.Fatal(err)
   346  			}
   347  
   348  			for _, typ := range types {
   349  				s, ok := typ.(*Struct)
   350  				if !ok {
   351  					continue
   352  				}
   353  
   354  				i := s.Members[0]
   355  				if i.BitfieldSize != size {
   356  					t.Errorf("Expected bitfield size %d, got %d", size, i.BitfieldSize)
   357  				}
   358  
   359  				if i.Offset != offset {
   360  					t.Errorf("Expected offset %d, got %d", offset, i.Offset)
   361  				}
   362  
   363  				return
   364  			}
   365  
   366  			t.Fatal("No Struct returned from inflateRawTypes")
   367  		})
   368  	}
   369  }
   370  
   371  func BenchmarkWalk(b *testing.B) {
   372  	types := []Type{
   373  		&Void{},
   374  		&Int{},
   375  		&Pointer{},
   376  		&Array{},
   377  		&Struct{Members: make([]Member, 2)},
   378  		&Union{Members: make([]Member, 2)},
   379  		&Enum{},
   380  		&Fwd{},
   381  		&Typedef{},
   382  		&Volatile{},
   383  		&Const{},
   384  		&Restrict{},
   385  		&Func{},
   386  		&FuncProto{Params: make([]FuncParam, 2)},
   387  		&Var{},
   388  		&Datasec{Vars: make([]VarSecinfo, 2)},
   389  	}
   390  
   391  	for _, typ := range types {
   392  		b.Run(fmt.Sprint(typ), func(b *testing.B) {
   393  			b.ReportAllocs()
   394  
   395  			for i := 0; i < b.N; i++ {
   396  				var dq typeDeque
   397  				walkType(typ, dq.Push)
   398  			}
   399  		})
   400  	}
   401  }
   402  
   403  func BenchmarkUnderlyingType(b *testing.B) {
   404  	b.Run("no unwrapping", func(b *testing.B) {
   405  		v := &Int{}
   406  		b.ReportAllocs()
   407  		b.ResetTimer()
   408  
   409  		for i := 0; i < b.N; i++ {
   410  			UnderlyingType(v)
   411  		}
   412  	})
   413  
   414  	b.Run("single unwrapping", func(b *testing.B) {
   415  		v := &Typedef{Type: &Int{}}
   416  		b.ReportAllocs()
   417  		b.ResetTimer()
   418  
   419  		for i := 0; i < b.N; i++ {
   420  			UnderlyingType(v)
   421  		}
   422  	})
   423  }
   424  
   425  // Copy can be used with UnderlyingType to strip qualifiers from a type graph.
   426  func ExampleCopy_stripQualifiers() {
   427  	a := &Volatile{Type: &Pointer{Target: &Typedef{Name: "foo", Type: &Int{Size: 2}}}}
   428  	b := Copy(a, UnderlyingType)
   429  	// b has Volatile and Typedef removed.
   430  	fmt.Printf("%3v\n", b)
   431  	// Output: Pointer[target=Int[unsigned size=16]]
   432  }