Skip to content

Commit

Permalink
btf: make copying types infallible
Browse files Browse the repository at this point in the history
We currently have an unexported copyTypes function which allows transforming
a type graph before copying. This is very useful to strip qualifiers or
typedefs out of a graph. The function currently returns an error, since we
might not be able to remove all qualifiers, etc. in the case of deeply
nested or cyclical types.

Instead of returning an error, introduce an invalid sentinel Type "cycle" and
use it to signal that something went wrong. This allows removing the error return
which in turn allows us to extend Copy to the more powerful copyType paradigm.
  • Loading branch information
lmb authored and ti-mo committed Apr 27, 2022
1 parent 8a8aa58 commit 5fd10f5
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 110 deletions.
2 changes: 1 addition & 1 deletion internal/btf/btf.go
Expand Up @@ -525,7 +525,7 @@ func fixupDatasec(rawTypes []rawType, rawStrings *stringTable, sectionSizes map[

// Copy creates a copy of Spec.
func (s *Spec) Copy() *Spec {
types, _ := copyTypes(s.types, nil)
types := copyTypes(s.types, nil)

namedTypes := make(map[essentialName][]Type)
for _, typ := range types {
Expand Down
38 changes: 2 additions & 36 deletions internal/btf/core.go
Expand Up @@ -269,19 +269,13 @@ var errImpossibleRelocation = errors.New("impossible relocation")
// the better the target is.
func coreCalculateFixups(byteOrder binary.ByteOrder, local Type, targets []Type, relos CORERelos) ([]COREFixup, error) {
localID := local.ID()
local, err := copyType(local, skipQualifiersAndTypedefs)
if err != nil {
return nil, err
}
local = Copy(local, UnderlyingType)

bestScore := len(relos)
var bestFixups []COREFixup
for i := range targets {
targetID := targets[i].ID()
target, err := copyType(targets[i], skipQualifiersAndTypedefs)
if err != nil {
return nil, err
}
target := Copy(targets[i], UnderlyingType)

score := 0 // lower is better
fixups := make([]COREFixup, 0, len(relos))
Expand Down Expand Up @@ -1009,31 +1003,3 @@ func coreAreMembersCompatible(localType Type, targetType Type) error {
return fmt.Errorf("type %s: %w", localType, ErrNotSupported)
}
}

func skipQualifiersAndTypedefs(typ Type) (Type, error) {
result := typ
for depth := 0; depth <= maxTypeDepth; depth++ {
switch v := (result).(type) {
case qualifier:
result = v.qualify()
case *Typedef:
result = v.Type
default:
return result, nil
}
}
return nil, errors.New("exceeded type depth")
}

func skipQualifiers(typ Type) (Type, error) {
result := typ
for depth := 0; depth <= maxTypeDepth; depth++ {
switch v := (result).(type) {
case qualifier:
result = v.qualify()
default:
return result, nil
}
}
return nil, errors.New("exceeded type depth")
}
11 changes: 5 additions & 6 deletions internal/btf/core_test.go
Expand Up @@ -584,8 +584,9 @@ func TestCORECopyWithoutQualifiers(t *testing.T) {
root := &Volatile{}
root.Type = test.fn(root)

_, err := copyType(root, skipQualifiersAndTypedefs)
qt.Assert(t, err, qt.Not(qt.IsNil))
cycle, ok := Copy(root, UnderlyingType).(*cycle)
qt.Assert(t, ok, qt.IsTrue)
qt.Assert(t, cycle.root, qt.Equals, root)
})
}

Expand All @@ -595,8 +596,7 @@ func TestCORECopyWithoutQualifiers(t *testing.T) {
v := a.fn(&Pointer{Target: b.fn(&Int{Name: "z"})})
want := &Pointer{Target: &Int{Name: "z"}}

got, err := copyType(v, skipQualifiersAndTypedefs)
qt.Assert(t, err, qt.IsNil)
got := Copy(v, UnderlyingType)
qt.Assert(t, got, qt.DeepEquals, want)
})
}
Expand All @@ -611,8 +611,7 @@ func TestCORECopyWithoutQualifiers(t *testing.T) {
t.Log(q.name)
}

got, err := copyType(v, skipQualifiersAndTypedefs)
qt.Assert(t, err, qt.IsNil)
got := Copy(v, UnderlyingType)
qt.Assert(t, got, qt.DeepEquals, root)
})
}
42 changes: 22 additions & 20 deletions internal/btf/format.go
Expand Up @@ -63,12 +63,7 @@ func (gf *GoFormatter) writeTypeDecl(name string, typ Type) error {
return fmt.Errorf("need a name for type %s", typ)
}

typ, err := skipQualifiers(typ)
if err != nil {
return err
}

