Repository: cilium/ebpf
Pull request #1582 (2024-10-10 11:09:40)

btf: Handle Tags when making copies
d4b2f8cc introduced Tags on different types implementing btf.Type. Tags arrays are initialized at load time from declTag-s referring to the respective type.

Type-s are explicitly copied with btf.Copy, also implicitly when copying a btf.Spec. Semantically, it is a deep copy, hence Tags must be cloned as well.


Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 8001416..33ef404 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -105,7 +105,7 @@ jobs:
 
   test-on-prev-go:
     name: Run tests on previous stable Go
-    runs-on: ubuntu-latest-4cores-16gb
+    runs-on: ubuntu-latest
     needs: build-and-lint
     timeout-minutes: 15
     env:
@@ -186,14 +186,13 @@ jobs:
 
   vm-test:
     name: Run tests
-    runs-on: ubuntu-latest-4cores-16gb
+    runs-on: ubuntu-latest
     needs: build-and-lint
     timeout-minutes: 15
     strategy:
       matrix:
         tag:
-          # Re-enable when https://github.com/lmb/vimto/issues/19 is fixed.
-          # - "mainline"
+          - "mainline"
           - "stable"
           - "6.6"
           - "6.1"
diff --git a/btf/types.go b/btf/types.go
index bbabfb7..44d3930 100644
--- a/btf/types.go
+++ b/btf/types.go
@@ -183,6 +183,7 @@ func (s *Struct) size() uint32 { return s.Size }
 func (s *Struct) copy() Type {
 	cpy := *s
 	cpy.Members = copyMembers(s.Members)
+	cpy.Tags = copyTags(cpy.Tags)
 	return &cpy
 }
 
@@ -210,6 +211,7 @@ func (u *Union) size() uint32 { return u.Size }
 func (u *Union) copy() Type {
 	cpy := *u
 	cpy.Members = copyMembers(u.Members)
+	cpy.Tags = copyTags(cpy.Tags)
 	return &cpy
 }
 
@@ -220,6 +222,18 @@ func (u *Union) members() []Member {
 func copyMembers(orig []Member) []Member {
 	cpy := make([]Member, len(orig))
 	copy(cpy, orig)
+	for i, member := range cpy {
+		cpy[i].Tags = copyTags(member.Tags)
+	}
+	return cpy
+}
+
+func copyTags(orig []string) []string {
+	if orig == nil { // preserve nil vs zero-len slice distinction
+		return nil
+	}
+	cpy := make([]string, len(orig))
+	copy(cpy, orig)
 	return cpy
 }
 
@@ -348,6 +362,7 @@ func (td *Typedef) TypeName() string { return td.Name }
 
 func (td *Typedef) copy() Type {
 	cpy := *td
+	cpy.Tags = copyTags(td.Tags)
 	return &cpy
 }
 
@@ -434,6 +449,14 @@ func (f *Func) TypeName() string { return f.Name }
 
 func (f *Func) copy() Type {
 	cpy := *f
+	cpy.Tags = copyTags(f.Tags)
+	if f.ParamTags != nil { // preserve nil vs zero-len slice distinction
+		ptCopy := make([][]string, len(f.ParamTags))
+		for i, tags := range f.ParamTags {
+			ptCopy[i] = copyTags(tags)
+		}
+		cpy.ParamTags = ptCopy
+	}
 	return &cpy
 }
 
@@ -477,6 +500,7 @@ func (v *Var) TypeName() string { return v.Name }
 
 func (v *Var) copy() Type {
 	cpy := *v
+	cpy.Tags = copyTags(v.Tags)
 	return &cpy
 }
 


Test patch:
diff --git a/btf/types_test.go b/btf/types_test.go
index 3812e1a..e6cbc46 100644
--- a/btf/types_test.go
+++ b/btf/types_test.go
@@ -42,6 +42,7 @@ func TestSizeof(t *testing.T) {
 
 func TestCopy(t *testing.T) {
 	i := &Int{Size: 4}
+	tags := []string{"bar:foo"}
 
 	got := Copy(&Struct{
 		Members: []Member{
@@ -60,6 +61,11 @@ func TestCopy(t *testing.T) {
 		{"void", (*Void)(nil)},
 		{"int", i},
 		{"cyclical", newCyclicalType(2)},
+		{"struct tags", &Struct{Tags: tags, Members: []Member{{Tags: tags}}}},
+		{"union tags", &Union{Tags: tags, Members: []Member{{Tags: tags}}}},
+		{"typedef tags", &Typedef{Type: i, Tags: tags}},
+		{"var tags", &Var{Type: i, Tags: tags}},
+		{"func tags", &Func{Tags: tags, ParamTags: [][]string{tags}}},
 	} {
 		t.Run(test.name, func(t *testing.T) {
 			cpy := Copy(test.typ)
diff --git a/prog_test.go b/prog_test.go
index b43fcff..b1ff509 100644
--- a/prog_test.go
+++ b/prog_test.go
@@ -472,8 +472,7 @@ func TestProgramVerifierLog(t *testing.T) {
 	}
 
 	// Generate a base program of sufficient size whose verifier log does not fit
-	// a 128-byte buffer. This should always result in ENOSPC, setting the
-	// VerifierError.Truncated flag.
+	// a 128-byte buffer. This should always result in ENOSPC.
 	var base asm.Instructions
 	for i := 0; i < 32; i++ {
 		base = append(base, asm.Mov.Reg(asm.R0, asm.R1))
@@ -505,13 +504,22 @@ func TestProgramVerifierLog(t *testing.T) {
 	})
 	check(t, err)
 
+	// Disabling the verifier log should result in a VerifierError without a log.
+	_, err = NewProgramWithOptions(spec, ProgramOptions{
+		LogDisabled: true,
+	})
+	var ve *internal.VerifierError
+	qt.Assert(t, qt.ErrorAs(err, &ve))
+	qt.Assert(t, qt.HasLen(ve.Log, 0))
+
 	// Run tests against a valid program from here on out.
 	spec.Instructions = valid
 
-	// Don't request a verifier log, only set LogSize. Expect the valid program to
-	// be created without errors.
+	// Don't request a verifier log, expect the valid program to be created
+	// without errors.
 	prog, err := NewProgramWithOptions(spec, ProgramOptions{})
 	qt.Assert(t, qt.IsNil(err))
+	qt.Assert(t, qt.HasLen(prog.VerifierLog, 0))
 	prog.Close()
 
 	// Explicitly request verifier log for a valid program. If a log is requested