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

735
rlp/rlpgen/gen.go Normal file
View File

@ -0,0 +1,735 @@
package main
import (
"bytes"
"fmt"
"go/format"
"go/types"
"sort"
"github.com/ethereum/go-ethereum/rlp/internal/rlpstruct"
)
// buildContext keeps the data needed for make*Op.
type buildContext struct {
topType *types.Named // the type we're creating methods for
encoderIface *types.Interface
decoderIface *types.Interface
rawValueType *types.Named
typeToStructCache map[types.Type]*rlpstruct.Type
}
func newBuildContext(packageRLP *types.Package) *buildContext {
enc := packageRLP.Scope().Lookup("Encoder").Type().Underlying()
dec := packageRLP.Scope().Lookup("Decoder").Type().Underlying()
rawv := packageRLP.Scope().Lookup("RawValue").Type()
return &buildContext{
typeToStructCache: make(map[types.Type]*rlpstruct.Type),
encoderIface: enc.(*types.Interface),
decoderIface: dec.(*types.Interface),
rawValueType: rawv.(*types.Named),
}
}
func (bctx *buildContext) isEncoder(typ types.Type) bool {
return types.Implements(typ, bctx.encoderIface)
}
func (bctx *buildContext) isDecoder(typ types.Type) bool {
return types.Implements(typ, bctx.decoderIface)
}
// typeToStructType converts typ to rlpstruct.Type.
func (bctx *buildContext) typeToStructType(typ types.Type) *rlpstruct.Type {
if prev := bctx.typeToStructCache[typ]; prev != nil {
return prev // short-circuit for recursive types.
}
// Resolve named types to their underlying type, but keep the name.
name := types.TypeString(typ, nil)
for {
utype := typ.Underlying()
if utype == typ {
break
}
typ = utype
}
// Create the type and store it in cache.
t := &rlpstruct.Type{
Name: name,
Kind: typeReflectKind(typ),
IsEncoder: bctx.isEncoder(typ),
IsDecoder: bctx.isDecoder(typ),
}
bctx.typeToStructCache[typ] = t
// Assign element type.
switch typ.(type) {
case *types.Array, *types.Slice, *types.Pointer:
etype := typ.(interface{ Elem() types.Type }).Elem()
t.Elem = bctx.typeToStructType(etype)
}
return t
}
// genContext is passed to the gen* methods of op when generating
// the output code. It tracks packages to be imported by the output
// file and assigns unique names of temporary variables.
type genContext struct {
inPackage *types.Package
imports map[string]struct{}
tempCounter int
}
func newGenContext(inPackage *types.Package) *genContext {
return &genContext{
inPackage: inPackage,
imports: make(map[string]struct{}),
}
}
func (ctx *genContext) temp() string {
v := fmt.Sprintf("_tmp%d", ctx.tempCounter)
ctx.tempCounter++
return v
}
func (ctx *genContext) resetTemp() {
ctx.tempCounter = 0
}
func (ctx *genContext) addImport(path string) {
if path == ctx.inPackage.Path() {
return // avoid importing the package that we're generating in.
}
// TODO: renaming?
ctx.imports[path] = struct{}{}
}
// importsList returns all packages that need to be imported.
func (ctx *genContext) importsList() []string {
imp := make([]string, 0, len(ctx.imports))
for k := range ctx.imports {
imp = append(imp, k)
}
sort.Strings(imp)
return imp
}
// qualify is the types.Qualifier used for printing types.
func (ctx *genContext) qualify(pkg *types.Package) string {
if pkg.Path() == ctx.inPackage.Path() {
return ""
}
ctx.addImport(pkg.Path())
// TODO: renaming?
return pkg.Name()
}
type op interface {
// genWrite creates the encoder. The generated code should write v,
// which is any Go expression, to the rlp.EncoderBuffer 'w'.
genWrite(ctx *genContext, v string) string
// genDecode creates the decoder. The generated code should read
// a value from the rlp.Stream 'dec' and store it to dst.
genDecode(ctx *genContext) (string, string)
}
// basicOp handles basic types bool, uint*, string.
type basicOp struct {
typ types.Type
writeMethod string // calle write the value
writeArgType types.Type // parameter type of writeMethod
decMethod string
decResultType types.Type // return type of decMethod
decUseBitSize bool // if true, result bit size is appended to decMethod
}
func (*buildContext) makeBasicOp(typ *types.Basic) (op, error) {
op := basicOp{typ: typ}
kind := typ.Kind()
switch {
case kind == types.Bool:
op.writeMethod = "WriteBool"
op.writeArgType = types.Typ[types.Bool]
op.decMethod = "Bool"
op.decResultType = types.Typ[types.Bool]
case kind >= types.Uint8 && kind <= types.Uint64:
op.writeMethod = "WriteUint64"
op.writeArgType = types.Typ[types.Uint64]
op.decMethod = "Uint"
op.decResultType = typ
op.decUseBitSize = true
case kind == types.String:
op.writeMethod = "WriteString"
op.writeArgType = types.Typ[types.String]
op.decMethod = "String"
op.decResultType = types.Typ[types.String]
default:
return nil, fmt.Errorf("unhandled basic type: %v", typ)
}
return op, nil
}
func (*buildContext) makeByteSliceOp(typ *types.Slice) op {
if !isByte(typ.Elem()) {
panic("non-byte slice type in makeByteSliceOp")
}
bslice := types.NewSlice(types.Typ[types.Uint8])
return basicOp{
typ: typ,
writeMethod: "WriteBytes",
writeArgType: bslice,
decMethod: "Bytes",
decResultType: bslice,
}
}
func (bctx *buildContext) makeRawValueOp() op {
bslice := types.NewSlice(types.Typ[types.Uint8])
return basicOp{
typ: bctx.rawValueType,
writeMethod: "Write",
writeArgType: bslice,
decMethod: "Raw",
decResultType: bslice,
}
}
func (op basicOp) writeNeedsConversion() bool {
return !types.AssignableTo(op.typ, op.writeArgType)
}
func (op basicOp) decodeNeedsConversion() bool {
return !types.AssignableTo(op.decResultType, op.typ)
}
func (op basicOp) genWrite(ctx *genContext, v string) string {
if op.writeNeedsConversion() {
v = fmt.Sprintf("%s(%s)", op.writeArgType, v)
}
return fmt.Sprintf("w.%s(%s)\n", op.writeMethod, v)
}
func (op basicOp) genDecode(ctx *genContext) (string, string) {
var (
resultV = ctx.temp()
result = resultV
method = op.decMethod
)
if op.decUseBitSize {
// Note: For now, this only works for platform-independent integer
// sizes. makeBasicOp forbids the platform-dependent types.
var sizes types.StdSizes
method = fmt.Sprintf("%s%d", op.decMethod, sizes.Sizeof(op.typ)*8)
}
// Call the decoder method.
var b bytes.Buffer
fmt.Fprintf(&b, "%s, err := dec.%s()\n", resultV, method)
fmt.Fprintf(&b, "if err != nil { return err }\n")
if op.decodeNeedsConversion() {
conv := ctx.temp()
fmt.Fprintf(&b, "%s := %s(%s)\n", conv, types.TypeString(op.typ, ctx.qualify), resultV)
result = conv
}
return result, b.String()
}
// byteArrayOp handles [...]byte.
type byteArrayOp struct {
typ types.Type
name types.Type // name != typ for named byte array types (e.g. common.Address)
}
func (bctx *buildContext) makeByteArrayOp(name *types.Named, typ *types.Array) byteArrayOp {
nt := types.Type(name)
if name == nil {
nt = typ
}
return byteArrayOp{typ, nt}
}
func (op byteArrayOp) genWrite(ctx *genContext, v string) string {
return fmt.Sprintf("w.WriteBytes(%s[:])\n", v)
}
func (op byteArrayOp) genDecode(ctx *genContext) (string, string) {
var resultV = ctx.temp()
var b bytes.Buffer
fmt.Fprintf(&b, "var %s %s\n", resultV, types.TypeString(op.name, ctx.qualify))
fmt.Fprintf(&b, "if err := dec.ReadBytes(%s[:]); err != nil { return err }\n", resultV)
return resultV, b.String()
}
// bigIntNoPtrOp handles non-pointer big.Int.
// This exists because big.Int has it's own decoder operation on rlp.Stream,
// but the decode method returns *big.Int, so it needs to be dereferenced.
type bigIntOp struct {
pointer bool
}
func (op bigIntOp) genWrite(ctx *genContext, v string) string {
var b bytes.Buffer
fmt.Fprintf(&b, "if %s.Sign() == -1 {\n", v)
fmt.Fprintf(&b, " return rlp.ErrNegativeBigInt\n")
fmt.Fprintf(&b, "}\n")
dst := v
if !op.pointer {
dst = "&" + v
}
fmt.Fprintf(&b, "w.WriteBigInt(%s)\n", dst)
// Wrap with nil check.
if op.pointer {
code := b.String()
b.Reset()
fmt.Fprintf(&b, "if %s == nil {\n", v)
fmt.Fprintf(&b, " w.Write(rlp.EmptyString)")
fmt.Fprintf(&b, "} else {\n")
fmt.Fprint(&b, code)
fmt.Fprintf(&b, "}\n")
}
return b.String()
}
func (op bigIntOp) genDecode(ctx *genContext) (string, string) {
var resultV = ctx.temp()
var b bytes.Buffer
fmt.Fprintf(&b, "%s, err := dec.BigInt()\n", resultV)
fmt.Fprintf(&b, "if err != nil { return err }\n")
result := resultV
if !op.pointer {
result = "(*" + resultV + ")"
}
return result, b.String()
}
// encoderDecoderOp handles rlp.Encoder and rlp.Decoder.
// In order to be used with this, the type must implement both interfaces.
// This restriction may be lifted in the future by creating separate ops for
// encoding and decoding.
type encoderDecoderOp struct {
typ types.Type
}
func (op encoderDecoderOp) genWrite(ctx *genContext, v string) string {
return fmt.Sprintf("if err := %s.EncodeRLP(w); err != nil { return err }\n", v)
}
func (op encoderDecoderOp) genDecode(ctx *genContext) (string, string) {
// DecodeRLP must have pointer receiver, and this is verified in makeOp.
etyp := op.typ.(*types.Pointer).Elem()
var resultV = ctx.temp()
var b bytes.Buffer
fmt.Fprintf(&b, "%s := new(%s)\n", resultV, types.TypeString(etyp, ctx.qualify))
fmt.Fprintf(&b, "if err := %s.DecodeRLP(dec); err != nil { return err }\n", resultV)
return resultV, b.String()
}
// ptrOp handles pointer types.
type ptrOp struct {
elemTyp types.Type
elem op
nilOK bool
nilValue rlpstruct.NilKind
}
func (bctx *buildContext) makePtrOp(elemTyp types.Type, tags rlpstruct.Tags) (op, error) {
elemOp, err := bctx.makeOp(nil, elemTyp, rlpstruct.Tags{})
if err != nil {
return nil, err
}
op := ptrOp{elemTyp: elemTyp, elem: elemOp}
// Determine nil value.
if tags.NilOK {
op.nilOK = true
op.nilValue = tags.NilKind
} else {
styp := bctx.typeToStructType(elemTyp)
op.nilValue = styp.DefaultNilValue()
}
return op, nil
}
func (op ptrOp) genWrite(ctx *genContext, v string) string {
// Note: in writer functions, accesses to v are read-only, i.e. v is any Go
// expression. To make all accesses work through the pointer, we substitute
// v with (*v). This is required for most accesses including `v`, `call(v)`,
// and `v[index]` on slices.
//
// For `v.field` and `v[:]` on arrays, the dereference operation is not required.
var vv string
_, isStruct := op.elem.(structOp)
_, isByteArray := op.elem.(byteArrayOp)
if isStruct || isByteArray {
vv = v
} else {
vv = fmt.Sprintf("(*%s)", v)
}
var b bytes.Buffer
fmt.Fprintf(&b, "if %s == nil {\n", v)
fmt.Fprintf(&b, " w.Write([]byte{0x%X})\n", op.nilValue)
fmt.Fprintf(&b, "} else {\n")
fmt.Fprintf(&b, " %s", op.elem.genWrite(ctx, vv))
fmt.Fprintf(&b, "}\n")
return b.String()
}
func (op ptrOp) genDecode(ctx *genContext) (string, string) {
result, code := op.elem.genDecode(ctx)
if !op.nilOK {
// If nil pointers are not allowed, we can just decode the element.
return "&" + result, code
}
// nil is allowed, so check the kind and size first.
// If size is zero and kind matches the nilKind of the type,
// the value decodes as a nil pointer.
var (
resultV = ctx.temp()
kindV = ctx.temp()
sizeV = ctx.temp()
wantKind string
)
if op.nilValue == rlpstruct.NilKindList {
wantKind = "rlp.List"
} else {
wantKind = "rlp.String"
}
var b bytes.Buffer
fmt.Fprintf(&b, "var %s %s\n", resultV, types.TypeString(types.NewPointer(op.elemTyp), ctx.qualify))
fmt.Fprintf(&b, "if %s, %s, err := dec.Kind(); err != nil {\n", kindV, sizeV)
fmt.Fprintf(&b, " return err\n")
fmt.Fprintf(&b, "} else if %s != 0 || %s != %s {\n", sizeV, kindV, wantKind)
fmt.Fprint(&b, code)
fmt.Fprintf(&b, " %s = &%s\n", resultV, result)
fmt.Fprintf(&b, "}\n")
return resultV, b.String()
}
// structOp handles struct types.
type structOp struct {
named *types.Named
typ *types.Struct
fields []*structField
optionalFields []*structField
}
type structField struct {
name string
typ types.Type
elem op
}
func (bctx *buildContext) makeStructOp(named *types.Named, typ *types.Struct) (op, error) {
// Convert fields to []rlpstruct.Field.
var allStructFields []rlpstruct.Field
for i := 0; i < typ.NumFields(); i++ {
f := typ.Field(i)
allStructFields = append(allStructFields, rlpstruct.Field{
Name: f.Name(),
Exported: f.Exported(),
Index: i,
Tag: typ.Tag(i),
Type: *bctx.typeToStructType(f.Type()),
})
}
// Filter/validate fields.
fields, tags, err := rlpstruct.ProcessFields(allStructFields)
if err != nil {
return nil, err
}
// Create field ops.
var op = structOp{named: named, typ: typ}
for i, field := range fields {
// Advanced struct tags are not supported yet.
tag := tags[i]
if err := checkUnsupportedTags(field.Name, tag); err != nil {
return nil, err
}
typ := typ.Field(field.Index).Type()
elem, err := bctx.makeOp(nil, typ, tags[i])
if err != nil {
return nil, fmt.Errorf("field %s: %v", field.Name, err)
}
f := &structField{name: field.Name, typ: typ, elem: elem}
if tag.Optional {
op.optionalFields = append(op.optionalFields, f)
} else {
op.fields = append(op.fields, f)
}
}
return op, nil
}
func checkUnsupportedTags(field string, tag rlpstruct.Tags) error {
if tag.Tail {
return fmt.Errorf(`field %s has unsupported struct tag "tail"`, field)
}
return nil
}
func (op structOp) genWrite(ctx *genContext, v string) string {
var b bytes.Buffer
var listMarker = ctx.temp()
fmt.Fprintf(&b, "%s := w.List()\n", listMarker)
for _, field := range op.fields {
selector := v + "." + field.name
fmt.Fprint(&b, field.elem.genWrite(ctx, selector))
}
op.writeOptionalFields(&b, ctx, v)
fmt.Fprintf(&b, "w.ListEnd(%s)\n", listMarker)
return b.String()
}
func (op structOp) writeOptionalFields(b *bytes.Buffer, ctx *genContext, v string) {
if len(op.optionalFields) == 0 {
return
}
// First check zero-ness of all optional fields.
var zeroV = make([]string, len(op.optionalFields))
for i, field := range op.optionalFields {
selector := v + "." + field.name
zeroV[i] = ctx.temp()
fmt.Fprintf(b, "%s := %s\n", zeroV[i], nonZeroCheck(selector, field.typ, ctx.qualify))
}
// Now write the fields.
for i, field := range op.optionalFields {
selector := v + "." + field.name
cond := ""
for j := i; j < len(op.optionalFields); j++ {
if j > i {
cond += " || "
}
cond += zeroV[j]
}
fmt.Fprintf(b, "if %s {\n", cond)
fmt.Fprint(b, field.elem.genWrite(ctx, selector))
fmt.Fprintf(b, "}\n")
}
}
func (op structOp) genDecode(ctx *genContext) (string, string) {
// Get the string representation of the type.
// Here, named types are handled separately because the output
// would contain a copy of the struct definition otherwise.
var typeName string
if op.named != nil {
typeName = types.TypeString(op.named, ctx.qualify)
} else {
typeName = types.TypeString(op.typ, ctx.qualify)
}
// Create struct object.
var resultV = ctx.temp()
var b bytes.Buffer
fmt.Fprintf(&b, "var %s %s\n", resultV, typeName)
// Decode fields.
fmt.Fprintf(&b, "{\n")
fmt.Fprintf(&b, "if _, err := dec.List(); err != nil { return err }\n")
for _, field := range op.fields {
result, code := field.elem.genDecode(ctx)
fmt.Fprintf(&b, "// %s:\n", field.name)
fmt.Fprint(&b, code)
fmt.Fprintf(&b, "%s.%s = %s\n", resultV, field.name, result)
}
op.decodeOptionalFields(&b, ctx, resultV)
fmt.Fprintf(&b, "if err := dec.ListEnd(); err != nil { return err }\n")
fmt.Fprintf(&b, "}\n")
return resultV, b.String()
}
func (op structOp) decodeOptionalFields(b *bytes.Buffer, ctx *genContext, resultV string) {
var suffix bytes.Buffer
for _, field := range op.optionalFields {
result, code := field.elem.genDecode(ctx)
fmt.Fprintf(b, "// %s:\n", field.name)
fmt.Fprintf(b, "if dec.MoreDataInList() {\n")
fmt.Fprint(b, code)
fmt.Fprintf(b, "%s.%s = %s\n", resultV, field.name, result)
fmt.Fprintf(&suffix, "}\n")
}
suffix.WriteTo(b)
}
// sliceOp handles slice types.
type sliceOp struct {
typ *types.Slice
elemOp op
}
func (bctx *buildContext) makeSliceOp(typ *types.Slice) (op, error) {
elemOp, err := bctx.makeOp(nil, typ.Elem(), rlpstruct.Tags{})
if err != nil {
return nil, err
}
return sliceOp{typ: typ, elemOp: elemOp}, nil
}
func (op sliceOp) genWrite(ctx *genContext, v string) string {
var (
listMarker = ctx.temp() // holds return value of w.List()
iterElemV = ctx.temp() // iteration variable
elemCode = op.elemOp.genWrite(ctx, iterElemV)
)
var b bytes.Buffer
fmt.Fprintf(&b, "%s := w.List()\n", listMarker)
fmt.Fprintf(&b, "for _, %s := range %s {\n", iterElemV, v)
fmt.Fprint(&b, elemCode)
fmt.Fprintf(&b, "}\n")
fmt.Fprintf(&b, "w.ListEnd(%s)\n", listMarker)
return b.String()
}
func (op sliceOp) genDecode(ctx *genContext) (string, string) {
var sliceV = ctx.temp() // holds the output slice
elemResult, elemCode := op.elemOp.genDecode(ctx)
var b bytes.Buffer
fmt.Fprintf(&b, "var %s %s\n", sliceV, types.TypeString(op.typ, ctx.qualify))
fmt.Fprintf(&b, "if _, err := dec.List(); err != nil { return err }\n")
fmt.Fprintf(&b, "for dec.MoreDataInList() {\n")
fmt.Fprintf(&b, " %s", elemCode)
fmt.Fprintf(&b, " %s = append(%s, %s)\n", sliceV, sliceV, elemResult)
fmt.Fprintf(&b, "}\n")
fmt.Fprintf(&b, "if err := dec.ListEnd(); err != nil { return err }\n")
return sliceV, b.String()
}
func (bctx *buildContext) makeOp(name *types.Named, typ types.Type, tags rlpstruct.Tags) (op, error) {
switch typ := typ.(type) {
case *types.Named:
if isBigInt(typ) {
return bigIntOp{}, nil
}
if typ == bctx.rawValueType {
return bctx.makeRawValueOp(), nil
}
if bctx.isDecoder(typ) {
return nil, fmt.Errorf("type %v implements rlp.Decoder with non-pointer receiver", typ)
}
// TODO: same check for encoder?
return bctx.makeOp(typ, typ.Underlying(), tags)
case *types.Pointer:
if isBigInt(typ.Elem()) {
return bigIntOp{pointer: true}, nil
}
// Encoder/Decoder interfaces.
if bctx.isEncoder(typ) {
if bctx.isDecoder(typ) {
return encoderDecoderOp{typ}, nil
}
return nil, fmt.Errorf("type %v implements rlp.Encoder but not rlp.Decoder", typ)
}
if bctx.isDecoder(typ) {
return nil, fmt.Errorf("type %v implements rlp.Decoder but not rlp.Encoder", typ)
}
// Default pointer handling.
return bctx.makePtrOp(typ.Elem(), tags)
case *types.Basic:
return bctx.makeBasicOp(typ)
case *types.Struct:
return bctx.makeStructOp(name, typ)
case *types.Slice:
etyp := typ.Elem()
if isByte(etyp) && !bctx.isEncoder(etyp) {
return bctx.makeByteSliceOp(typ), nil
}
return bctx.makeSliceOp(typ)
case *types.Array:
etyp := typ.Elem()
if isByte(etyp) && !bctx.isEncoder(etyp) {
return bctx.makeByteArrayOp(name, typ), nil
}
return nil, fmt.Errorf("unhandled array type: %v", typ)
default:
return nil, fmt.Errorf("unhandled type: %v", typ)
}
}
// generateDecoder generates the DecodeRLP method on 'typ'.
func generateDecoder(ctx *genContext, typ string, op op) []byte {
ctx.resetTemp()
ctx.addImport(pathOfPackageRLP)
result, code := op.genDecode(ctx)
var b bytes.Buffer
fmt.Fprintf(&b, "func (obj *%s) DecodeRLP(dec *rlp.Stream) error {\n", typ)
fmt.Fprint(&b, code)
fmt.Fprintf(&b, " *obj = %s\n", result)
fmt.Fprintf(&b, " return nil\n")
fmt.Fprintf(&b, "}\n")
return b.Bytes()
}
// generateEncoder generates the EncodeRLP method on 'typ'.
func generateEncoder(ctx *genContext, typ string, op op) []byte {
ctx.resetTemp()
ctx.addImport("io")
ctx.addImport(pathOfPackageRLP)
var b bytes.Buffer
fmt.Fprintf(&b, "func (obj *%s) EncodeRLP(_w io.Writer) error {\n", typ)
fmt.Fprintf(&b, " w := rlp.NewEncoderBuffer(_w)\n")
fmt.Fprint(&b, op.genWrite(ctx, "obj"))
fmt.Fprintf(&b, " return w.Flush()\n")
fmt.Fprintf(&b, "}\n")
return b.Bytes()
}
func (bctx *buildContext) generate(typ *types.Named, encoder, decoder bool) ([]byte, error) {
bctx.topType = typ
pkg := typ.Obj().Pkg()
op, err := bctx.makeOp(nil, typ, rlpstruct.Tags{})
if err != nil {
return nil, err
}
var (
ctx = newGenContext(pkg)
encSource []byte
decSource []byte
)
if encoder {
encSource = generateEncoder(ctx, typ.Obj().Name(), op)
}
if decoder {
decSource = generateDecoder(ctx, typ.Obj().Name(), op)
}
var b bytes.Buffer
fmt.Fprintf(&b, "package %s\n\n", pkg.Name())
for _, imp := range ctx.importsList() {
fmt.Fprintf(&b, "import %q\n", imp)
}
if encoder {
fmt.Fprintln(&b)
b.Write(encSource)
}
if decoder {
fmt.Fprintln(&b)
b.Write(decSource)
}
source := b.Bytes()
// fmt.Println(string(source))
return format.Source(source)
}

