@ -6,22 +6,37 @@ import (
"github.com/ethereum/go-ethereum/params"
)
type bounds struct {
min int
max int
}
func validateControlFlow ( code [ ] byte , section int , metadata [ ] * functionMetadata , jt * JumpTable ) ( int , error ) {
var (
stackBounds = make ( map [ int ] * bounds )
maxStackHeight = int ( metadata [ section ] . inputs )
debugging = ! true
visitCount = 0
next = make ( [ ] int , 0 , 1 )
)
setBounds := func ( pos , min , maxi int ) * bounds {
stackBounds [ pos ] = & bounds { min , maxi }
var (
stackBoundsMax = make ( [ ] uint16 , len ( code ) )
stackBoundsMin = make ( [ ] uint16 , len ( code ) )
)
setBounds := func ( pos , min , maxi int ) {
// The stackboundMax slice is a bit peculiar. We use `0` to denote
// not set. Therefore, we use `1` to represent the value `0`, and so on.
// So if the caller wants to store `1` as max bound, we internally store it as
// `2`.
if stackBoundsMax [ pos ] == 0 { // Not yet set
visitCount ++
}
if maxi < 65535 {
stackBoundsMax [ pos ] = uint16 ( maxi + 1 )
}
stackBoundsMin [ pos ] = uint16 ( min )
maxStackHeight = max ( maxStackHeight , maxi )
return stackBounds [ pos ]
}
getStackMaxMin := func ( pos int ) ( ok bool , min , max int ) {
maxi := stackBoundsMax [ pos ]
if maxi == 0 { // Not yet set
return false , 0 , 0
}
return true , int ( stackBoundsMin [ pos ] ) , int ( maxi - 1 )
}
// set the initial stack bounds
setBounds ( 0 , int ( metadata [ section ] . inputs ) , int ( metadata [ section ] . inputs ) )
@ -29,97 +44,89 @@ func validateControlFlow(code []byte, section int, metadata []*functionMetadata,
qualifiedExit := false
for pos := 0 ; pos < len ( code ) ; pos ++ {
op := OpCode ( code [ pos ] )
currentBounds := stackBounds [ pos ]
if currentBounds == nil {
ok , currentStackMin , currentStackMax := getStackMaxMin ( pos )
if ! ok {
if debugging {
fmt . Printf ( "Stack bounds not set: %v at %v \n" , op , pos )
}
return 0 , ErrUnreachableCode
}
if debugging {
fmt . Println ( pos , op , maxStackHeight , currentBounds )
fmt . Println ( pos , op , maxStackHeight , currentStackMin , currentStackMax )
}
var (
currentStackMax = currentBounds . max
currentStackMin = currentBounds . min
)
switch op {
case CALLF :
arg , _ := parseUint16 ( code [ pos + 1 : ] )
newSection := metadata [ arg ]
if want , have := int ( newSection . inputs ) , currentBounds . m in ; want > have {
if want , have := int ( newSection . inputs ) , currentStackMin ; want > have {
return 0 , fmt . Errorf ( "%w: at pos %d" , ErrStackUnderflow { stackLen : have , required : want } , pos )
}
if have , limit := currentBounds . m ax + int ( newSection . maxStackHeight ) - int ( newSection . inputs ) , int ( params . StackLimit ) ; have > limit {
if have , limit := currentStackM ax + int ( newSection . maxStackHeight ) - int ( newSection . inputs ) , int ( params . StackLimit ) ; have > limit {
return 0 , fmt . Errorf ( "%w: at pos %d" , ErrStackOverflow { stackLen : have , limit : limit } , pos )
}
change := int ( newSection . outputs ) - int ( newSection . inputs )
currentStackMax += change
currentStackMin += change
case RETF :
if currentBounds . max != currentBounds . m in {
return 0 , fmt . Errorf ( "%w: max %d, min %d, at pos %d" , ErrInvalidOutputs , currentBounds . max , currentBounds . m in , pos )
if currentStackMax != currentStackM in {
return 0 , fmt . Errorf ( "%w: max %d, min %d, at pos %d" , ErrInvalidOutputs , currentStackMax , currentStackM in , pos )
}
have := int ( metadata [ section ] . outputs )
if have >= maxOutputItems {
return 0 , fmt . Errorf ( "%w: at pos %d" , ErrInvalidNonReturningFlag , pos )
}
if want := currentBounds . m in ; have != want {
if want := currentStackM in ; have != want {
return 0 , fmt . Errorf ( "%w: have %d, want %d, at pos %d" , ErrInvalidOutputs , have , want , pos )
}
qualifiedExit = true
case JUMPF :
arg , _ := parseUint16 ( code [ pos + 1 : ] )
newSection := metadata [ arg ]
if have , limit := currentBounds . m ax + int ( newSection . maxStackHeight ) - int ( newSection . inputs ) , int ( params . StackLimit ) ; have > limit {
if have , limit := currentStackM ax + int ( newSection . maxStackHeight ) - int ( newSection . inputs ) , int ( params . StackLimit ) ; have > limit {
return 0 , fmt . Errorf ( "%w: at pos %d" , ErrStackOverflow { stackLen : have , limit : limit } , pos )
}
if newSection . outputs == 0x80 {
if want , have := int ( newSection . inputs ) , currentBounds . m in ; want > have {
if want , have := int ( newSection . inputs ) , currentStackM in ; want > have {
return 0 , fmt . Errorf ( "%w: at pos %d" , ErrStackUnderflow { stackLen : have , required : want } , pos )
}
} else {
if currentBounds . max != currentBounds . m in {
return 0 , fmt . Errorf ( "%w: max %d, min %d, at pos %d" , ErrInvalidOutputs , currentBounds . max , currentBounds . m in , pos )
if currentStackMax != currentStackM in {
return 0 , fmt . Errorf ( "%w: max %d, min %d, at pos %d" , ErrInvalidOutputs , currentStackMax , currentStackM in , pos )
}
if have , want := currentBounds . m ax , int ( metadata [ section ] . outputs ) + int ( newSection . inputs ) - int ( newSection . outputs ) ; have != want {
if have , want := currentStackM ax , int ( metadata [ section ] . outputs ) + int ( newSection . inputs ) - int ( newSection . outputs ) ; have != want {
return 0 , fmt . Errorf ( "%w: at pos %d" , ErrInvalidOutputs , pos )
}
}
qualifiedExit = qualifiedExit || newSection . outputs < maxOutputItems
case DUPN :
arg := int ( code [ pos + 1 ] ) + 1
if want , have := arg , currentBounds . m in ; want > have {
if want , have := arg , currentStackM in ; want > have {
return 0 , fmt . Errorf ( "%w: at pos %d" , ErrStackUnderflow { stackLen : have , required : want } , pos )
}
case SWAPN :
arg := int ( code [ pos + 1 ] ) + 1
if want , have := arg + 1 , currentBounds . m in ; want > have {
if want , have := arg + 1 , currentStackM in ; want > have {
return 0 , fmt . Errorf ( "%w: at pos %d" , ErrStackUnderflow { stackLen : have , required : want } , pos )
}
case EXCHANGE :
arg := int ( code [ pos + 1 ] )
n := arg >> 4 + 1
m := arg & 0x0f + 1
if want , have := n + m + 1 , currentBounds . m in ; want > have {
if want , have := n + m + 1 , currentStackM in ; want > have {
return 0 , fmt . Errorf ( "%w: at pos %d" , ErrStackUnderflow { stackLen : have , required : want } , pos )
}
default :
if want , have := jt [ op ] . minStack , currentBounds . m in ; want > have {
if want , have := jt [ op ] . minStack , currentStackM in ; want > have {
return 0 , fmt . Errorf ( "%w: at pos %d" , ErrStackUnderflow { stackLen : have , required : want } , pos )
}
}
if ! terminals [ op ] && op != CALLF {
change := int ( params . StackLimit ) - jt [ op ] . maxStack
currentStackMax += change
currentStackMin += change
}
var next [ ] int
next = next [ : 0 ]
switch op {
case RJUMP :
nextPos := pos + 2 + parseInt16 ( code [ pos + 1 : ] )
@ -127,19 +134,20 @@ func validateControlFlow(code []byte, section int, metadata []*functionMetadata,
// We set the stack bounds of the destination
// and skip the argument, only for RJUMP, all other opcodes are handled later
if nextPos + 1 < pos {
nextBounds , ok := stackBounds [ nextPos + 1 ]
ok , nextMin , nextMax := getStackMaxMin ( nextPos + 1 )
if ! ok {
return 0 , ErrInvalidBackwardJump
}
if nextBounds . m ax != currentStackMax || nextBounds . m in != currentStackMin {
if nextM ax != currentStackMax || nextM in != currentStackMin {
return 0 , ErrInvalidMaxStackHeight
}
}
nextBounds , ok := stackBounds [ nextPos + 1 ]
if ! ok {
setBounds ( nextPos + 1 , currentStackMin , currentStackMax )
} else {
setBounds ( nextPos + 1 , min ( nextBounds . min , currentStackMin ) , max ( nextBounds . max , currentStackMax ) )
ok , nextMin , nextMax := getStackMaxMin ( nextPos + 1 )
if ! ok {
setBounds ( nextPos + 1 , currentStackMin , currentStackMax )
} else {
setBounds ( nextPos + 1 , min ( nextMin , currentStackMin ) , max ( nextMax , currentStackMax ) )
}
}
case RJUMPI :
arg := parseInt16 ( code [ pos + 1 : ] )
@ -172,23 +180,23 @@ func validateControlFlow(code []byte, section int, metadata []*functionMetadata,
}
if nextPC > pos {
// target reached via forward jump or seq flow
nextBounds , ok := stackBounds [ nextPC ]
ok , nextMin , nextMax := getStackMaxMin ( nextPC )
if ! ok {
setBounds ( nextPC , currentStackMin , currentStackMax )
} else {
setBounds ( nextPC , min ( nextBounds . m in , currentStackMin ) , max ( nextBounds . m ax , currentStackMax ) )
setBounds ( nextPC , min ( nextM in , currentStackMin ) , max ( nextM ax , currentStackMax ) )
}
} else {
// target reached via backwards jump
nextBounds , ok := stackBounds [ nextPC ]
ok , nextMin , nextMax := getStackMaxMin ( nextPC )
if ! ok {
return 0 , ErrInvalidBackwardJump
}
if currentStackMax != nextBounds . m ax {
return 0 , fmt . Errorf ( "%w want %d as current max got %d at pos %d," , ErrInvalidBackwardJump , currentStackMax , nextBounds . m ax , pos )
if currentStackMax != nextM ax {
return 0 , fmt . Errorf ( "%w want %d as current max got %d at pos %d," , ErrInvalidBackwardJump , currentStackMax , nextM ax , pos )
}
if currentStackMin != nextBounds . m in {
return 0 , fmt . Errorf ( "%w want %d as current min got %d at pos %d," , ErrInvalidBackwardJump , currentStackMin , nextBounds . m in , pos )
if currentStackMin != nextM in {
return 0 , fmt . Errorf ( "%w want %d as current min got %d at pos %d," , ErrInvalidBackwardJump , currentStackMin , nextM in , pos )
}
}
}
@ -199,7 +207,6 @@ func validateControlFlow(code []byte, section int, metadata []*functionMetadata,
} else {
pos = next [ 0 ]
}
}
if qualifiedExit != ( metadata [ section ] . outputs < maxOutputItems ) {
return 0 , fmt . Errorf ( "%w no RETF or qualified JUMPF" , ErrInvalidNonReturningFlag )
@ -213,5 +220,5 @@ func validateControlFlow(code []byte, section int, metadata []*functionMetadata,
}
return 0 , fmt . Errorf ( "%w in code section %d: have %d, want %d" , ErrInvalidMaxStackHeight , section , maxStackHeight , metadata [ section ] . maxStackHeight )
}
return len ( stackBounds ) , nil
return visitCount , nil
}