rlp/rlpgen: RLP encoder code generator (#24251)

This change adds a code generator tool for creating EncodeRLP method
implementations. The generated methods will behave identically to the
reflect-based encoder, but run faster because there is no reflection overhead.

Package rlp now provides the EncoderBuffer type for incremental encoding. This
is used by generated code, but the new methods can also be useful for
hand-written encoders.

There is also experimental support for generating DecodeRLP, and some new
methods have been added to the existing Stream type to support this. Creating
decoders with rlpgen is not recommended at this time because the generated
methods create very poor error reporting.

More detail about package rlp changes:

* rlp: externalize struct field processing / validation

This adds a new package, rlp/internal/rlpstruct, in preparation for the
RLP encoder generator.

I think the struct field rules are subtle enough to warrant extracting
this into their own package, even though it means that a bunch of
adapter code is needed for converting to/from rlpstruct.Type.

* rlp: add more decoder methods (for rlpgen)

This adds new methods on rlp.Stream:

- Uint64, Uint32, Uint16, Uint8, BigInt
- ReadBytes for decoding into []byte
- MoreDataInList - useful for optional list elements

* rlp: expose encoder buffer (for rlpgen)

This exposes the internal encoder buffer type for use in EncodeRLP
implementations.

The new EncoderBuffer type is a sort-of 'opaque handle' for a pointer to
encBuffer. It is implemented this way to ensure the global encBuffer pool
is handled correctly.
This commit is contained in:
Felix Lange
2022-02-16 18:14:12 +01:00
committed by GitHub
parent 4335bbbf0a
commit 9b93564e21
25 changed files with 2701 additions and 474 deletions

View File

@ -19,9 +19,10 @@ package rlp
import (
"fmt"
"reflect"
"strings"
"sync"
"sync/atomic"
"github.com/ethereum/go-ethereum/rlp/internal/rlpstruct"
)
// typeinfo is an entry in the type cache.
@ -32,35 +33,16 @@ type typeinfo struct {
writerErr error // error from makeWriter
}
// tags represents struct tags.
type tags struct {
// rlp:"nil" controls whether empty input results in a nil pointer.
// nilKind is the kind of empty value allowed for the field.
nilKind Kind
nilOK bool
// rlp:"optional" allows for a field to be missing in the input list.
// If this is set, all subsequent fields must also be optional.
optional bool
// rlp:"tail" controls whether this field swallows additional list elements. It can
// only be set for the last field, which must be of slice type.
tail bool
// rlp:"-" ignores fields.
ignored bool
}
// typekey is the key of a type in typeCache. It includes the struct tags because
// they might generate a different decoder.
type typekey struct {
reflect.Type
tags
rlpstruct.Tags
}
type decoder func(*Stream, reflect.Value) error
type writer func(reflect.Value, *encbuf) error
type writer func(reflect.Value, *encBuffer) error
var theTC = newTypeCache()
@ -95,10 +77,10 @@ func (c *typeCache) info(typ reflect.Type) *typeinfo {
}
// Not in the cache, need to generate info for this type.
return c.generate(typ, tags{})
return c.generate(typ, rlpstruct.Tags{})
}
func (c *typeCache) generate(typ reflect.Type, tags tags) *typeinfo {
func (c *typeCache) generate(typ reflect.Type, tags rlpstruct.Tags) *typeinfo {
c.mu.Lock()
defer c.mu.Unlock()
@ -122,7 +104,7 @@ func (c *typeCache) generate(typ reflect.Type, tags tags) *typeinfo {
return info
}
func (c *typeCache) infoWhileGenerating(typ reflect.Type, tags tags) *typeinfo {
func (c *typeCache) infoWhileGenerating(typ reflect.Type, tags rlpstruct.Tags) *typeinfo {
key := typekey{typ, tags}
if info := c.next[key]; info != nil {
return info
@ -144,35 +126,40 @@ type field struct {
// structFields resolves the typeinfo of all public fields in a struct type.
func structFields(typ reflect.Type) (fields []field, err error) {
var (
lastPublic = lastPublicField(typ)
anyOptional = false
)
// Convert fields to rlpstruct.Field.
var allStructFields []rlpstruct.Field
for i := 0; i < typ.NumField(); i++ {
if f := typ.Field(i); f.PkgPath == "" { // exported
tags, err := parseStructTag(typ, i, lastPublic)
if err != nil {
return nil, err
}
rf := typ.Field(i)
allStructFields = append(allStructFields, rlpstruct.Field{
Name: rf.Name,
Index: i,
Exported: rf.PkgPath == "",
Tag: string(rf.Tag),
Type: *rtypeToStructType(rf.Type, nil),
})
}
// Skip rlp:"-" fields.
if tags.ignored {
continue
}
// If any field has the "optional" tag, subsequent fields must also have it.
if tags.optional || tags.tail {
anyOptional = true
} else if anyOptional {
return nil, fmt.Errorf(`rlp: struct field %v.%s needs "optional" tag`, typ, f.Name)
}
info := theTC.infoWhileGenerating(f.Type, tags)
fields = append(fields, field{i, info, tags.optional})
// Filter/validate fields.
structFields, structTags, err := rlpstruct.ProcessFields(allStructFields)
if err != nil {
if tagErr, ok := err.(rlpstruct.TagError); ok {
tagErr.StructType = typ.String()
return nil, tagErr
}
return nil, err
}
// Resolve typeinfo.
for i, sf := range structFields {
typ := typ.Field(sf.Index).Type
tags := structTags[i]
info := theTC.infoWhileGenerating(typ, tags)
fields = append(fields, field{sf.Index, info, tags.Optional})
}
return fields, nil
}
// anyOptionalFields returns the index of the first field with "optional" tag.
// firstOptionalField returns the index of the first field with "optional" tag.
func firstOptionalField(fields []field) int {
for i, f := range fields {
if f.optional {
@ -192,82 +179,56 @@ func (e structFieldError) Error() string {
return fmt.Sprintf("%v (struct field %v.%s)", e.err, e.typ, e.typ.Field(e.field).Name)
}
type structTagError struct {
typ reflect.Type
field, tag, err string
}
func (e structTagError) Error() string {
return fmt.Sprintf("rlp: invalid struct tag %q for %v.%s (%s)", e.tag, e.typ, e.field, e.err)
}
func parseStructTag(typ reflect.Type, fi, lastPublic int) (tags, error) {
f := typ.Field(fi)
var ts tags
for _, t := range strings.Split(f.Tag.Get("rlp"), ",") {
switch t = strings.TrimSpace(t); t {
case "":
case "-":
ts.ignored = true
case "nil", "nilString", "nilList":
ts.nilOK = true
if f.Type.Kind() != reflect.Ptr {
return ts, structTagError{typ, f.Name, t, "field is not a pointer"}
}
switch t {
case "nil":
ts.nilKind = defaultNilKind(f.Type.Elem())
case "nilString":
ts.nilKind = String
case "nilList":
ts.nilKind = List
}
case "optional":
ts.optional = true
if ts.tail {
return ts, structTagError{typ, f.Name, t, `also has "tail" tag`}
}
case "tail":
ts.tail = true
if fi != lastPublic {
return ts, structTagError{typ, f.Name, t, "must be on last field"}
}
if ts.optional {
return ts, structTagError{typ, f.Name, t, `also has "optional" tag`}
}
if f.Type.Kind() != reflect.Slice {
return ts, structTagError{typ, f.Name, t, "field type is not slice"}
}
default:
return ts, fmt.Errorf("rlp: unknown struct tag %q on %v.%s", t, typ, f.Name)
}
}
return ts, nil
}
func lastPublicField(typ reflect.Type) int {
last := 0
for i := 0; i < typ.NumField(); i++ {
if typ.Field(i).PkgPath == "" {
last = i
}
}
return last
}
func (i *typeinfo) generate(typ reflect.Type, tags tags) {
func (i *typeinfo) generate(typ reflect.Type, tags rlpstruct.Tags) {
i.decoder, i.decoderErr = makeDecoder(typ, tags)
i.writer, i.writerErr = makeWriter(typ, tags)
}
// defaultNilKind determines whether a nil pointer to typ encodes/decodes
// as an empty string or empty list.
func defaultNilKind(typ reflect.Type) Kind {
// rtypeToStructType converts typ to rlpstruct.Type.
func rtypeToStructType(typ reflect.Type, rec map[reflect.Type]*rlpstruct.Type) *rlpstruct.Type {
k := typ.Kind()
if isUint(k) || k == reflect.String || k == reflect.Bool || isByteArray(typ) {
return String
if k == reflect.Invalid {
panic("invalid kind")
}
if prev := rec[typ]; prev != nil {
return prev // short-circuit for recursive types
}
if rec == nil {
rec = make(map[reflect.Type]*rlpstruct.Type)
}
t := &rlpstruct.Type{
Name: typ.String(),
Kind: k,
IsEncoder: typ.Implements(encoderInterface),
IsDecoder: typ.Implements(decoderInterface),
}
rec[typ] = t
if k == reflect.Array || k == reflect.Slice || k == reflect.Ptr {
t.Elem = rtypeToStructType(typ.Elem(), rec)
}
return t
}
// typeNilKind gives the RLP value kind for nil pointers to 'typ'.
func typeNilKind(typ reflect.Type, tags rlpstruct.Tags) Kind {
styp := rtypeToStructType(typ, nil)
var nk rlpstruct.NilKind
if tags.NilOK {
nk = tags.NilKind
} else {
nk = styp.DefaultNilValue()
}
switch nk {
case rlpstruct.NilKindString:
return String
case rlpstruct.NilKindList:
return List
default:
panic("invalid nil kind value")
}
return List
}
func isUint(k reflect.Kind) bool {
@ -277,7 +238,3 @@ func isUint(k reflect.Kind) bool {
func isByte(typ reflect.Type) bool {
return typ.Kind() == reflect.Uint8 && !typ.Implements(encoderInterface)
}
func isByteArray(typ reflect.Type) bool {
return (typ.Kind() == reflect.Slice || typ.Kind() == reflect.Array) && isByte(typ.Elem())
}