accouns/abi: refactored ABI package

Refactored the abi package parsing and type handling. Relying mostly on
package reflect as opposed to most of our own type reflection. Our own
type reflection is still used however for cases such as Bytes and
FixedBytes (abi: bytes•).

This also inclused several fixes for slice handling of arbitrary and
fixed size for all supported types.

This also further removes implicit type casting such as assigning,
for example `[2]T{} = []T{1}` will fail, however `[2]T{} == []T{1, 2}`
(notice assigning *slice* to fixed size *array*). Assigning arrays to
slices will always succeed if they are of the same element type.

Incidentally also fixes #2379
This commit is contained in:
Jeffrey Wilcke
2016-04-07 11:39:22 +02:00
parent 18580e152c
commit 5127ec10cb
8 changed files with 491 additions and 314 deletions

View File

@ -21,8 +21,6 @@ import (
"reflect"
"regexp"
"strconv"
"github.com/ethereum/go-ethereum/common"
)
const (
@ -40,53 +38,59 @@ const (
// Type is the reflection of the supported argument type
type Type struct {
IsSlice bool
SliceSize int
IsSlice, IsArray bool
SliceSize int
Elem *Type
Kind reflect.Kind
Type reflect.Type
Size int
T byte // Our own type checking
Kind reflect.Kind
Type reflect.Type
Size int
T byte // Our own type checking
stringKind string // holds the unparsed string for deriving signatures
}
var (
// fullTypeRegex parses the abi types
//
// Types can be in the format of:
//
// Input = Type [ "[" [ Number ] "]" ] Name .
// Type = [ "u" ] "int" [ Number ] .
//
// Examples:
//
// string int uint real
// string32 int8 uint8 uint[]
// address int256 uint256 real[2]
fullTypeRegex = regexp.MustCompile("([a-zA-Z0-9]+)(\\[([0-9]*)?\\])?")
typeRegex = regexp.MustCompile("([a-zA-Z]+)([0-9]*)?")
// typeRegex parses the abi sub types
typeRegex = regexp.MustCompile("([a-zA-Z]+)([0-9]*)?")
)
// NewType returns a fully parsed Type given by the input string or an error if it can't be parsed.
//
// Strings can be in the format of:
//
// Input = Type [ "[" [ Number ] "]" ] Name .
// Type = [ "u" ] "int" [ Number ] .
//
// Examples:
//
// string int uint real
// string32 int8 uint8 uint[]
// address int256 uint256 real[2]
// NewType creates a new reflection type of abi type given in t.
func NewType(t string) (typ Type, err error) {
// 1. full string 2. type 3. (opt.) is slice 4. (opt.) size
// parse the full representation of the abi-type definition; including:
// * full string
// * type
// * is slice
// * slice size
res := fullTypeRegex.FindAllStringSubmatch(t, -1)[0]
// check if type is slice and parse type.
switch {
case res[3] != "":
// err is ignored. Already checked for number through the regexp
typ.SliceSize, _ = strconv.Atoi(res[3])
typ.IsSlice = true
typ.IsArray = true
case res[2] != "":
typ.IsSlice, typ.SliceSize = true, -1
case res[0] == "":
return Type{}, fmt.Errorf("abi: type parse error: %s", t)
}
if typ.IsArray || typ.IsSlice {
sliceType, err := NewType(res[1])
if err != nil {
return Type{}, err
}
typ.Elem = &sliceType
return typ, nil
}
// parse the type and size of the abi-type.
parsedType := typeRegex.FindAllStringSubmatch(res[1], -1)[0]
@ -109,21 +113,20 @@ func NewType(t string) (typ Type, err error) {
switch varType {
case "int":
typ.Kind = reflect.Int
typ.Kind = reflectIntKind(false, varSize)
typ.Type = big_t
typ.Size = varSize
typ.T = IntTy
case "uint":
typ.Kind = reflect.Uint
typ.Kind = reflectIntKind(true, varSize)
typ.Type = ubig_t
typ.Size = varSize
typ.T = UintTy
case "bool":
typ.Kind = reflect.Bool
typ.T = BoolTy
case "real": // TODO
typ.Kind = reflect.Invalid
case "address":
typ.Kind = reflect.Array
typ.Type = address_t
typ.Size = 20
typ.T = AddressTy
@ -131,22 +134,17 @@ func NewType(t string) (typ Type, err error) {
typ.Kind = reflect.String
typ.Size = -1
typ.T = StringTy
if varSize > 0 {
typ.Size = 32
}
case "hash":
typ.Kind = reflect.Array
typ.Size = 32
typ.Type = hash_t
typ.T = HashTy
case "bytes":
typ.Kind = reflect.Array
typ.Type = byte_ts
typ.Size = varSize
sliceType, _ := NewType("uint8")
typ.Elem = &sliceType
if varSize == 0 {
typ.IsSlice = true
typ.T = BytesTy
typ.SliceSize = -1
} else {
typ.IsArray = true
typ.T = FixedBytesTy
typ.SliceSize = varSize
}
default:
return Type{}, fmt.Errorf("unsupported arg type: %s", t)
@ -156,98 +154,30 @@ func NewType(t string) (typ Type, err error) {
return
}
// String implements Stringer
func (t Type) String() (out string) {
return t.stringKind
}
// packBytesSlice packs the given bytes as [L, V] as the canonical representation
// bytes slice
func packBytesSlice(bytes []byte, l int) []byte {
len := packNum(reflect.ValueOf(l), UintTy)
return append(len, common.RightPadBytes(bytes, (l+31)/32*32)...)
}
func (t Type) pack(v reflect.Value) ([]byte, error) {
// dereference pointer first if it's a pointer
v = indirect(v)
// Test the given input parameter `v` and checks if it matches certain
// criteria
// * Big integers are checks for ptr types and if the given value is
// assignable
// * Integer are checked for size
// * Strings, addresses and bytes are checks for type and size
func (t Type) pack(v interface{}) ([]byte, error) {
value := reflect.ValueOf(v)
switch kind := value.Kind(); kind {
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
// check input is unsigned
if t.Type != ubig_t {
return nil, fmt.Errorf("abi: type mismatch: %s for %T", t.Type, v)
}
// no implicit type casting
if int(value.Type().Size()*8) != t.Size {
return nil, fmt.Errorf("abi: cannot use type %T as type uint%d", v, t.Size)
}
return packNum(value, t.T), nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if t.Type != ubig_t {
return nil, fmt.Errorf("type mismatch: %s for %T", t.Type, v)
}
// no implicit type casting
if int(value.Type().Size()*8) != t.Size {
return nil, fmt.Errorf("abi: cannot use type %T as type uint%d", v, t.Size)
}
return packNum(value, t.T), nil
case reflect.Ptr:
// If the value is a ptr do a assign check (only used by
// big.Int for now)
if t.Type == ubig_t && value.Type() != ubig_t {
return nil, fmt.Errorf("type mismatch: %s for %T", t.Type, v)
}
return packNum(value, t.T), nil
case reflect.String:
if t.Size > -1 && value.Len() > t.Size {
return nil, fmt.Errorf("%v out of bound. %d for %d", value.Kind(), value.Len(), t.Size)
}
return packBytesSlice([]byte(value.String()), value.Len()), nil
case reflect.Slice:
// Byte slice is a special case, it gets treated as a single value
if t.T == BytesTy {
return packBytesSlice(value.Bytes(), value.Len()), nil
}
if t.SliceSize > -1 && value.Len() > t.SliceSize {
return nil, fmt.Errorf("%v out of bound. %d for %d", value.Kind(), value.Len(), t.Size)
}
// Signed / Unsigned check
if value.Type() == big_t && (t.T != IntTy && isSigned(value)) || (t.T == UintTy && isSigned(value)) {
return nil, fmt.Errorf("slice of incompatible types.")
}
if err := typeCheck(t, v); err != nil {
return nil, err
}
if (t.IsSlice || t.IsArray) && t.T != BytesTy && t.T != FixedBytesTy {
var packed []byte
for i := 0; i < value.Len(); i++ {
val, err := t.pack(value.Index(i).Interface())
for i := 0; i < v.Len(); i++ {
val, err := t.Elem.pack(v.Index(i))
if err != nil {
return nil, err
}
packed = append(packed, val...)
}
return packBytesSlice(packed, value.Len()), nil
case reflect.Bool:
if value.Bool() {
return common.LeftPadBytes(common.Big1.Bytes(), 32), nil
} else {
return common.LeftPadBytes(common.Big0.Bytes(), 32), nil
}
case reflect.Array:
if v, ok := value.Interface().(common.Address); ok {
return common.LeftPadBytes(v[:], 32), nil
} else if v, ok := value.Interface().(common.Hash); ok {
return v[:], nil
}
return packBytesSlice(packed, v.Len()), nil
}
return nil, fmt.Errorf("ABI: bad input given %v", value.Kind())
return packElement(t, v), nil
}