92
rlp/rlpgen/gen_test.go Normal file
View File

@ -0,0 +1,92 @@
package main
import (
"bytes"
"fmt"
"go/ast"
"go/importer"
"go/parser"
"go/token"
"go/types"
"io/ioutil"
"os"
"path/filepath"
"testing"
)
// Package RLP is loaded only once and reused for all tests.
var (
testFset = token.NewFileSet()
testImporter = importer.ForCompiler(testFset, "source", nil).(types.ImporterFrom)
testPackageRLP *types.Package
)
func init() {
cwd, err := os.Getwd()
if err != nil {
panic(err)
}
testPackageRLP, err = testImporter.ImportFrom(pathOfPackageRLP, cwd, 0)
if err != nil {
panic(fmt.Errorf("can't load package RLP: %v", err))
}
}
var tests = []string{"uints", "nil", "rawvalue", "optional", "bigint"}
func TestOutput(t *testing.T) {
for _, test := range tests {
test := test
t.Run(test, func(t *testing.T) {
inputFile := filepath.Join("testdata", test+".in.txt")
outputFile := filepath.Join("testdata", test+".out.txt")
bctx, typ, err := loadTestSource(inputFile, "Test")
if err != nil {
t.Fatal("error loading test source:", err)
}
output, err := bctx.generate(typ, true, true)
if err != nil {
t.Fatal("error in generate:", err)
}
// Set this environment variable to regenerate the test outputs.
if os.Getenv("WRITE_TEST_FILES") != "" {
ioutil.WriteFile(outputFile, output, 0644)
}
// Check if output matches.
wantOutput, err := ioutil.ReadFile(outputFile)
if err != nil {
t.Fatal("error loading expected test output:", err)
}
if !bytes.Equal(output, wantOutput) {
t.Fatal("output mismatch:\n", string(output))
}
})
}
}
func loadTestSource(file string, typeName string) (*buildContext, *types.Named, error) {
// Load the test input.
content, err := ioutil.ReadFile(file)
if err != nil {
return nil, nil, err
}
f, err := parser.ParseFile(testFset, file, content, 0)
if err != nil {
return nil, nil, err
}
conf := types.Config{Importer: testImporter}
pkg, err := conf.Check("test", testFset, []*ast.File{f}, nil)
if err != nil {
return nil, nil, err
}
// Find the test struct.
bctx := newBuildContext(testPackageRLP)
typ, err := lookupStructType(pkg.Scope(), typeName)
if err != nil {
return nil, nil, fmt.Errorf("can't find type %s: %v", typeName, err)
}
return bctx, typ, nil
}

