Skip to content

Commit

Permalink
core/vm: remove custom parser error
Browse files Browse the repository at this point in the history
  • Loading branch information
lightclient committed Jan 11, 2023
1 parent 0cd867f commit 1327798
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 65 deletions.
54 changes: 15 additions & 39 deletions core/vm/eof.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ import (
"errors"
"fmt"
"io"

"github.com/ethereum/go-ethereum/common"
)

const (
Expand All @@ -43,27 +41,6 @@ const (
maxStackHeight = 1023
)

type ParseError struct {
inner error
index int
msg string
}

func NewParseError(err error, index int, format string, a ...any) ParseError {
return ParseError{inner: err, index: index, msg: fmt.Sprintf(format, a...)}
}

func (p ParseError) Error() string {
if p.msg == "" {
return fmt.Sprintf("%s at position %d", p.inner, p.index)
}
return fmt.Sprintf("%s at position %d, %s", p.inner, p.index, p.msg)
}

func (p ParseError) Unwrap() error {
return p.inner
}

var (
ErrInvalidMagic = errors.New("invalid magic")
ErrInvalidVersion = errors.New("invalid version")
Expand Down Expand Up @@ -146,15 +123,14 @@ func (c *Container) MarshalBinary() []byte {

// UnmarshalBinary decodes an EOF container.
func (c *Container) UnmarshalBinary(b []byte) error {
e := NewParseError
if !hasEOFMagic(b) {
return e(ErrInvalidMagic, 0, "have %s, want %s", common.Bytes2Hex(b[:len(eofMagic)]), eofMagic)
return fmt.Errorf("%w: want %s", ErrInvalidMagic, eofMagic)
}
if len(b) < 14 {
return io.ErrUnexpectedEOF
}
if !isEOFVersion1(b) {
return e(ErrInvalidVersion, 2, "have %d, want %d", b[2], eof1Version)
return fmt.Errorf("%w: have %d, want %d", ErrInvalidVersion, b[2], eof1Version)
}

var (
Expand All @@ -169,13 +145,13 @@ func (c *Container) UnmarshalBinary(b []byte) error {
return err
}
if kind != kindTypes {
return e(ErrMissingTypeHeader, offsetTypesKind, "found section kind %x instead", kind)
return fmt.Errorf("%w: found section kind %x instead", ErrMissingTypeHeader, kind)
}
if typesSize < 4 || typesSize%4 != 0 {
return e(ErrInvalidTypeSize, offsetTypesKind+1, "type section size must be divisible by 4: have %d", typesSize)
return fmt.Errorf("%w: type section size must be divisible by 4, have %d", ErrInvalidTypeSize, typesSize)
}
if typesSize/4 > 1024 {
return e(ErrInvalidTypeSize, offsetTypesKind+1, "type section must not exceed 4*1024: have %d", typesSize/4)
return fmt.Errorf("%w: type section must not exceed 4*1024, have %d", ErrInvalidTypeSize, typesSize)
}

// Parse code section header.
Expand All @@ -184,10 +160,10 @@ func (c *Container) UnmarshalBinary(b []byte) error {
return err
}
if kind != kindCode {
return e(ErrMissingCodeHeader, offsetCodeKind, "found section kind %x instead", kind)
return fmt.Errorf("%w: found section kind %x instead", ErrMissingCodeHeader, kind)
}
if len(codeSizes) != typesSize/4 {
return e(ErrInvalidCodeSize, offsetCodeKind+1, "mismatch of code sections count and type signatures: types %d, code %d)", typesSize/4, len(codeSizes))
return fmt.Errorf("%w: mismatch of code sections cound and type signatures, types %d, code %d", ErrInvalidCodeSize, typesSize/4, len(codeSizes))
}

// Parse data section header.
Expand All @@ -197,7 +173,7 @@ func (c *Container) UnmarshalBinary(b []byte) error {
return err
}
if kind != kindData {
return e(ErrMissingDataHeader, offsetDataKind, "found section kind %x instead", kind)
return fmt.Errorf("%w: found section %x instead", ErrMissingDataHeader, kind)
}

// Check for terminator.
Expand All @@ -206,13 +182,13 @@ func (c *Container) UnmarshalBinary(b []byte) error {
return io.ErrUnexpectedEOF
}
if b[offsetTerminator] != 0 {
return e(ErrMissingTerminator, offsetTerminator, "have %x", b[offsetTerminator])
return fmt.Errorf("%w: have %x", ErrMissingTerminator, b[offsetTerminator])
}

// Verify overall container size.
expectedSize := offsetTerminator + typesSize + sum(codeSizes) + dataSize + 1
if len(b) != expectedSize {
return e(ErrInvalidContainerSize, 0, "have %d, want %d", len(b), expectedSize)
return fmt.Errorf("%w: have %d, want %d", ErrInvalidContainerSize, len(b), expectedSize)
}

// Parse types section.
Expand All @@ -225,18 +201,18 @@ func (c *Container) UnmarshalBinary(b []byte) error {
MaxStackHeight: binary.BigEndian.Uint16(b[idx+i*4+2:]),
}
if sig.Input > maxInputItems {
return e(ErrTooManyInputs, idx+i*4, "for section %d, have %d", i, sig.Input)
return fmt.Errorf("%w for section %d: have %d", ErrTooManyInputs, i, sig.Input)
}
if sig.Output > maxOutputItems {
return e(ErrTooManyOutputs, idx+i*4+1, "for section %d, have %d", i, sig.Output)
return fmt.Errorf("%w for section %d: have %d", ErrTooManyOutputs, i, sig.Output)
}
if sig.MaxStackHeight > maxStackHeight {
return e(ErrTooLargeMaxStackHeight, idx+i*4+2, "for section %d, have %d", i, sig.MaxStackHeight)
return fmt.Errorf("%w for section %d: have %d", ErrTooLargeMaxStackHeight, i, sig.MaxStackHeight)
}
types = append(types, sig)
}
if types[0].Input != 0 || types[0].Output != 0 {
return e(ErrInvalidSection0Type, idx, "have %d, %d", types[0].Input, types[0].Output)
return fmt.Errorf("%w: have %d, %d", ErrInvalidSection0Type, types[0].Input, types[0].Output)
}
c.Types = types

Expand All @@ -245,7 +221,7 @@ func (c *Container) UnmarshalBinary(b []byte) error {
code := make([][]byte, len(codeSizes))
for i, size := range codeSizes {
if size == 0 {
return e(ErrInvalidCodeSize, offsetCodeKind+2+i*2, "invalid code section %d: size must not be 0", i)
return fmt.Errorf("%w for section %d: size must not be 0", ErrInvalidCodeSize, i)
}
code[i] = b[idx : idx+size]
idx += size
Expand Down
51 changes: 25 additions & 26 deletions core/vm/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package vm

import (
"errors"
"fmt"
"io"

"github.com/ethereum/go-ethereum/params"
Expand All @@ -38,7 +39,6 @@ var (
func validateCode(code []byte, section int, metadata []*FunctionMetadata, jt *JumpTable) error {
var (
i = 0
e = NewParseError
// Tracks the number of actual instructions in the code (e.g.
// non-immediate values). This is used at the end to determine
// if each instruction is reachable.
Expand All @@ -56,33 +56,33 @@ func validateCode(code []byte, section int, metadata []*FunctionMetadata, jt *Ju
count++
op = OpCode(code[i])
if jt[op].undefined {
return e(ErrUndefinedInstruction, i, "opcode=%s", op)
return fmt.Errorf("%w: op %s, pos %d", ErrUndefinedInstruction, op, i)
}
switch {
case op >= PUSH1 && op <= PUSH32:
size := int(op - PUSH0)
if len(code) <= i+size {
return e(ErrTruncatedImmediate, i, "op=%s", op)
return fmt.Errorf("%w: op %s, pos %d", ErrTruncatedImmediate, op, i)
}
i += size
case op == RJUMP || op == RJUMPI:
if len(code) <= i+2 {
return e(ErrTruncatedImmediate, i, "op=%s", op)
return fmt.Errorf("%w: op %s, pos %d", ErrTruncatedImmediate, op, i)
}
if err := checkDest(code, &analysis, i+1, i+3, len(code)); err != nil {
return err
}
i += 2
case op == RJUMPV:
if len(code) <= i+1 {
return e(ErrTruncatedImmediate, i, "jump table size missing")
return fmt.Errorf("%w: jump table size missing, op %s, pos %d", ErrTruncatedImmediate, op, i)
}
count := int(code[i+1])
if count == 0 {
return e(ErrInvalidBranchCount, i, "must not be 0")
return fmt.Errorf("%w: must not be 0, pos %d", ErrInvalidBranchCount, i)
}
if len(code) <= i+count {
return e(ErrTruncatedImmediate, i, "jump table truncated")
return fmt.Errorf("%w: jump table truncated, op %s, pos %d", ErrTruncatedImmediate, op, i)
}
for j := 0; j < count; j++ {
if err := checkDest(code, &analysis, i+2+j*2, i+2*count+2, len(code)); err != nil {
Expand All @@ -92,11 +92,11 @@ func validateCode(code []byte, section int, metadata []*FunctionMetadata, jt *Ju
i += 1 + 2*count
case op == CALLF:
if i+2 >= len(code) {
return e(ErrTruncatedImmediate, i, "op=%s", op)
return fmt.Errorf("%w: op %s, pos %d", ErrTruncatedImmediate, op, i)
}
arg, _ := parseUint16(code[i+1:])
if arg >= len(metadata) {
return e(ErrInvalidSectionArgument, i, "arg %d, last section %d", arg, len(metadata))
return fmt.Errorf("%w: arg %d, last %d, pos %d", ErrInvalidSectionArgument, arg, len(metadata), i)
}
i += 2
}
Expand All @@ -105,13 +105,13 @@ func validateCode(code []byte, section int, metadata []*FunctionMetadata, jt *Ju
// Code sections may not "fall through" and require proper termination.
// Therefore, the last instruction must be considered terminal.
if !jt[op].terminal {
return e(ErrInvalidCodeTermination, i, "ends with op %s", op)
return fmt.Errorf("%w: end with %s, pos %d", ErrInvalidCodeTermination, op, i)
}
if paths, err := validateControlFlow(code, section, metadata, jt); err != nil {
return err
} else if paths != count {
// TODO(matt): return actual position of unreacable code
return e(ErrUnreachableCode, 0, "")
return ErrUnreachableCode
}
return nil
}
Expand All @@ -127,10 +127,10 @@ func checkDest(code []byte, analysis *bitvec, imm, from, length int) error {
offset := parseInt16(code[imm:])
dest := from + offset
if dest < 0 || dest >= length {
return NewParseError(ErrInvalidJumpDest, imm, "relative offset out-of-bounds: offset %d, dest %d", offset, dest)
return fmt.Errorf("%w: out-of-bounds offset: offset %d, dest %d, pos %d", ErrInvalidJumpDest, offset, dest, imm)
}
if !analysis.codeSegment(uint64(dest)) {
return NewParseError(ErrInvalidJumpDest, imm, "relative offset into immediate value: offset %d, dest %d", offset, dest)
return fmt.Errorf("%w: offset into immediate: offset %d, dest %d, pos %d", ErrInvalidJumpDest, offset, dest, imm)
}
return nil
}
Expand All @@ -143,7 +143,6 @@ func validateControlFlow(code []byte, section int, metadata []*FunctionMetadata,
height int
}
var (
e = NewParseError
heights = make(map[int]int)
worklist = []item{{0, int(metadata[section].Input)}}
maxStackHeight = int(metadata[section].Input)
Expand All @@ -162,7 +161,7 @@ func validateControlFlow(code []byte, section int, metadata []*FunctionMetadata,
// Check if pos has already be visited; if so, the stack heights should be the same.
if want, ok := heights[pos]; ok {
if height != want {
return 0, e(ErrConflictingStack, pos, "have %d, want %d", height, want)
return 0, fmt.Errorf("%w: have %d, want %d", ErrConflictingStack, height, want)
}
// Already visited this path and stack height
// matches.
Expand All @@ -171,29 +170,29 @@ func validateControlFlow(code []byte, section int, metadata []*FunctionMetadata,
heights[pos] = height

// Validate height for current op and update as needed.
if jt[op].minStack > height {
return 0, e(ErrStackUnderflow{stackLen: height, required: jt[op].minStack}, pos, "")
if want, have := jt[op].minStack, height; want > have {
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
}
if jt[op].maxStack < height {
return 0, e(ErrStackOverflow{stackLen: height, limit: jt[op].maxStack}, pos, "")
if want, have := jt[op].maxStack, height; want < have {
return 0, fmt.Errorf("%w: at pos %d", ErrStackOverflow{stackLen: have, limit: want}, pos)
}
height += int(params.StackLimit) - jt[op].maxStack

switch {
case op == CALLF:
arg, _ := parseUint16(code[pos+1:])
if metadata[arg].Input > uint8(height) {
return 0, e(ErrStackUnderflow{stackLen: height, required: int(metadata[arg].Input)}, pos, "CALLF underflow to section %d", arg)
if want, have := int(metadata[arg].Input), height; want > have {
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
}
if int(metadata[arg].Output)+height > int(params.StackLimit) {
return 0, e(ErrStackOverflow{stackLen: int(metadata[arg].Output) + height, limit: int(params.StackLimit)}, pos, "CALLF overflow to section %d")
if have, limit := int(metadata[arg].Output)+height, int(params.StackLimit); have > limit {
return 0, fmt.Errorf("%w: at pos %d", ErrStackOverflow{stackLen: have, limit: limit}, pos)
}
height -= int(metadata[arg].Input)
height += int(metadata[arg].Output)
pos += 3
case op == RETF:
if int(metadata[section].Output) != height {
return 0, e(ErrInvalidOutputs, pos, "have %d, want %d", height, metadata[section].Output)
if have, want := int(metadata[section].Output), height; have != want {
return 0, fmt.Errorf("%w: have %d, want %d, at pos %d", ErrInvalidOutputs, have, want, pos)
}
break outer
case op == RJUMP:
Expand Down Expand Up @@ -224,7 +223,7 @@ func validateControlFlow(code []byte, section int, metadata []*FunctionMetadata,
}
}
if maxStackHeight != int(metadata[section].MaxStackHeight) {
return 0, e(ErrInvalidMaxStackHeight, 0, "at code section %d, have %d, want %d", section, maxStackHeight, metadata[section].MaxStackHeight)
return 0, fmt.Errorf("%w in code section %d: have %d, want %d", ErrInvalidMaxStackHeight, section, maxStackHeight, metadata[section].MaxStackHeight)
}
return len(heights), nil
}

0 comments on commit 1327798

Please sign in to comment.