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

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