148
rlp/rlpgen/main.go Normal file
View File

@ -0,0 +1,148 @@
// Copyright 2021 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"bytes"
"errors"
"flag"
"fmt"
"go/types"
"io/ioutil"
"os"
"golang.org/x/tools/go/packages"
)
const pathOfPackageRLP = "github.com/ethereum/go-ethereum/rlp"
func main() {
var (
pkgdir = flag.String("dir", ".", "input package")
output = flag.String("out", "-", "output file (default is stdout)")
genEncoder = flag.Bool("encoder", true, "generate EncodeRLP?")
genDecoder = flag.Bool("decoder", false, "generate DecodeRLP?")
typename = flag.String("type", "", "type to generate methods for")
)
flag.Parse()
cfg := Config{
Dir: *pkgdir,
Type: *typename,
GenerateEncoder: *genEncoder,
GenerateDecoder: *genDecoder,
}
code, err := cfg.process()
if err != nil {
fatal(err)
}
if *output == "-" {
os.Stdout.Write(code)
} else if err := ioutil.WriteFile(*output, code, 0644); err != nil {
fatal(err)
}
}
func fatal(args ...interface{}) {
fmt.Fprintln(os.Stderr, args...)
os.Exit(1)
}
type Config struct {
Dir string // input package directory
Type string
GenerateEncoder bool
GenerateDecoder bool
}
// process generates the Go code.
func (cfg *Config) process() (code []byte, err error) {
// Load packages.
pcfg := &packages.Config{
Mode: packages.NeedName | packages.NeedTypes | packages.NeedImports | packages.NeedDeps,
Dir: cfg.Dir,
BuildFlags: []string{"-tags", "norlpgen"},
}
ps, err := packages.Load(pcfg, pathOfPackageRLP, ".")
if err != nil {
return nil, err
}
if len(ps) == 0 {
return nil, fmt.Errorf("no Go package found in %s", cfg.Dir)
}
packages.PrintErrors(ps)
// Find the packages that were loaded.
var (
pkg *types.Package
packageRLP *types.Package
)
for _, p := range ps {
if len(p.Errors) > 0 {
return nil, fmt.Errorf("package %s has errors", p.PkgPath)
}
if p.PkgPath == pathOfPackageRLP {
packageRLP = p.Types
} else {
pkg = p.Types
}
}
bctx := newBuildContext(packageRLP)
// Find the type and generate.
typ, err := lookupStructType(pkg.Scope(), cfg.Type)
if err != nil {
return nil, fmt.Errorf("can't find %s in %s: %v", typ, pkg, err)
}
code, err = bctx.generate(typ, cfg.GenerateEncoder, cfg.GenerateDecoder)
if err != nil {
return nil, err
}
// Add build comments.
// This is done here to avoid processing these lines with gofmt.
var header bytes.Buffer
fmt.Fprint(&header, "// Code generated by rlpgen. DO NOT EDIT.\n\n")
fmt.Fprint(&header, "//go:build !norlpgen\n")
fmt.Fprint(&header, "// +build !norlpgen\n\n")
return append(header.Bytes(), code...), nil
}
func lookupStructType(scope *types.Scope, name string) (*types.Named, error) {
typ, err := lookupType(scope, name)
if err != nil {
return nil, err
}
_, ok := typ.Underlying().(*types.Struct)
if !ok {
return nil, errors.New("not a struct type")
}
return typ, nil
}
func lookupType(scope *types.Scope, name string) (*types.Named, error) {
obj := scope.Lookup(name)
if obj == nil {
return nil, errors.New("no such identifier")
}
typ, ok := obj.(*types.TypeName)
if !ok {
return nil, errors.New("not a type")
}
return typ.Type().(*types.Named), nil
}

