From 1fa91729f2a591df2baf2dc77e81711a6e61c028 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Mon, 29 Nov 2021 14:46:24 +0100 Subject: [PATCH] core/vm: simplify error handling in interpreter loop (#23952) * core/vm: break loop on any error * core/vm: move ErrExecutionReverted to opRevert() * core/vm: use "stop token" to stop the loop * core/vm: unconditionally pc++ in the loop * core/vm: set return data in instruction impls --- core/vm/errors.go | 4 ++++ core/vm/instructions.go | 23 +++++++++++++++-------- core/vm/interpreter.go | 24 +++++++++--------------- core/vm/jump_table.go | 19 +------------------ 4 files changed, 29 insertions(+), 41 deletions(-) diff --git a/core/vm/errors.go b/core/vm/errors.go index 565eecdd7..004f8ef1c 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -36,6 +36,10 @@ var ( ErrGasUintOverflow = errors.New("gas uint64 overflow") ErrInvalidCode = errors.New("invalid code: must not begin with 0xef") ErrNonceUintOverflow = errors.New("nonce uint64 overflow") + + // errStopToken is an internal token indicating interpreter loop termination, + // never returned to outside callers. + errStopToken = errors.New("stop token") ) // ErrStackUnderflow wraps an evm error when the items on the stack less diff --git a/core/vm/instructions.go b/core/vm/instructions.go index bda480f08..dd91f2ec4 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -526,7 +526,7 @@ func opJump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt if !scope.Contract.validJumpdest(&pos) { return nil, ErrInvalidJump } - *pc = pos.Uint64() + *pc = pos.Uint64() - 1 // pc will be increased by the interpreter loop return nil, nil } @@ -536,9 +536,7 @@ func opJumpi(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by if !scope.Contract.validJumpdest(&pos) { return nil, ErrInvalidJump } - *pc = pos.Uint64() - } else { - *pc++ + *pc = pos.Uint64() - 1 // pc will be increased by the interpreter loop } return nil, nil } @@ -598,8 +596,10 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b scope.Contract.Gas += returnGas if suberr == ErrExecutionReverted { + interpreter.returnData = res // set REVERT data to return data buffer return res, nil } + interpreter.returnData = nil // clear dirty return data buffer return nil, nil } @@ -634,8 +634,10 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] scope.Contract.Gas += returnGas if suberr == ErrExecutionReverted { + interpreter.returnData = res // set REVERT data to return data buffer return res, nil } + interpreter.returnData = nil // clear dirty return data buffer return nil, nil } @@ -674,6 +676,7 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt } scope.Contract.Gas += returnGas + interpreter.returnData = ret return ret, nil } @@ -709,6 +712,7 @@ func opCallCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ } scope.Contract.Gas += returnGas + interpreter.returnData = ret return ret, nil } @@ -737,6 +741,7 @@ func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext } scope.Contract.Gas += returnGas + interpreter.returnData = ret return ret, nil } @@ -765,6 +770,7 @@ func opStaticCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) } scope.Contract.Gas += returnGas + interpreter.returnData = ret return ret, nil } @@ -772,18 +778,19 @@ func opReturn(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b offset, size := scope.Stack.pop(), scope.Stack.pop() ret := scope.Memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64())) - return ret, nil + return ret, errStopToken } func opRevert(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { offset, size := scope.Stack.pop(), scope.Stack.pop() ret := scope.Memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64())) - return ret, nil + interpreter.returnData = ret + return ret, ErrExecutionReverted } func opStop(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - return nil, nil + return nil, errStopToken } func opSuicide(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { @@ -795,7 +802,7 @@ func opSuicide(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] interpreter.cfg.Tracer.CaptureEnter(SELFDESTRUCT, scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance) interpreter.cfg.Tracer.CaptureExit([]byte{}, 0, nil) } - return nil, nil + return nil, errStopToken } // following functions are used by the instruction jump table diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index afa3c0f12..8b029f2e3 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -259,22 +259,16 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( // execute the operation res, err = operation.execute(&pc, in, callContext) - // if the operation clears the return data (e.g. it has returning data) - // set the last return to the result of the operation. - if operation.returns { - in.returnData = res - } - switch { - case err != nil: - return nil, err - case operation.reverts: - return res, ErrExecutionReverted - case operation.halts: - return res, nil - case !operation.jumps: - pc++ + if err != nil { + break } + pc++ } - return nil, nil + + if err == errStopToken { + err = nil // clear stop token error + } + + return res, err } diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 329ad77cb..d56222f08 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -41,11 +41,7 @@ type operation struct { // memorySize returns the memory size required for the operation memorySize memorySizeFunc - halts bool // indicates whether the operation should halt further execution - jumps bool // indicates whether the program counter should not increment - writes bool // determines whether this a state modifying operation - reverts bool // determines whether the operation reverts state (implicitly halts) - returns bool // determines whether the operations sets the return data content + writes bool // determines whether this a state modifying operation } var ( @@ -128,7 +124,6 @@ func newConstantinopleInstructionSet() JumpTable { maxStack: maxStack(4, 1), memorySize: memoryCreate2, writes: true, - returns: true, } return instructionSet } @@ -144,7 +139,6 @@ func newByzantiumInstructionSet() JumpTable { minStack: minStack(6, 1), maxStack: maxStack(6, 1), memorySize: memoryStaticCall, - returns: true, } instructionSet[RETURNDATASIZE] = &operation{ execute: opReturnDataSize, @@ -166,8 +160,6 @@ func newByzantiumInstructionSet() JumpTable { minStack: minStack(2, 0), maxStack: maxStack(2, 0), memorySize: memoryRevert, - reverts: true, - returns: true, } return instructionSet } @@ -204,7 +196,6 @@ func newHomesteadInstructionSet() JumpTable { minStack: minStack(6, 1), maxStack: maxStack(6, 1), memorySize: memoryDelegateCall, - returns: true, } return instructionSet } @@ -218,7 +209,6 @@ func newFrontierInstructionSet() JumpTable { constantGas: 0, minStack: minStack(0, 0), maxStack: maxStack(0, 0), - halts: true, }, ADD: { execute: opAdd, @@ -528,14 +518,12 @@ func newFrontierInstructionSet() JumpTable { constantGas: GasMidStep, minStack: minStack(1, 0), maxStack: maxStack(1, 0), - jumps: true, }, JUMPI: { execute: opJumpi, constantGas: GasSlowStep, minStack: minStack(2, 0), maxStack: maxStack(2, 0), - jumps: true, }, PC: { execute: opPc, @@ -993,7 +981,6 @@ func newFrontierInstructionSet() JumpTable { maxStack: maxStack(3, 1), memorySize: memoryCreate, writes: true, - returns: true, }, CALL: { execute: opCall, @@ -1002,7 +989,6 @@ func newFrontierInstructionSet() JumpTable { minStack: minStack(7, 1), maxStack: maxStack(7, 1), memorySize: memoryCall, - returns: true, }, CALLCODE: { execute: opCallCode, @@ -1011,7 +997,6 @@ func newFrontierInstructionSet() JumpTable { minStack: minStack(7, 1), maxStack: maxStack(7, 1), memorySize: memoryCall, - returns: true, }, RETURN: { execute: opReturn, @@ -1019,14 +1004,12 @@ func newFrontierInstructionSet() JumpTable { minStack: minStack(2, 0), maxStack: maxStack(2, 0), memorySize: memoryReturn, - halts: true, }, SELFDESTRUCT: { execute: opSuicide, dynamicGas: gasSelfdestruct, minStack: minStack(1, 0), maxStack: maxStack(1, 0), - halts: true, writes: true, }, }