switch v := typ.(type) {
switch v := skipQualifiers(typ).(type) {
case *Enum:
fmt.Fprintf(&gf.w, "type %s int32", name)
if len(v.Values) == 0 {
Expand All @@ -83,10 +78,11 @@ func (gf *GoFormatter) writeTypeDecl(name string, typ Type) error {
gf.w.WriteString(")")

return nil
}

fmt.Fprintf(&gf.w, "type %s ", name)
return gf.writeTypeLit(typ, 0)
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.
Expand All @@ -96,10 +92,7 @@ func (gf *GoFormatter) writeTypeDecl(name string, typ Type) error {
// foo (if foo is a named type)
// uint32
func (gf *GoFormatter) writeType(typ Type, depth int) error {
typ, err := skipQualifiers(typ)
if err != nil {
return err
}
typ = skipQualifiers(typ)

name := gf.Names[typ]
if name != "" {
Expand All @@ -124,12 +117,8 @@ func (gf *GoFormatter) writeTypeLit(typ Type, depth int) error {
return errNestedTooDeep
}

typ, err := skipQualifiers(typ)
if err != nil {
return err
}

switch v := typ.(type) {
var err error
switch v := skipQualifiers(typ).(type) {
case *Int:
gf.writeIntLit(v)

Expand All @@ -154,7 +143,7 @@ func (gf *GoFormatter) writeTypeLit(typ Type, depth int) error {
err = gf.writeDatasecLit(v, depth)

default:
return fmt.Errorf("type %s: %w", typ, ErrNotSupported)
return fmt.Errorf("type %T: %w", v, ErrNotSupported)
}

if err != nil {
Expand Down Expand Up @@ -302,3 +291,16 @@ func (gf *GoFormatter) writePadding(bytes uint32) {
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}
}
73 changes: 34 additions & 39 deletions internal/btf/types.go
Expand Up @@ -547,6 +547,20 @@ func (f *Float) copy() Type {
return &cpy
}

// cycle is a type which had to be elided since it exceeded maxTypeDepth.
type cycle struct {
root Type
}

func (c *cycle) ID() TypeID { return math.MaxUint32 }
func (c *cycle) Format(fs fmt.State, verb rune) { formatType(fs, verb, c, "root=", c.root) }
func (c *cycle) TypeName() string { return "" }
func (c *cycle) walk(*typeDeque) {}
func (c *cycle) copy() Type {
cpy := *c
return &cpy
}

type sizer interface {
size() uint32
}
Expand Down Expand Up @@ -626,12 +640,7 @@ func Sizeof(typ Type) (int, error) {
//
// Currently only supports the subset of types necessary for bitfield relocations.
func alignof(typ Type) (int, error) {
typ, err := skipQualifiersAndTypedefs(typ)
if err != nil {
return 0, err
}

switch t := typ.(type) {
switch t := UnderlyingType(typ).(type) {
case *Enum:
return int(t.size()), nil
case *Int:
Expand All @@ -641,44 +650,40 @@ func alignof(typ Type) (int, error) {
}
}

// Copy a Type recursively.
func Copy(typ Type) Type {
typ, _ = copyType(typ, nil)
return typ
}

// copy a Type recursively.
// Transformer modifies a given Type and returns the result.
//
// typ may form a cycle.
// For example, UnderlyingType removes any qualifiers or typedefs from a type.
// See the example on Copy for how to use a transform.
type Transformer func(Type) Type

// Copy a Type recursively.
//
// Returns any errors from transform verbatim.
func copyType(typ Type, transform func(Type) (Type, error)) (Type, error) {
// typ may form a cycle. If transform is not nil, it is called with the
// to be copied type, and the returned value is copied instead.
func Copy(typ Type, transform Transformer) Type {
copies := make(copier)
return typ, copies.copy(&typ, transform)
copies.copy(&typ, transform)
return typ
}

// copy a slice of Types recursively.
//
// Types may form a cycle.
//
// Returns any errors from transform verbatim.
func copyTypes(types []Type, transform func(Type) (Type, error)) ([]Type, error) {
// See Copy for the semantics.
func copyTypes(types []Type, transform Transformer) []Type {
result := make([]Type, len(types))
copy(result, types)

copies := make(copier)
for i := range result {
if err := copies.copy(&result[i], transform); err != nil {
return nil, err
}
copies.copy(&result[i], transform)
}

return result, nil
return result
}

type copier map[Type]Type

func (c copier) copy(typ *Type, transform func(Type) (Type, error)) error {
func (c copier) copy(typ *Type, transform Transformer) {
var work typeDeque
for t := typ; t != nil; t = work.pop() {
// *t is the identity of the type.
Expand All @@ -689,11 +694,7 @@ func (c copier) copy(typ *Type, transform func(Type) (Type, error)) error {

var cpy Type
if transform != nil {
tf, err := transform(*t)
if err != nil {
return fmt.Errorf("copy %s: %w", *t, err)
}
cpy = tf.copy()
cpy = transform(*t).copy()
} else {
cpy = (*t).copy()
}
Expand All @@ -704,8 +705,6 @@ func (c copier) copy(typ *Type, transform func(Type) (Type, error)) error {
// Mark any nested types for copying.
cpy.walk(&work)
}

return nil
}

// typeDeque keeps track of pointers to types which still
Expand Down Expand Up @@ -1040,9 +1039,6 @@ func newEssentialName(name string) essentialName {
}

// UnderlyingType skips qualifiers and Typedefs.
//
// May return typ verbatim if too many types have to be skipped to protect against
// circular Types.
func UnderlyingType(typ Type) Type {
result := typ
for depth := 0; depth <= maxTypeDepth; depth++ {
Expand All @@ -1055,8 +1051,7 @@ func UnderlyingType(typ Type) Type {
return result
}
}
// Return the original argument, since we can't find an underlying type.
return typ
return &cycle{typ}
}

type formatState struct {
Expand Down Expand Up @@ -1132,7 +1127,7 @@ func formatType(f fmt.State, verb rune, t formattableType, extra ...interface{})
switch v := arg.(type) {
case string:
_, _ = io.WriteString(f, v)
wantSpace = v[len(v)-1] != '='
wantSpace = len(v) > 0 && v[len(v)-1] != '='
continue

case formattableType:
Expand Down

0 comments on commit 5fd10f5

Please sign in to comment.