10
rlp/rlpgen/testdata/bigint.in.txt vendored Normal file
View File

@ -0,0 +1,10 @@
// -*- mode: go -*-
package test
import "math/big"
type Test struct {
Int *big.Int
IntNoPtr big.Int
}

49
rlp/rlpgen/testdata/bigint.out.txt vendored Normal file
View File

@ -0,0 +1,49 @@
package test
import "github.com/ethereum/go-ethereum/rlp"
import "io"
func (obj *Test) EncodeRLP(_w io.Writer) error {
w := rlp.NewEncoderBuffer(_w)
_tmp0 := w.List()
if obj.Int == nil {
w.Write(rlp.EmptyString)
} else {
if obj.Int.Sign() == -1 {
return rlp.ErrNegativeBigInt
}
w.WriteBigInt(obj.Int)
}
if obj.IntNoPtr.Sign() == -1 {
return rlp.ErrNegativeBigInt
}
w.WriteBigInt(&obj.IntNoPtr)
w.ListEnd(_tmp0)
return w.Flush()
}
func (obj *Test) DecodeRLP(dec *rlp.Stream) error {
var _tmp0 Test
{
if _, err := dec.List(); err != nil {
return err
}
// Int:
_tmp1, err := dec.BigInt()
if err != nil {
return err
}
_tmp0.Int = _tmp1
// IntNoPtr:
_tmp2, err := dec.BigInt()
if err != nil {
return err
}
_tmp0.IntNoPtr = (*_tmp2)
if err := dec.ListEnd(); err != nil {
return err
}
}
*obj = _tmp0
return nil
}

