Skip to content

Commit ad7e33b

Browse files
committed
bpf2go: generate short names for enum elements
Constant names emitted for enum elements end up quite unwieldy. They are prefixed with enum name because in C struct, union and enum names are in a separate namespace, allowing for overlaps with identifiers, e.g: enum E { E }; While such overlaps are possible in *theory*, people usually don't do it. If a typicall C naming convention is followed, we get enum something { SOMETHING_FOO, SOMETHING_BAR }; generating <STEM>SomethingSOMETHING_FOO and <STEM>SomethingSOMETHING_BAR. In addition to "safe" long names, generate shorter ones if the respective name is not taken. <STEM>SOMETHING_FOO and <STEM>SOMETHING_BAR are much nicer to work with. Signed-off-by: Nick Zavaritsky <[email protected]>
1 parent afb67f8 commit ad7e33b

File tree

3 files changed

+74
-2
lines changed

3 files changed

+74
-2
lines changed

btf/format.go

+17-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ type GoFormatter struct {
2525
// EnumIdentifier is called for each element of an enum. By default the
2626
// name of the enum type is concatenated with Identifier(element).
2727
EnumIdentifier func(name, element string) string
28+
29+
// ShortEnumIdentifier is called for each element of an enum. An
30+
// empty result causes short name to be omitted (default).
31+
ShortEnumIdentifier func(name, element string) string
2832
}
2933

3034
// TypeDeclaration generates a Go type declaration for a BTF type.
@@ -52,6 +56,14 @@ func (gf *GoFormatter) enumIdentifier(name, element string) string {
5256
return name + gf.identifier(element)
5357
}
5458

59+
func (gf *GoFormatter) shortEnumIdentifier(name, element string) string {
60+
if gf.ShortEnumIdentifier != nil {
61+
return gf.ShortEnumIdentifier(name, element)
62+
}
63+
64+
return ""
65+
}
66+
5567
// writeTypeDecl outputs a declaration of the given type.
5668
//
5769
// It encodes https://golang.org/ref/spec#Type_declarations:
@@ -76,13 +88,17 @@ func (gf *GoFormatter) writeTypeDecl(name string, typ Type) error {
7688

7789
gf.w.WriteString("; const ( ")
7890
for _, ev := range e.Values {
79-
id := gf.enumIdentifier(name, ev.Name)
8091
var value any
8192
if e.Signed {
8293
value = int64(ev.Value)
8394
} else {
8495
value = ev.Value
8596
}
97+
if shortID := gf.shortEnumIdentifier(name, ev.Name); shortID != "" {
98+
// output short identifier first (stringer prefers earlier decalarations)
99+
fmt.Fprintf(&gf.w, "%s %s = %d; ", shortID, name, value)
100+
}
101+
id := gf.enumIdentifier(name, ev.Name)
86102
fmt.Fprintf(&gf.w, "%s %s = %d; ", id, name, value)
87103
}
88104
gf.w.WriteString(")")

cmd/bpf2go/gen/output.go

+12-1
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,17 @@ func Generate(args GenerateArgs) error {
173173
gf := &btf.GoFormatter{
174174
Names: nameByType,
175175
Identifier: args.Identifier,
176+
ShortEnumIdentifier: func(_, element string) string {
177+
elementName := args.Stem + args.Identifier(element)
178+
if _, nameTaken := typeByName[elementName]; nameTaken {
179+
return ""
180+
}
181+
if _, nameReserved := reservedNames[elementName]; nameReserved {
182+
return ""
183+
}
184+
reservedNames[elementName] = struct{}{}
185+
return elementName
186+
},
176187
}
177188

178189
ctx := struct {
@@ -201,7 +212,7 @@ func Generate(args GenerateArgs) error {
201212

202213
var buf bytes.Buffer
203214
if err := commonTemplate.Execute(&buf, &ctx); err != nil {
204-
return fmt.Errorf("can't generate types: %s", err)
215+
return fmt.Errorf("can't generate types: %v", err)
205216
}
206217

207218
return internal.WriteFormatted(buf.Bytes(), args.Output)

cmd/bpf2go/gen/output_test.go

+45
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ package gen
33
import (
44
"bytes"
55
"fmt"
6+
"regexp"
67
"strings"
78
"testing"
89

910
"github.com/go-quicktest/qt"
1011

12+
"github.com/cilium/ebpf/btf"
1113
"github.com/cilium/ebpf/cmd/bpf2go/internal"
1214
)
1315

@@ -63,3 +65,46 @@ func TestObjects(t *testing.T) {
6365
qt.Assert(t, qt.StringContains(str, "Var1 *ebpf.Variable `ebpf:\"var_1\"`"))
6466
qt.Assert(t, qt.StringContains(str, "ProgFoo1 *ebpf.Program `ebpf:\"prog_foo_1\"`"))
6567
}
68+
69+
func TestEnums(t *testing.T) {
70+
for _, conflict := range []bool{false, true} {
71+
t.Run(fmt.Sprintf("conflict=%v", conflict), func(t *testing.T) {
72+
var buf bytes.Buffer
73+
args := GenerateArgs{
74+
Package: "foo",
75+
Stem: "bar",
76+
Types: []btf.Type{
77+
&btf.Enum{Name: "EnumName", Size: 4, Values: []btf.EnumValue{
78+
{Name: "V1", Value: 1}, {Name: "V2", Value: 2}, {Name: "conflict", Value: 0}}},
79+
},
80+
Output: &buf,
81+
}
82+
if conflict {
83+
args.Types = append(args.Types, &btf.Struct{Name: "conflict", Size: 4})
84+
}
85+
err := Generate(args)
86+
qt.Assert(t, qt.IsNil(err))
87+
88+
str := buf.String()
89+
90+
qt.Assert(t, qt.Matches(str, wsSeparated("barEnumNameV1", "barEnumName", "=", "1")))
91+
qt.Assert(t, qt.Matches(str, wsSeparated("barEnumNameV2", "barEnumName", "=", "2")))
92+
qt.Assert(t, qt.Matches(str, wsSeparated("barEnumNameConflict", "barEnumName", "=", "0")))
93+
94+
// short enum element names, only generated if they don't conflict with other decls
95+
qt.Assert(t, qt.Matches(str, wsSeparated("barV1", "barEnumName", "=", "1")))
96+
qt.Assert(t, qt.Matches(str, wsSeparated("barV2", "barEnumName", "=", "2")))
97+
98+
pred := qt.Matches(str, wsSeparated("barConflict", "barEnumName", "=", "0"))
99+
if conflict {
100+
qt.Assert(t, qt.Not(pred))
101+
} else {
102+
qt.Assert(t, pred)
103+
}
104+
})
105+
}
106+
}
107+
108+
func wsSeparated(terms ...string) *regexp.Regexp {
109+
return regexp.MustCompile(strings.Join(terms, `\s+`))
110+
}

0 commit comments

Comments
 (0)