mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-11-27 14:16:39 +00:00
320 lines
6.7 KiB
Go
320 lines
6.7 KiB
Go
|
package btf
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
var errNestedTooDeep = errors.New("nested too deep")
|
||
|
|
||
|
// GoFormatter converts a Type to Go syntax.
|
||
|
//
|
||
|
// A zero GoFormatter is valid to use.
|
||
|
type GoFormatter struct {
|
||
|
w strings.Builder
|
||
|
|
||
|
// Types present in this map are referred to using the given name if they
|
||
|
// are encountered when outputting another type.
|
||
|
Names map[Type]string
|
||
|
|
||
|
// Identifier is called for each field of struct-like types. By default the
|
||
|
// field name is used as is.
|
||
|
Identifier func(string) string
|
||
|
|
||
|
// EnumIdentifier is called for each element of an enum. By default the
|
||
|
// name of the enum type is concatenated with Identifier(element).
|
||
|
EnumIdentifier func(name, element string) string
|
||
|
}
|
||
|
|
||
|
// TypeDeclaration generates a Go type declaration for a BTF type.
|
||
|
func (gf *GoFormatter) TypeDeclaration(name string, typ Type) (string, error) {
|
||
|
gf.w.Reset()
|
||
|
if err := gf.writeTypeDecl(name, typ); err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
return gf.w.String(), nil
|
||
|
}
|
||
|
|
||
|
func (gf *GoFormatter) identifier(s string) string {
|
||
|
if gf.Identifier != nil {
|
||
|
return gf.Identifier(s)
|
||
|
}
|
||
|
|
||
|
return s
|
||
|
}
|
||
|
|
||
|
func (gf *GoFormatter) enumIdentifier(name, element string) string {
|
||
|
if gf.EnumIdentifier != nil {
|
||
|
return gf.EnumIdentifier(name, element)
|
||
|
}
|
||
|
|
||
|
return name + gf.identifier(element)
|
||
|
}
|
||
|
|
||
|
// writeTypeDecl outputs a declaration of the given type.
|
||
|
//
|
||
|
// It encodes https://golang.org/ref/spec#Type_declarations:
|
||
|
//
|
||
|
// type foo struct { bar uint32; }
|
||
|
// type bar int32
|
||
|
func (gf *GoFormatter) writeTypeDecl(name string, typ Type) error {
|
||
|
if name == "" {
|
||
|
return fmt.Errorf("need a name for type %s", typ)
|
||
|
}
|
||
|
|
||
|
switch v := skipQualifiers(typ).(type) {
|
||
|
case *Enum:
|
||
|
fmt.Fprintf(&gf.w, "type %s ", name)
|
||
|
switch v.Size {
|
||
|
case 1:
|
||
|
gf.w.WriteString("int8")
|
||
|
case 2:
|
||
|
gf.w.WriteString("int16")
|
||
|
case 4:
|
||
|
gf.w.WriteString("int32")
|
||
|
case 8:
|
||
|
gf.w.WriteString("int64")
|
||
|
default:
|
||
|
return fmt.Errorf("%s: invalid enum size %d", typ, v.Size)
|
||
|
}
|
||
|
|
||
|
if len(v.Values) == 0 {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
gf.w.WriteString("; const ( ")
|
||
|
for _, ev := range v.Values {
|
||
|
id := gf.enumIdentifier(name, ev.Name)
|
||
|
fmt.Fprintf(&gf.w, "%s %s = %d; ", id, name, ev.Value)
|
||
|
}
|
||
|
gf.w.WriteString(")")
|
||
|
|
||
|
return nil
|
||
|
|
||
|
default:
|
||
|
fmt.Fprintf(&gf.w, "type %s ", name)
|
||
|
return gf.writeTypeLit(v, 0)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// writeType outputs the name of a named type or a literal describing the type.
|
||
|
//
|
||
|
// It encodes https://golang.org/ref/spec#Types.
|
||
|
//
|
||
|
// foo (if foo is a named type)
|
||
|
// uint32
|
||
|
func (gf *GoFormatter) writeType(typ Type, depth int) error {
|
||
|
typ = skipQualifiers(typ)
|
||
|
|
||
|
name := gf.Names[typ]
|
||
|
if name != "" {
|
||
|
gf.w.WriteString(name)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
return gf.writeTypeLit(typ, depth)
|
||
|
}
|
||
|
|
||
|
// writeTypeLit outputs a literal describing the type.
|
||
|
//
|
||
|
// The function ignores named types.
|
||
|
//
|
||
|
// It encodes https://golang.org/ref/spec#TypeLit.
|
||
|
//
|
||
|
// struct { bar uint32; }
|
||
|
// uint32
|
||
|
func (gf *GoFormatter) writeTypeLit(typ Type, depth int) error {
|
||
|
depth++
|
||
|
if depth > maxTypeDepth {
|
||
|
return errNestedTooDeep
|
||
|
}
|
||
|
|
||
|
var err error
|
||
|
switch v := skipQualifiers(typ).(type) {
|
||
|
case *Int:
|
||
|
gf.writeIntLit(v)
|
||
|
|
||
|
case *Enum:
|
||
|
gf.w.WriteString("int32")
|
||
|
|
||
|
case *Typedef:
|
||
|
err = gf.writeType(v.Type, depth)
|
||
|
|
||
|
case *Array:
|
||
|
fmt.Fprintf(&gf.w, "[%d]", v.Nelems)
|
||
|
err = gf.writeType(v.Type, depth)
|
||
|
|
||
|
case *Struct:
|
||
|
err = gf.writeStructLit(v.Size, v.Members, depth)
|
||
|
|
||
|
case *Union:
|
||
|
// Always choose the first member to represent the union in Go.
|
||
|
err = gf.writeStructLit(v.Size, v.Members[:1], depth)
|
||
|
|
||
|
case *Datasec:
|
||
|
err = gf.writeDatasecLit(v, depth)
|
||
|
|
||
|
default:
|
||
|
return fmt.Errorf("type %T: %w", v, ErrNotSupported)
|
||
|
}
|
||
|
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("%s: %w", typ, err)
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (gf *GoFormatter) writeIntLit(i *Int) {
|
||
|
// NB: Encoding.IsChar is ignored.
|
||
|
if i.Encoding.IsBool() && i.Size == 1 {
|
||
|
gf.w.WriteString("bool")
|
||
|
return
|
||
|
}
|
||
|
|
||
|
bits := i.Size * 8
|
||
|
if i.Encoding.IsSigned() {
|
||
|
fmt.Fprintf(&gf.w, "int%d", bits)
|
||
|
} else {
|
||
|
fmt.Fprintf(&gf.w, "uint%d", bits)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (gf *GoFormatter) writeStructLit(size uint32, members []Member, depth int) error {
|
||
|
gf.w.WriteString("struct { ")
|
||
|
|
||
|
prevOffset := uint32(0)
|
||
|
skippedBitfield := false
|
||
|
for i, m := range members {
|
||
|
if m.BitfieldSize > 0 {
|
||
|
skippedBitfield = true
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
offset := m.Offset.Bytes()
|
||
|
if n := offset - prevOffset; skippedBitfield && n > 0 {
|
||
|
fmt.Fprintf(&gf.w, "_ [%d]byte /* unsupported bitfield */; ", n)
|
||
|
} else {
|
||
|
gf.writePadding(n)
|
||
|
}
|
||
|
|
||
|
size, err := Sizeof(m.Type)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("field %d: %w", i, err)
|
||
|
}
|
||
|
prevOffset = offset + uint32(size)
|
||
|
|
||
|
if err := gf.writeStructField(m, depth); err != nil {
|
||
|
return fmt.Errorf("field %d: %w", i, err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
gf.writePadding(size - prevOffset)
|
||
|
gf.w.WriteString("}")
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (gf *GoFormatter) writeStructField(m Member, depth int) error {
|
||
|
if m.BitfieldSize > 0 {
|
||
|
return fmt.Errorf("bitfields are not supported")
|
||
|
}
|
||
|
if m.Offset%8 != 0 {
|
||
|
return fmt.Errorf("unsupported offset %d", m.Offset)
|
||
|
}
|
||
|
|
||
|
if m.Name == "" {
|
||
|
// Special case a nested anonymous union like
|
||
|
// struct foo { union { int bar; int baz }; }
|
||
|
// by replacing the whole union with its first member.
|
||
|
union, ok := m.Type.(*Union)
|
||
|
if !ok {
|
||
|
return fmt.Errorf("anonymous fields are not supported")
|
||
|
|
||
|
}
|
||
|
|
||
|
if len(union.Members) == 0 {
|
||
|
return errors.New("empty anonymous union")
|
||
|
}
|
||
|
|
||
|
depth++
|
||
|
if depth > maxTypeDepth {
|
||
|
return errNestedTooDeep
|
||
|
}
|
||
|
|
||
|
m := union.Members[0]
|
||
|
size, err := Sizeof(m.Type)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
if err := gf.writeStructField(m, depth); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
gf.writePadding(union.Size - uint32(size))
|
||
|
return nil
|
||
|
|
||
|
}
|
||
|
|
||
|
fmt.Fprintf(&gf.w, "%s ", gf.identifier(m.Name))
|
||
|
|
||
|
if err := gf.writeType(m.Type, depth); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
gf.w.WriteString("; ")
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (gf *GoFormatter) writeDatasecLit(ds *Datasec, depth int) error {
|
||
|
gf.w.WriteString("struct { ")
|
||
|
|
||
|
prevOffset := uint32(0)
|
||
|
for i, vsi := range ds.Vars {
|
||
|
v := vsi.Type.(*Var)
|
||
|
if v.Linkage != GlobalVar {
|
||
|
// Ignore static, extern, etc. for now.
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
if v.Name == "" {
|
||
|
return fmt.Errorf("variable %d: empty name", i)
|
||
|
}
|
||
|
|
||
|
gf.writePadding(vsi.Offset - prevOffset)
|
||
|
prevOffset = vsi.Offset + vsi.Size
|
||
|
|
||
|
fmt.Fprintf(&gf.w, "%s ", gf.identifier(v.Name))
|
||
|
|
||
|
if err := gf.writeType(v.Type, depth); err != nil {
|
||
|
return fmt.Errorf("variable %d: %w", i, err)
|
||
|
}
|
||
|
|
||
|
gf.w.WriteString("; ")
|
||
|
}
|
||
|
|
||
|
gf.writePadding(ds.Size - prevOffset)
|
||
|
gf.w.WriteString("}")
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (gf *GoFormatter) writePadding(bytes uint32) {
|
||
|
if bytes > 0 {
|
||
|
fmt.Fprintf(&gf.w, "_ [%d]byte; ", bytes)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func skipQualifiers(typ Type) Type {
|
||
|
result := typ
|
||
|
for depth := 0; depth <= maxTypeDepth; depth++ {
|
||
|
switch v := (result).(type) {
|
||
|
case qualifier:
|
||
|
result = v.qualify()
|
||
|
default:
|
||
|
return result
|
||
|
}
|
||
|
}
|
||
|
return &cycle{typ}
|
||
|
}
|