30
rlp/rlpgen/testdata/nil.in.txt vendored Normal file
View File

@ -0,0 +1,30 @@
// -*- mode: go -*-
package test
type Aux struct{
A uint32
}
type Test struct{
Uint8 *byte `rlp:"nil"`
Uint8List *byte `rlp:"nilList"`
Uint32 *uint32 `rlp:"nil"`
Uint32List *uint32 `rlp:"nilList"`
Uint64 *uint64 `rlp:"nil"`
Uint64List *uint64 `rlp:"nilList"`
String *string `rlp:"nil"`
StringList *string `rlp:"nilList"`
ByteArray *[3]byte `rlp:"nil"`
ByteArrayList *[3]byte `rlp:"nilList"`
ByteSlice *[]byte `rlp:"nil"`
ByteSliceList *[]byte `rlp:"nilList"`
Struct *Aux `rlp:"nil"`
StructString *Aux `rlp:"nilString"`
}

289
rlp/rlpgen/testdata/nil.out.txt vendored Normal file
View File

@ -0,0 +1,289 @@
package test
import "github.com/ethereum/go-ethereum/rlp"
import "io"
func (obj *Test) EncodeRLP(_w io.Writer) error {
w := rlp.NewEncoderBuffer(_w)
_tmp0 := w.List()
if obj.Uint8 == nil {
w.Write([]byte{0x80})
} else {
w.WriteUint64(uint64((*obj.Uint8)))
}
if obj.Uint8List == nil {
w.Write([]byte{0xC0})
} else {
w.WriteUint64(uint64((*obj.Uint8List)))
}
if obj.Uint32 == nil {
w.Write([]byte{0x80})
} else {
w.WriteUint64(uint64((*obj.Uint32)))
}
if obj.Uint32List == nil {
w.Write([]byte{0xC0})
} else {
w.WriteUint64(uint64((*obj.Uint32List)))
}
if obj.Uint64 == nil {
w.Write([]byte{0x80})
} else {
w.WriteUint64((*obj.Uint64))
}
if obj.Uint64List == nil {
w.Write([]byte{0xC0})
} else {
w.WriteUint64((*obj.Uint64List))
}
if obj.String == nil {
w.Write([]byte{0x80})
} else {
w.WriteString((*obj.String))
}
if obj.StringList == nil {
w.Write([]byte{0xC0})
} else {
w.WriteString((*obj.StringList))
}
if obj.ByteArray == nil {
w.Write([]byte{0x80})
} else {
w.WriteBytes(obj.ByteArray[:])
}
if obj.ByteArrayList == nil {
w.Write([]byte{0xC0})
} else {
w.WriteBytes(obj.ByteArrayList[:])
}
if obj.ByteSlice == nil {
w.Write([]byte{0x80})
} else {
w.WriteBytes((*obj.ByteSlice))
}
if obj.ByteSliceList == nil {
w.Write([]byte{0xC0})
} else {
w.WriteBytes((*obj.ByteSliceList))
}
if obj.Struct == nil {
w.Write([]byte{0xC0})
} else {
_tmp1 := w.List()
w.WriteUint64(uint64(obj.Struct.A))
w.ListEnd(_tmp1)
}
if obj.StructString == nil {
w.Write([]byte{0x80})
} else {
_tmp2 := w.List()
w.WriteUint64(uint64(obj.StructString.A))
w.ListEnd(_tmp2)
}
w.ListEnd(_tmp0)
return w.Flush()
}
func (obj *Test) DecodeRLP(dec *rlp.Stream) error {
var _tmp0 Test
{
if _, err := dec.List(); err != nil {
return err
}
// Uint8:
var _tmp2 *byte
if _tmp3, _tmp4, err := dec.Kind(); err != nil {
return err
} else if _tmp4 != 0 || _tmp3 != rlp.String {
_tmp1, err := dec.Uint8()
if err != nil {
return err
}
_tmp2 = &_tmp1
}
_tmp0.Uint8 = _tmp2
// Uint8List:
var _tmp6 *byte
if _tmp7, _tmp8, err := dec.Kind(); err != nil {
return err
} else if _tmp8 != 0 || _tmp7 != rlp.List {
_tmp5, err := dec.Uint8()
if err != nil {
return err
}
_tmp6 = &_tmp5
}
_tmp0.Uint8List = _tmp6
// Uint32:
var _tmp10 *uint32
if _tmp11, _tmp12, err := dec.Kind(); err != nil {
return err
} else if _tmp12 != 0 || _tmp11 != rlp.String {
_tmp9, err := dec.Uint32()
if err != nil {
return err
}
_tmp10 = &_tmp9
}
_tmp0.Uint32 = _tmp10
// Uint32List:
var _tmp14 *uint32
if _tmp15, _tmp16, err := dec.Kind(); err != nil {
return err
} else if _tmp16 != 0 || _tmp15 != rlp.List {
_tmp13, err := dec.Uint32()
if err != nil {
return err
}
_tmp14 = &_tmp13
}
_tmp0.Uint32List = _tmp14
// Uint64:
var _tmp18 *uint64
if _tmp19, _tmp20, err := dec.Kind(); err != nil {
return err
} else if _tmp20 != 0 || _tmp19 != rlp.String {
_tmp17, err := dec.Uint64()
if err != nil {
return err
}
_tmp18 = &_tmp17
}
_tmp0.Uint64 = _tmp18
// Uint64List:
var _tmp22 *uint64
if _tmp23, _tmp24, err := dec.Kind(); err != nil {
return err
} else if _tmp24 != 0 || _tmp23 != rlp.List {
_tmp21, err := dec.Uint64()
if err != nil {
return err
}
_tmp22 = &_tmp21
}
_tmp0.Uint64List = _tmp22
// String:
var _tmp26 *string
if _tmp27, _tmp28, err := dec.Kind(); err != nil {
return err
} else if _tmp28 != 0 || _tmp27 != rlp.String {
_tmp25, err := dec.String()
if err != nil {
return err
}
_tmp26 = &_tmp25
}
_tmp0.String = _tmp26
// StringList:
var _tmp30 *string
if _tmp31, _tmp32, err := dec.Kind(); err != nil {
return err
} else if _tmp32 != 0 || _tmp31 != rlp.List {
_tmp29, err := dec.String()
if err != nil {
return err
}
_tmp30 = &_tmp29
}
_tmp0.StringList = _tmp30
// ByteArray:
var _tmp34 *[3]byte
if _tmp35, _tmp36, err := dec.Kind(); err != nil {
return err
} else if _tmp36 != 0 || _tmp35 != rlp.String {
var _tmp33 [3]byte
if err := dec.ReadBytes(_tmp33[:]); err != nil {
return err
}
_tmp34 = &_tmp33
}
_tmp0.ByteArray = _tmp34
// ByteArrayList:
var _tmp38 *[3]byte
if _tmp39, _tmp40, err := dec.Kind(); err != nil {
return err
} else if _tmp40 != 0 || _tmp39 != rlp.List {
var _tmp37 [3]byte
if err := dec.ReadBytes(_tmp37[:]); err != nil {
return err
}
_tmp38 = &_tmp37
}
_tmp0.ByteArrayList = _tmp38
// ByteSlice:
var _tmp42 *[]byte
if _tmp43, _tmp44, err := dec.Kind(); err != nil {
return err
} else if _tmp44 != 0 || _tmp43 != rlp.String {
_tmp41, err := dec.Bytes()
if err != nil {
return err
}
_tmp42 = &_tmp41
}
_tmp0.ByteSlice = _tmp42
// ByteSliceList:
var _tmp46 *[]byte
if _tmp47, _tmp48, err := dec.Kind(); err != nil {
return err
} else if _tmp48 != 0 || _tmp47 != rlp.List {
_tmp45, err := dec.Bytes()
if err != nil {
return err
}
_tmp46 = &_tmp45
}
_tmp0.ByteSliceList = _tmp46
// Struct:
var _tmp51 *Aux
if _tmp52, _tmp53, err := dec.Kind(); err != nil {
return err
} else if _tmp53 != 0 || _tmp52 != rlp.List {
var _tmp49 Aux
{
if _, err := dec.List(); err != nil {
return err
}
// A:
_tmp50, err := dec.Uint32()
if err != nil {
return err
}
_tmp49.A = _tmp50
if err := dec.ListEnd(); err != nil {
return err
}
}
_tmp51 = &_tmp49
}
_tmp0.Struct = _tmp51
// StructString:
var _tmp56 *Aux
if _tmp57, _tmp58, err := dec.Kind(); err != nil {
return err
} else if _tmp58 != 0 || _tmp57 != rlp.String {
var _tmp54 Aux
{
if _, err := dec.List(); err != nil {
return err
}
// A:
_tmp55, err := dec.Uint32()
if err != nil {
return err
}
_tmp54.A = _tmp55
if err := dec.ListEnd(); err != nil {
return err
}
}
_tmp56 = &_tmp54
}
_tmp0.StructString = _tmp56
if err := dec.ListEnd(); err != nil {
return err
}
}
*obj = _tmp0
return nil
}

