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 }