17
rlp/rlpgen/testdata/optional.in.txt vendored Normal file
View File

@ -0,0 +1,17 @@
// -*- mode: go -*-
package test
type Aux struct {
A uint64
}
type Test struct {
Uint64 uint64 `rlp:"optional"`
Pointer *uint64 `rlp:"optional"`
String string `rlp:"optional"`
Slice []uint64 `rlp:"optional"`
Array [3]byte `rlp:"optional"`
NamedStruct Aux `rlp:"optional"`
AnonStruct struct{ A string } `rlp:"optional"`
}

153
rlp/rlpgen/testdata/optional.out.txt vendored Normal file
View File

@ -0,0 +1,153 @@
package test
import "github.com/ethereum/go-ethereum/rlp"
import "io"
func (obj *Test) EncodeRLP(_w io.Writer) error {
w := rlp.NewEncoderBuffer(_w)
_tmp0 := w.List()
_tmp1 := obj.Uint64 != 0
_tmp2 := obj.Pointer != nil
_tmp3 := obj.String != ""
_tmp4 := len(obj.Slice) > 0
_tmp5 := obj.Array != ([3]byte{})
_tmp6 := obj.NamedStruct != (Aux{})
_tmp7 := obj.AnonStruct != (struct{ A string }{})
if _tmp1 || _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 {
w.WriteUint64(obj.Uint64)
}
if _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 {
if obj.Pointer == nil {
w.Write([]byte{0x80})
} else {
w.WriteUint64((*obj.Pointer))
}
}
if _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 {
w.WriteString(obj.String)
}
if _tmp4 || _tmp5 || _tmp6 || _tmp7 {
_tmp8 := w.List()
for _, _tmp9 := range obj.Slice {
w.WriteUint64(_tmp9)
}
w.ListEnd(_tmp8)
}
if _tmp5 || _tmp6 || _tmp7 {
w.WriteBytes(obj.Array[:])
}
if _tmp6 || _tmp7 {
_tmp10 := w.List()
w.WriteUint64(obj.NamedStruct.A)
w.ListEnd(_tmp10)
}
if _tmp7 {
_tmp11 := w.List()
w.WriteString(obj.AnonStruct.A)
w.ListEnd(_tmp11)
}
w.ListEnd(_tmp0)
return w.Flush()
}
func (obj *Test) DecodeRLP(dec *rlp.Stream) error {
var _tmp0 Test
{
if _, err := dec.List(); err != nil {
return err
}
// Uint64:
if dec.MoreDataInList() {
_tmp1, err := dec.Uint64()
if err != nil {
return err
}
_tmp0.Uint64 = _tmp1
// Pointer:
if dec.MoreDataInList() {
_tmp2, err := dec.Uint64()
if err != nil {
return err
}
_tmp0.Pointer = &_tmp2
// String:
if dec.MoreDataInList() {
_tmp3, err := dec.String()
if err != nil {
return err
}
_tmp0.String = _tmp3
// Slice:
if dec.MoreDataInList() {
var _tmp4 []uint64
if _, err := dec.List(); err != nil {
return err
}
for dec.MoreDataInList() {
_tmp5, err := dec.Uint64()
if err != nil {
return err
}
_tmp4 = append(_tmp4, _tmp5)
}
if err := dec.ListEnd(); err != nil {
return err
}
_tmp0.Slice = _tmp4
// Array:
if dec.MoreDataInList() {
var _tmp6 [3]byte
if err := dec.ReadBytes(_tmp6[:]); err != nil {
return err
}
_tmp0.Array = _tmp6
// NamedStruct:
if dec.MoreDataInList() {
var _tmp7 Aux
{
if _, err := dec.List(); err != nil {
return err
}
// A:
_tmp8, err := dec.Uint64()
if err != nil {
return err
}
_tmp7.A = _tmp8
if err := dec.ListEnd(); err != nil {
return err
}
}
_tmp0.NamedStruct = _tmp7
// AnonStruct:
if dec.MoreDataInList() {
var _tmp9 struct{ A string }
{
if _, err := dec.List(); err != nil {
return err
}
// A:
_tmp10, err := dec.String()
if err != nil {
return err
}
_tmp9.A = _tmp10
if err := dec.ListEnd(); err != nil {
return err
}
}
_tmp0.AnonStruct = _tmp9
}
}
}
}
}
}
}
if err := dec.ListEnd(); err != nil {
return err
}
}
*obj = _tmp0
return nil
}

11
rlp/rlpgen/testdata/rawvalue.in.txt vendored Normal file
View File

@ -0,0 +1,11 @@
// -*- mode: go -*-
package test
import "github.com/ethereum/go-ethereum/rlp"
type Test struct {
RawValue rlp.RawValue
PointerToRawValue *rlp.RawValue
SliceOfRawValue []rlp.RawValue
}

64
rlp/rlpgen/testdata/rawvalue.out.txt vendored Normal file
View File

@ -0,0 +1,64 @@
package test
import "github.com/ethereum/go-ethereum/rlp"
import "io"
func (obj *Test) EncodeRLP(_w io.Writer) error {
w := rlp.NewEncoderBuffer(_w)
_tmp0 := w.List()
w.Write(obj.RawValue)
if obj.PointerToRawValue == nil {
w.Write([]byte{0x80})
} else {
w.Write((*obj.PointerToRawValue))
}
_tmp1 := w.List()
for _, _tmp2 := range obj.SliceOfRawValue {
w.Write(_tmp2)
}
w.ListEnd(_tmp1)
w.ListEnd(_tmp0)
return w.Flush()
}
func (obj *Test) DecodeRLP(dec *rlp.Stream) error {
var _tmp0 Test
{
if _, err := dec.List(); err != nil {
return err
}
// RawValue:
_tmp1, err := dec.Raw()
if err != nil {
return err
}
_tmp0.RawValue = _tmp1
// PointerToRawValue:
_tmp2, err := dec.Raw()
if err != nil {
return err
}
_tmp0.PointerToRawValue = &_tmp2
// SliceOfRawValue:
var _tmp3 []rlp.RawValue
if _, err := dec.List(); err != nil {
return err
}
for dec.MoreDataInList() {
_tmp4, err := dec.Raw()
if err != nil {
return err
}
_tmp3 = append(_tmp3, _tmp4)
}
if err := dec.ListEnd(); err != nil {
return err
}
_tmp0.SliceOfRawValue = _tmp3
if err := dec.ListEnd(); err != nil {
return err
}
}
*obj = _tmp0
return nil
}

10
rlp/rlpgen/testdata/uints.in.txt vendored Normal file
View File

@ -0,0 +1,10 @@
// -*- mode: go -*-
package test
type Test struct{
A uint8
B uint16
C uint32
D uint64
}

53
rlp/rlpgen/testdata/uints.out.txt vendored Normal file
View File

@ -0,0 +1,53 @@
package test
import "github.com/ethereum/go-ethereum/rlp"
import "io"
func (obj *Test) EncodeRLP(_w io.Writer) error {
w := rlp.NewEncoderBuffer(_w)
_tmp0 := w.List()
w.WriteUint64(uint64(obj.A))
w.WriteUint64(uint64(obj.B))
w.WriteUint64(uint64(obj.C))
w.WriteUint64(obj.D)
w.ListEnd(_tmp0)
return w.Flush()
}
func (obj *Test) DecodeRLP(dec *rlp.Stream) error {
var _tmp0 Test
{
if _, err := dec.List(); err != nil {
return err
}
// A:
_tmp1, err := dec.Uint8()
if err != nil {
return err
}
_tmp0.A = _tmp1
// B:
_tmp2, err := dec.Uint16()
if err != nil {
return err
}
_tmp0.B = _tmp2
// C:
_tmp3, err := dec.Uint32()
if err != nil {
return err
}
_tmp0.C = _tmp3
// D:
_tmp4, err := dec.Uint64()
if err != nil {
return err
}
_tmp0.D = _tmp4
if err := dec.ListEnd(); err != nil {
return err
}
}
*obj = _tmp0
return nil
}

98
rlp/rlpgen/types.go Normal file
View File

@ -0,0 +1,98 @@
package main
import (
"fmt"
"go/types"
"reflect"
)
// typeReflectKind gives the reflect.Kind that represents typ.
func typeReflectKind(typ types.Type) reflect.Kind {
switch typ := typ.(type) {
case *types.Basic:
k := typ.Kind()
if k >= types.Bool && k <= types.Complex128 {
// value order matches for Bool..Complex128
return reflect.Bool + reflect.Kind(k-types.Bool)
}
if k == types.String {
return reflect.String
}
if k == types.UnsafePointer {
return reflect.UnsafePointer
}
panic(fmt.Errorf("unhandled BasicKind %v", k))
case *types.Array:
return reflect.Array
case *types.Chan:
return reflect.Chan
case *types.Interface:
return reflect.Interface
case *types.Map:
return reflect.Map
case *types.Pointer:
return reflect.Ptr
case *types.Signature:
return reflect.Func
case *types.Slice:
return reflect.Slice
case *types.Struct:
return reflect.Struct
default:
panic(fmt.Errorf("unhandled type %T", typ))
}
}
// nonZeroCheck returns the expression that checks whether 'v' is a non-zero value of type 'vtyp'.
func nonZeroCheck(v string, vtyp types.Type, qualify types.Qualifier) string {
// Resolve type name.
typ := resolveUnderlying(vtyp)
switch typ := typ.(type) {
case *types.Basic:
k := typ.Kind()
switch {
case k == types.Bool:
return v
case k >= types.Uint && k <= types.Complex128:
return fmt.Sprintf("%s != 0", v)
case k == types.String:
return fmt.Sprintf(`%s != ""`, v)
default:
panic(fmt.Errorf("unhandled BasicKind %v", k))
}
case *types.Array, *types.Struct:
return fmt.Sprintf("%s != (%s{})", v, types.TypeString(vtyp, qualify))
case *types.Interface, *types.Pointer, *types.Signature:
return fmt.Sprintf("%s != nil", v)
case *types.Slice, *types.Map:
return fmt.Sprintf("len(%s) > 0", v)
default:
panic(fmt.Errorf("unhandled type %T", typ))
}
}
// isBigInt checks whether 'typ' is "math/big".Int.
func isBigInt(typ types.Type) bool {
named, ok := typ.(*types.Named)
if !ok {
return false
}
name := named.Obj()
return name.Pkg().Path() == "math/big" && name.Name() == "Int"
}
// isByte checks whether the underlying type of 'typ' is uint8.
func isByte(typ types.Type) bool {
basic, ok := resolveUnderlying(typ).(*types.Basic)
return ok && basic.Kind() == types.Uint8
}
func resolveUnderlying(typ types.Type) types.Type {
for {
t := typ.Underlying()
if t == typ {
return t
}
typ = t
}
}